huaweicloud-devkit 1.1.0-next.17 → 1.1.0-next.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.1.0-next.17",
3
+ "version": "1.1.0-next.19",
4
4
  "description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
5
5
  "type": "module",
6
6
  "files": [
@@ -17,7 +17,7 @@
17
17
  "mcpServers": "./.mcp.json",
18
18
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
19
19
  "skills": "./skills/",
20
- "version": "1.1.0-next.17",
20
+ "version": "1.1.0-next.19",
21
21
  "author": {
22
22
  "name": "HuaweiCloud Mate",
23
23
  "url": "https://github.com/huaweicloud"
@@ -20,7 +20,7 @@
20
20
  "mcpServers": "./.mcp.json",
21
21
  "description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
22
22
  "skills": "./skills/",
23
- "version": "1.1.0-next.17",
23
+ "version": "1.1.0-next.19",
24
24
  "author": {
25
25
  "name": "HuaweiCloud Mate",
26
26
  "url": "https://github.com/huaweicloud"
@@ -17,7 +17,7 @@
17
17
  "mcpServers": "./.mcp.json",
18
18
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
19
19
  "skills": "./skills/",
20
- "version": "1.1.0-next.17",
20
+ "version": "1.1.0-next.19",
21
21
  "author": {
22
22
  "name": "HuaweiCloud Mate",
23
23
  "url": "https://github.com/huaweicloud"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.1.0-next.17",
3
+ "version": "1.1.0-next.19",
4
4
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
5
5
  "author": {
6
6
  "name": "HuaweiCloud Mate",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.1.0-next.17",
3
+ "version": "1.1.0-next.19",
4
4
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
5
5
  "author": {
6
6
  "name": "HuaweiCloud Mate",
@@ -2,7 +2,7 @@
2
2
  "name": "huaweicloud-devkit",
3
3
  "id": "huaweicloud-devkit",
4
4
  "displayName": "HuaweiCloud DevKit",
5
- "version": "1.1.0-next.17",
5
+ "version": "1.1.0-next.19",
6
6
  "family": "bundle-plugin",
7
7
  "bundleFormat": "codex",
8
8
  "description": "Guide coding agents to use Huawei Cloud safely — KooCLI, APIs, SDKs, 28 MCP tools, skills, and safety guardrails.",
@@ -13,7 +13,10 @@
13
13
  "field": "text",
14
14
  "regex": "(^|\\s)(cat|type|Get-Content|gc|less|more)\\s+[^\\n]*(\\.hcloud|\\.huaweicloud)"
15
15
  },
16
- { "field": "text", "regex": "(hcloud|huaweicloud)[/\\\\](config|credentials)" }
16
+ {
17
+ "field": "text",
18
+ "regex": "(hcloud|huaweicloud)[/\\\\](config|credentials)"
19
+ }
17
20
  ]
18
21
  },
19
22
  "message": "The command may read local Huawei Cloud credential or profile files into the agent context.",
@@ -27,8 +30,14 @@
27
30
  "stages": ["command"],
28
31
  "match": {
29
32
  "all": [
30
- { "field": "text", "regex": "(^|\\s)(env|printenv|Get-ChildItem\\s+Env:|gci\\s+Env:|dir\\s+Env:)" },
31
- { "field": "text", "regex": "(HUAWEICLOUD|HWC_|HCLOUD|OS_)" }
33
+ {
34
+ "field": "text",
35
+ "regex": "(^|\\s)(env|printenv|Get-ChildItem\\s+Env:|gci\\s+Env:|dir\\s+Env:)"
36
+ },
37
+ {
38
+ "field": "text",
39
+ "regex": "(HUAWEICLOUD|HWC_|HCLOUD|OS_)"
40
+ }
32
41
  ]
33
42
  },
34
43
  "message": "The command may print cloud credential environment variables.",
@@ -42,13 +51,36 @@
42
51
  "stages": ["command"],
43
52
  "match": {
44
53
  "any": [
45
- { "field": "text", "regex": "(ShowSecretVersion|DownloadSecret|GetSecretValue)" },
46
- { "field": "text", "regex": "(secret_string|secret_binary|secretString|secretBinary)" }
54
+ {
55
+ "field": "text",
56
+ "regex": "(ShowSecretVersion|DownloadSecret|GetSecretValue)"
57
+ },
58
+ {
59
+ "field": "text",
60
+ "regex": "(secret_string|secret_binary|secretString|secretBinary)"
61
+ }
47
62
  ]
48
63
  },
49
64
  "message": "The command appears to retrieve plaintext secret values.",
50
65
  "remediation": "Use runtime secret references, user-side inspection, or redacted metadata checks instead of returning secret values to the agent."
51
66
  },
67
+ {
68
+ "id": "hwc-command-sts-credential",
69
+ "title": "Temporary credential exposure",
70
+ "category": "credential",
71
+ "severity": "warn",
72
+ "stages": ["command"],
73
+ "match": {
74
+ "any": [
75
+ {
76
+ "field": "text",
77
+ "regex": "hcloud\\s+STS\\s+(AssumeAgency|GetCallerIdentity|GetTemporaryCredential)\\b"
78
+ }
79
+ ]
80
+ },
81
+ "message": "此操作将返回临时 AK/SK 和 SecurityToken。注意敏感数据保密,不要将凭证输出到日志或分享给第三方。",
82
+ "remediation": "临时凭证仅用于当前会话,使用完毕后及时销毁,不要持久化存储。"
83
+ },
52
84
  {
53
85
  "id": "hwc-command-encoded-shell-exec",
54
86
  "title": "Encoded payload piped to shell",
@@ -61,8 +93,14 @@
61
93
  "field": "text",
62
94
  "regex": "(base64\\s+(-d|--decode)|xxd\\s+-r|certutil\\s+-decode|FromBase64String|hex\\s*decode)"
63
95
  },
64
- { "field": "text", "regex": "(\\||;|&&)" },
65
- { "field": "text", "regex": "\\b(bash|sh|zsh|powershell|pwsh|cmd|python|node)\\b" }
96
+ {
97
+ "field": "text",
98
+ "regex": "(\\||;|&&)"
99
+ },
100
+ {
101
+ "field": "text",
102
+ "regex": "\\b(bash|sh|zsh|powershell|pwsh|cmd|python|node)\\b"
103
+ }
66
104
  ]
67
105
  },
68
106
  "message": "The command decodes an encoded payload and pipes it into an interpreter.",
@@ -96,7 +134,12 @@
96
134
  "severity": "deny",
97
135
  "stages": ["command", "artifact", "deploy_plan"],
98
136
  "match": {
99
- "all": [{ "field": "text", "regex": "(OBS|obs://|bucket|object|Statement|Principal)" }],
137
+ "all": [
138
+ {
139
+ "field": "text",
140
+ "regex": "(OBS|obs://|bucket|object|Statement|Principal)"
141
+ }
142
+ ],
100
143
  "any": [
101
144
  {
102
145
  "field": "text",
@@ -119,7 +162,10 @@
119
162
  "stages": ["command", "artifact", "deploy_plan"],
120
163
  "match": {
121
164
  "all": [
122
- { "field": "text", "regex": "(FunctionGraph|APIG|CreateTrigger|CreateApi|DEDICATEDGATEWAY|API Gateway)" },
165
+ {
166
+ "field": "text",
167
+ "regex": "(FunctionGraph|APIG|CreateTrigger|CreateApi|DEDICATEDGATEWAY|API Gateway)"
168
+ },
123
169
  {
124
170
  "field": "text",
125
171
  "regex": "(public|PUBLIC|0\\.0\\.0\\.0\\s*/\\s*0|auth[\\\"']?\\s*[=:]\\s*[\\\"']?(NONE|none|false)|security_authentication[\\\"']?\\s*[=:]\\s*[\\\"']?(NONE|none))"
@@ -137,12 +183,18 @@
137
183
  "stages": ["command", "artifact", "deploy_plan"],
138
184
  "match": {
139
185
  "all": [
140
- { "field": "text", "regex": "(IAM|policy|role|agency|Statement|Action)" },
186
+ {
187
+ "field": "text",
188
+ "regex": "(IAM|policy|role|agency|Statement|Action)"
189
+ },
141
190
  {
142
191
  "field": "text",
143
192
  "regex": "(\\\"Action\\\"\\s*:\\s*(\\\"(\\*|\\*:\\*)\\\"|\\[\\s*\\\"(\\*|\\*:\\*)\\\")|Action\\s*[=:]\\s*(\\*|\\*:\\*)|AdministratorAccess|FullAccess)"
144
193
  },
145
- { "field": "text", "regex": "(\\\"Effect\\\"\\s*:\\s*\\\"Allow\\\"|Effect\\s*[=:]\\s*Allow)" }
194
+ {
195
+ "field": "text",
196
+ "regex": "(\\\"Effect\\\"\\s*:\\s*\\\"Allow\\\"|Effect\\s*[=:]\\s*Allow)"
197
+ }
146
198
  ]
147
199
  },
148
200
  "message": "The change appears to grant broad administrator permissions.",
@@ -156,13 +208,40 @@
156
208
  "stages": ["command"],
157
209
  "match": {
158
210
  "all": [
159
- { "field": "text", "regex": "(Delete|BatchDelete|Remove|\\brm\\b|delete)" },
160
- { "field": "text", "regex": "(--force|-f|--recursive|-r|delete_publicip\\s*[=:]\\s*true)" }
211
+ {
212
+ "field": "text",
213
+ "regex": "(Delete|BatchDelete|Remove|\\brm\\b|delete)"
214
+ },
215
+ {
216
+ "field": "text",
217
+ "regex": "(\\s--force|\\s-f\\s|\\s--recursive|\\s-r\\s|delete_publicip\\s*[=:]\\s*true)"
218
+ }
161
219
  ]
162
220
  },
163
221
  "message": "The command combines deletion with force, recursive, or cascading deletion behavior.",
164
222
  "remediation": "Show the exact resources to be deleted, confirm backups or recovery path, and require explicit user approval for the exact command."
165
223
  },
224
+ {
225
+ "id": "hwc-destructive-delete-operation",
226
+ "title": "Destructive cloud resource operation",
227
+ "category": "destructive",
228
+ "severity": "warn",
229
+ "stages": ["command"],
230
+ "match": {
231
+ "any": [
232
+ {
233
+ "field": "text",
234
+ "regex": "hcloud\\s+\\w+\\s+(Delete\\w*|Detach\\w*|Remove\\w*|Dissociate\\w*|Unassign\\w*|Revoke\\w*)\\b"
235
+ },
236
+ {
237
+ "field": "text",
238
+ "regex": "hcloud\\s+\\w+\\s+(BatchDelete\\w*|BatchRemove\\w*)\\b"
239
+ }
240
+ ]
241
+ },
242
+ "message": "This hcloud command will delete, detach, or remove cloud resources. The operation may be irreversible.",
243
+ "remediation": "List the resources to be affected first, confirm with the user, and require explicit approval before executing any destructive operation."
244
+ },
166
245
  {
167
246
  "id": "hwc-sandbox-missing-ttl",
168
247
  "title": "Sandbox deployment missing cleanup metadata",
@@ -175,10 +254,16 @@
175
254
  "field": "text",
176
255
  "regex": "(sandbox|preview|temporary|ephemeral|dev environment|FunctionGraph|ECS|CCE|APIG)"
177
256
  },
178
- { "field": "text", "regex": "(Create|Deploy|Provision|resource|stack|environment)" }
257
+ {
258
+ "field": "text",
259
+ "regex": "(Create|Deploy|Provision|resource|stack|environment)"
260
+ }
179
261
  ],
180
262
  "none": [
181
- { "field": "text", "regex": "(ttl|expires_at|expire_at|cleanup|owner|cost_center|auto_delete|auto-delete)" }
263
+ {
264
+ "field": "text",
265
+ "regex": "(ttl|expires_at|expire_at|cleanup|owner|cost_center|auto_delete|auto-delete)"
266
+ }
182
267
  ]
183
268
  },
184
269
  "message": "The preview or sandbox deployment does not show cleanup or ownership metadata.",
@@ -196,7 +281,10 @@
196
281
  "field": "text",
197
282
  "regex": "(max_instances|max_node_count|max_replica|desired\\s*[=:]\\s*[5-9][0-9]|replicas\\s*[=:]\\s*[5-9][0-9])"
198
283
  },
199
- { "field": "text", "regex": "(GPU|gpu|large|xlarge|charging_mode\\s*[=:]\\s*prePaid|period_type|period_num)" }
284
+ {
285
+ "field": "text",
286
+ "regex": "(GPU|gpu|large|xlarge|charging_mode\\s*[=:]\\s*prePaid|period_type|period_num)"
287
+ }
200
288
  ]
201
289
  },
202
290
  "message": "The generated plan may create high-cost or unbounded capacity.",
@@ -210,14 +298,38 @@
210
298
  "stages": ["command"],
211
299
  "match": {
212
300
  "any": [
213
- { "field": "text", "regex": "rm\\s+-rf\\s+/" },
214
- { "field": "text", "regex": "mkfs" },
215
- { "field": "text", "regex": "dd\\s+if=" },
216
- { "field": "text", "regex": ":\\\\(\\\\)\\\\{[^}]*\\\\|[^}]*&[^}]*\\\\}[^;]*;:" },
217
- { "field": "text", "regex": "\\bshutdown\\b" },
218
- { "field": "text", "regex": "\\breboot\\b" },
219
- { "field": "text", "regex": "\\binit\\s+[06]\\b" },
220
- { "field": "text", "regex": "\\bfdisk\\b" }
301
+ {
302
+ "field": "text",
303
+ "regex": "rm\\s+-rf\\s+/"
304
+ },
305
+ {
306
+ "field": "text",
307
+ "regex": "mkfs"
308
+ },
309
+ {
310
+ "field": "text",
311
+ "regex": "dd\\s+if="
312
+ },
313
+ {
314
+ "field": "text",
315
+ "regex": ":\\\\(\\\\)\\\\{[^}]*\\\\|[^}]*&[^}]*\\\\}[^;]*;:"
316
+ },
317
+ {
318
+ "field": "text",
319
+ "regex": "\\bshutdown\\b"
320
+ },
321
+ {
322
+ "field": "text",
323
+ "regex": "\\breboot\\b"
324
+ },
325
+ {
326
+ "field": "text",
327
+ "regex": "\\binit\\s+[06]\\b"
328
+ },
329
+ {
330
+ "field": "text",
331
+ "regex": "\\bfdisk\\b"
332
+ }
221
333
  ]
222
334
  },
223
335
  "message": "The sandbox terminal command is destructively dangerous and could destroy the workspace.",
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: huawei-ecs
3
- description: 'Use when creating, configuring, managing, or troubleshooting ECS instances on Huawei Cloud. Covers instance creation (hcloud ECS CreateServers), flavor selection, image management, security groups, EIP binding, disk attachment, auto-scaling (AS), and troubleshooting. Triggers on: ECS, instance, flavor, image, security group, EIP, EVS, auto-scaling. NOT for: CCE container workloads (use huawei-cce), BMS bare metal servers.'
3
+ description: 'Use when creating, configuring, managing, or troubleshooting ECS instances on Huawei Cloud. Covers instance creation (hcloud ECS CreateServers), flavor selection, image management, security groups, EIP binding, disk attachment, auto-scaling (AS), and troubleshooting. Triggers on: ECS, instance, flavor, image, security group, EIP, EVS, auto-scaling. NOT for: CCE container workloads (use huawei-cce), BMS bare metal servers.
4
+
5
+ HC活动场景(ECS+Nginx部署后端服务)触发词: HC活动, 华为HC, HC大会, 活动部署, HC ECS, 活动流程, 部署后端, Nginx部署, Nodejs部署, 上线后端服务, 部署个人网站, /hc-activity/.'
4
6
  version: 1
5
7
  ---
6
8
 
@@ -59,6 +61,7 @@ Flavor families are **region-dependent**. Always run `hcloud ECS ListFlavors --c
59
61
  | Attach disk | hcloud EVS AttachVolume --volume_id=<id> --server_id=<id> | references/evs.md |
60
62
  | Delete instance | hcloud ECS DeleteServers --servers.1.id=<id> --delete_publicip=true --delete_volume=true | references/create-instance.md |
61
63
  | Reboot instance | hcloud ECS BatchRebootServers --reboot.servers.1.id=<id> --reboot.type=SOFT | NOT `RebootServer` — that operation does not exist |
64
+ | HC活动部署 | 按量付费购买ECS+Nginx部署后端服务 | references/hc-activity.md |
62
65
 
63
66
  ## How to Search for Instances
64
67
 
@@ -186,3 +189,4 @@ For Flexus X, use standard ECS CreateServers flow with `x1.*` flavors. For Flexu
186
189
  - ECS Docs: https://support.huaweicloud.com/ecs/
187
190
  - Flavor specs: references/flavors.md
188
191
  - Create instance: references/create-instance.md
192
+ - HC活动 ECS+Nginx部署: references/hc-activity.md
@@ -0,0 +1,27 @@
1
+ # HC活动 ECS + Nginx/Node.js 部署后端服务
2
+
3
+ ## 目标
4
+
5
+ 在华为云 cn-north-4 区域,按量付费购买 ECS,部署后端代码仓的后端服务。
6
+
7
+ ## 前置条件
8
+
9
+ - 后端代码仓 git 地址
10
+ - 账户已有默认 VPC 和子网(Agent 自动查询)
11
+
12
+ ## 流程
13
+
14
+ 1. 查询 VPC/子网,创建安全组,放通 SSH(22)仅本机IP、HTTP(80)全网
15
+ 2. 查询镜像(推荐 HCE 2.0)和规格,直接购买1核2G
16
+ 3. 创建 keypair,创建按量付费 ECS(系统盘 SSD 40GB),轮询等待 ACTIVE
17
+ 4. 创建 5_bgp 按流量计费 EIP,绑定到 ECS 端口
18
+ 5. SSH 登录,安装 Node.js 18,git clone 代码仓,npm install
19
+ 6. Nginx反向代理:80->:3000
20
+ 7. 验证 /hc-activity/api/title 和 /hc-activity/health
21
+
22
+ ## 注意事项
23
+
24
+ - 后端3000仅监听回环
25
+ - Nginx修改后 nginx -t 再 reload
26
+ - 按量付费停机仍计费
27
+ - 删除实例时 --delete_publicip=true --delete_volume=true
@@ -40,7 +40,7 @@ hcloud RDS <Operation> --cli-region=<region> [--key=value ...]
40
40
  | Automated backups use OBS | Backup storage incurs separate charges. Set retention period explicitly |
41
41
  | Storage auto-scaling off by default | Enable before storage runs out or instance goes read-only |
42
42
  | `--password` conflicts with KooCLI | Use `--cli-jsonInput=<file>` with JSON file (see `--cli-jsonInput` section below). The `printf "b\n"` workaround is broken in KooCLI 7.2.12+ |
43
- | Volume type must match flavor | General→CLOUDSSD; Dedicated→CLOUDSSD\|ESSD; ARM→ULTRAHIGH |
43
+ | Volume type must match flavor | General→CLOUDSSD; Dedicated→CLOUDSSD\|ESSD; ARM→CLOUDSSD |
44
44
  | Flavor not in region | Always `ListFlavors` first. Spec codes vary by region |
45
45
  | `database_name` is case-sensitive | Use `MySQL` / `PostgreSQL` / `SQLServer` / `MariaDB` — NOT lower-case `mysql` |
46
46
  | Instance creation takes 3–8 min | Status: BUILD→MODIFYING→ACTIVE. Poll every 15s: `hcloud RDS ListInstances --cli-region=<r> --instance_id=<id> \| jq '.instances[0].status'` |
@@ -109,7 +109,7 @@ hcloud RDS CreateInstance --cli-region=<r> \
109
109
  | `--name` | Yes | Instance name |
110
110
  | `--datastore` | Yes | `--datastore.type=<engine> --datastore.version=<version>` |
111
111
  | `--flavor_ref` | Yes | From `ListFlavors` output |
112
- | `--volume` | Yes | Type matching: General→CLOUDSSD, Dedicated→CLOUDSSD\|ESSD, ARM→ULTRAHIGH |
112
+ | `--volume` | Yes | Type matching: General→CLOUDSSD, Dedicated→CLOUDSSD\|ESSD, ARM→CLOUDSSD |
113
113
  | `--vpc_id` | Yes | Must exist in target region |
114
114
  | `--subnet_id` | Yes | Must exist in target region |
115
115
  | `--security_group_id` | Yes | Must have DB port open |
@@ -95,7 +95,7 @@ Setup is a **plugin-side preflight** — the developer should be asked a questio
95
95
  2. **Real-name verification only** (`HDKIT_NOT_REALNAME`): tell the developer once, "Huawei Cloud requires real-name verification before using the sandbox — please complete it in the Huawei Cloud console (实名认证)." and stop — do not retry `connect` in a loop
96
96
  3. **Sign agreement only** (`HDKIT_NOT_AGREEMENT`): **STOP and do NOT sign on your own.** Ask the developer: "Huawei Cloud sandbox requires signing the latest developer service agreement. May I sign it for you?" Then **wait for the developer to explicitly agree** (e.g. "签署" / "确认" / "sign it"). Only after explicit consent call `huaweicloud_sandbox_sign_agreement` and return its result (`signed`/`signedCount`) to the developer. **Never sign a legal agreement on the developer's behalf without their explicit, unambiguous consent.** Do not expose the underlying sandbox/DevBridge service as a separate entity the developer must understand or sign up for
97
97
  4. **Both missing** (`HDKIT_NOT_REALNAME_AND_AGREEMENT`): present **both** requirements together in one message — the real-name verification steps (console, step 2) **and** the agreement-signing request (step 3, wait for explicit consent) — so the developer can complete both at once
98
- 5. **Connect**: `huaweicloud_sandbox_connect` — returns `session_id`, `dev_stage_id`, `connection_id`, `connection_address`. The `source` parameter identifies the calling agent (valid values: `'CLI'`, `'WEb'`, `'VSCODE'`, `'CURSO'`, `'WEBIDE'`, etc. — case-sensitive). The `git` parameter (with `repo_url`, `repo_name`, `target_path`) is accepted but does NOT auto-clone the repository — always clone manually.
98
+ 5. **Connect**: `huaweicloud_sandbox_connect` — returns `session_id`, `dev_stage_id`, `connection_id`, `connection_address`. The `source` parameter identifies the calling agent (valid values: `CLI`, `WEB`, `VSCODE`, `WEBVNC`, `WEBPTY`, `WEBIDE`, `CURSOR`, etc. — case-sensitive, all uppercase). The `git` parameter (with `repo_url`, `repo_name`, `target_path`) is accepted but does NOT auto-clone the repository — always clone manually.
99
99
  6. **Cleanup previous deployments** (after first connect to a sandbox): nginx configs, DevBridge tunnels, and stale web processes from previous deployments can cause port conflicts and quota errors. Run cleanup immediately after connect:
100
100
 
101
101
  ```bash
@@ -357,13 +357,13 @@ Use the detected `PKG_MGR` for all package installations below.
357
357
 
358
358
  **Architecture awareness**: the sandbox runs Linux aarch64 (ARM64). Native binaries built on x64 (Windows/macOS Intel) will not execute. Always install dependencies and build inside the sandbox. For projects with native addons (Taro `@swc/core`, Prisma, `esbuild`, `node-gyp`), local x64 pre-build + upload of `dist/` output is a viable alternative when sandbox builds fail.
359
359
 
360
- **GitCode SSL**: if `git clone` from GitCode fails with SSL certificate errors:
360
+ **GitCode SSL**: if `git clone` from GitCode fails with SSL certificate errors, use a one-shot override (do NOT set it globally — that would disable cert verification for every repo):
361
361
 
362
362
  ```bash
363
- git config --global http.sslVerify false
363
+ git -c http.sslVerify=false clone <repo-url>
364
364
  ```
365
365
 
366
- Then retry the clone. This is a known sandbox environment limitation.
366
+ Then retry the clone. This bypasses SSL verification only for this single clone.
367
367
 
368
368
  #### 3b: Install nginx (before project upload)
369
369
 
@@ -18,19 +18,19 @@ Domain expertise for Huawei Cloud Virtual Private Cloud (VPC). Covers VPC/subnet
18
18
 
19
19
  ## Critical Warnings
20
20
 
21
- | Trap | Why |
22
- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
23
- | VPC CIDR cannot change | Once set, VPC CIDR block is immutable |
24
- | Security group stateful | SG rules are stateful. Return traffic auto-allowed |
25
- | Network ACL stateless | ACL rules must allow both inbound AND outbound |
26
- | EIP bills when idle | Unbound EIP still charges. Release when unused |
27
- | Subnet AZ binding | Subnet tied to single AZ. Cross-AZ needs multiple subnets |
28
- | EIP PER type needs `--bandwidth.name` | PER bandwidth requires explicit name; `--help` marks it optional but it's required |
29
- | **VPC params need nested prefix** | KooCLI 7.x VPC API uses `--vpc.<param>`, `--subnet.<param>`, `--security_group.<param>`. Example: `--vpc.name=xxx` NOT `--name=xxx` |
30
- | **Security group needs no vpc_id** | VPC v3 API `CreateSecurityGroup` does NOT accept `vpc_id`. Security groups are region-level, not VPC-bound |
31
- | Subnet DNS empty → ECS no DNS | DNS params (`--subnet.primary_dns`, `--subnet.secondary_dns`) marked optional but empty default breaks cloud-init domain resolution — `yum`/`apt` installs fail silently. Always set both. Common DNS IPs: cn-north-4 (100.125.1.250 / 100.125.1.251), cn-north-1 (100.125.1.250 / 100.125.129.250), cn-east-3 (100.125.1.250 / 100.125.129.250), ap-southeast-3 (100.125.1.250 / 100.125.128.250) |
32
- | SCP blocks 0.0.0.0/0 SG rules | If `CreateSecurityGroupRule` with `--remote_ip_prefix=0.0.0.0/0` fails with `SYS.0403`, an org-level SCP policy is denying wide-open rules. Narrow to a specific CIDR range (e.g., your office IP) instead |
33
- | **VPC tags use `*` separator** | VPC tag format: `--vpc.tags.1=env*test` (asterisk between key and value). NOT `--vpc.tags.1.key=env` (ECS-style). This is different from ECS `--server.metadata.key=value` |
21
+ | Trap | Why |
22
+ | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
23
+ | VPC CIDR cannot change | Once set, VPC CIDR block is immutable |
24
+ | Security group stateful | SG rules are stateful. Return traffic auto-allowed |
25
+ | Network ACL stateless | ACL rules must allow both inbound AND outbound |
26
+ | EIP bills when idle | Unbound EIP still charges. Release when unused |
27
+ | Subnet AZ binding | Subnet tied to single AZ. Cross-AZ needs multiple subnets |
28
+ | EIP PER type needs `--bandwidth.name` | PER bandwidth requires explicit name; `--help` marks it optional but it's required |
29
+ | **VPC params need nested prefix** | KooCLI 7.x VPC API uses `--vpc.<param>`, `--subnet.<param>`, `--security_group.<param>`. Example: `--vpc.name=xxx` NOT `--name=xxx` |
30
+ | **Security group needs no vpc_id** | VPC v3 API `CreateSecurityGroup` does NOT accept `vpc_id`. Security groups are region-level, not VPC-bound |
31
+ | Subnet DNS empty → ECS no DNS | DNS params (`--subnet.primary_dns`, `--subnet.secondary_dns`) marked optional but empty default breaks cloud-init domain resolution — `yum`/`apt` installs fail silently. Always set both. Common DNS (primary / secondary): cn-north-4 (100.125.1.250 / 100.125.129.250), cn-north-1 (100.125.1.250 / 100.125.21.250), cn-east-3 (100.125.1.250 / 100.125.64.250), cn-south-1 (100.125.1.250 / 100.125.136.29), ap-southeast-3 (100.125.1.250 / 100.125.128.250). Authoritative per region: `hcloud DNS ListNameServers --type=private --region=<r>` |
32
+ | SCP blocks 0.0.0.0/0 SG rules | If `CreateSecurityGroupRule` with `--remote_ip_prefix=0.0.0.0/0` fails with `SYS.0403`, an org-level SCP policy is denying wide-open rules. Narrow to a specific CIDR range (e.g., your office IP) instead |
33
+ | **VPC tags use `*` separator** | VPC tag format: `--vpc.tags.1=env*test` (asterisk between key and value). NOT `--vpc.tags.1.key=env` (ECS-style). This is different from ECS `--server.metadata.key=value` |
34
34
 
35
35
  ## Common Workflows
36
36
 
@@ -50,18 +50,18 @@ Domain expertise for Huawei Cloud Virtual Private Cloud (VPC). Covers VPC/subnet
50
50
 
51
51
  ## Troubleshooting
52
52
 
53
- | Error | Root Cause -> Fix |
54
- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
55
- | Cannot reach instance | SG missing rule or no EIP -> Add SG rule / Bind EIP |
56
- | Subnet CIDR conflict | Overlapping with existing subnets -> Choose non-overlapping CIDR |
57
- | NAT gateway no internet | Route table missing default route -> Add 0.0.0.0/0 via NAT |
58
- | EIP quota exceeded | Check actual quota: `hcloud EIP ListPublicips --cli-region=<r>` to see current usage. Default varies by account (typically 5-10) |
59
- | EIP.7905 | Run `hcloud EIP ListPublicips --cli-region=<r>` first to check current usage |
60
- | VPC.0301: Bandwidth name invalid | PER type requires `--bandwidth.name`, even though `--help` marks it optional |
61
- | EIP has no public IP after binding | May need AddIngressEipV2 for ELB-type resources (see huawei-apig) |
62
- | ECS cloud-init fails silently (port 80/443 closed) | Subnet likely has no DNS. Check `hcloud VPC ShowSubnet --subnet_id=<id>` → `dnsList` empty? Rebuild subnet with `--subnet.primary_dns=<dns1> --subnet.secondary_dns=<dns2>`. DNS addresses per region: `hcloud VPC CreateSubnet --help` |
63
- | VPC.0209: Subnet still used | Subnet has dependent resources (ECS/RDS) — delete instances first, then subnet |
64
- | SYS.0403 / SCP deny | Service Control Policy explicitly denies this operation — contact org admin to adjust SCP, or use an account/region without the restriction. If SSH is blocked, bootstrap via cloud-init user_data instead: see `huawei-ecs` — no SSH needed |
53
+ | Error | Root Cause -> Fix |
54
+ | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
55
+ | Cannot reach instance | SG missing rule or no EIP -> Add SG rule / Bind EIP |
56
+ | Subnet CIDR conflict | Overlapping with existing subnets -> Choose non-overlapping CIDR |
57
+ | NAT gateway no internet | Route table missing default route -> Add 0.0.0.0/0 via NAT |
58
+ | EIP quota exceeded | Check actual quota: `hcloud EIP ListPublicips --cli-region=<r>` to see current usage. Default varies by account (typically 5-10) |
59
+ | EIP.7905 | Run `hcloud EIP ListPublicips --cli-region=<r>` first to check current usage |
60
+ | VPC.0301: Bandwidth name invalid | PER type requires `--bandwidth.name`, even though `--help` marks it optional |
61
+ | EIP has no public IP after binding | May need AddIngressEipV2 for ELB-type resources (see huawei-apig) |
62
+ | ECS cloud-init fails silently (port 80/443 closed) | Subnet likely has no DNS. Check `hcloud VPC ShowSubnet --subnet_id=<id>` → `dnsList` empty? Rebuild subnet with `--subnet.primary_dns=<dns1> --subnet.secondary_dns=<dns2>`. DNS addresses per region: `hcloud DNS ListNameServers --type=private --region=<r>` |
63
+ | VPC.0209: Subnet still used | Subnet has dependent resources (ECS/RDS) — delete instances first, then subnet |
64
+ | SYS.0403 / SCP deny | Service Control Policy explicitly denies this operation — contact org admin to adjust SCP, or use an account/region without the restriction. If SSH is blocked, bootstrap via cloud-init user_data instead: see `huawei-ecs` — no SSH needed |
65
65
 
66
66
  ## Security Considerations
67
67
 
@@ -6,11 +6,17 @@ Subnets without DNS configuration will fail cloud-init. Use these DNS addresses:
6
6
 
7
7
  | Region | Primary DNS | Secondary DNS |
8
8
  | -------------- | ------------- | --------------- |
9
- | cn-north-1 | 100.125.1.250 | 100.125.129.250 |
9
+ | cn-north-1 | 100.125.1.250 | 100.125.21.250 |
10
10
  | cn-north-4 | 100.125.1.250 | 100.125.129.250 |
11
- | cn-east-3 | 100.125.1.250 | 100.125.129.250 |
12
- | cn-south-1 | 100.125.1.250 | 100.125.129.250 |
13
- | ap-southeast-3 | 100.125.1.250 | 100.125.129.250 |
11
+ | cn-east-3 | 100.125.1.250 | 100.125.64.250 |
12
+ | cn-south-1 | 100.125.1.250 | 100.125.136.29 |
13
+ | ap-southeast-3 | 100.125.1.250 | 100.125.128.250 |
14
+
15
+ Get the authoritative values for any region at runtime (preferred over this table):
16
+
17
+ ```bash
18
+ hcloud DNS ListNameServers --type=private --region=<region>
19
+ ```
14
20
 
15
21
  **Subnet create with DNS**:
16
22
 
@@ -21,8 +27,8 @@ hcloud VPC CreateSubnet \
21
27
  --subnet.cidr=10.50.1.0/24 \
22
28
  --subnet.gateway_ip=10.50.1.1 \
23
29
  --subnet.availability_zone=<az> \
24
- --subnet.dnsList.1=100.125.1.250 \
25
- --subnet.dnsList.2=100.125.129.250
30
+ --subnet.dnsList.1=<dns1> \
31
+ --subnet.dnsList.2=<dns2>
26
32
  ```
27
33
 
28
34
  ## Nested Prefix Summary
@@ -54,9 +54,42 @@ export function syncAuth(target = 'all') {
54
54
  };
55
55
  }
56
56
 
57
+ let hcloud = { ok: false, message: 'KooCLI not installed' };
58
+ if (hcloudInstalled()) {
59
+ const bin = process.env.HCLOUD_BIN || 'hcloud';
60
+ const r = spawnSync(
61
+ bin,
62
+ [
63
+ 'configure',
64
+ 'set',
65
+ `--cli-access-key=${credentials.ak}`,
66
+ `--cli-secret-key=${credentials.sk}`,
67
+ `--cli-region=${credentials.region || ''}`,
68
+ ],
69
+ {
70
+ shell: false,
71
+ windowsHide: true,
72
+ stdio: 'pipe',
73
+ timeout: 30000,
74
+ },
75
+ );
76
+ if (r.status === 0) {
77
+ hcloud = { ok: true, message: 'KooCLI config synced' };
78
+ } else {
79
+ hcloud = {
80
+ ok: false,
81
+ message: 'KooCLI config sync failed',
82
+ error: String(r.stderr || '')
83
+ .trim()
84
+ .slice(0, 240),
85
+ };
86
+ }
87
+ }
88
+
57
89
  return {
58
90
  ok: true,
59
91
  obs: { configured: true, path: obs.path, endpoint: obs.endpoint },
92
+ hcloud,
60
93
  credentialsConfigured: true,
61
94
  agents: getAgentRegistrationStatuses(target).agents,
62
95
  note: 'OBS credentials were synced from the global credential vault. Agent MCP registration is managed by "npx huaweicloud-devkit install --target <agent>".',
@@ -1,5 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { existsSync } from 'node:fs';
2
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
4
  import { join } from 'node:path';
5
5
 
@@ -9,6 +9,20 @@ import { getProxySettings } from './proxy/proxy-config.mjs';
9
9
  const DEFAULT_TIMEOUT_MS = 60_000;
10
10
  const DEFAULT_FORCE_KILL_AFTER_MS = 2_000;
11
11
  const DEFAULT_MAX_RETRIES = 1;
12
+ const LARGE_OUTPUT_THRESHOLD = 50_000;
13
+ const OUTPUT_DIR = join('/tmp', 'huaweicloud-devkit');
14
+
15
+ function saveLargeOutput(rawStdout) {
16
+ if (rawStdout.length <= LARGE_OUTPUT_THRESHOLD) return null;
17
+ try {
18
+ mkdirSync(OUTPUT_DIR, { recursive: true });
19
+ const filePath = join(OUTPUT_DIR, `output-${Date.now()}.json`);
20
+ writeFileSync(filePath, rawStdout, { encoding: 'utf8' });
21
+ return filePath;
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
12
26
 
13
27
  export function planHcloudCommand(args, options = {}) {
14
28
  const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
@@ -71,6 +85,7 @@ function runHcloudOnce(plan, options) {
71
85
  const executable = options.executable || options.env?.HCLOUD_BIN || discoverHcloudPath() || 'hcloud';
72
86
  const executableArgs = Array.isArray(options.executableArgs) ? options.executableArgs.map(String) : [];
73
87
  const cwd = options.cwd || undefined;
88
+ const stdin = options.stdin ?? 'y\n';
74
89
 
75
90
  return new Promise((resolve) => {
76
91
  const proxySettings = getProxySettings();
@@ -90,11 +105,11 @@ function runHcloudOnce(plan, options) {
90
105
  ...options.env,
91
106
  },
92
107
  });
93
- if (options.stdin) {
94
- if (typeof options.stdin === 'function') {
95
- options.stdin(child.stdin);
108
+ if (stdin) {
109
+ if (typeof stdin === 'function') {
110
+ stdin(child.stdin);
96
111
  } else {
97
- child.stdin.write(String(options.stdin));
112
+ child.stdin.write(String(stdin));
98
113
  child.stdin.end();
99
114
  }
100
115
  }
@@ -111,6 +126,13 @@ function runHcloudOnce(plan, options) {
111
126
  clearTimeout(timer);
112
127
  clearTimeout(forceTimer);
113
128
  clearTimeout(settleTimer);
129
+ if (result.stdout && String(result.stdout).length > LARGE_OUTPUT_THRESHOLD) {
130
+ const outputFile = saveLargeOutput(stdout);
131
+ if (outputFile) {
132
+ result.outputFile = outputFile;
133
+ result.stdout = String(result.stdout).slice(0, 2000) + `\n...(truncated, full output saved to ${outputFile})`;
134
+ }
135
+ }
114
136
  resolve(result);
115
137
  }
116
138
 
@@ -934,20 +934,17 @@ export async function deployCheck(
934
934
  ` echo "tunnel_url_accessible:FAIL (no tunnel URL)"`,
935
935
  `fi`,
936
936
  ``,
937
- `${
938
- isCrossPlatform
939
- ? `
937
+ `${`
940
938
  TOTAL=$((TOTAL+1))
941
939
  if [ -f "${outputPath}/qr.png" ]; then
942
940
  echo "qr_code:PASS"
943
941
  PASS=$((PASS+1))
944
942
  else
945
- echo "qr_code:FAIL (cross-platform app missing QR code)"
943
+ echo "qr_code:FAIL (no QR code — generate one for cross-platform H5 apps)"
946
944
  fi
947
- `
948
- : ''
949
- }`,
945
+ `}`,
950
946
  `echo "SCORE:\${PASS}/\${TOTAL}"`,
947
+ `echo "TUNNEL_URL:\${TUNNEL_URL:-}"`,
951
948
  `[ "\${PASS}" = "\${TOTAL}" ] && echo "VERDICT:COMPLETE" || echo "VERDICT:INCOMPLETE"`,
952
949
  ]
953
950
  .filter(Boolean)
@@ -958,10 +955,11 @@ fi
958
955
  const checks = {};
959
956
  const lines = stdout.split('\n');
960
957
  for (const line of lines) {
961
- const m = line.match(/^(\w+):(PASS|FAIL)\b(.*)/);
958
+ const m = line.match(/^(\w+):(\w+)\b(.*)/);
962
959
  if (m) checks[m[1]] = { status: m[2], detail: (m[3] || '').trim() };
963
960
  }
964
961
  const scoreMatch = stdout.match(/SCORE:(\d+)\/(\d+)/);
962
+ const tunnelMatch = stdout.match(/TUNNEL_URL:(https:\/\/[^\s]+)/);
965
963
  const complete = /VERDICT:COMPLETE/.test(stdout);
966
964
 
967
965
  const missing = [];
@@ -977,6 +975,7 @@ fi
977
975
  checkType: isCrossPlatform ? 'cross-platform' : 'standard',
978
976
  checks,
979
977
  score: scoreMatch ? { pass: parseInt(scoreMatch[1], 10), total: parseInt(scoreMatch[2], 10) } : null,
978
+ publicUrl: tunnelMatch ? tunnelMatch[1] : undefined,
980
979
  missingSteps: missing.length > 0 ? missing.join(', ') : undefined,
981
980
  nextStep: !complete
982
981
  ? missing.includes('devbridge_tunnel') || missing.includes('tunnel_url_accessible')
@@ -474,6 +474,7 @@ function installRuntimeDeps(pluginsDir) {
474
474
  };
475
475
  let r = spawnSync('npm', ['install', '--omit=dev'], spawnOpts);
476
476
  const isRetryable = (res) => {
477
+ if (res.status === 0) return false;
477
478
  const stderr = (res.stderr || '').toString();
478
479
  return res.error?.code === 'EPERM' || res.error?.code === 'EBUSY' || /EPERM|EBUSY/.test(stderr);
479
480
  };
@@ -482,6 +483,11 @@ function installRuntimeDeps(pluginsDir) {
482
483
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2000);
483
484
  r = spawnSync('npm', ['install', '--omit=dev'], spawnOpts);
484
485
  }
486
+ if (r.status !== 0 && !isRetryable(r)) {
487
+ console.log(` \x1b[33m[RETRY]\x1b[0m npm install failed (exit ${r.status}), retrying in 3s...`);
488
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 3000);
489
+ r = spawnSync('npm', ['install', '--omit=dev'], spawnOpts);
490
+ }
485
491
  const undiciDir = join(pluginsDir, 'node_modules', 'undici');
486
492
  if (r.status === 0 && existsSync(undiciDir)) {
487
493
  console.log(` Runtime deps installed -> ${join(pluginsDir, 'node_modules')}`);
@@ -489,12 +495,14 @@ function installRuntimeDeps(pluginsDir) {
489
495
  const errCode = r.error?.code;
490
496
  const err = (r.stderr || '').toString().trim().split(/\r?\n/).slice(-2).join(' ');
491
497
  const hint = errCode === 'ENOENT' ? ' (npm not found — ensure Node.js/npm is in PATH)' : '';
492
- console.log(` \x1b[33m[WARN]\x1b[0m npm install failed in ${pluginsDir}${err ? `: ${err}` : ''}${hint}`);
498
+ const msg = `npm install failed in ${pluginsDir}${err ? `: ${err}` : ''}${hint}`;
499
+ console.log(` \x1b[31m[ERROR]\x1b[0m ${msg}`);
493
500
  console.log(' Manual fix: cd %s && npm install', pluginsDir);
494
501
  if (!existsSync(undiciDir)) {
495
502
  console.log(` \x1b[31m[ERROR]\x1b[0m undici is NOT installed. MCP server will fail to start.`);
496
503
  console.log(` Run manually: cd "${pluginsDir}" && npm install undici@^8.10.0`);
497
504
  }
505
+ throw new Error(msg);
498
506
  }
499
507
  }
500
508
 
@@ -106,6 +106,10 @@ function hermesSkillsDir() {
106
106
  return join(home, '.hermes', 'skills');
107
107
  }
108
108
 
109
+ function codexDesktopSkillsDir() {
110
+ return join(homedir(), '.agents', 'skills');
111
+ }
112
+
109
113
  export function listSkillDirs(root) {
110
114
  if (!existsSync(root)) return [];
111
115
  try {
@@ -135,6 +139,7 @@ function resolveSkillsRoot() {
135
139
  workbuddySkillsDir(),
136
140
  officeaceSkillsRoot(),
137
141
  hermesSkillsDir(),
142
+ codexDesktopSkillsDir(),
138
143
  ]) || SKILLS_ROOT_DEV
139
144
  );
140
145
  }
@@ -1228,7 +1233,10 @@ async function runApprovedCommand(args = {}) {
1228
1233
  }
1229
1234
  const strictPlan = planHcloudCommand(args.args || [], { allowWrites: false });
1230
1235
  const plan = planHcloudCommand(args.args || [], { allowWrites: true });
1231
- if (String(args.approvedCommand || '') !== plan.command) {
1236
+ const approved = String(args.approvedCommand || '');
1237
+ const planned = String(plan.command || '');
1238
+ const normalized = (s) => s.replace(/--\S+[= ]<redacted>/g, '--<placeholder>');
1239
+ if (normalized(approved) !== normalized(planned)) {
1232
1240
  throw new Error('approvedCommand must exactly match the planned hcloud command.');
1233
1241
  }
1234
1242
  const result = await runHcloud(args.args || [], {