huaweicloud-devkit 1.1.7-next.2 → 1.1.8-next.0

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.
Files changed (28) hide show
  1. package/README.md +13 -1
  2. package/README.zh-CN.md +13 -1
  3. package/package.json +2 -1
  4. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
  5. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
  6. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
  7. package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
  8. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
  9. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +19 -1
  10. package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
  11. package/plugins/huaweicloud-core/safety/policy.json +1 -1
  12. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +57 -2
  13. package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +19 -16
  14. package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +1 -1
  15. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +6 -0
  16. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +1 -1
  17. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +7 -1
  18. package/plugins/huaweicloud-core/src/auth/service.mjs +36 -0
  19. package/plugins/huaweicloud-core/src/detect-framework.mjs +41 -5
  20. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +46 -7
  21. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +94 -1
  22. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +1 -1
  23. package/plugins/huaweicloud-core/src/safety-policy.mjs +165 -33
  24. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +181 -10
  25. package/plugins/huaweicloud-core/src/search-market.mjs +40 -1
  26. package/plugins/huaweicloud-core/src/setup-cli.mjs +34 -0
  27. package/plugins/huaweicloud-core/src/tools.mjs +266 -17
  28. package/rules/huawei-agent-rules.mdc +78 -0
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
5
5
  [![CI](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml/badge.svg)](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml)
6
6
  [![npm version](https://img.shields.io/npm/v/huaweicloud-devkit)](https://www.npmjs.com/package/huaweicloud-devkit)
7
- [![Beta](https://img.shields.io/badge/beta-v1.1.7-orange)](https://github.com/huaweicloud/huaweicloud-devkit)
7
+ [![Beta](https://img.shields.io/badge/beta-v1.1.8-orange)](https://github.com/huaweicloud/huaweicloud-devkit)
8
8
 
9
9
  **[中文](README.zh-CN.md) | English**
10
10
 
@@ -332,6 +332,18 @@ npx --yes huaweicloud-devkit@latest update --target all
332
332
  config. Always keep `@latest` so npm fetches the newest version instead of a
333
333
  locally cached older one.
334
334
 
335
+ ### Configure Proxy
336
+
337
+ ```bash
338
+ npx --yes huaweicloud-devkit proxy init # configure proxy (interactive)
339
+ npx --yes huaweicloud-devkit proxy show # show effective proxy settings
340
+ npx --yes huaweicloud-devkit proxy clear # remove proxy configuration
341
+ ```
342
+
343
+ Configures HTTP/HTTPS proxy for connections to Huawei Cloud services (e.g. enterprise intranet environments). Settings are saved to `~/.config/huaweicloud/proxy.json`.
344
+
345
+ Environment variables `HTTPS_PROXY`, `HTTP_PROXY`, and `NO_PROXY` take precedence over the file configuration — use `proxy show` to inspect the effective settings.
346
+
335
347
  ## What It Does
336
348
 
337
349
  - **Guided cloud operations** — agents get step-by-step guidance for 20+ commonly used Huawei Cloud services (ECS, OBS, VPC, RDS, GaussDB, FunctionGraph, APIG, CCE, and more)
package/README.zh-CN.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
5
5
  [![CI](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml/badge.svg)](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml)
6
6
  [![npm version](https://img.shields.io/npm/v/huaweicloud-devkit)](https://www.npmjs.com/package/huaweicloud-devkit)
7
- [![Beta](https://img.shields.io/badge/beta-v1.1.7-orange)](https://github.com/huaweicloud/huaweicloud-devkit)
7
+ [![Beta](https://img.shields.io/badge/beta-v1.1.8-orange)](https://github.com/huaweicloud/huaweicloud-devkit)
8
8
 
9
9
  **中文 | [English](README.md)**
10
10
 
@@ -326,6 +326,18 @@ npx --yes huaweicloud-devkit@latest update --target all
326
326
 
327
327
  `update` 是增量更新——只刷新已安装的文件,不动配置文件。请务必保留 `@latest`,确保 npm 获取最新版本而非本地缓存的旧版本。
328
328
 
329
+ ### 配置代理
330
+
331
+ ```bash
332
+ npx --yes huaweicloud-devkit proxy init # 配置代理(交互式)
333
+ npx --yes huaweicloud-devkit proxy show # 查看生效的代理设置
334
+ npx --yes huaweicloud-devkit proxy clear # 删除代理配置
335
+ ```
336
+
337
+ 为连接华为云服务配置 HTTP/HTTPS 代理(如企业内网环境)。配置保存在 `~/.config/huaweicloud/proxy.json`。
338
+
339
+ 环境变量 `HTTPS_PROXY`、`HTTP_PROXY`、`NO_PROXY` 优先级高于文件配置——可使用 `proxy show` 查看最终生效的设置。
340
+
329
341
  ## 功能特性
330
342
 
331
343
  - **引导式云操作** — Agent 获得 20+ 常用华为云服务的分步操作指引(ECS、OBS、VPC、RDS、GaussDB、FunctionGraph、APIG、CCE 等)
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.7-next.2",
4
+ "version": "1.1.8-next.0",
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",
@@ -9,6 +9,7 @@
9
9
  "cordis.patch.yml",
10
10
  "bin",
11
11
  ".agents",
12
+ "rules",
12
13
  "plugins/huaweicloud-core",
13
14
  "integrations/hermes",
14
15
  "integrations/opencode",
@@ -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.7-next.2",
20
+ "version": "1.1.8-next.0",
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.7-next.2",
23
+ "version": "1.1.8-next.0",
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.7-next.2",
20
+ "version": "1.1.8-next.0",
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.7-next.2",
3
+ "version": "1.1.8-next.0",
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.7-next.2",
3
+ "version": "1.1.8-next.0",
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",
@@ -51,7 +51,23 @@ def load_policy():
51
51
  load_policy()
52
52
 
53
53
  ENV_DUMP_RE = re.compile(
54
- r"(env|printenv|Get-ChildItem\s+Env:|gci\s+Env:|dir\s+Env:).*(HUAWEICLOUD|HWC_|HCLOUD|OS_)", re.I,
54
+ r"(env|printenv|Get-ChildItem\s+Env:|gci\s+Env:|dir\s+Env:).*(HUAWEICLOUD|HWC_|HCLOUD|HW_|OS_)", re.I,
55
+ )
56
+ # Credential variable references bypass the env-command gate above: HW_ is
57
+ # the plugin's own documented credential prefix (HW_ACCESS_KEY/HW_SECRET_KEY/
58
+ # HW_SECURITY_TOKEN), cloud environments also expose the AWS-style
59
+ # HUAWEICLOUD_SECRET_ACCESS_KEY name, and `echo $HW_SECRET_KEY` /
60
+ # `printenv HW_ACCESS_KEY` / `echo $HUAWEICLOUD_SECRET_ACCESS_KEY` previously
61
+ # fell through to allow (#561 D4-2, #770 D4-2).
62
+ #
63
+ # The negative lookbehind exempts literal-NAME references — backslash-escaped
64
+ # (\$HW_*) or single-quoted ('$HW_*', which the shell never expands) —
65
+ # while unescaped `$HW_*` is a potential expansion/dump regardless of the
66
+ # command. Aligned with safety-policy.mjs L419-423.
67
+ CRED_VAR_REF_RE = re.compile(
68
+ r"(?<!['\\])\$\{?(?:HUAWEICLOUD|HWC|HW|OS)_(?:SECRET_ACCESS_KEY|ACCESS_KEY|SECRET_KEY|SECURITY_TOKEN)"
69
+ r"|(?:^|\s)printenv\s+(?:HUAWEICLOUD|HWC|HW|OS)_(?:SECRET_ACCESS_KEY|ACCESS_KEY|SECRET_KEY|SECURITY_TOKEN)",
70
+ re.I,
55
71
  )
56
72
  HCLOUD_RE = re.compile(r"(^|\s)hcloud(\.exe)?\s+", re.I)
57
73
  READ_OPERATION_RE = re.compile(r"\b(List|Show|Get|Describe|NovaList|NovaShow)\w*", re.I)
@@ -177,6 +193,8 @@ def evaluate(tool_name, tool_input):
177
193
  return "reading Huawei Cloud credential/profile files can expose AK/SK or tokens. Use redacted toolkit tools."
178
194
  if ENV_DUMP_RE.search(text):
179
195
  return "dumping cloud credential environment variables is not allowed."
196
+ if CRED_VAR_REF_RE.search(text):
197
+ return "printing cloud credential environment variables is not allowed."
180
198
  if SECRET_READ_RE and SECRET_READ_RE.search(text):
181
199
  return "direct secret value retrieval would put plaintext secrets into the agent context."
182
200
  denied_rule = first_denied_command_rule(text)
@@ -2,7 +2,7 @@
2
2
  "name": "huaweicloud-devkit",
3
3
  "id": "huaweicloud-devkit",
4
4
  "displayName": "HuaweiCloud DevKit",
5
- "version": "1.1.7-next.2",
5
+ "version": "1.1.8-next.0",
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.",
@@ -23,7 +23,7 @@
23
23
  "huaweicloud[/\\\\](config|credentials)"
24
24
  ],
25
25
  "blockedConfigureSubcommands": ["show", "get", "list", "export", "init"],
26
- "blockedSecretOperations": ["ShowSecretVersion", "DownloadSecret", "GetSecretValue"],
26
+ "blockedSecretOperations": ["ShowSecret", "ShowSecretVersion", "DownloadSecret", "GetSecretValue"],
27
27
  "writeOperationPrefixes": [
28
28
  "Create",
29
29
  "BatchCreate",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  {
38
38
  "field": "text",
39
- "regex": "(HUAWEICLOUD|HWC_|HCLOUD|OS_)"
39
+ "regex": "(HUAWEICLOUD|HWC_|HCLOUD|HW_|OS_)"
40
40
  }
41
41
  ]
42
42
  },
@@ -53,7 +53,7 @@
53
53
  "any": [
54
54
  {
55
55
  "field": "text",
56
- "regex": "(ShowSecretVersion|DownloadSecret|GetSecretValue)"
56
+ "regex": "(ShowSecretVersion|ShowSecret|DownloadSecret|GetSecretValue)"
57
57
  },
58
58
  {
59
59
  "field": "text",
@@ -64,6 +64,23 @@
64
64
  "message": "The command appears to retrieve plaintext secret values.",
65
65
  "remediation": "Use runtime secret references, user-side inspection, or redacted metadata checks instead of returning secret values to the agent."
66
66
  },
67
+ {
68
+ "id": "hwc-command-adminpass-exposure",
69
+ "title": "Plaintext adminPass exposure",
70
+ "category": "secret",
71
+ "severity": "warn",
72
+ "stages": ["artifact", "deploy_plan"],
73
+ "match": {
74
+ "any": [
75
+ {
76
+ "field": "text",
77
+ "regex": "(adminPass|admin_pass|admin-password)"
78
+ }
79
+ ]
80
+ },
81
+ "message": "The artifact or plan contains a plaintext adminPass parameter. The password may be exposed in version control, logs, or agent context.",
82
+ "remediation": "Use userData cloud-init, KMS/CSMS secret references, or encrypted password injection instead of embedding adminPass as plaintext in deployment files."
83
+ },
67
84
  {
68
85
  "id": "hwc-command-sts-credential",
69
86
  "title": "Temporary credential exposure",
@@ -200,6 +217,23 @@
200
217
  "message": "The change appears to grant broad administrator permissions.",
201
218
  "remediation": "Use least-privilege service actions, resource constraints, and short-lived credentials for generated application workflows."
202
219
  },
220
+ {
221
+ "id": "hwc-iam-highrisk-write",
222
+ "title": "High-risk IAM write operation",
223
+ "category": "iam",
224
+ "severity": "warn",
225
+ "stages": ["command"],
226
+ "match": {
227
+ "any": [
228
+ {
229
+ "field": "text",
230
+ "regex": "hcloud\\s+IAM\\s+(CreateUser|CreatePolicy|CreateRole|CreateAgency)\\b"
231
+ }
232
+ ]
233
+ },
234
+ "message": "This hcloud command creates or modifies IAM principals (users, policies, roles, or agencies). These operations can grant elevated privileges.",
235
+ "remediation": "Confirm the principal name, scope, and attached permissions with the user before creating IAM users, policies, roles, or agencies."
236
+ },
203
237
  {
204
238
  "id": "hwc-destructive-delete-force",
205
239
  "title": "Forced destructive operation",
@@ -334,6 +368,27 @@
334
368
  "message": "The generated plan may create high-cost or unbounded capacity.",
335
369
  "remediation": "Use small preview defaults, explicit quotas, budget labels, and user approval before creating high-cost resources."
336
370
  },
371
+ {
372
+ "id": "hwc-command-secret-in-arg",
373
+ "title": "Plaintext secret in command argument",
374
+ "category": "secret",
375
+ "severity": "warn",
376
+ "stages": ["command"],
377
+ "match": {
378
+ "all": [
379
+ {
380
+ "field": "text",
381
+ "regex": "\\bhcloud\\b"
382
+ },
383
+ {
384
+ "field": "text",
385
+ "regex": "(--adminPass|--password|--secret|--token)\\s*[=:]\\s*[^\\s<]"
386
+ }
387
+ ]
388
+ },
389
+ "message": "The command passes a plaintext password or secret as a direct CLI argument, which may leak into shell history, logs, or process listings.",
390
+ "remediation": "Use environment variables, DEW secret references, or interactive prompts instead of passing secrets as command-line arguments."
391
+ },
337
392
  {
338
393
  "id": "hwc-sandbox-destructive-command",
339
394
  "title": "Destructive command in sandbox terminal",
@@ -16,25 +16,28 @@ 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 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` |
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
+ | Partner vs Customer-level APIs | BSS splits operations by caller identity. Customer-level APIs (`ShowCustomerAccountBalances`, `ListCustomerBillsFeeRecords`, `ListCustomerCouponChangeRecords`) work with a normal IAM user. Partner-level APIs (`ListQuotaCoupons`, `ListSubCustomerCoupons`) require a partner/dealer identity. |
27
+ | APIGW.0301 is identity, not domain-id | `APIGW.0301: invalid x-security-token` on a BSS Partner-level API means the caller identity is not a partner — a customer credential is rejected. This is NOT a missing `cli-domain-id` and NOT invalid AK/SK. Switch to the Customer-level API below that matches the current identity. |
27
28
 
28
29
  ## Common Workflows
29
30
 
30
- | Task | Operation |
31
- | -------------------- | ---------------------------------------------------------------------- |
32
- | List costs | `ListCosts --cli-region=cn-north-1 --project_id=<p>` |
33
- | List customer bills | `ListCustomerBillsFeeRecords --cli-region=cn-north-1 --project_id=<p>` |
34
- | List resource usage | `ListResourceUsage --cli-region=cn-north-1 --project_id=<p>` |
35
- | List sub-customers | `ListConsumeSubCustomers --cli-region=cn-north-1 --project_id=<p>` |
36
- | Show account balance | `ShowCustomerAccountBalances --cli-region=cn-north-1` |
37
- | List conversions | `ListConversions --cli-region=cn-north-1 --project_id=<p>` |
31
+ | Task | Identity | Operation |
32
+ | -------------------------- | -------- | ---------------------------------------------------------------------- |
33
+ | List costs | Customer | `ListCosts --cli-region=cn-north-1 --project_id=<p>` |
34
+ | List customer bills | Customer | `ListCustomerBillsFeeRecords --cli-region=cn-north-1 --project_id=<p>` |
35
+ | Show account balance | Customer | `ShowCustomerAccountBalances --cli-region=cn-north-1` |
36
+ | List coupon change records | Customer | `ListCustomerCouponChangeRecords --cli-region=cn-north-1` |
37
+ | List resource usage | Customer | `ListResourceUsage --cli-region=cn-north-1 --project_id=<p>` |
38
+ | List conversions | Customer | `ListConversions --cli-region=cn-north-1 --project_id=<p>` |
39
+ | List sub-customers | Partner | `ListConsumeSubCustomers --cli-region=cn-north-1 --project_id=<p>` |
40
+ | List quota coupons | Partner | `ListQuotaCoupons --cli-region=cn-north-1` (APIGW.0301 for customers) |
38
41
 
39
42
  Discover exact parameters with `--help` before executing any command. All BSS operations are read-only.
40
43
 
@@ -69,5 +69,5 @@ Discover operation parameters with `--help` before executing any write operation
69
69
  For advanced workflows (notebook lifecycle, training job diagnosis,
70
70
  hyperparameter tuning), search the marketplace:
71
71
  `huaweicloud_search_marketplace` query="ModelArts notebook/training/diagnosis"
72
- → install via `npx skills add huaweicloud/huaweicloud-skills --skill <name> -y -g`
72
+ → install via the marketplace tool's `installCommand` (HTTP ZIP download, no git required) and its `installHint` on failure.
73
73
  → load and execute.
@@ -852,6 +852,10 @@ If the status code is not 2xx/3xx:
852
852
 
853
853
  ### Step 7: Expose via DevBridge [REQUIRED — deployment incomplete without this]
854
854
 
855
+ **Preferred: call `huaweicloud_sandbox_expose_tunnel` with the ACTUAL port returned by `deploy_nginx` (`{"port": <port>}`)** — it authenticates automatically (probing AK/SK vs API-Key builds), pre-cleans stale tunnels, starts the host, extracts the public URL, and health-checks it before returning. If it reports `ok: false` with an API-Key hint, create the key and re-run `sandbox_credentials`, then call it again. The tool's returned `publicUrl` is the deployment result.
856
+
857
+ **Manual fallback** (only when the tool is unavailable or must be debugged by hand): follow [Expose the deployed app](#expose-the-deployed-app-public-url) below.
858
+
855
859
  When `deploy_check` returns `nextStep: expose_via_devbridge`, it also returns an executable `remediation` string — run those devbridge commands in the sandbox (after `source /tmp/hw_creds.sh`). Until a tunnel exists, `deploy_check` returns no `publicUrl`; do not fabricate a URL.
856
860
 
857
861
  Follow the standard [Expose the deployed app](#expose-the-deployed-app-public-url) procedure. The app is already running on the detected port — only DevBridge tunnel setup is needed.
@@ -860,6 +864,8 @@ Use `exec_with_session` to background DevBridge. For SSR, DevBridge tunnels the
860
864
 
861
865
  **Pre-flight**: always run `devbridge delete-all` before creating a new tunnel to prevent `10006: quota exceeded` from accumulated stale tunnels. If you still get quota error, list tunnels with `devbridge list` (0.2.x plain table; 0.1.x legacy `-j` JSON), delete stale ones, and retry.
862
866
 
867
+ > **Concurrency warning**: `devbridge delete-all` and `pkill -f "devbridge host"` are GLOBAL operations scoped to the whole sandbox — if multiple sessions share the same sandbox, pre-cleaning can kill tunnels published by other sessions and invalidate URLs you handed out earlier. For shared sandboxes, prefer `devbridge list` and delete only stale/your-own tunnels instead of blanket `delete-all`, and re-expose only after confirming the target port is still served.
868
+
863
869
  Extract the tunnel URL from DevBridge output. The public URL has the form `https://<id>-<port>.devbridge-s2.hwtunnel.com`. **Return this URL to the developer as the deployment result.**
864
870
 
865
871
  #### Cross-platform H5 QR code
@@ -21,7 +21,7 @@ Use this skill to turn vague developer intent into a precise Huawei Cloud capabi
21
21
  8. Treat Terraform as a secondary V1 path for reviewed IaC, not the default.
22
22
  9. When no built-in devkit skill matches:
23
23
  a. Search: use `huaweicloud_search_marketplace` to find a matching skill in the community marketplace
24
- b. Install: run `npx skills add huaweicloud/huaweicloud-skills --skill <skill-name> -y -g`
24
+ b. Install: use the `installCommand` returned by the marketplace tool — it downloads the skill repo ZIP over HTTP and copies the skill folder into the agent skills directory. **No git is required** (`npx skills add` needs git on PATH, which is often absent on Windows Server/sandbox environments). Replace the `<your-agent-skills-dir>` placeholder with this agent's skills directory (Claude Code `~/.claude/skills`, OpenCode `~/.config/opencode/skills`, Cursor `~/.cursor/skills`) before running. If the command reports `SKILL_SRC_NOT_FOUND` (mis-pathed skill location) or the HTTP download itself fails, follow the returned `installHint`: download `https://github.com/huaweicloud/huaweicloud-skills/archive/refs/heads/master.zip` manually, extract it, copy the skill folder, and confirm to the user how it was installed. Never auto-install git just to fetch a skill.
25
25
  c. Execute: load the installed skill and follow its procedure
26
26
  10. When the deliverable is a PPT, architecture diagram (draw.io), or frontend page that needs official Huawei Cloud service logos, use the `huaweicloud_get_service_icon` MCP tool to get logo URLs from the official Icons library instead of guessing or hotlinking unofficial images.
27
27
 
@@ -84,7 +84,13 @@ Agent processes find executables through `PATH`. If OpenCode/Codex cannot find `
84
84
  2. **KooCLI first-run privacy agreement**: On a fresh KooCLI install, `hcloud` blocks with `同意并继续使用(y)/不同意并退出(N)` and fails with `[USE_ERROR]您输入的是无效字符` in non-interactive mode. Detection: check command output for these strings. Ask the user: "KooCLI needs to accept its privacy agreement. May I accept it on your behalf?" If the user agrees, run `huaweicloud_run_readonly_command` with `args=["version"]` and `stdin="y\n"`. This accepts the agreement once, after which hcloud works normally.
85
85
  3. Ask the user to configure credentials outside the agent conversation when setup is needed.
86
86
  4. Inspect profile and region only through redacted tooling.
87
- 5. Discover exact operation names with `hcloud <Service> --help` before guessing. Example: ECS instance listing is commonly `ECS ListServersDetails`; ECS creation is commonly `ECS CreateServers`; image lookup may be under `IMS GlanceShowImage`.
87
+ 5. **Always discover exact operation names and parameters with `--help` BEFORE constructing the command.** Never guess from general knowledge — parameter names change across API versions. The discovery chain is:
88
+ 1. `hcloud --help` → available services
89
+ 2. `hcloud <Service> --help` → operations (use `huaweicloud_list_operations` or step 2/3 directly)
90
+ 3. `hcloud <Service> <Operation> --help` → exact parameter names, required flags, and value formats
91
+ 4. Execute only after the help text confirms the operation and its parameters
92
+ - Service skills may skip steps 1-2 when they already provide the correct service and operation names, but step 3 (`<Operation> --help`) is never skippable before planning a write operation or a command with parameters.
93
+ - Example: ECS instance listing is commonly `ECS ListServersDetails`; ECS creation is commonly `ECS CreateServers`; image lookup may be under `IMS GlanceShowImage`.
88
94
  6. Use `--cli-output=json` for machine-readable responses when supported.
89
95
  7. For resource operations, include `--cli-region`, `--cli-profile`, and service-specific project information when required.
90
96
  8. Classify every command before running it:
@@ -8,8 +8,10 @@ import {
8
8
  globalCredentialsPath,
9
9
  isPlaceholder,
10
10
  obsConfigPath,
11
+ parseStsExpiry,
11
12
  readCodeArtsCredentials,
12
13
  readGlobalCredentials,
14
+ resolveCredentialsWithRuntime,
13
15
  writeLastSync,
14
16
  writeObsConfig,
15
17
  } from './credentials.mjs';
@@ -162,18 +164,52 @@ export function computeOnboarding({ credentials, reconciled } = {}) {
162
164
  return { needsSetup: true, scenario, reason, message, steps, accountHint };
163
165
  }
164
166
 
167
+ // Compute temporary-STS expiry info for auth_status. Permanent AK/SK creds carry
168
+ // no security token and therefore have no expiry — we return null so callers only
169
+ // see expiry for the platform-injected temporary STS case.
170
+ function computeStsExpiry() {
171
+ let creds;
172
+ try {
173
+ creds = resolveCredentialsWithRuntime({ allowMissing: true });
174
+ } catch {
175
+ creds = null;
176
+ }
177
+ if (!creds?.ak || !creds?.sk || !creds.securityToken) return null;
178
+ const expiresAt = parseStsExpiry({ securityToken: creds.securityToken });
179
+ if (expiresAt === null) return { status: 'unknown', expiresAt: null };
180
+ const remainingMs = expiresAt - Date.now();
181
+ const expiresAtUtc = new Date(expiresAt).toISOString();
182
+ const expiresAtLocal = new Date(expiresAt).toString();
183
+ let status = 'valid';
184
+ if (remainingMs <= 0) status = 'expired';
185
+ else if (remainingMs <= 5 * 60 * 1000) status = 'expiring_soon';
186
+ return { expiresAt, expiresAtUtc, expiresAtLocal, remainingMs, status };
187
+ }
188
+
165
189
  export function getAuthStatus(target = 'all') {
166
190
  const credentials = readGlobalCredentials();
167
191
  const reconciled = { ...exportStateForStatus(), runtimeActive: hasRuntimeCredentials() };
168
192
  const hcloud = probeHcloud();
169
193
  const s4Creds = readCodeArtsCredentials();
170
194
  const onboarding = computeOnboarding({ credentials, reconciled });
195
+ const stsExpiry = computeStsExpiry();
196
+ const credentialPanel = {
197
+ sts: stsExpiry, // 临时 STS(MCP==沙箱同源)
198
+ s1: {
199
+ configured: Boolean(credentials?.ak && credentials?.sk),
200
+ fingerprint: credentials?.ak ? fingerprint(credentials.ak, credentials.sk) : null,
201
+ persistent: true, // 永久,无过期
202
+ },
203
+ activeSource: stsExpiry ? 'temporary-sts' : credentials?.ak ? 's1-persistent' : 'none',
204
+ };
171
205
  return {
172
206
  target,
173
207
  credentialsConfigured: Boolean(credentials?.ak && credentials?.sk),
174
208
  credentialsPath: globalCredentialsPath(),
175
209
  mcpSettingsConfigured: Boolean(s4Creds?.ak && s4Creds?.sk),
176
210
  mcpSettingsSource: s4Creds ? 'codearts' : null,
211
+ stsExpiry,
212
+ credentialPanel,
177
213
  obsConfigured: existsSync(obsConfigPath()),
178
214
  obsConfigPath: obsConfigPath(),
179
215
  kooCliInstalled: hcloud.installed,
@@ -282,7 +282,8 @@ export function detectFramework(projectPath) {
282
282
  if (
283
283
  existsSync(join(projectPath, 'next.config.js')) ||
284
284
  existsSync(join(projectPath, 'next.config.mjs')) ||
285
- existsSync(join(projectPath, 'next.config.ts'))
285
+ existsSync(join(projectPath, 'next.config.ts')) ||
286
+ existsSync(join(projectPath, 'next.config.cjs'))
286
287
  ) {
287
288
  return frameworkResult(FRAMEWORKS.nextjs, pm, projectPath);
288
289
  }
@@ -290,7 +291,9 @@ export function detectFramework(projectPath) {
290
291
  if (
291
292
  existsSync(join(projectPath, 'nuxt.config.js')) ||
292
293
  existsSync(join(projectPath, 'nuxt.config.mjs')) ||
293
- existsSync(join(projectPath, 'nuxt.config.ts'))
294
+ existsSync(join(projectPath, 'nuxt.config.ts')) ||
295
+ existsSync(join(projectPath, 'nuxt.config.cjs')) ||
296
+ existsSync(join(projectPath, 'nuxt.config.mts'))
294
297
  ) {
295
298
  return frameworkResult(FRAMEWORKS.nuxt, pm, projectPath);
296
299
  }
@@ -315,7 +318,8 @@ export function detectFramework(projectPath) {
315
318
  if (
316
319
  existsSync(join(projectPath, 'docusaurus.config.js')) ||
317
320
  existsSync(join(projectPath, 'docusaurus.config.ts')) ||
318
- existsSync(join(projectPath, 'docusaurus.config.mjs'))
321
+ existsSync(join(projectPath, 'docusaurus.config.mjs')) ||
322
+ existsSync(join(projectPath, 'docusaurus.config.cjs'))
319
323
  ) {
320
324
  return frameworkResult(FRAMEWORKS.docusaurus, pm, projectPath);
321
325
  }
@@ -336,7 +340,9 @@ export function detectFramework(projectPath) {
336
340
  if (
337
341
  existsSync(join(projectPath, 'vite.config.js')) ||
338
342
  existsSync(join(projectPath, 'vite.config.mjs')) ||
339
- existsSync(join(projectPath, 'vite.config.ts'))
343
+ existsSync(join(projectPath, 'vite.config.ts')) ||
344
+ existsSync(join(projectPath, 'vite.config.cjs')) ||
345
+ existsSync(join(projectPath, 'vite.config.mts'))
340
346
  ) {
341
347
  return frameworkResult(FRAMEWORKS.vite, pm, projectPath);
342
348
  }
@@ -358,9 +364,39 @@ export function detectFramework(projectPath) {
358
364
  }
359
365
  }
360
366
 
361
- if (existsSync(join(projectPath, 'index.html'))) {
367
+ if (
368
+ existsSync(join(projectPath, 'index.html')) ||
369
+ existsSync(join(projectPath, 'public', 'index.html')) ||
370
+ existsSync(join(projectPath, 'src', 'index.html'))
371
+ ) {
362
372
  return frameworkResult(FRAMEWORKS.static, pm, projectPath);
363
373
  }
364
374
 
375
+ if (pkg) {
376
+ const entryPoints = [
377
+ 'src/main.js',
378
+ 'src/main.ts',
379
+ 'src/main.jsx',
380
+ 'src/main.tsx',
381
+ 'src/App.vue',
382
+ 'src/app.vue',
383
+ 'src/main.vue',
384
+ 'src/index.js',
385
+ 'src/index.ts',
386
+ ];
387
+ const hasEntry = entryPoints.some((p) => existsSync(join(projectPath, p)));
388
+ if (hasEntry) {
389
+ if ('vue' in deps || '@vue/cli-service' in deps) {
390
+ return frameworkResult(FRAMEWORKS.vueCli, pm, projectPath);
391
+ }
392
+ if ('react-scripts' in deps) {
393
+ return frameworkResult(FRAMEWORKS.cra, pm, projectPath);
394
+ }
395
+ if ('svelte' in deps || 'solid-js' in deps) {
396
+ return frameworkResult(FRAMEWORKS.vite, pm, projectPath);
397
+ }
398
+ }
399
+ }
400
+
365
401
  return null;
366
402
  }
@@ -6,7 +6,7 @@ import { dirname, join } from 'node:path';
6
6
 
7
7
  import { classifyHcloudArgs, redactSecrets, assertAllowed } from './safety-policy.mjs';
8
8
  import { getProxySettings } from './proxy/proxy-config.mjs';
9
- import { findHcloudBin, resolveHcloudCommand } from './hcloud-probe.mjs';
9
+ import { resolveHcloudCommand } from './hcloud-probe.mjs';
10
10
  import { parseStsExpiry, resolveCredentialsWithRuntime } from './auth/credentials.mjs';
11
11
 
12
12
  const DEFAULT_TIMEOUT_MS = 60_000;
@@ -391,6 +391,24 @@ function appendLangHint(result, service, metaDir) {
391
391
  };
392
392
  }
393
393
 
394
+ // Returns true when the current credentials are a TEMPORARY STS set that has
395
+ // already expired (per HW_STS_EXPIRES_AT / token expiry). Lets us fail fast with
396
+ // a clear CREDENTIAL_EXPIRED instead of letting a doomed IAM call return a
397
+ // misleading error. Permanent AK/SK (no token) and unparseable expiry are never
398
+ // treated as expired.
399
+ function isTemporaryStsExpired() {
400
+ let creds;
401
+ try {
402
+ creds = resolveCredentialsWithRuntime({ allowMissing: true });
403
+ } catch {
404
+ return false;
405
+ }
406
+ if (!creds?.securityToken) return false;
407
+ const expiry = parseStsExpiry({ securityToken: creds.securityToken });
408
+ if (expiry === null) return false; // unknown → don't block
409
+ return expiry <= Date.now();
410
+ }
411
+
394
412
  export async function runHcloud(args, options = {}) {
395
413
  const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
396
414
  const plan = {
@@ -399,6 +417,21 @@ export async function runHcloud(args, options = {}) {
399
417
  };
400
418
  assertAllowed(plan.classification);
401
419
 
420
+ // A: fail fast when the platform-injected temporary STS is already expired —
421
+ // otherwise hcloud would make a doomed IAM round trip and surface a misleading
422
+ // error (e.g. "缺少 cli-domain-id"). Permanent AK/SK are unaffected.
423
+ if (isTemporaryStsExpired()) {
424
+ return {
425
+ ok: false,
426
+ code: 'CREDENTIAL_EXPIRED',
427
+ error:
428
+ 'Temporary STS credentials have expired. Re-login or start a new session so the platform injects fresh credentials, then retry.',
429
+ stderr: '',
430
+ stdout: '',
431
+ plan,
432
+ };
433
+ }
434
+
402
435
  // Link B: when the runtime/environment carries live temporary STS credentials
403
436
  // (security token set), KooCLI/obsutil would not see them (they read S2/S3 only
404
437
  // and R3 forbids writing them to disk). Inject per-command so hcloud actually
@@ -430,6 +463,8 @@ async function runHcloudOnceWithRetries(plan, options) {
430
463
  if (result.ok || attempt >= maxRetries || !isRetryableNetworkError(result)) {
431
464
  const merged = {
432
465
  ...result,
466
+ ok: result.ok ?? false,
467
+ exitCode: result.exitCode ?? null,
433
468
  retries: attempt,
434
469
  attempts: attempt + 1,
435
470
  };
@@ -460,15 +495,15 @@ async function runtimeCurrentMismatchWarning() {
460
495
  }
461
496
  }
462
497
 
463
- function discoverHcloudPath() {
464
- return findHcloudBin();
465
- }
466
-
467
498
  function runHcloudOnce(plan, options) {
468
499
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
469
500
  const forceKillAfterMs = options.forceKillAfterMs ?? DEFAULT_FORCE_KILL_AFTER_MS;
470
- const executable = options.executable || options.env?.HCLOUD_BIN || discoverHcloudPath() || 'hcloud';
471
- const executableArgs = Array.isArray(options.executableArgs) ? options.executableArgs.map(String) : [];
501
+ const { executable: resolvedExec, argsPrefix } = resolveHcloudCommand({
502
+ executable: options.executable || options.env?.HCLOUD_BIN,
503
+ executableArgs: options.executableArgs,
504
+ });
505
+ const executable = resolvedExec || 'hcloud';
506
+ const executableArgs = argsPrefix;
472
507
  const cwd = options.cwd || undefined;
473
508
  const stdin = options.stdin ?? 'y\n';
474
509
  const childOptions = { ...options };
@@ -540,6 +575,8 @@ function runHcloudOnce(plan, options) {
540
575
  ok: false,
541
576
  code: 'TIMEOUT',
542
577
  error: `hcloud command timed out after ${timeoutMs} ms.`,
578
+ exitCode: null,
579
+ signal: null,
543
580
  stdout: redactOutput(stdout),
544
581
  stderr: redactOutput(stderr),
545
582
  plan,
@@ -558,6 +595,8 @@ function runHcloudOnce(plan, options) {
558
595
  ok: false,
559
596
  code: 'SPAWN_ERROR',
560
597
  error: error.message,
598
+ exitCode: null,
599
+ signal: null,
561
600
  plan,
562
601
  });
563
602
  });