huaweicloud-devkit 1.1.8 → 1.1.9-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 (35) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/integrations/opencode/package-lock.json +24 -0
  4. package/integrations/opencode/package.json +10 -0
  5. package/integrations/opencode/plugins/huaweicloud-devkit.js +194 -0
  6. package/package.json +2 -2
  7. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
  8. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
  9. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
  10. package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
  11. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
  12. package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
  13. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +5 -0
  14. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +16 -2
  15. package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +22 -0
  16. package/plugins/huaweicloud-core/skills/huawei-ecs/references/windows-ssh.md +118 -0
  17. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +3 -3
  18. package/plugins/huaweicloud-core/skills/huawei-iac/SKILL.md +36 -36
  19. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +2 -2
  20. package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +30 -3
  21. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +35 -6
  22. package/plugins/huaweicloud-core/skills/huawei-voucher/SKILL.md +1 -0
  23. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +2 -2
  24. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +6 -6
  25. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +16 -9
  26. package/plugins/huaweicloud-core/src/auth/credential-validator.mjs +37 -2
  27. package/plugins/huaweicloud-core/src/auth/credentials.mjs +14 -27
  28. package/plugins/huaweicloud-core/src/auth/guidance.mjs +112 -0
  29. package/plugins/huaweicloud-core/src/auth/reconcile.mjs +36 -2
  30. package/plugins/huaweicloud-core/src/auth/service.mjs +54 -46
  31. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +32 -4
  32. package/plugins/huaweicloud-core/src/hcloud-probe.mjs +26 -6
  33. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +120 -4
  34. package/plugins/huaweicloud-core/src/setup-cli.mjs +111 -8
  35. package/plugins/huaweicloud-core/src/tools.mjs +23 -3
@@ -21,16 +21,16 @@ Always run `hcloud <Service> <Operation> --help` before constructing commands to
21
21
 
22
22
  **Proactive by design**: any deployment intent ("部署一个博客", "搭一套环境", "买个服务器") automatically triggers stages 1-5 BEFORE anything is created. The user NEVER needs to ask for an architecture proposal, cost estimate, or risk warnings - they are built into the flow. Only stages 6-8 (create / verify / manage) wait for explicit user confirmation at the approval gates.
23
23
 
24
- | Stage | What happens | Hard rule |
25
- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
26
- | 1. Scale assessment | **FIRST inspect the project code** (clone remote repos to a temp dir, or read the local path): tech stack, data services (Redis/MySQL/SQLite...), build artifacts, existing Dockerfile/k8s manifests → then classify or ask scale | **NEVER present deployment options (sandbox/ECS/OBS...) before the codebase has been inspected** - options without code analysis are guesses. If scale is not stated or clearly inferable, ASK - present the 3 tiers as options with what each includes and its monthly cost band (see `references/architectures.md`), and let the user choose. Never assume a tier. The scale question and the hosting-target question are SEPARATE questions on separate axes. FunctionGraph filesystem is ephemeral - never recommend SQLite/local files for persistent data |
27
- | 2. Frontend hosting choice | Present BOTH options: (a) OBS+CDN+DNS custom domain, (b) ECS+EIP | For SPA/static frontends (Vue/React dist), **OBS hosting must always appear as an option** with cost comparison against same-host Nginx - do not silently merge the frontend into a compute node. For option (a) three prerequisites: domain ownership verification (CDN requires a DNS record or file upload first - `CDN.00010185` otherwise), ICP filing check, DNS hosting check; domain registration link: https://www.huaweicloud.com/product/domain.html |
28
- | 3. Architecture proposal | ASCII topology tree with dependencies | User confirms before continuing. **Architecture decision points must be surfaced as explicit choices, never silently made**: (a) app depends on Redis/MySQL/Kafka etc. → managed service (DCS/RDS/DMS, billed) vs same-host install (cheap, ops burden) - state the trade-off and ask; (b) repo ships a Dockerfile / SWR image / k8s manifests → offer container path (Docker on ECS or CCE) alongside source-build path; (c) frontend hosting layer (Nginx same-host vs OBS+CDN). Silent defaults here are a correctness failure |
29
- | 4. Parameter discovery | `ListFlavors` / `ListImages` / `huaweicloud_list_regions` | Never hardcode flavor/image names |
30
- | 5. Cost + balance gate | Price every resource via BSS `ListOnDemandResourceRatings`, then query balance | Balance = 0 → the FIRST message must state: "您的余额为 0,按时长计费的资源(ECS/EIP/RDS 等)下单将失败(Ecs.7000),请先充值或使用免费额度"。充值入口:https://account.huaweicloud.com/usercenter/?region=cn-north-4#/userindex/balanceRecharge 。Remedy priority: (1) FIRST claim the one-time incentive voucher (huaweicloud_voucher_status → huaweicloud_voucher_claim) to offset usage fees; (2) then instruct the user to top up balance (via the link above) if pay-per-use orders are needed, otherwise orders fail with `Ecs.7000`; (3) free-tier resources (FunctionGraph, small OBS) proceed without any balance. Balance > 0 → report how long it can sustain (balance ÷ monthly estimate). **权限降级(实测)**:IAM 用户无 `billing:balance:view` 时余额查询报 `CBC.0151` → 不阻塞流程,明确告知"余额无法程序化查询,请自行核对;若下单报 Ecs.7000 即余额不足",领券后继续。定价编码逐服务查证,见 `references/resource-catalog.md` § Pricing |
31
- | 6. Provision | deployment_id, session state, batch approval, ordered creation | See "Provisioning Rules" below |
32
- | 7. Verify | `curl -I` the site / API; expect 200 | DNS CNAME may take minutes to propagate - not an error |
33
- | 8. Manage | Session-scoped status query and destroy | See "Destroy Rules" below |
24
+ | Stage | What happens | Hard rule |
25
+ | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
26
+ | 1. Scale assessment | **FIRST inspect the project code** (clone remote repos to a temp dir, or read the local path): tech stack, data services (Redis/MySQL/SQLite...), build artifacts, existing Dockerfile/k8s manifests → then classify or ask scale | **NEVER present deployment options (sandbox/ECS/OBS...) before the codebase has been inspected** - options without code analysis are guesses. If scale is not stated or clearly inferable, ASK - present the 3 tiers as options with what each includes and its monthly cost band (see `references/architectures.md`), and let the user choose. Never assume a tier. The scale question and the hosting-target question are SEPARATE questions on separate axes. FunctionGraph filesystem is ephemeral - never recommend SQLite/local files for persistent data |
27
+ | 2. Frontend hosting choice | Present BOTH options: (a) OBS+CDN+DNS custom domain, (b) ECS+EIP | For SPA/static frontends (Vue/React dist), **OBS hosting must always appear as an option** with cost comparison against same-host Nginx - do not silently merge the frontend into a compute node. For option (a) three prerequisites: domain ownership verification (CDN requires a DNS record or file upload first - `CDN.00010185` otherwise), ICP filing check, DNS hosting check; domain registration link: https://www.huaweicloud.com/product/domain.html |
28
+ | 3. Architecture proposal | ASCII topology tree with dependencies | User confirms before continuing. **Architecture decision points must be surfaced as explicit choices, never silently made**: (a) app depends on Redis/MySQL/Kafka etc. → managed service (DCS/RDS/DMS, billed) vs same-host install (cheap, ops burden) - state the trade-off and ask; (b) repo ships a Dockerfile / SWR image / k8s manifests → offer container path (Docker on ECS or CCE) alongside source-build path; (c) frontend hosting layer (Nginx same-host vs OBS+CDN). Silent defaults here are a correctness failure |
29
+ | 4. Parameter discovery | `ListFlavors` / `ListImages` / `huaweicloud_list_regions` | Never hardcode flavor/image names |
30
+ | 5. Cost + balance gate | Price every resource via BSS `ListOnDemandResourceRatings`, then query balance | **前置拦截(实测 #922):估算成本 > 余额(含余额为 0)时 STOP — 不要带着一份注定失败的下单继续 stage 6**。第一条消息必须说明:"您的余额不足以覆盖本次部署的按时长计费资源(预计首月 <估算>,当前余额 <余额>),下单将失败(Ecs.7000 Insufficient account balance)——已暂停,请先充值或领券,完成后回复"已充值/继续"即可从本阶段续跑原配置"。充值入口:https://account.huaweicloud.com/usercenter/?region=cn-north-4#/userindex/balanceRecharge 。Remedy priority: (1) FIRST claim the one-time incentive voucher (huaweicloud_voucher_status → huaweicloud_voucher_claim) to offset usage fees; (2) then instruct the user to top up balance (via the link above) — pause the flow and resume from stage 5 with the SAME configuration after the top-up, never walk a known-doomed order; (3) free-tier resources (FunctionGraph, small OBS) proceed without any balance. Balance >= 估算成本 → report how long it can sustain (balance ÷ monthly estimate). **权限降级(实测)**:IAM 用户无 `billing:balance:view` 时余额查询报 `CBC.0151` → 不阻塞流程,明确告知"余额无法程序化查询,请自行核对;若下单报 Ecs.7000 即余额不足",领券后继续。定价编码逐服务查证,见 `references/resource-catalog.md` § Pricing |
31
+ | 6. Provision | deployment_id, session state, batch approval, ordered creation | See "Provisioning Rules" below |
32
+ | 7. Verify | `curl -I` the site / API; expect 200 | DNS CNAME may take minutes to propagate - not an error |
33
+ | 8. Manage | Session-scoped status query and destroy | See "Destroy Rules" below |
34
34
 
35
35
  ## Provisioning Rules (Stage 6)
36
36
 
@@ -52,32 +52,32 @@ Execution: show the to-be-deleted list for final confirmation → delete in **re
52
52
 
53
53
  ## Critical Warnings
54
54
 
55
- | Trap | Why / Rule |
56
- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
57
- | Stale `securityToken` poisons every hcloud call | Symptom: every API returns `APIGW.0301 Incorrect IAM authentication information` while OBS/obsutil still works. `configure set` refuses empty values and auth-sync never clears the field. Fix: `hcloud configure delete --cli-profile=default`, then re-run auth init with AK/SK only (no token) |
58
- | OBS writes bypass the REST write-prefix policy | `mb` / `cp` / `rm` / `chattri` are obsutil-style commands. ALWAYS route them through `huaweicloud_plan_cli_command` → approval → `huaweicloud_run_approved_command`. Never treat them as read-only |
59
- | `OBS mb` flag rules differ from cp/rm | `mb` does NOT accept `-f` (flag parse error) and REQUIRES explicit `-location=<region>` even with a regional endpoint (else `IllegalLocationConstraintException`) |
60
- | Duplicate `OBS mb` is idempotent success | Same-account same-name bucket creation returns success, not an error - never treat a second create as failure. OBS deletes are also idempotent (deleting a nonexistent object succeeds) |
61
- | Bucket ACL does not cascade to objects | Every uploaded object needs `-acl=public-read` at upload time (`-f` IS valid on cp/rm) |
62
- | OBS static website bucket name must equal the domain | e.g. bucket `www.example.com` for domain `www.example.com` |
63
- | VPC v3 API differs from older docs | v3 `CreateSecurityGroup` has NO `vpc_id` (account-scoped); `CreateSecurityGroupRule` uses `multiport=80` not `port_range_min/max`; `DeleteSubnet` requires `vpc_id` too. Always `--help` first |
64
- | Public exposure is auto-blocked | Risk rules deny `0.0.0.0/0` port rules at plan time. For test/dev use a narrower `remote_ip_prefix` (e.g. the VPC CIDR) or get user consent for a documented exception |
65
- | ECS CreateServers hidden required params | Besides flavor/image/nics/az it also needs `--server.vpcid` and `--server.root_volume.volumetype` - order submission fails without them |
66
- | FunctionGraph runtime/code enums are strict | runtime must be exact (`Node.js18.15`, `Python3.9`, `Go1.x`, ...); inline code is the PAIR `--package=default --code_type=inline` (`--package=inline` → `FSS.1006`); TIMER triggers use `--event_data.schedule_type=Rate --event_data.schedule=1m` (`@every` style → `FSS.1109`); DeleteFunction takes `--function_urn` WITHOUT `:latest` |
67
- | Keypairs: `NovaCreateKeypair`, not CreateKeypair | `ECS CreateKeypair` does NOT exist - use `hcloud ECS NovaCreateKeypair`; delete takes `--keypair_name` (not `--keypair.name`). The API-generated `private_key` is MASKED in tool output - for SSH access create the keypair in the console or import the user's own public key, then bind with `--server.key_name=<n>` |
68
- | Long inline args break MCP JSON | Values >~2KB inline (e.g. `--server.user_data=<base64 cloud-init>`) fail JSON parsing silently. Write a local JSON file and pass `--cli-jsonInput=<file>` (see `references/resource-catalog.md` header note) |
69
- | EIP quota fails at RUN stage, not at plan | `EIP.7905 Quota exceeded` appears only after plan+approval - pre-check quota during stage 5 (and count precisely: `ListPublicips --limit=50` truncates, a "50/50" read on a 59-EIP account is wrong). Released quotas may lag minutes before `EIP.7905` clears |
70
- | Zero balance blocks order submission | ECS creation fails with `Ecs.7000 Insufficient account balance` when cash + voucher accounts are 0. This is the stage-5 gate materialized: stop, mark `partial`, tell the user pay-by-duration resources need a balance top-up. Free-tier resources (FunctionGraph, small OBS) DO work at 0 balance - the gate applies to order-submission resources only |
71
- | CDN mainland acceleration requires ICP filing | Unregistered domains cannot go live on CDN - check before stage 3. CDN create ALSO requires domain ownership verification first (`CDN.00010185 Verify domain ownership failed` - user must add a DNS record or upload a verification file) |
72
- | CDN is a global service in KooCLI | CDN operations only accept `--cli-region=cn-north-1` or `ap-southeast-1` - never the resource region |
73
- | OBS static website hosting has NO CLI path | `chattri` only sets ACL/storage-class; obsutil has no website command. Options: console setup, or CDN OBS-origin with `--domain.sources.1.enable_obs_web_hosting=1`. Without website hosting, verify via direct object URLs (still public HTTP 200) |
74
- | DNS hosting location | `hcloud DNS ListPublicZones` decides: hosted in Huawei Cloud → auto-create CNAME record set; hosted elsewhere → output the CNAME value for manual setup at the user's DNS provider |
75
- | EIP bills when idle; stopped ECS still bills | Include in cost estimate; destroy unattached EIPs |
76
- | ECS delete defaults leak resources | `--delete_publicip` and `--delete_volume` are false by default - set `true` when tearing down |
77
- | EIP binding type is non-obvious | `--publicip.associate_instance_type=PORT` (not INSTANCE) |
78
- | State is session-only | Temp-dir file, dies with the session. Never promise cross-session deploy management |
79
- | State file must be UTF-8 WITHOUT BOM | PowerShell `Set-Content -Encoding utf8` adds a BOM that breaks JSON parsers - write state with Node or another BOM-less writer |
80
- | HTTPS needs a certificate | CDN serves plain HTTP by default; SSL cert (upload or SCM) required for HTTPS |
55
+ | Trap | Why / Rule |
56
+ | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
57
+ | Stale `securityToken` poisons every hcloud call | Symptom: every API returns `APIGW.0301 Incorrect IAM authentication information` while OBS/obsutil still works. `configure set` refuses empty values and auth-sync never clears the field. Fix: `hcloud configure delete --cli-profile=default`, then re-run auth init with AK/SK only (no token) |
58
+ | OBS writes bypass the REST write-prefix policy | `mb` / `cp` / `rm` / `chattri` are obsutil-style commands. ALWAYS route them through `huaweicloud_plan_cli_command` → approval → `huaweicloud_run_approved_command`. Never treat them as read-only |
59
+ | `OBS mb` flag rules differ from cp/rm | `mb` does NOT accept `-f` (flag parse error) and REQUIRES explicit `-location=<region>` even with a regional endpoint (else `IllegalLocationConstraintException`) |
60
+ | Duplicate `OBS mb` is idempotent success | Same-account same-name bucket creation returns success, not an error - never treat a second create as failure. OBS deletes are also idempotent (deleting a nonexistent object succeeds) |
61
+ | Bucket ACL does not cascade to objects | Every uploaded object needs `-acl=public-read` at upload time (`-f` IS valid on cp/rm) |
62
+ | OBS static website bucket name must equal the domain | e.g. bucket `www.example.com` for domain `www.example.com` |
63
+ | VPC v3 API differs from older docs | v3 `CreateSecurityGroup` has NO `vpc_id` (account-scoped); `CreateSecurityGroupRule` uses `multiport=80` not `port_range_min/max`; `DeleteSubnet` requires `vpc_id` too. Always `--help` first |
64
+ | Public exposure is auto-blocked | Risk rules deny `0.0.0.0/0` port rules at plan time. For test/dev use a narrower `remote_ip_prefix` (e.g. the VPC CIDR) or get user consent for a documented exception |
65
+ | ECS CreateServers hidden required params | Besides flavor/image/nics/az it also needs `--server.vpcid` and `--server.root_volume.volumetype` - order submission fails without them |
66
+ | FunctionGraph runtime/code enums are strict | runtime must be exact (`Node.js18.15`, `Python3.9`, `Go1.x`, ...); inline code is the PAIR `--package=default --code_type=inline` (`--package=inline` → `FSS.1006`); TIMER triggers use `--event_data.schedule_type=Rate --event_data.schedule=1m` (`@every` style → `FSS.1109`); DeleteFunction takes `--function_urn` WITHOUT `:latest` |
67
+ | Keypairs: `NovaCreateKeypair`, not CreateKeypair | `ECS CreateKeypair` does NOT exist - use `hcloud ECS NovaCreateKeypair`; delete takes `--keypair_name` (not `--keypair.name`). The API-generated `private_key` is MASKED in tool output - for SSH access create the keypair in the console or import the user's own public key, then bind with `--server.key_name=<n>` |
68
+ | Long inline args break MCP JSON | Values >~2KB inline (e.g. `--server.user_data=<base64 cloud-init>`) fail JSON parsing silently. Write a local JSON file and pass `--cli-jsonInput=<file>` (see `references/resource-catalog.md` header note) |
69
+ | EIP quota fails at RUN stage, not at plan | `EIP.7905 Quota exceeded` appears only after plan+approval - pre-check quota during stage 5 (and count precisely: `ListPublicips --limit=50` truncates, a "50/50" read on a 59-EIP account is wrong). Released quotas may lag minutes before `EIP.7905` clears |
70
+ | Zero balance blocks order submission | ECS creation fails with `Ecs.7000 Insufficient account balance` when cash + voucher accounts are 0. This is the stage-5 gate materialized — and since #922 the gate INTERCEPTS before the order: when the estimate exceeds the balance, STOP at stage 5 (recharge entry + resume after top-up) instead of walking a known-doomed `CreateServers`. Free-tier resources (FunctionGraph, small OBS) DO work at 0 balance - the gate applies to order-submission resources only |
71
+ | CDN mainland acceleration requires ICP filing | Unregistered domains cannot go live on CDN - check before stage 3. CDN create ALSO requires domain ownership verification first (`CDN.00010185 Verify domain ownership failed` - user must add a DNS record or upload a verification file) |
72
+ | CDN is a global service in KooCLI | CDN operations only accept `--cli-region=cn-north-1` or `ap-southeast-1` - never the resource region |
73
+ | OBS static website hosting has NO CLI path | `chattri` only sets ACL/storage-class; obsutil has no website command. Options: console setup, or CDN OBS-origin with `--domain.sources.1.enable_obs_web_hosting=1`. Without website hosting, verify via direct object URLs (still public HTTP 200) |
74
+ | DNS hosting location | `hcloud DNS ListPublicZones` decides: hosted in Huawei Cloud → auto-create CNAME record set; hosted elsewhere → output the CNAME value for manual setup at the user's DNS provider |
75
+ | EIP bills when idle; stopped ECS still bills | Include in cost estimate; destroy unattached EIPs |
76
+ | ECS delete defaults leak resources | `--delete_publicip` and `--delete_volume` are false by default - set `true` when tearing down |
77
+ | EIP binding type is non-obvious | `--publicip.associate_instance_type=PORT` (not INSTANCE) |
78
+ | State is session-only | Temp-dir file, dies with the session. Never promise cross-session deploy management |
79
+ | State file must be UTF-8 WITHOUT BOM | PowerShell `Set-Content -Encoding utf8` adds a BOM that breaks JSON parsers - write state with Node or another BOM-less writer |
80
+ | HTTPS needs a certificate | CDN serves plain HTTP by default; SSL cert (upload or SCM) required for HTTPS |
81
81
 
82
82
  ## MCP Tools
83
83
 
@@ -10,8 +10,8 @@ version: 1
10
10
 
11
11
  ## Routing Guard: Deploy vs Store
12
12
 
13
- - If the developer's goal is to **deploy, host, or preview a web app / static website** (temporary hosting, quick preview), do NOT default to OBS. Present deployment-target options with the sandbox first: ① huawei-sandbox (recommended), ② OBS static hosting, ③ ECS, ④ CCE. Ask: "建议优先部署到沙箱(临时运行环境,可预览访问),也可选 OBS 静态托管/ECS/CCE,你想部署到哪里?"
14
- - Proceed with OBS only when the developer selects OBS, explicitly asks for OBS, or the intent is long-term static hosting / CDN / file storage. **Deployment intent proceeding on OBS is a resource purchase: load `huawei-iac` for orchestration (cost+balance gate, batch approval, state tracking, ordered destroy) - this skill then supplies the bucket/object commands.** Sandbox choice (huawei-sandbox) bypasses iac - no purchase involved.
13
+ - If the developer's goal is to **deploy, host, or preview a web app / static website** (temporary hosting, quick preview), do NOT default to OBS. You MUST present deployment-target options via the `question` tool with the sandbox first: ① huawei-sandbox (recommended), ② OBS static hosting (需提前准备好自定义域名 — the default OBS endpoint does not support HTTPS for static hosting), ③ ECS (需搭配弹性公网IP/EIP 才能公网访问), ④ CCE. Ask: "建议优先部署到沙箱(临时运行环境,可预览访问),也可选 OBS 静态托管(需提前准备好自定义域名)、ECS(需搭配弹性公网IP/EIP)或 CCE,你想部署到哪里?"
14
+ - Proceed with OBS only when the developer explicitly names OBS, or selects OBS through the `question` gate after being asked. Long-term static hosting / CDN / file storage intent alone is NOT a named target and cannot bypass the gate — still present the options via the `question` tool first. **NEVER infer or default the target** when it is unstated. **Deployment intent proceeding on OBS is a resource purchase: load `huawei-iac` for orchestration (cost+balance gate, batch approval, state tracking, ordered destroy) - this skill then supplies the bucket/object commands.** Sandbox choice (huawei-sandbox) bypasses iac - no purchase involved.
15
15
  - OBS is a storage service; it is not a general web-hosting default.
16
16
 
17
17
  ## Critical: OBS Command Syntax
@@ -54,10 +54,37 @@ hcloud OBS config -i
54
54
 
55
55
  KooCLI OBS **不支持** `SetBucketWebsite` 命令。配置静态网站托管需要:
56
56
 
57
- 1. **REST API**: `PUT /?website` 到桶的 endpoint
58
- 2. **控制台**: OBS 控制台 → 桶 → 静态网站托管 → 开启
57
+ 1. **MCP 工具**: `huaweicloud_obs_set_website_config`(插件内置,AWS4 签名处理,action=set/get/delete,实测可用)
58
+ 2. **REST API**: `PUT /?website` 到桶的 endpoint
59
+ 3. **控制台**: OBS 控制台 → 桶 → 静态网站托管 → 开启
59
60
 
60
- 插件只能完成"上传文件 + 设置 ACL",静态托管开关需用户手动完成。
61
+ ## Content-Disposition 强制下载陷阱(2022-01-01 后创建的桶)
62
+
63
+ **实测发现(2026-10,cn-north-4)**:2022 年 1 月 1 日之后创建的 OBS 桶,对象 **GET 响应被 OBS 强制注入 `Content-Disposition: attachment`**——浏览器访问 HTML 一律下载而不是渲染,静态网站托管端点(`<bucket>.obs-website.<region>.myhuaweicloud.com`)也不例外,与对象元数据无关。
64
+
65
+ | 请求方式 | 返回的 Content-Disposition | 说明 |
66
+ | ----------------------- | ---------------------------- | -------------------------- |
67
+ | GET(浏览器、在线预览) | `attachment`(OBS 强制注入) | 网页被下载,无法渲染 |
68
+ | HEAD | 元数据中的值(如 inline) | 会误导诊断,不是浏览器行为 |
69
+
70
+ **避坑方法**:
71
+
72
+ 1. **验证网页行为必须用 GET 直连**(`curl -s -D - -o NUL <url>` 或浏览器)——HEAD 和代理层会给出假象
73
+ 2. **官方解法**:绑定自定义域名接入 **CDN**,在"CDN 控制台 > 域名管理 > 高级设置 > HTTP header 配置"设置 `Content-Disposition: inline`(参见华为云 CDN 文档《如何在浏览器中在线预览 OBS 中的对象?》)
74
+ 3. 跨云对照:阿里云 OSS 同样策略(Bucket 域名访问 HTML 强制下载,绑定自定义域名即可在线预览)
75
+
76
+ **对静态托管的结论**:2022 年后创建的桶,裸 OBS(含 obs-website 端点)**无法交付可渲染的 web 站点**;自定义域名 + CDN(+ header 配置 inline)是**必要条件**,不是增强项。
77
+
78
+ ## obsutil 元数据大小写陷阱
79
+
80
+ `cp -meta=content-disposition:inline`(小写)会被存为用户自定义元数据 `x-amz-meta-content-disposition`,**不命中标准头**;标准 HTTP 头(Content-Type、Content-Disposition、Cache-Control 等)必须按标准大小写书写:
81
+
82
+ ```bash
83
+ # 错误:存为 x-amz-meta-content-disposition,浏览器不识别
84
+ hcloud OBS cp index.html obs://<bucket>/ -meta=content-disposition:inline
85
+ # 正确:命中标准 Content-Disposition 头
86
+ hcloud OBS cp index.html obs://<bucket>/ -meta=Content-Disposition:inline
87
+ ```
61
88
 
62
89
  ## 权限模型(三层)
63
90
 
@@ -15,8 +15,8 @@ When the developer asks to **deploy / host / publish / 上线 / 部署 / 发布
15
15
  Use the `question` tool and present, in this exact order:
16
16
 
17
17
  1. **沙箱 (Sandbox)** — temporary runtime, instant preview URL (recommended for quick preview)
18
- 2. **OBS 静态托管** — long-term static hosting + CDN + custom domain
19
- 3. **ECS** — server-based hosting
18
+ 2. **OBS 静态托管** — long-term static hosting + CDN + custom domain. **Remind the developer: a custom domain (域名) must be prepared in advance — the default OBS endpoint does not support HTTPS for static hosting**
19
+ 3. **ECS** — server-based hosting. **Remind the developer: ECS must be paired with an EIP (弹性公网IP) for public access — extra billing applies**
20
20
  4. **CCE** — container-based hosting
21
21
 
22
22
  Hard rules:
@@ -24,6 +24,7 @@ Hard rules:
24
24
  - **NEVER infer or default the target** when it is unstated. Calling `huaweicloud_sandbox_connect` before this choice is made is a violation.
25
25
  - Only skip the question when: (a) the developer already named a target (e.g. "部署到 ECS", "用 OBS"), or (b) the developer explicitly asked for a temporary / sandbox / preview runtime.
26
26
  - "部署到华为云" / "deploy to Huawei Cloud" / "上线" alone is NOT a named target — still run the gate.
27
+ - Include the OBS domain reminder and the ECS EIP pairing reminder in the options as presented.
27
28
  - After the choice: sandbox → continue with this skill; OBS → `huawei-obs`; ECS → `huawei-ecs` / `huawei-iac`; CCE → `huawei-cce`.
28
29
 
29
30
  ## Overview
@@ -33,7 +34,7 @@ Domain expertise for Huawei Cloud Sandbox (DevStation) instances and workspace t
33
34
  ## Activation
34
35
 
35
36
  - **Proactive offering**: when the developer's task needs a temporary runtime (e.g. "deploy this web app", "run this app and preview it"), offer the sandbox proactively — the developer never has to say "use sandbox". Prompt: "This task can be satisfied by a sandbox — use it?"
36
- - **Static website hosting — present options, sandbox first (MANDATORY)**: when the developer asks to deploy/host/publish a static website or web app without naming a target, do NOT silently pick a service and do NOT default to the sandbox. Run the [Target-Selection Gate](#target-selection-gate-mandatory--do-not-skip) via the `question` tool first. Present, in order: ① 沙箱 (recommended — temporary runtime, instant preview URL) ② OBS 静态托管 (long-term static hosting/CDN) ③ ECS ④ CCE. Recommended prompt: "建议优先部署到沙箱(临时运行环境,可预览访问),也可选择 OBS 静态托管、ECS 或 CCE,你想部署到哪里?" Only if the developer, after being asked, expresses no preference may you proceed with the sandbox.
37
+ - **Static website hosting — present options, sandbox first (MANDATORY)**: when the developer asks to deploy/host/publish a static website or web app without naming a target, do NOT silently pick a service and do NOT default to the sandbox. Run the [Target-Selection Gate](#target-selection-gate-mandatory--do-not-skip) via the `question` tool first. Present, in order: ① 沙箱 (recommended — temporary runtime, instant preview URL) ② OBS 静态托管 (long-term static hosting/CDN; 需提前准备好自定义域名) ③ ECS (需搭配弹性公网IP/EIP 才能公网访问) ④ CCE. Recommended prompt: "建议优先部署到沙箱(临时运行环境,可预览访问),也可选择 OBS 静态托管(需提前准备好自定义域名)、ECS(需搭配弹性公网IP/EIP)或 CCE,你想部署到哪里?" Only if the developer, after being asked, expresses no preference may you proceed with the sandbox.
37
38
  - **Detect web apps in deploy-a-repo prompts**: prompts like "部署 GitHub - <owner>/<repo>" or "deploy <github repo>" do not say "web app". Clone or inspect the repository first (package.json with dev/serve/build scripts, index.html, frontend framework dependencies such as vue/react/angular/vite, static sites, Dockerfile running a web server). If it is a web application, offer the sandbox first: "检测到该项目是 Web 应用,建议优先使用沙箱部署(临时运行环境,可预览访问),是否使用?"
38
39
  - **Clone into the sandbox workspace directory**: always put project code under `/workspace/<repo-name>` (create the directory if missing) — `/workspace` is the sandbox's dedicated workspace mount at the filesystem root, not `$HOME/workspace`. Never use `/tmp` or other ephemeral locations. This keeps the project with the sandbox session, is easy to reference for serving/exposing, and survives session-level restarts of the shell.
39
40
  - **Deployment must end with a public URL**: after deploying and exposing the app with DevBridge, always return the tunnel URL to the developer as the final result — a deployment without an accessible link is incomplete.
@@ -147,6 +148,8 @@ Setup is a **plugin-side preflight** — the developer should be asked a questio
147
148
 
148
149
  If `source /tmp/hw_creds.sh` returns empty, the credentials injection may not have completed — re-run `huaweicloud_sandbox_credentials`.
149
150
 
151
+ **DevBridge API Key 前置引导(before expose, not mid-expose)**: the address-generation service (DevBridge 0.2.x) requires an API Key. Recent sandbox images auto-acquire one from the platform keyring — but if the sandbox was created on an older image (cannot auto-acquire), prepare the key at THIS stage instead of being interrupted halfway through the expose flow: create it once at https://devstation.connect.huaweicloud.com/space/devbridge/apikey (value shown only once, `devbridge_` prefix), hand it over via `export HW_API_KEY=<key>` locally (preferred) or paste it in the conversation for the `api_key` param, then run `huaweicloud_sandbox_credentials`. Full why/where/how guide: [Step 1, NO_CREDENTIAL branch](#step-1-authenticate--verified-waterfall-keyring-first).
152
+
150
153
  8. **Execute commands**: use `huaweicloud_sandbox_exec_with_session` for interactive work (cd, env setup), or `huaweicloud_sandbox_exec_one_shot` for long-running builds/installs. See [Tool Selection Guide](#tool-selection-guide).
151
154
  9. **Upload project** (if deploying local code): `huaweicloud_sandbox_upload_project` to transfer a local project directory into `/workspace/<name>` — uses HTTP tunnel, much faster than base64 for multi-file projects. Use `huaweicloud_sandbox_upload_file` for single files.
152
155
  10. **Expose the deployed app and return the public URL**: set up a public access link for the app port and give the URL to the developer as the final result (see "Expose the deployed app (public URL)") — this step uses an internal tunnel mechanism that must never be mentioned to the developer
@@ -220,6 +223,11 @@ curl -s -o /workspace/<repo>/<file> https://<bucket>.obs.<region>.myhuaweicloud.
220
223
 
221
224
  Expose a deployed web app to a public URL and return that URL to the developer.
222
225
 
226
+ ### Workspace URL vs public share link (401 trap — do not confuse them)
227
+
228
+ - The sandbox **workspace URL** (`https://<port>-<container_id>.workspace.developer.huaweicloud.com/`) is **authenticated**: opening it directly (or sharing it with someone who is not logged in) returns **401 Unauthorized**. A 401 here does NOT mean the port failed to expose — never debug it as an exposure failure.
229
+ - A **publicly shareable address is ONLY the DevBridge tunnel URL** (`https://<id>-<port>.devbridge-s2.hwtunnel.com`, produced in Step 2 below). Public sharing must go through the DevBridge tunnel — never hand the workspace URL to the developer as the deployment result link.
230
+
223
231
  **User-facing language rule**: the tunnel mechanism below is an internal implementation detail. Never mention "DevBridge", "tunnel", or the tunnel CLI to the developer. Tell the developer only: "正在生成访问地址..." while exposing, then hand over the final URL.
224
232
 
225
233
  ### Step 0: Ensure DevBridge CLI 0.2.x (install or upgrade in place)
@@ -249,6 +257,23 @@ if ! bash /tmp/devbridge-install.sh -s -u "https://gitcode.com/CloudDeveloperDep
249
257
  bash /tmp/devbridge-install.sh -s # fallback: GitHub (baked-in URL)
250
258
  fi
251
259
  export PATH="$HOME/.huawei/bin:$PATH" # PREPEND (session shells do not re-source ~/.bashrc)
260
+
261
+ # Layer 3 (both installer paths failed): direct GitHub release tar.gz.
262
+ # Asset naming is fixed: devbridge_linux_arm64_<version>-release.tar.gz (the sandbox
263
+ # is Linux aarch64). If DB_TARGET is empty the installer download itself failed —
264
+ # resolve the latest tag from the GitHub releases API first.
265
+ if ! command -v devbridge >/dev/null 2>&1; then
266
+ [ -z "$DB_TARGET" ] && DB_TARGET=$(curl -fsSL --http1.1 --retry 2 --max-time 60 \
267
+ https://api.github.com/repos/huaweicloud/devspace-devbridge/releases/latest | grep -m1 '"tag_name"' | cut -d'"' -f4)
268
+ mkdir -p "$HOME/.huawei/bin"
269
+ curl -fsSL --http1.1 --retry 3 --retry-all-errors --max-time 180 \
270
+ "https://github.com/huaweicloud/devspace-devbridge/releases/download/${DB_TARGET}/devbridge_linux_arm64_${DB_TARGET}.tar.gz" \
271
+ -o /tmp/devbridge.tar.gz \
272
+ && rm -rf /tmp/devbridge-pkg && mkdir -p /tmp/devbridge-pkg \
273
+ && tar -xzf /tmp/devbridge.tar.gz -C /tmp/devbridge-pkg \
274
+ && install -m 0755 /tmp/devbridge-pkg/devbridge "$HOME/.huawei/bin/devbridge"
275
+ export PATH="$HOME/.huawei/bin:$PATH"
276
+ fi
252
277
  devbridge version # must print 0.2.x-release
253
278
 
254
279
  # Remove the stale image-installed binary so only ONE version remains.
@@ -325,7 +350,8 @@ sleep 2
325
350
  devbridge delete-all 2>/dev/null || true
326
351
 
327
352
  # 1. Start tunnel
328
- nohup devbridge host -p <port> -e 8 > /tmp/host.log 2>&1 &
353
+ if command -v setsid > /dev/null 2>&1; then setsid nohup devbridge host -p <port> -e 72 > /tmp/host.log 2>&1 < /dev/null & else nohup devbridge host -p <port> -e 72 > /tmp/host.log 2>&1 < /dev/null & fi
354
+ disown 2>/dev/null || true
329
355
  sleep 10 && cat /tmp/host.log
330
356
 
331
357
  # 2. Extract tunnel URL and health-check before returning
@@ -337,7 +363,8 @@ else
337
363
  echo "WARN: Tunnel URL unreachable (HTTP $HTTP_CODE). Rebuilding tunnel..."
338
364
  pkill -f "devbridge host" && sleep 2
339
365
  devbridge delete-all 2>/dev/null || true
340
- nohup devbridge host -p <port> -e 8 > /tmp/host.log 2>&1 &
366
+ if command -v setsid > /dev/null 2>&1; then setsid nohup devbridge host -p <port> -e 72 > /tmp/host.log 2>&1 < /dev/null & else nohup devbridge host -p <port> -e 72 > /tmp/host.log 2>&1 < /dev/null & fi
367
+ disown 2>/dev/null || true
341
368
  sleep 10
342
369
  TUNNEL_URL=$(grep -oP 'Tunnel URL: \K.*' /tmp/host.log)
343
370
  HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$TUNNEL_URL" 2>/dev/null || echo "000")
@@ -357,13 +384,15 @@ devbridge list
357
384
  # Step B: Remove all stale tunnels
358
385
  devbridge delete-all
359
386
  # Step C: Retry tunnel creation
360
- nohup devbridge host -p <port> -e 8 > /tmp/host.log 2>&1 &
387
+ if command -v setsid > /dev/null 2>&1; then setsid nohup devbridge host -p <port> -e 72 > /tmp/host.log 2>&1 < /dev/null & else nohup devbridge host -p <port> -e 72 > /tmp/host.log 2>&1 < /dev/null & fi
388
+ disown 2>/dev/null || true
361
389
  sleep 10 && cat /tmp/host.log
362
390
  ```
363
391
 
364
392
  This eliminates the most common deployment failure — historical tunnels from previous sessions accumulating past the max=10 quota.
365
393
 
366
394
  - The public URL has the form `https://<id>-<port>.devbridge-s2.hwtunnel.com` (from the `Tunnel URL:` line; 8-char lowercase base32 id). The pre-migration bridge domain `cn-north-4-bridge.myhuaweicloud.com` is dead — treat any URL on it as broken and never hand one to the developer.
395
+ - **Start the host so it survives the exec session**: `setsid nohup ... < /dev/null & disown` (all three: setsid detaches the process group, `< /dev/null` drops the controlling tty, disown removes it from the shell's job table). On images without `setsid`, fall back to a bare `nohup ... &` launch (`if command -v setsid ...; else nohup ... ; fi`) and guard `disown` with `2>/dev/null || true` — a missing `setsid` must not leave the host unstarted. A bare `nohup ... &` (when `setsid` IS available) is reaped when the one-shot session ends — the tunnel dies right after a green in-session health check (the "200 but tunnel not found" trap). If the independent re-verify fails or answers 200 with a "tunnel not found" body, the URL is NOT persistent — always tell the developer 隧道将在会话结束后失效,请勿分享. `-e 72` sets the tunnel lifetime in **hours** — surface it to the developer with the URL ("链接有效期 72 小时").
367
396
  - **Return this URL to the developer as the deployment result link.** Keep the host process running (do not close the session before handing over the URL).
368
397
  - Tunnel `description` (`-d`) accepts only Chinese characters, letters, and digits (0-64). Symbols such as `-`/`_`/spaces are rejected (`Invalid tunnel description`).
369
398
  - Official docs: https://huaweicloud.github.io/devspace-devbridge/
@@ -74,6 +74,7 @@ version: 2
74
74
  | `code=HDKIT_NOT_SIGNED_UP`(您尚未报名…) | 未报名该赛事 | 告知需先完成赛事报名后再来领取 |
75
75
  | `已领取过` / `已领取` | 该账号已领过赛事券 | 告知已领取,不打扰 |
76
76
  | `code=HDKIT_COMPETITION_NOT_FOUND`(赛事不存在或已下线) | 赛事配置异常 | 告知赛事暂不可用,稍后再试 |
77
+ | `code=HDKIT_COMPETITION_NOT_CONFIGURED`(赛事代金券服务配置缺失…) | 服务端活动参数未配置 | 告知赛事代金券暂未开放,稍后再试 |
77
78
  | `本月代金券总额度已用完,所有账号均无法领取` | 全局额度用尽(HD.60630042) | 告知下月再试 |
78
79
  | `请先完成实名认证` | 账号未实名(HD.60630022) | 引导去控制台实名认证 |
79
80
  | `code=HDKIT_CRED_*` / 含「未配置 / not configured / AK/SK 无效 / 限额」 | 凭证未配置、无效或达限额 | 引导用户检查并配置 AK/SK |
@@ -41,9 +41,9 @@ Route to the sandbox when the developer signals free/quick preview intent ("免
41
41
  - Use `huaweicloud_get_service_icon` with the service name, alias, or Chinese name (e.g. `ecs`, `obs`, `modelarts`, `对象存储`). It returns the official CDN logo URL (`logo.source_url`), category, and product page link from https://open.huaweicloud.com/openplatform/icons.html.
42
42
  - Prefer `logo.source_url` for web deliverables and the Icons library search page for browsing; do not scrape logos from third-party sites.
43
43
 
44
- ## Deployment Target Options
44
+ ## Deployment Target Options (MANDATORY — use the `question` tool)
45
45
 
46
- When the intent is to **deploy, host, or preview a web app or static website** and the developer has not named a target, do NOT default to OBS or any single service. Present the options and let the developer choose, recommending the sandbox first: ① huawei-sandbox (recommended — temporary runtime, instant preview URL) ② huawei-obs (long-term static hosting/CDN) ③ huawei-ecs ④ huawei-cce. Only follow an explicit target when the developer names one.
46
+ When the intent is to **deploy, host, or preview a web app or static website** and the developer has not named a target, do NOT default to OBS or any single service. **NEVER infer or default the target** when it is unstated. You MUST present the options via the `question` tool and obtain an explicit choice before calling any deploy tool, recommending the sandbox first: ① huawei-sandbox (recommended — temporary runtime, instant preview URL) ② huawei-obs (long-term static hosting/CDN; 需提前准备好自定义域名) ③ huawei-ecs (需搭配弹性公网IP/EIP 才能公网访问) ④ huawei-cce. Only follow an explicit target when the developer names one; "部署到华为云" alone is NOT a named target — still ask.
47
47
 
48
48
  ## Region Intent
49
49
 
@@ -13,11 +13,11 @@ Use KooCLI `hcloud` for local inspection and reviewed operations. Never ask the
13
13
 
14
14
  Official guide: `https://support.huaweicloud.com/qs-hcli/hcli_02_003.html`.
15
15
 
16
- > **Version pairing**: this plugin is paired with **KooCLI v7.2.12** (`kooCliVersion` in `package.json`). Prefer the pinned download below. The one-liner script `hcloud_install.sh` always installs the `latest` version and cannot be pinned — if you use it, `check_cli`/`doctor` will warn when the installed version does not match v7.2.12.
16
+ > **Version pairing**: this plugin is paired with **KooCLI v7.3.12** (`kooCliVersion` in `package.json`). Prefer the pinned download below. The one-liner script `hcloud_install.sh` always installs the `latest` version and cannot be pinned — if you use it, `check_cli`/`doctor` will warn when the installed version does not match v7.3.12.
17
17
 
18
18
  ### Windows
19
19
 
20
- 1. Download and unzip: `https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/7.2.12/huaweicloud-cli-windows-amd64.zip`
20
+ 1. Download and unzip: `https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/7.3.12/huaweicloud-cli-windows-amd64.zip`
21
21
  2. Extract to `%USERPROFILE%\hcloud`, add to user `PATH`
22
22
  3. Verify: `hcloud version`
23
23
 
@@ -27,10 +27,10 @@ Fixed download (recommended):
27
27
 
28
28
  ```bash
29
29
  # amd64
30
- curl -LO "https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/7.2.12/huaweicloud-cli-linux-amd64.tar.gz"
30
+ curl -LO "https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/7.3.12/huaweicloud-cli-linux-amd64.tar.gz"
31
31
  tar -zxvf huaweicloud-cli-linux-amd64.tar.gz
32
32
  # arm64
33
- curl -LO "https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/7.2.12/huaweicloud-cli-linux-arm64.tar.gz"
33
+ curl -LO "https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/7.3.12/huaweicloud-cli-linux-arm64.tar.gz"
34
34
  tar -zxvf huaweicloud-cli-linux-arm64.tar.gz
35
35
  ```
36
36
 
@@ -49,10 +49,10 @@ Fixed download (recommended):
49
49
 
50
50
  ```bash
51
51
  # amd64
52
- curl -LO "https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/7.2.12/huaweicloud-cli-mac-amd64.tar.gz"
52
+ curl -LO "https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/7.3.12/huaweicloud-cli-mac-amd64.tar.gz"
53
53
  tar -zxvf huaweicloud-cli-mac-amd64.tar.gz
54
54
  # arm64 (Apple Silicon)
55
- curl -LO "https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/7.2.12/huaweicloud-cli-mac-arm64.tar.gz"
55
+ curl -LO "https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/7.3.12/huaweicloud-cli-mac-arm64.tar.gz"
56
56
  tar -zxvf huaweicloud-cli-mac-arm64.tar.gz
57
57
  ```
58
58
 
@@ -85,18 +85,25 @@ Do not rely on training data for facts. Huawei Cloud services, pricing, quotas,
85
85
 
86
86
  **Web app scenario layering**: for "deploy a web app", prefer `huawei-sandbox` for free/quick try (hello world, prototype, demo, temporary preview — temporary runtime + public URL (runtime lifetime not exposed via API); credential expiry see `expiresAt` from `connect`/`credentials`, zero billed resources); route to `huawei-functiongraph` / `huawei-ecs` (or `huawei-cce`) for production / long-term / custom-domain / high-availability hosting. When the chosen target requires **multiple purchased resources** (e.g. VPC + subnet + ECS + EIP + RDS, or OBS + CDN + DNS) or the user asks for an architecture/cost proposal first, hand off to `huawei-iac` for orchestrated provisioning with a mandatory cost gate.
87
87
 
88
- ## Deployment Target Options
88
+ ## Deployment Target Options (MANDATORY GATE — do not skip)
89
89
 
90
- When the intent is to **deploy, host, or preview a web app or static website** (no explicit target named), NEVER default to a single service such as OBS. Ask the developer to choose a target and present the options in this order:
90
+ When the intent is to **deploy, host, or preview a web app or static website** (no explicit target named), NEVER default to a single service such as OBS. You MUST present the target options via the `question` tool and obtain an explicit choice BEFORE calling any deployment tool. Present the options in this order:
91
91
 
92
- | # | Option | When to choose | Skill |
93
- | --- | -------------------------------------- | ---------------------------------------------------------- | -------------- |
94
- | 1 | **Sandbox (DevStation) — recommended** | Temporary runtime, quick preview, demo; instant public URL | huawei-sandbox |
95
- | 2 | OBS static website hosting | Long-term static hosting / CDN, no backend | huawei-obs |
96
- | 3 | ECS | Needs a server runtime (backend, custom server) | huawei-ecs |
97
- | 4 | CCE | Containerized workloads, scaling | huawei-cce |
92
+ | # | Option | When to choose | Skill |
93
+ | --- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
94
+ | 1 | **Sandbox (DevStation) — recommended** | Temporary runtime, quick preview, demo; instant public URL | huawei-sandbox |
95
+ | 2 | OBS static website hosting | Long-term static hosting / CDN, no backend. **Remind: a custom domain (域名) must be prepared — the default OBS endpoint does not support HTTPS for static hosting** | huawei-obs |
96
+ | 3 | ECS | Needs a server runtime (backend, custom server). **Remind: ECS must be paired with an EIP (弹性公网IP) for public access — extra billing** | huawei-ecs |
97
+ | 4 | CCE | Containerized workloads, scaling | huawei-cce |
98
98
 
99
- - Recommended prompt: "这个项目是 Web 应用/静态网站,建议优先部署到沙箱(临时运行环境,立即可预览)。也可选择 OBS 静态托管、ECS 或 CCE,你想部署到哪里?"
99
+ Hard rules:
100
+
101
+ - **NEVER infer or default the target** when it is unstated. Calling any deploy tool (`sandbox_connect`, `OBS mb`, `ECS CreateServers`, ...) before the choice is made is a violation.
102
+ - Only skip the question when: (a) the developer already named a target (e.g. "部署到 ECS", "用 OBS"), or (b) the developer explicitly asked for a temporary / sandbox / preview runtime.
103
+ - "部署到华为云" / "deploy to Huawei Cloud" / "上线" alone is NOT a named target — still run the gate.
104
+ - Include the OBS domain reminder and the ECS EIP pairing reminder in the options as presented.
105
+
106
+ - Recommended prompt: "这个项目是 Web 应用/静态网站,建议优先部署到沙箱(临时运行环境,立即可预览)。也可选择 OBS 静态托管(需提前准备好自定义域名)、ECS(需搭配弹性公网IP/EIP 才能公网访问)或 CCE,你想部署到哪里?"
100
107
  - If the developer explicitly names a target (e.g. "deploy to OBS"), the SANDBOX target hands off directly to `huawei-sandbox`; every other target (OBS / ECS / CCE / FunctionGraph / ...) hands off to `huawei-iac` for orchestration - iac loads the corresponding service skill for commands and adds the mandatory cost+balance gate, batch approval, state tracking, and ordered destroy. Direct handoff to the bare service skill skips these safety stages and must not happen for deployment intent. **Existing network resources (a reusable VPC) do NOT downgrade this to a single-service task - the purchase itself (compute, EIP, disk, rules) is what triggers iac.**
101
108
  - If the developer has no preference, recommend the sandbox first.
102
109
 
@@ -1,6 +1,7 @@
1
1
  import crypto from 'node:crypto';
2
2
 
3
3
  import { getProxyDispatcher } from '../proxy/proxy-agent.mjs';
4
+ import { errorFromTable } from './guidance.mjs';
4
5
 
5
6
  function iamBaseUrl() {
6
7
  return process.env.HW_IAM_ENDPOINT || 'https://iam.myhuaweicloud.com';
@@ -81,15 +82,28 @@ function projectForRegion(projects, region) {
81
82
  * SDK-HMAC-SHA256 request signing. A wrong SK produces an invalid signature,
82
83
  * which IAM rejects with HTTP 401 before any project data is returned.
83
84
  *
84
- * Returns { valid, projectId, error, warning }:
85
+ * Returns { valid, projectId, error, warning, code, messageZh, action, nextStep }:
85
86
  * - valid: true when IAM verified the signature (or the rejection is not
86
87
  * authentication-related), false when the credentials are unusable.
87
88
  * - projectId: first project matching `region`, else the first visible project.
88
89
  * - warning: set when credentials passed but project discovery was denied.
90
+ * - code/messageZh/action/nextStep: unified #900 shape from the shared
91
+ * ERROR_TABLE (guidance.mjs). The English `error`/`warning` detail text is
92
+ * intentionally kept — existing consumers (tools.mjs sandbox_credentials,
93
+ * external string-matching tests) rely on it.
89
94
  */
90
95
  export async function validateIamCredentials({ ak, sk, securityToken, region, timeoutMs = 15000 } = {}) {
91
96
  if (!ak || !sk) {
92
- return { valid: false, projectId: null, error: 'AK and SK are both required for credential validation.' };
97
+ const e = errorFromTable('HDKIT_CRED_MISSING');
98
+ return {
99
+ valid: false,
100
+ projectId: null,
101
+ error: 'AK and SK are both required for credential validation.',
102
+ code: e.code,
103
+ messageZh: e.messageZh,
104
+ action: e.action,
105
+ nextStep: e.nextStep,
106
+ };
93
107
  }
94
108
 
95
109
  const base = iamBaseUrl();
@@ -115,11 +129,16 @@ export async function validateIamCredentials({ ak, sk, securityToken, region, ti
115
129
  resp = await fetch(url, fetchOpts);
116
130
  }
117
131
  } catch (error) {
132
+ const e = errorFromTable('HDKIT_CRED_UNEXPECTED', error.message);
118
133
  return {
119
134
  valid: false,
120
135
  projectId: null,
121
136
  skipped: true,
122
137
  error: `IAM validation request failed (treating credentials as unverified): ${error.message}`,
138
+ code: e.code,
139
+ messageZh: e.messageZh,
140
+ action: e.action,
141
+ nextStep: e.nextStep,
123
142
  };
124
143
  } finally {
125
144
  clearTimeout(timer);
@@ -138,26 +157,42 @@ export async function validateIamCredentials({ ak, sk, securityToken, region, ti
138
157
 
139
158
  if (resp.status === 401) {
140
159
  const msg = data?.error?.message || text.slice(0, 200);
160
+ const e = errorFromTable('HDKIT_CRED_INVALID', `HTTP 401: ${msg}`);
141
161
  return {
142
162
  valid: false,
143
163
  projectId: null,
144
164
  error: `IAM rejected the credentials (HTTP 401: ${msg}). The AK/SK is invalid - check the SK for typos or expired security tokens.`,
165
+ code: e.code,
166
+ messageZh: e.messageZh,
167
+ action: e.action,
168
+ nextStep: e.nextStep,
145
169
  };
146
170
  }
147
171
 
148
172
  if (resp.status === 403) {
173
+ const msg = data?.error?.message || text.slice(0, 200);
174
+ const e = errorFromTable('HDKIT_CRED_FORBIDDEN', `HTTP 403: ${msg}`);
149
175
  return {
150
176
  valid: true,
151
177
  projectId: null,
152
178
  error: null,
153
179
  warning: `Credentials signed successfully but project listing was denied (HTTP 403). Continuing without project_id.`,
180
+ code: e.code,
181
+ messageZh: e.messageZh,
182
+ action: e.action,
183
+ nextStep: e.nextStep,
154
184
  };
155
185
  }
156
186
 
187
+ const e = errorFromTable('HDKIT_CRED_UNEXPECTED', `HTTP ${resp.status}`);
157
188
  return {
158
189
  valid: false,
159
190
  projectId: null,
160
191
  skipped: true,
161
192
  error: `Unexpected IAM response (HTTP ${resp.status}): ${text.slice(0, 200)}`,
193
+ code: e.code,
194
+ messageZh: e.messageZh,
195
+ action: e.action,
196
+ nextStep: e.nextStep,
162
197
  };
163
198
  }