huaweicloud-devkit 1.1.4-next.0 → 1.1.4-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
- package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +8 -8
- package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +1 -1
- package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +6 -6
- package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +36 -1
- package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +0 -1
- package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +2 -2
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +2 -2
- package/plugins/huaweicloud-core/src/auth/service.mjs +4 -1
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +5 -73
- package/plugins/huaweicloud-core/src/tools.mjs +37 -3
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
3
|
"mcpName": "io.github.huaweicloud/huaweicloud-devkit",
|
|
4
|
-
"version": "1.1.4-next.
|
|
4
|
+
"version": "1.1.4-next.2",
|
|
5
5
|
"kooCliVersion": "7.2.12",
|
|
6
6
|
"description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
|
|
7
7
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"mcpServers": "./.mcp.json",
|
|
18
18
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
19
19
|
"skills": "./skills/",
|
|
20
|
-
"version": "1.1.4-next.
|
|
20
|
+
"version": "1.1.4-next.2",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"mcpServers": "./.mcp.json",
|
|
21
21
|
"description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
|
|
22
22
|
"skills": "./skills/",
|
|
23
|
-
"version": "1.1.4-next.
|
|
23
|
+
"version": "1.1.4-next.2",
|
|
24
24
|
"author": {
|
|
25
25
|
"name": "HuaweiCloud Mate",
|
|
26
26
|
"url": "https://github.com/huaweicloud"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"mcpServers": "./.mcp.json",
|
|
18
18
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
19
19
|
"skills": "./skills/",
|
|
20
|
-
"version": "1.1.4-next.
|
|
20
|
+
"version": "1.1.4-next.2",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.4-next.
|
|
3
|
+
"version": "1.1.4-next.2",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.4-next.
|
|
3
|
+
"version": "1.1.4-next.2",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
3
|
"id": "huaweicloud-devkit",
|
|
4
4
|
"displayName": "HuaweiCloud DevKit",
|
|
5
|
-
"version": "1.1.4-next.
|
|
5
|
+
"version": "1.1.4-next.2",
|
|
6
6
|
"family": "bundle-plugin",
|
|
7
7
|
"bundleFormat": "codex",
|
|
8
8
|
"description": "Guide coding agents to use Huawei Cloud safely — KooCLI, APIs, SDKs, 28 MCP tools, skills, and safety guardrails.",
|
|
@@ -16,14 +16,14 @@ Domain expertise for billing queries (BSS). Covers cost tracking, bill details,
|
|
|
16
16
|
|
|
17
17
|
## Critical Warnings
|
|
18
18
|
|
|
19
|
-
| Trap | Why
|
|
20
|
-
| ----------------------------------------- |
|
|
21
|
-
| Bills delayed ~24h | Yesterday's costs may not appear until the next day
|
|
22
|
-
| BSS Admin role needed | IAM user must have BSS Administrator or Finance role
|
|
23
|
-
| Currency conversion varies | Cross-region costs use daily exchange rates
|
|
24
|
-
| Region fixed to cn-north-1 | BSS operations only support `--cli-region=cn-north-1` in KooCLI. This is a KooCLI metadata limitation — the billing data itself covers all regions.
|
|
25
|
-
| English catalog missing BSS | `Unsupported service: BSS` under the default `en` language is a KooCLI catalog gap, not a missing service. devkit
|
|
26
|
-
| `缺少必填参数 cli-domain-id` / APIGW.0301 | Invalid credentials — KooCLI can't resolve the account-id
|
|
19
|
+
| Trap | Why |
|
|
20
|
+
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
21
|
+
| Bills delayed ~24h | Yesterday's costs may not appear until the next day |
|
|
22
|
+
| BSS Admin role needed | IAM user must have BSS Administrator or Finance role |
|
|
23
|
+
| Currency conversion varies | Cross-region costs use daily exchange rates |
|
|
24
|
+
| Region fixed to cn-north-1 | BSS operations only support `--cli-region=cn-north-1` in KooCLI. This is a KooCLI metadata limitation — the billing data itself covers all regions. |
|
|
25
|
+
| English catalog missing BSS | `Unsupported service: BSS` under the default `en` language is a KooCLI catalog gap, not a missing service. devkit detects it and advises the supported fix `hcloud configure set --cli-lang=cn` (switch is global-only; KooCLI has no per-command `--cli-lang` flag). Running with Chinese mode from the start (see Prerequisites) avoids the issue entirely. |
|
|
26
|
+
| `缺少必填参数 cli-domain-id` / APIGW.0301 | Invalid credentials — KooCLI can't resolve the account-id | Re-run `npx huaweicloud-devkit auth init` |
|
|
27
27
|
|
|
28
28
|
## Common Workflows
|
|
29
29
|
|
|
@@ -58,7 +58,7 @@ Flavor families are **region-dependent**. Always run `hcloud ECS ListFlavors --c
|
|
|
58
58
|
| Create instance | hcloud ECS CreateServers --server.name=<n> --server.flavorRef=<id> --server.imageRef=<id> --server.nics.1.subnet_id=<id> --server.availability_zone=<az> | references/create-instance.md |
|
|
59
59
|
| Find by name | See "How to search for instances" below | |
|
|
60
60
|
| Bind EIP | hcloud EIP AssociatePublicips --publicip_id=<id> --publicip.associate_instance_id=<port-id> --publicip.associate_instance_type=PORT | Get port ID from `hcloud ECS ListServersDetails --server_id=<id>` → `OS-EXT-IPS:port_id` |
|
|
61
|
-
| Security group rule | hcloud VPC CreateSecurityGroupRule --security_group_id=<id> --direction=<direction> --protocol=<protocol> |
|
|
61
|
+
| Security group rule | hcloud VPC CreateSecurityGroupRule --security_group_id=<id> --direction=<direction> --protocol=<protocol> | see huawei-vpc |
|
|
62
62
|
| Attach disk | hcloud ECS AttachServerVolume --server_id=<id> --volumeAttachment.volumeId=<id> | references/evs.md |
|
|
63
63
|
| Delete instance | hcloud ECS DeleteServers --servers.1.id=<id> --delete_publicip=true --delete_volume=true | references/create-instance.md |
|
|
64
64
|
| Reboot instance | hcloud ECS BatchRebootServers --reboot.servers.1.id=<id> --reboot.type=SOFT | NOT `RebootServer` — that operation does not exist |
|
|
@@ -45,12 +45,12 @@ Domain expertise for Huawei Cloud Identity and Access Management (IAM). Covers u
|
|
|
45
45
|
|
|
46
46
|
| Task | Command | Steps |
|
|
47
47
|
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
|
|
48
|
-
| List users | hcloud IAM KeystoneListUsers --cli-region=<r> | references/
|
|
49
|
-
| Create group | hcloud IAM CreateGroupV5 --group_name=<name> | references/
|
|
48
|
+
| List users | hcloud IAM KeystoneListUsers --cli-region=<r> | references/quick-ref.md |
|
|
49
|
+
| Create group | hcloud IAM CreateGroupV5 --group_name=<name> | references/quick-ref.md |
|
|
50
50
|
| Create custom policy | hcloud IAM CreateCloudServiceCustomPolicy --role.display_name=<n> --role.type=<type> --role.policy.Statement.1.Effect=Allow | references/policy-examples.md |
|
|
51
|
-
| Create agency | hcloud IAM CreateAgency --agency.name=<n> --agency.domain_id=<id> --agency.trust_domain_name=<domain> | references/
|
|
52
|
-
| Get temporary credential | hcloud STS AssumeAgency --agency_urn=<urn> --agency_session_name=<n> --duration_seconds=3600 | references/
|
|
53
|
-
| Attach policy to group | hcloud IAM AttachGroupPolicyV5 --group_id=<id> --policy_id=<id> | references/
|
|
51
|
+
| Create agency | hcloud IAM CreateAgency --agency.name=<n> --agency.domain_id=<id> --agency.trust_domain_name=<domain> | references/quick-ref.md |
|
|
52
|
+
| Get temporary credential | hcloud STS AssumeAgency --agency_urn=<urn> --agency_session_name=<n> --duration_seconds=3600 | references/quick-ref.md |
|
|
53
|
+
| Attach policy to group | hcloud IAM AttachGroupPolicyV5 --group_id=<id> --policy_id=<id> | references/quick-ref.md |
|
|
54
54
|
|
|
55
55
|
## Policy Examples (Least Privilege)
|
|
56
56
|
|
|
@@ -109,4 +109,4 @@ Common roles for FunctionGraph: VPC Administrator, RDS Administrator, DCS Admini
|
|
|
109
109
|
|
|
110
110
|
- IAM Docs: https://support.huaweicloud.com/iam/
|
|
111
111
|
- Policy examples: references/policy-examples.md
|
|
112
|
-
- Agency setup: references/
|
|
112
|
+
- Agency setup: references/quick-ref.md
|
|
@@ -7,11 +7,46 @@ hcloud IAM KeystoneListProjects # list projects
|
|
|
7
7
|
hcloud IAM KeystoneListUsers # list users
|
|
8
8
|
hcloud IAM KeystoneListGroups # list groups
|
|
9
9
|
hcloud IAM ListCustomPolicies # list custom policies
|
|
10
|
-
hcloud IAM ListAgencies
|
|
10
|
+
hcloud IAM ListAgencies --domain_id=<domain-id> # list agencies
|
|
11
11
|
hcloud IAM KeystoneListAuthDomains # list domains
|
|
12
12
|
hcloud IAM KeystoneShowUser # show user detail
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
> **Group lifecycle uses the V5 API, not Keystone.** `CreateGroupV5` / `AttachGroupPolicyV5` take `group_id`/`policy_id`; `KeystoneListGroups` is the older read API. The V5 group `description` forbids `@ # % & < > \ $ ^ *`.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
hcloud IAM CreateGroupV5 --group_name=<name>
|
|
19
|
+
hcloud IAM AttachGroupPolicyV5 --group_id=<id> --policy_id=<id>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Agencies & Temporary Credentials (STS)
|
|
23
|
+
|
|
24
|
+
An agency + `STS AssumeAgency` is the only safe way to mint temporary credentials (never store long-term AK/SK):
|
|
25
|
+
|
|
26
|
+
1. Create the delegation:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
hcloud IAM CreateAgency --agency.name=<name> --agency.domain_id=<domain-id> --agency.trust_domain_name=<trust-domain>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`--agency.trust_domain_name` and `--agency.trust_domain_id` are two ways to name the entrusted account — provide at least one; when both are present, `trust_domain_name` wins validation.
|
|
33
|
+
|
|
34
|
+
2. Grant a role to the agency (optional):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
hcloud IAM AssociateAgencyWithDomainPermission --agency_id=<id> --role_id=<role-id>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
3. Exchange for temporary credentials:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
hcloud STS AssumeAgency --agency_urn=<urn> --agency_session_name=<name> --duration_seconds=3600
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- `--agency_urn` is the agency **URN**, not its name or id — query it with `hcloud IAM ListAgencies --domain_id=<domain-id>` first.
|
|
47
|
+
- `--duration_seconds` range is `[900, 43200]`; it must not exceed the agency's own session duration, and is capped at `3600` when `--X-Security-Token` is present.
|
|
48
|
+
- The returned credential includes a `security_token` — pass it as `--X-Security-Token` for subsequent calls.
|
|
49
|
+
|
|
15
50
|
## Credential Management
|
|
16
51
|
|
|
17
52
|
```bash
|
|
@@ -131,4 +131,3 @@ See `references/single-file-share.md` for the full workflow to host one file and
|
|
|
131
131
|
- Static website: references/static-website.md
|
|
132
132
|
- Single-file share: references/single-file-share.md
|
|
133
133
|
- Lifecycle: references/bucket-lifecycle.md
|
|
134
|
-
- Replication: references/replication.md
|
|
@@ -43,8 +43,8 @@ Domain expertise for Huawei Cloud Virtual Private Cloud (VPC). Covers VPC/subnet
|
|
|
43
43
|
| SG rule | hcloud VPC CreateSecurityGroupRule --security_group_rule.security_group_id=<id> --security_group_rule.direction=<direction> --security_group_rule.protocol=<protocol> --security_group_rule.multiport=<port> --security_group_rule.remote_ip_prefix=<cidr> | references/security-group.md |
|
|
44
44
|
| Create EIP | hcloud EIP CreatePublicip --publicip.type=<type> --bandwidth.size=<size> --bandwidth.share_type=<share-type> --bandwidth.name=<name> | Run `hcloud EIP CreatePublicip --help` to confirm valid type values per region |
|
|
45
45
|
| Bind EIP to ECS | hcloud EIP AssociatePublicips --publicip_id=<id> --publicip.associate_instance_id=<port-id> --publicip.associate_instance_type=PORT | Get port ID from `hcloud ECS ListServersDetails --server_id=<id>` → `OS-EXT-IPS:port_id` |
|
|
46
|
-
| Unbind EIP | hcloud EIP DisassociatePublicips --publicip_id=<id> | references/
|
|
47
|
-
| Delete EIP | hcloud EIP DeletePublicip --publicip_id=<id> | references/
|
|
46
|
+
| Unbind EIP | hcloud EIP DisassociatePublicips --publicip_id=<id> | references/network.md |
|
|
47
|
+
| Delete EIP | hcloud EIP DeletePublicip --publicip_id=<id> | references/network.md |
|
|
48
48
|
| List EIPs | hcloud EIP ListPublicips | |
|
|
49
49
|
| NAT gateway | hcloud NAT CreateNatGateway --nat.name=<name> --nat.spec=<spec> --router_id=<vpc-id> --internal_network_id=<subnet-id> | Run `hcloud NAT CreateNatGateway --help` for available spec values |
|
|
50
50
|
|
|
@@ -109,8 +109,8 @@ If a command needs an `adminPass` or other password field, do not leave plaintex
|
|
|
109
109
|
## Language / Service Catalog Trap
|
|
110
110
|
|
|
111
111
|
- KooCLI loads the service list from a per-language catalog: `~/.hcloud/metaRepo/services_{cn,en}.json`. The **English catalog is incomplete** (149 services vs 220 in Chinese; ~70 services — BSS, DevStar, CloudTable, FRS, ASM, ... — are missing). A command against a service that only exists in the Chinese catalog fails with a misleading `Unsupported service: X`.
|
|
112
|
-
-
|
|
113
|
-
-
|
|
112
|
+
- KooCLI has **no per-command `--cli-lang` flag** — appending `--cli-lang=cn` is rejected as `不正确的参数:cli-lang`. The language switch is **global only**: `hcloud configure set --cli-lang=cn` (changes ALL CLI output language).
|
|
113
|
+
- `huaweicloud-devkit` detects `Unsupported service: X` and, when the cause is the en-catalog gap, returns an actionable hint to run `hcloud configure set --cli-lang=cn` (or to check the service name / refresh metadata for genuinely unknown services). It never mutates your command.
|
|
114
114
|
- Distinguish from **region** metadata limits (e.g., BSS only supports `cn-north-1`): a region error is NOT solved by switching language.
|
|
115
115
|
|
|
116
116
|
## Output Formatting
|
|
@@ -57,10 +57,13 @@ export function syncAuth(target = 'all') {
|
|
|
57
57
|
try {
|
|
58
58
|
obs = writeObsConfig(credentials);
|
|
59
59
|
} catch (error) {
|
|
60
|
+
const missingRegion = !String(credentials?.region || '').trim();
|
|
60
61
|
return {
|
|
61
62
|
ok: false,
|
|
62
63
|
error: error.message,
|
|
63
|
-
nextStep:
|
|
64
|
+
nextStep: missingRegion
|
|
65
|
+
? 'Credential region is missing — run huaweicloud_auth_switch action=persist with --region, or add "region" to creds-import.json and re-import.'
|
|
66
|
+
: 'Run "npx huaweicloud-devkit auth init" to refresh credentials and region.',
|
|
64
67
|
};
|
|
65
68
|
}
|
|
66
69
|
|
|
@@ -196,24 +196,6 @@ function obsWriteHint(args) {
|
|
|
196
196
|
return 'OBS write operations are obsutil-style and always write-class. Before executing, present the full resource manifest (bucket/object list) to the user for ONE batch approval, then run each command through plan → approve (see huawei-iac skill, Provisioning Rules).';
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
const OBS_SUBCOMMANDS = new Set([
|
|
200
|
-
'help',
|
|
201
|
-
'ls',
|
|
202
|
-
'mb',
|
|
203
|
-
'cp',
|
|
204
|
-
'mv',
|
|
205
|
-
'rm',
|
|
206
|
-
'chattri',
|
|
207
|
-
'config',
|
|
208
|
-
'cors',
|
|
209
|
-
'lifecycle',
|
|
210
|
-
'policy',
|
|
211
|
-
'share',
|
|
212
|
-
'sync',
|
|
213
|
-
'url',
|
|
214
|
-
'sign',
|
|
215
|
-
]);
|
|
216
|
-
|
|
217
199
|
const CATALOG_TTL_MS = 5 * 60 * 1000;
|
|
218
200
|
let _catalogCache = { dir: null, t: 0, cn: new Set(), en: new Set() };
|
|
219
201
|
|
|
@@ -252,19 +234,9 @@ export function classifyUnsupported(service, metaDir) {
|
|
|
252
234
|
}
|
|
253
235
|
// One or both catalog files are missing locally — we cannot distinguish
|
|
254
236
|
// "service missing from the en catalog" from "en catalog not downloaded".
|
|
255
|
-
// Only the reactive fallback may attempt --cli-lang=cn for this case.
|
|
256
237
|
return 'unknown';
|
|
257
238
|
}
|
|
258
239
|
|
|
259
|
-
export function shouldInjectLang(args, metaDir) {
|
|
260
|
-
const arr = Array.isArray(args) ? args.map(String) : [];
|
|
261
|
-
if (arr.some((a) => /^--cli-lang=/.test(a))) return false;
|
|
262
|
-
const s = (arr[0] || '').toUpperCase();
|
|
263
|
-
if (!s) return false;
|
|
264
|
-
if (s === 'OBS' && OBS_SUBCOMMANDS.has((arr[1] || '').toLowerCase())) return false;
|
|
265
|
-
return classifyUnsupported(s, metaDir) === 'lang-missing';
|
|
266
|
-
}
|
|
267
|
-
|
|
268
240
|
export function planHcloudCommand(args, options = {}) {
|
|
269
241
|
const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
|
|
270
242
|
const classification = classifyHcloudArgs(normalizedArgs, options);
|
|
@@ -301,7 +273,8 @@ const UNSUPPORTED_SERVICE_RE = /Unsupported service:\s*([A-Za-z0-9_-]+)/i;
|
|
|
301
273
|
function appendLangHint(result, service, metaDir) {
|
|
302
274
|
const cause = classifyUnsupported(service, metaDir);
|
|
303
275
|
if (cause === 'lang-missing' || cause === 'unknown') {
|
|
304
|
-
const nextStep =
|
|
276
|
+
const nextStep =
|
|
277
|
+
'KooCLI switches language only via global config: hcloud configure set --cli-lang=cn (changes CLI output language; BSS requires Chinese mode).';
|
|
305
278
|
return {
|
|
306
279
|
...result,
|
|
307
280
|
langCause: cause,
|
|
@@ -318,16 +291,6 @@ function appendLangHint(result, service, metaDir) {
|
|
|
318
291
|
};
|
|
319
292
|
}
|
|
320
293
|
|
|
321
|
-
function isObsUtilStyle(args) {
|
|
322
|
-
const s = String(args[0] || '').toUpperCase();
|
|
323
|
-
return s === 'OBS' && OBS_SUBCOMMANDS.has(String(args[1] || '').toLowerCase());
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function canRetryLang(service, metaDir) {
|
|
327
|
-
const cause = classifyUnsupported(service, metaDir);
|
|
328
|
-
return cause === 'lang-missing' || cause === 'unknown';
|
|
329
|
-
}
|
|
330
|
-
|
|
331
294
|
export async function runHcloud(args, options = {}) {
|
|
332
295
|
const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
|
|
333
296
|
const plan = {
|
|
@@ -337,44 +300,13 @@ export async function runHcloud(args, options = {}) {
|
|
|
337
300
|
assertAllowed(plan.classification);
|
|
338
301
|
|
|
339
302
|
const metaDir = options.metaDir;
|
|
340
|
-
const langGiven = normalizedArgs.some((a) => /^--cli-lang=/.test(a));
|
|
341
|
-
const injectedArgs =
|
|
342
|
-
!langGiven && shouldInjectLang(normalizedArgs, metaDir) ? [...normalizedArgs, '--cli-lang=cn'] : null;
|
|
343
|
-
|
|
344
|
-
if (injectedArgs) {
|
|
345
|
-
const result = await runHcloudOnceWithRetries(
|
|
346
|
-
{
|
|
347
|
-
...plan,
|
|
348
|
-
rawArgs: injectedArgs,
|
|
349
|
-
},
|
|
350
|
-
options,
|
|
351
|
-
);
|
|
352
|
-
const tagged = {
|
|
353
|
-
...result,
|
|
354
|
-
autoRetried: true,
|
|
355
|
-
injection: 'proactive',
|
|
356
|
-
injectedLang: 'cn',
|
|
357
|
-
};
|
|
358
|
-
if (tagged.ok) return tagged;
|
|
359
|
-
const match = `${result.stderr || ''}\n${result.stdout || ''}`.match(UNSUPPORTED_SERVICE_RE);
|
|
360
|
-
return match ? appendLangHint(tagged, match[1], metaDir) : tagged;
|
|
361
|
-
}
|
|
362
303
|
|
|
304
|
+
// KooCLI only switches language globally (`hcloud configure set --cli-lang=cn`); there is no
|
|
305
|
+
// per-command `--cli-lang` flag (it is rejected as "不正确的参数:cli-lang"). So we never mutate
|
|
306
|
+
// the command — we run it as-is and annotate `Unsupported service` with an actionable cause.
|
|
363
307
|
const result = await runHcloudOnceWithRetries(plan, options);
|
|
364
308
|
const text = `${result.stderr || ''}\n${result.stdout || ''}`;
|
|
365
309
|
const match = text.match(UNSUPPORTED_SERVICE_RE);
|
|
366
|
-
if (match && !langGiven && !result.ok && canRetryLang(match[1], metaDir) && !isObsUtilStyle(normalizedArgs)) {
|
|
367
|
-
const retry = await runHcloudOnceWithRetries(
|
|
368
|
-
{
|
|
369
|
-
...plan,
|
|
370
|
-
rawArgs: [...normalizedArgs, '--cli-lang=cn'],
|
|
371
|
-
},
|
|
372
|
-
options,
|
|
373
|
-
);
|
|
374
|
-
return retry.ok
|
|
375
|
-
? { ...retry, autoRetried: true, reactiveFallback: true, injection: 'reactive', injectedLang: 'cn' }
|
|
376
|
-
: appendLangHint(retry, match[1], metaDir);
|
|
377
|
-
}
|
|
378
310
|
if (match && !result.ok) {
|
|
379
311
|
return appendLangHint(result, match[1], metaDir);
|
|
380
312
|
}
|
|
@@ -961,9 +961,23 @@ function readImportFile() {
|
|
|
961
961
|
region: String(data.region || ''),
|
|
962
962
|
};
|
|
963
963
|
} catch {
|
|
964
|
+
// Malformed/undecodable import file is un-replayable — wipe it. A VALID
|
|
965
|
+
// file is kept so a rejected persist can be replayed (see #502).
|
|
966
|
+
try {
|
|
967
|
+
rmSync(path, { force: true });
|
|
968
|
+
} catch {
|
|
969
|
+
// ignore
|
|
970
|
+
}
|
|
964
971
|
return null;
|
|
965
|
-
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
function clearImportFile() {
|
|
976
|
+
const path = join(dirname(globalCredentialsPath()), 'creds-import.json');
|
|
977
|
+
try {
|
|
966
978
|
rmSync(path, { force: true });
|
|
979
|
+
} catch {
|
|
980
|
+
// best-effort: an absent or locked file is not an error
|
|
967
981
|
}
|
|
968
982
|
}
|
|
969
983
|
|
|
@@ -996,7 +1010,7 @@ function persistCredentials(ak, sk, securityToken, region) {
|
|
|
996
1010
|
writeLastSync({ kooCliProfile: profile, s1Fingerprint: fingerprint(ak, sk) });
|
|
997
1011
|
}
|
|
998
1012
|
return {
|
|
999
|
-
status: 'ok',
|
|
1013
|
+
status: obs.ok && hcloud.ok ? 'ok' : 'partial',
|
|
1000
1014
|
scope: 'persist',
|
|
1001
1015
|
backedUp: Boolean(before),
|
|
1002
1016
|
obs: obs.ok ? { configured: true } : { configured: false, error: obs.error },
|
|
@@ -1124,10 +1138,14 @@ export async function callTool(name, rawArgs = {}) {
|
|
|
1124
1138
|
let securityToken = args.securityToken || '';
|
|
1125
1139
|
let region = args.region || '';
|
|
1126
1140
|
const sourceChannel = args.mode || 'memory';
|
|
1141
|
+
let importedFromFile = false;
|
|
1127
1142
|
|
|
1128
1143
|
if (sourceChannel === 'import' && (!ak || !sk)) {
|
|
1129
1144
|
const imported = readImportFile();
|
|
1130
|
-
if (imported)
|
|
1145
|
+
if (imported) {
|
|
1146
|
+
({ ak, sk, securityToken, region } = imported);
|
|
1147
|
+
importedFromFile = true;
|
|
1148
|
+
}
|
|
1131
1149
|
}
|
|
1132
1150
|
if (sourceChannel === 'mcp-config' && (!ak || !sk)) {
|
|
1133
1151
|
const cc = readCodeArtsCredentials();
|
|
@@ -1143,9 +1161,19 @@ export async function callTool(name, rawArgs = {}) {
|
|
|
1143
1161
|
throw new Error('ak and sk are required (or provide creds-import.json for mode=import).');
|
|
1144
1162
|
}
|
|
1145
1163
|
|
|
1164
|
+
if (action === 'persist' && !String(region || '').trim()) {
|
|
1165
|
+
return {
|
|
1166
|
+
status: 'error',
|
|
1167
|
+
scope: 'invalid_region',
|
|
1168
|
+
error:
|
|
1169
|
+
'region is required to persist credentials. Pass --region, or include "region" in creds-import.json (mode=import).',
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1146
1173
|
if (action === 'temporary') {
|
|
1147
1174
|
setRuntimeCredentials(ak, sk, securityToken || undefined, region);
|
|
1148
1175
|
refreshUserHashAfterAuthChange();
|
|
1176
|
+
if (importedFromFile) clearImportFile();
|
|
1149
1177
|
return {
|
|
1150
1178
|
status: 'ok',
|
|
1151
1179
|
scope: 'temporary',
|
|
@@ -1166,6 +1194,7 @@ export async function callTool(name, rawArgs = {}) {
|
|
|
1166
1194
|
newRegion: region,
|
|
1167
1195
|
oldFingerprint: fingerprint(prev.ak, prev.sk),
|
|
1168
1196
|
newFingerprint: fingerprint(ak, sk),
|
|
1197
|
+
fromImport: importedFromFile,
|
|
1169
1198
|
});
|
|
1170
1199
|
return {
|
|
1171
1200
|
status: 'needs_confirmation',
|
|
@@ -1178,6 +1207,10 @@ export async function callTool(name, rawArgs = {}) {
|
|
|
1178
1207
|
}
|
|
1179
1208
|
|
|
1180
1209
|
const persisted = persistCredentials(ak, sk, securityToken, region);
|
|
1210
|
+
// Clear the import file for non-replayable outcomes (success, or an
|
|
1211
|
+
// unfixable rejection such as STS R3). Keep it only for a retryable
|
|
1212
|
+
// 'partial' (S1 written but a mirror failed).
|
|
1213
|
+
if (importedFromFile && persisted.status !== 'partial') clearImportFile();
|
|
1181
1214
|
refreshUserHashAfterAuthChange();
|
|
1182
1215
|
return persisted;
|
|
1183
1216
|
}
|
|
@@ -1189,6 +1222,7 @@ export async function callTool(name, rawArgs = {}) {
|
|
|
1189
1222
|
return { status: 'ok', outcome: 'aborted', message: '保持 S1 现有账号,未覆盖。' };
|
|
1190
1223
|
}
|
|
1191
1224
|
const confirmed = persistCredentials(pending.newAk, pending.newSk, pending.newSecurityToken, pending.newRegion);
|
|
1225
|
+
if (pending.fromImport && confirmed.status !== 'partial') clearImportFile();
|
|
1192
1226
|
refreshUserHashAfterAuthChange();
|
|
1193
1227
|
return confirmed;
|
|
1194
1228
|
}
|