huaweicloud-devkit 1.1.9-next.0 → 1.1.9-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/package.json +2 -2
  2. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
  3. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
  4. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
  5. package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
  6. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
  7. package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
  8. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +5 -0
  9. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +16 -2
  10. package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +22 -0
  11. package/plugins/huaweicloud-core/skills/huawei-ecs/references/windows-ssh.md +118 -0
  12. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +3 -3
  13. package/plugins/huaweicloud-core/skills/huawei-iac/SKILL.md +36 -36
  14. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +2 -2
  15. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +67 -37
  16. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +2 -2
  17. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +6 -6
  18. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +16 -9
  19. package/plugins/huaweicloud-core/src/auth/reconcile.mjs +26 -0
  20. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +32 -4
  21. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +206 -23
  22. package/plugins/huaweicloud-core/src/setup-cli.mjs +80 -8
  23. package/plugins/huaweicloud-core/src/tools.mjs +21 -2
@@ -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/
@@ -913,7 +942,7 @@ The tool checks:
913
942
  - **nginx_serving** — nginx responds with 2xx/3xx on the app port
914
943
  - **output_dir** — build output directory exists and is non-empty
915
944
  - **devbridge_tunnel** — DevBridge tunnel is active (version-aware: 0.1.x JSON / 0.2.x table)
916
- - **tunnel_url_accessible** — tunnel URL returns 200/304 with real content; a gateway migration placeholder page (「服务已迁移」 with HTTP 200) counts as FAIL
945
+ - **tunnel_url_accessible** — tunnel URL returns 200/304 with real content; these all count as FAIL: a gateway migration placeholder page (「服务已迁移」 with HTTP 200), `tunnel not found` (404 — no live host session on the gateway, even if the tunnel still appears in `devbridge list`), `port not configured` (404 — URL port not registered on the tunnel), and `bad gateway` (502 — tunnel alive but the local service is down). The check probes every listed tunnel and selects one that actually serves the port.
917
946
  - **qr_code** (cross-platform only) — QR image exists in output dir
918
947
 
919
948
  Returns `complete: true/false`, `score`, and `nextStep` to fix missing items.
@@ -936,36 +965,37 @@ Returns `complete: true/false`, `score`, and `nextStep` to fix missing items.
936
965
 
937
966
  ## Critical Warnings
938
967
 
939
- | Trap | Why |
940
- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
941
- | Target not confirmed | "部署到华为云" without a named target is NOT a go signal. You MUST run the Target-Selection Gate and get an explicit choice before calling any sandbox lifecycle tool. Skipping it and defaulting to the sandbox is a violation. |
942
- | Agreement required first | `sandbox_connect` fails if the agreement isn't signed; the `sandbox_check_user` preflight detects this, so surface it to the developer only when signing is needed |
943
- | Real-name required | `sandbox_connect` fails if `realnameVerified=false`; tell the developer once and stop, don't loop on connect |
944
- | Never expose tunnel details | Do not mention "DevBridge"/"tunnel"/"devbridge" to the developer — say "正在生成访问地址..." and hand over only the URL |
945
- | devbridge auth: probe the build | `0.2.2-release` has two builds: image builds retain AK/SK login + env auto-read (fully automatic via `/tmp/hw_creds.sh`); release builds accept only API Key (`/tmp/hw_api_key`). Never assume — probe with `devbridge auth login --help 2>&1 \| grep -q -- '--access-key'` (Step 1 of "Expose the deployed app") |
946
- | devbridge 0.1.x is dead | Sandboxes created before Sep 2026 ship 0.1.13, which connects to a migrated gateway serving a 「服务已迁移」 placeholder with HTTP 200. Check `devbridge version` first and upgrade in place (Step 0) — old tunnels never survive the upgrade |
947
- | CLI PATH | The installer only writes `~/.bashrc`; run `export PATH="$HOME/.huawei/bin:$PATH"` (prepend) in the session before using `devbridge` — appending lets a stale image binary in `/usr/local/bin` win |
948
- | Never install tunnel tooling locally | If the sandbox cannot install it, report a generic error and stop — installing on the developer's machine defeats sandbox deployment |
949
- | Return the deployment URL | Always hand the public URL from the host log to the developer as the final result |
950
- | Deploy is not just nginx | Configuring nginx does NOT complete the deployment. Steps 7 (DevBridge expose) and deploy_check are REQUIRED — `deploy_nginx` returns `nextStep: expose_via_devbridge` as a reminder. Do not stop after nginx. |
951
- | Call deploy_check before success | Always call `huaweicloud_sandbox_deploy_check` before reporting deployment success. A green nginx status does not mean the tunnel is accessible — verify end-to-end with the tool. |
952
- | Session state persists | `exec_with_session` preserves `cd`, env vars, aliases between calls |
953
- | Long commands prefer one-shot | `exec_one_shot` creates a fresh connection per call — more stable for builds, installs, and scripts >30s. See [Tool Selection Guide](#tool-selection-guide). |
954
- | SSR nginx/Node ports must differ | nginx `proxy_pass` targets `<nodePort>`, not `<port>`. `deploy_nginx` auto-defaults `nodePort` to `<port>+1` — always start the Node process with `PORT=<nodePort>` to match. Same-port = EADDRINUSE. |
955
- | HTTP 200 ≠ correct content | A green HTTP check does not guarantee the right project is serving — old processes from a previous session bound to the same port will still return 200. `deploy_check` verifies the deployment fingerprint to catch this. |
956
- | Destructive commands blocked | `rm -rf /`, `mkfs`, `dd if=`, fork bombs are denied by safety policy |
957
- | Workspace ID = dev_stage_id | Use `dev_stage_id` from `sandbox_connect` as `workspace_id` for terminal exec |
958
- | Projects live in `/workspace` | Clone/install project code under `/workspace/<repo-name>` (filesystem-root workspace mount, not `$HOME/workspace`), never in `/tmp` — ephemeral locations lose the project when the sandbox session restarts |
959
- | Upload project for local code | Use `sandbox_upload_project` to transfer local projects — packages as tar.gz, uploads via HTTP tunnel, extracts on sandbox. Much faster than base64 for multi-file projects |
960
- | Upload file for single files | Use `sandbox_upload_file` for individual files — base64 chunked, reliable for small files (<1MB) |
961
- | Node.js >= 22 required | Sandbox terminal uses built-in WebSocket (globalThis.WebSocket); if Node.js is missing, install it from the Huawei Cloud mirror (see "Node.js in the sandbox") |
962
- | Sandbox restart kills processes | After sandbox restarts, all user processes (nginx, Node.js, Python servers) are stopped. Re-run startup commands and verify ports are listening before proceeding. |
963
- | Cross-platform binaries incompatible | The sandbox runs Linux. Native binaries built on Windows/macOS (e.g., Prisma client, `node_modules/.prisma/`, platform-specific native addons) will not execute. Always install and build dependencies inside the sandbox, not locally. |
964
- | Cross-platform needs QR code | When `detect_framework` returns `type: "cross-platform"` (Taro, uni-app), generating a QR code image is **mandatory** — the deployment is incomplete without it. Check the Deployment Completion Check table in Step 7. |
965
- | Build fails do NOT auto-fix | When a build exits with non-zero exit code, STOP and present the error + fix options to the developer. Do not silently retry, modify configs, or change source files without explicit approval. See 4c-aux. |
966
- | Tunnel description: no spaces/hyphens | `devbridge create <name> -d 'hello world'` fails with `Invalid tunnel description: only Chinese characters, digits, letters, length 0-64`. The message is misleading — the real rule is no spaces/hyphens. Omit `-d` or use bare letters/digits. |
967
- | Use `host`, never `connect` | `devbridge connect` is the sender side (for on-prem machines dialing out). For public preview it loops `Connection failed, retrying...` forever with no hint. Always use `devbridge host <tunnelId> -p <port>`. |
968
- | Port drift detaches the tunnel | When deploy_nginx auto-increments to a free port, an existing tunnel stays bound to the old port — re-bind: `devbridge port create <tunnelId> -p <newPort> --protocol http -a` and restart `devbridge host`. deploy_nginx emits a warning when this happens. |
968
+ | Trap | Why |
969
+ | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
970
+ | Target not confirmed | "部署到华为云" without a named target is NOT a go signal. You MUST run the Target-Selection Gate and get an explicit choice before calling any sandbox lifecycle tool. Skipping it and defaulting to the sandbox is a violation. |
971
+ | Agreement required first | `sandbox_connect` fails if the agreement isn't signed; the `sandbox_check_user` preflight detects this, so surface it to the developer only when signing is needed |
972
+ | Real-name required | `sandbox_connect` fails if `realnameVerified=false`; tell the developer once and stop, don't loop on connect |
973
+ | Never expose tunnel details | Do not mention "DevBridge"/"tunnel"/"devbridge" to the developer — say "正在生成访问地址..." and hand over only the URL |
974
+ | devbridge auth: probe the build | `0.2.2-release` has two builds: image builds retain AK/SK login + env auto-read (fully automatic via `/tmp/hw_creds.sh`); release builds accept only API Key (`/tmp/hw_api_key`). Never assume — probe with `devbridge auth login --help 2>&1 \| grep -q -- '--access-key'` (Step 1 of "Expose the deployed app") |
975
+ | devbridge 0.1.x is dead | Sandboxes created before Sep 2026 ship 0.1.13, which connects to a migrated gateway serving a 「服务已迁移」 placeholder with HTTP 200. Check `devbridge version` first and upgrade in place (Step 0) — old tunnels never survive the upgrade |
976
+ | CLI PATH | The installer only writes `~/.bashrc`; run `export PATH="$HOME/.huawei/bin:$PATH"` (prepend) in the session before using `devbridge` — appending lets a stale image binary in `/usr/local/bin` win |
977
+ | Never install tunnel tooling locally | If the sandbox cannot install it, report a generic error and stop — installing on the developer's machine defeats sandbox deployment |
978
+ | Return the deployment URL | Always hand the public URL from the host log to the developer as the final result |
979
+ | Deploy is not just nginx | Configuring nginx does NOT complete the deployment. Steps 7 (DevBridge expose) and deploy_check are REQUIRED — `deploy_nginx` returns `nextStep: expose_via_devbridge` as a reminder. Do not stop after nginx. |
980
+ | Call deploy_check before success | Always call `huaweicloud_sandbox_deploy_check` before reporting deployment success. A green nginx status does not mean the tunnel is accessible — verify end-to-end with the tool. |
981
+ | Session state persists | `exec_with_session` preserves `cd`, env vars, aliases between calls |
982
+ | Long commands prefer one-shot | `exec_one_shot` creates a fresh connection per call — more stable for builds, installs, and scripts >30s. See [Tool Selection Guide](#tool-selection-guide). |
983
+ | SSR nginx/Node ports must differ | nginx `proxy_pass` targets `<nodePort>`, not `<port>`. `deploy_nginx` auto-defaults `nodePort` to `<port>+1` — always start the Node process with `PORT=<nodePort>` to match. Same-port = EADDRINUSE. |
984
+ | HTTP 200 ≠ correct content | A green HTTP check does not guarantee the right project is serving — old processes from a previous session bound to the same port will still return 200. `deploy_check` verifies the deployment fingerprint to catch this. |
985
+ | Destructive commands blocked | `rm -rf /`, `mkfs`, `dd if=`, fork bombs are denied by safety policy |
986
+ | Workspace ID = dev_stage_id | Use `dev_stage_id` from `sandbox_connect` as `workspace_id` for terminal exec |
987
+ | Projects live in `/workspace` | Clone/install project code under `/workspace/<repo-name>` (filesystem-root workspace mount, not `$HOME/workspace`), never in `/tmp` — ephemeral locations lose the project when the sandbox session restarts |
988
+ | Upload project for local code | Use `sandbox_upload_project` to transfer local projects — packages as tar.gz, uploads via HTTP tunnel, extracts on sandbox. Much faster than base64 for multi-file projects |
989
+ | Upload file for single files | Use `sandbox_upload_file` for individual files — base64 chunked, reliable for small files (<1MB) |
990
+ | Node.js >= 22 required | Sandbox terminal uses built-in WebSocket (globalThis.WebSocket); if Node.js is missing, install it from the Huawei Cloud mirror (see "Node.js in the sandbox") |
991
+ | Sandbox restart kills processes | After sandbox restarts, all user processes (nginx, Node.js, Python servers) are stopped. Re-run startup commands and verify ports are listening before proceeding. |
992
+ | Cross-platform binaries incompatible | The sandbox runs Linux. Native binaries built on Windows/macOS (e.g., Prisma client, `node_modules/.prisma/`, platform-specific native addons) will not execute. Always install and build dependencies inside the sandbox, not locally. |
993
+ | Cross-platform needs QR code | When `detect_framework` returns `type: "cross-platform"` (Taro, uni-app), generating a QR code image is **mandatory** — the deployment is incomplete without it. Check the Deployment Completion Check table in Step 7. |
994
+ | Build fails do NOT auto-fix | When a build exits with non-zero exit code, STOP and present the error + fix options to the developer. Do not silently retry, modify configs, or change source files without explicit approval. See 4c-aux. |
995
+ | Tunnel description: no spaces/hyphens | `devbridge create <name> -d 'hello world'` fails with `Invalid tunnel description: only Chinese characters, digits, letters, length 0-64`. The message is misleading — the real rule is no spaces/hyphens. Omit `-d` or use bare letters/digits. |
996
+ | Use `host`, never `connect` | `devbridge connect` is the sender side (for on-prem machines dialing out). For public preview it loops `Connection failed, retrying...` forever with no hint. Always use `devbridge host <tunnelId> -p <port>`. |
997
+ | Port drift detaches the tunnel | When deploy_nginx auto-increments to a free port, an existing tunnel stays bound to the old port — re-bind: `devbridge port create <tunnelId> -p <newPort> --protocol http -a` and restart `devbridge host`. deploy_nginx emits a warning when this happens. |
998
+ | 404 "tunnel not found" ≠ content issue | The s2 gateway answers 404 with body `tunnel not found` when the host process is dead (the tunnel record may STILL show in `devbridge list`) and `port not configured` for an unregistered URL port — both mean the URL is dead, not that the app returned 404. `expose_tunnel`/`deploy_check` verify the probe body and refuse such URLs; a shared sandbox's second deployment also kills earlier URLs (global pre-clean), so re-expose and hand over the fresh URL. |
969
999
 
970
1000
  ## Node.js in the sandbox
971
1001
 
@@ -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
 
@@ -229,3 +229,29 @@ export function runHcloudConfigure(profile, ak, sk, region) {
229
229
  ),
230
230
  };
231
231
  }
232
+
233
+ // Delete a KooCLI profile (best-effort). Used by the auth persist path (#920):
234
+ // `configure set` REFUSES an empty --cli-security-token, so a stale/expired
235
+ // token already in the profile can never be overwritten — it poisons every
236
+ // hcloud call with APIGW.0301. The documented manual bypass is
237
+ // `hcloud configure delete --cli-profile=<profile>` followed by a re-configure;
238
+ // this helper makes that path a runtime step instead of user knowledge.
239
+ // A missing profile reports a non-zero status, which callers treat as
240
+ // "nothing to clean" — never as an error.
241
+ export function runHcloudProfileDelete(profile) {
242
+ const { executable, argsPrefix } = resolveHcloudCommand();
243
+ const r = spawnSync(executable, [...argsPrefix, 'configure', 'delete', `--cli-profile=${profile}`], {
244
+ shell: false,
245
+ windowsHide: true,
246
+ stdio: 'pipe',
247
+ timeout: 30000,
248
+ });
249
+ return {
250
+ ok: r.status === 0,
251
+ error: redactSecrets(
252
+ String(r.stderr || '')
253
+ .trim()
254
+ .slice(0, 240),
255
+ ),
256
+ };
257
+ }
@@ -659,11 +659,39 @@ function runHcloudOnce(plan, options) {
659
659
  });
660
660
  return;
661
661
  }
662
+ // #921: the ok verdict converges on the exit code plus structured error
663
+ // parsing (errorCode-first, handled above). Two false-positive classes
664
+ // are closed here:
665
+ // 1. exit code 0 but the output carries a KooCLI parameter error (e.g.
666
+ // `--delete_ip` instead of `--delete_publicip`) — the command never
667
+ // executed; report ok:false with a PARAM_ERROR errorCode.
668
+ // 2. non-zero exit code with "successfully" in the output — the old
669
+ // broad regex flagged ANY failure mentioning "successfully". It is
670
+ // narrowed to OBS obsutil-style commands (which are known to return
671
+ // non-zero exit codes on success, #57) AND require the output to
672
+ // carry no error indicators.
673
+ const combined = `${stdout}${stderr}`;
674
+ const paramError = combined.match(
675
+ /\[?(?:ERROR|USE_ERROR)\]?[: ]*(?:unknown parameter|unknown option|incorrect (?:option|switch|parameter)|invalid (?:parameter|argument)|参数不存在|参数错误|无效的参数)[^\n]*/i,
676
+ );
677
+ if (paramError) {
678
+ finish({
679
+ ok: false,
680
+ exitCode: code,
681
+ signal,
682
+ errorCode: 'PARAM_ERROR',
683
+ errorMessage: paramError[0].trim(),
684
+ stdout: redactOutput(stdout),
685
+ stderr: redactOutput(stderr),
686
+ plan,
687
+ });
688
+ return;
689
+ }
690
+ const isObsCommand = /^obs\b/i.test(String(plan.rawArgs?.[0] || ''));
691
+ const successText = /successfully/i.test(combined);
692
+ const errorIndicators = /error|failed|失败|invalid|not found|denied|拒绝/i.test(combined);
662
693
  finish({
663
- ok:
664
- code === 0 ||
665
- (code !== 0 &&
666
- /successfully|succ?ess.*\[200\]|create bucket successfully|upload successfully/i.test(stdout + stderr)),
694
+ ok: code === 0 || (isObsCommand && successText && !errorIndicators),
667
695
  exitCode: code,
668
696
  signal,
669
697
  stdout: redactOutput(stdout),