huaweicloud-devkit 1.1.7 → 1.1.8-next.1

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 (32) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -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 +9 -3
  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 +15 -2
  18. package/plugins/huaweicloud-core/src/auth/service.mjs +36 -0
  19. package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +23 -5
  20. package/plugins/huaweicloud-core/src/detect-framework.mjs +41 -5
  21. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +78 -8
  22. package/plugins/huaweicloud-core/src/hcloud-probe.mjs +10 -2
  23. package/plugins/huaweicloud-core/src/mcp-server.mjs +9 -0
  24. package/plugins/huaweicloud-core/src/preflight.mjs +125 -0
  25. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +94 -1
  26. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +1 -1
  27. package/plugins/huaweicloud-core/src/safety-policy.mjs +165 -33
  28. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +214 -27
  29. package/plugins/huaweicloud-core/src/search-market.mjs +40 -1
  30. package/plugins/huaweicloud-core/src/setup-cli.mjs +57 -17
  31. package/plugins/huaweicloud-core/src/tools.mjs +307 -20
  32. 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
 
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
 
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",
4
+ "version": "1.1.8-next.1",
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",
20
+ "version": "1.1.8-next.1",
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",
23
+ "version": "1.1.8-next.1",
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",
20
+ "version": "1.1.8-next.1",
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",
3
+ "version": "1.1.8-next.1",
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",
3
+ "version": "1.1.8-next.1",
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",
5
+ "version": "1.1.8-next.1",
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.
@@ -785,7 +785,7 @@ check_port <nodePort>
785
785
  | SPA/SSG/Cross-platform | nginx port (from `framework.detect`) | Kill old process, then configure nginx |
786
786
  | SSR | nginx public port + Node app port | Kill old processes on both ports |
787
787
 
788
- If the port cannot be freed (different user/process), increment to the next available port: `<port>+1`, update all subsequent nginx config and DevBridge references accordingly.
788
+ If the port cannot be freed (different user/process), the `deploy_nginx` tool auto-increments to the next available port (up to 10 attempts). For SSR (proxy) mode, nginx listens on the resolved `targetPort`, and the Node app follows the returned `nodePort` field — bind the Node process to the `nodePort` that `deploy_nginx` returns, and update all subsequent nginx config and DevBridge references accordingly.
789
789
 
790
790
  #### Configure Nginx
791
791
 
@@ -806,7 +806,7 @@ Use `huaweicloud_sandbox_deploy_nginx` to write the correct template, fix direct
806
806
  - `port` — listen port from framework detection
807
807
  - `project` — project dir name under `/workspace`
808
808
  - `output_dir` — build output dir relative to `/workspace/<project>`
809
- - `node_port` — Node.js app port for SSR proxy. Defaults to `<port> + 1` if omitted, and the result includes `nodePort` so you know which port to bind the Node process to.
809
+ - `node_port` — Node.js app port for SSR proxy. When omitted it defaults to the resolved nginx listen port plus one (`targetPort + 1`), and the result includes `nodePort` so you know which port to bind the Node process to. When the nginx port auto-increments, `nodePort` follows it.
810
810
  - `public_port` — public listen port for SSR proxy (optional, defaults to `port`)
811
811
 
812
812
  The tool automatically handles:
@@ -834,7 +834,7 @@ If the status code is not 2xx/3xx:
834
834
  ### Step 6: Start the App [REQUIRED]
835
835
 
836
836
  - **Static (SPA/SSG/cross-platform)**: nginx is already serving. Skip.
837
- - **SSR**: `PORT=<nodePort>` prefix is REQUIRED before `<serveCmd>`. nginx `proxy_pass` targets `<nodePort>`, not `<port>` — the two must differ. `deployNginx` returns `nodePort` in its result (defaults to `<port> + 1` for proxy type).
837
+ - **SSR**: `PORT=<nodePort>` prefix is REQUIRED before `<serveCmd>`. nginx `proxy_pass` targets `<nodePort>`, not `<port>` — the two must differ. `deployNginx` returns `nodePort` in its result (defaults to the resolved `targetPort + 1` for proxy type). Always use the returned `port`/`nodePort` values — they follow any auto-increment.
838
838
 
839
839
  **Runtime environment variables**: SSR apps often need `DATABASE_URL`, `NEXTAUTH_URL`, `NEXTAUTH_SECRET`, etc. at runtime. Before starting, verify env vars from Step 4a are still loaded, and re-source `.env` if the shell session was reset:
840
840
 
@@ -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
 
@@ -65,7 +65,14 @@ One-liner (installs `latest`, may drift from v7.2.12):
65
65
  curl -sSL https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/latest/hcloud_install.sh -o ./hcloud_install.sh && bash ./hcloud_install.sh -y
66
66
  ```
67
67
 
68
- Agent processes find executables through `PATH`. If OpenCode/Codex cannot find `hcloud`, restart after updating `PATH`, or set `HCLOUD_BIN`.
68
+ **No restart needed after install**: DevKit locates `hcloud` dynamically on every
69
+ call — it checks `HCLOUD_BIN`, then the fixed install dirs (`~/.local/bin/hcloud`
70
+ on Linux, `/usr/local/bin/hcloud` on macOS, `~/hcloud/hcloud.exe` on Windows),
71
+ then PATH. Once `install-hcloud` or the background auto-install writes the binary
72
+ to one of those dirs, the very next `check_cli` / `run_*` call uses it. Do not
73
+ tell users to restart the agent.
74
+
75
+ Agent processes find executables through `PATH`; `HCLOUD_BIN` overrides it. DevKit re-locates hcloud from its **fixed install dirs** on every call, so a freshly installed binary (`~/.local/bin` on Linux, `/usr/local/bin` on macOS, `~/hcloud` on Windows) or a re-set `HCLOUD_BIN` is picked up by the next `check_cli` / `run_*` call without restarting the agent. A custom `PATH` entry is only honored via `HCLOUD_BIN` or the PATH locator fallback, and shell `PATH` changes take effect only for processes started after the update.
69
76
 
70
77
  ## Configure Credentials Outside Chat
71
78
 
@@ -84,7 +91,13 @@ Agent processes find executables through `PATH`. If OpenCode/Codex cannot find `
84
91
  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
92
  3. Ask the user to configure credentials outside the agent conversation when setup is needed.
86
93
  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`.
94
+ 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:
95
+ 1. `hcloud --help` → available services
96
+ 2. `hcloud <Service> --help` → operations (use `huaweicloud_list_operations` or step 2/3 directly)
97
+ 3. `hcloud <Service> <Operation> --help` → exact parameter names, required flags, and value formats
98
+ 4. Execute only after the help text confirms the operation and its parameters
99
+ - 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.
100
+ - Example: ECS instance listing is commonly `ECS ListServersDetails`; ECS creation is commonly `ECS CreateServers`; image lookup may be under `IMS GlanceShowImage`.
88
101
  6. Use `--cli-output=json` for machine-readable responses when supported.
89
102
  7. For resource operations, include `--cli-region`, `--cli-profile`, and service-specific project information when required.
90
103
  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,
@@ -5,7 +5,7 @@
5
5
  "icons": [
6
6
  {
7
7
  "id": "codeartsdoer",
8
- "name": "华为云码道(CodeArts)代码智能体",
8
+ "name": "华为云码道CodeArts代码智能体",
9
9
  "category": "人工智能",
10
10
  "subcategory": "智能体",
11
11
  "description": "更懂你编码需求的AI研发专家",
@@ -41,13 +41,13 @@
41
41
  },
42
42
  {
43
43
  "id": "modelarts",
44
- "name": "魔坊(ModelArts)模型训推平台",
44
+ "name": "魔坊(ModelArts)",
45
45
  "category": "人工智能",
46
46
  "subcategory": "模型开发平台",
47
47
  "description": "“数-训-推-强”,一站式AI开发平台",
48
48
  "product_url": "https://www.huaweicloud.com/product/modelarts.html",
49
49
  "logo": {
50
- "source_url": "https://res-static.hc-cdn.cn/cloudbu-site/public/new-product-icon/AI/ModelArts.png",
50
+ "source_url": "https://res-static.hc-cdn.cn/cloudbu-site/public/%E6%96%B0%E7%89%88/CF057503-AE1E-48CA-E8C1-1919F51A1261.png",
51
51
  "local_path": "icons/assets/logo/modelarts.png"
52
52
  },
53
53
  "architecture": {
@@ -155,8 +155,8 @@
155
155
  "description": "高效敏捷安全的一站式企业级智能体平台",
156
156
  "product_url": "https://www.huaweicloud.com/product/agentarts.html",
157
157
  "logo": {
158
- "source_url": "https://res-static.hc-cdn.cn/cloudbu-site/public/new-product-icon/aPaaS/Versatile.svg",
159
- "local_path": "icons/assets/logo/agentarts.svg"
158
+ "source_url": "https://res-static.hc-cdn.cn/cloudbu-site/china/zh-cn/%E6%99%BA%E6%9E%9CAgentArts/AgentArts-%E5%AE%98%E7%BD%91%E4%BA%A7%E5%93%81%E9%A1%B5%E5%88%87%E5%9B%BE/bannerlogo.png",
159
+ "local_path": "icons/assets/logo/agentarts.png"
160
160
  },
161
161
  "architecture": {
162
162
  "status": "planned",
@@ -165,6 +165,24 @@
165
165
  "aliases": ["agentarts"],
166
166
  "tags": ["人工智能", "智能体平台", "agentarts"]
167
167
  },
168
+ {
169
+ "id": "officeace",
170
+ "name": "果办OfficeAce 办公智能体",
171
+ "category": "人工智能",
172
+ "subcategory": "智能体",
173
+ "description": "专业办公级AI助手,PPT一键生成、专家团研讨,安全高效随时待命",
174
+ "product_url": "https://www.huaweicloud.com/product/agentarts/officeace.html",
175
+ "logo": {
176
+ "source_url": "https://res-static.hc-cdn.cn/cloudbu-site/china/zh-cn/yunying/product/OfficeAce-v2.svg",
177
+ "local_path": "icons/assets/logo/officeace.svg"
178
+ },
179
+ "architecture": {
180
+ "status": "planned",
181
+ "local_path": "icons/assets/architecture/officeace.svg"
182
+ },
183
+ "aliases": ["officeace", "office-ace"],
184
+ "tags": ["人工智能", "智能体", "办公", "officeace"]
185
+ },
168
186
  {
169
187
  "id": "pangu-nlp",
170
188
  "name": "LLM大模型",
@@ -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
  }