huaweicloud-devkit 1.1.0-next.3 → 1.1.0-next.5

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.3",
3
+ "version": "1.1.0-next.5",
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.3",
20
+ "version": "1.1.0-next.5",
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.3",
23
+ "version": "1.1.0-next.5",
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.3",
20
+ "version": "1.1.0-next.5",
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.3",
3
+ "version": "1.1.0-next.5",
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.3",
3
+ "version": "1.1.0-next.5",
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.3",
5
+ "version": "1.1.0-next.5",
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.",
@@ -221,24 +221,22 @@ Before installing project dependencies, verify the sandbox has the required runt
221
221
  ```bash
222
222
  # Core tools (expected pre-installed in sandbox image)
223
223
  echo "=== Checking core tools ==="
224
- command -v node && node --version || echo "MISSING: node"
225
- command -v npm && npm --version || echo "MISSING: npm"
226
- command -v nginx && nginx -v 2>&1 || echo "MISSING: nginx"
227
- command -v git && git --version || echo "MISSING: git"
228
- command -v python3 && python3 --version || echo "MISSING: python3"
229
- command -v curl && curl --version | head -1 || echo "MISSING: curl"
230
- command -v wget && wget --version | head -1 || echo "MISSING: wget"
231
- dpkg -l build-essential >/dev/null 2>&1 && echo "build-essential: found" || echo "MISSING: build-essential"
232
- command -v lsof && lsof -v 2>&1 | head -1 || echo "MISSING: lsof"
233
- command -v netstat && netstat --version 2>&1 | head -1 || echo "MISSING: net-tools"
234
- command -v make && make --version | head -1 || echo "MISSING: make"
224
+ if command -v node >/dev/null 2>&1; then node --version; else echo "MISSING: node"; fi
225
+ if command -v npm >/dev/null 2>&1; then npm --version; else echo "MISSING: npm"; fi
226
+ if command -v nginx >/dev/null 2>&1; then nginx -v 2>&1; else echo "MISSING: nginx"; fi
227
+ if command -v git >/dev/null 2>&1; then git --version; else echo "MISSING: git"; fi
228
+ if command -v python3 >/dev/null 2>&1; then python3 --version; else echo "MISSING: python3"; fi
229
+ if command -v curl >/dev/null 2>&1; then curl --version | head -1; else echo "MISSING: curl"; fi
230
+ if command -v wget >/dev/null 2>&1; then wget --version | head -1; else echo "MISSING: wget"; fi
231
+ if dpkg -l build-essential >/dev/null 2>&1; then echo "build-essential: found"; else echo "MISSING: build-essential"; fi
232
+ if command -v make >/dev/null 2>&1; then make --version | head -1; else echo "MISSING: make"; fi
235
233
 
236
234
  # Framework-specific tools (install on demand)
237
235
  echo "=== Checking framework tools ==="
238
- command -v pnpm && pnpm --version || echo "MISSING: pnpm"
239
- command -v yarn && yarn --version || echo "MISSING: yarn"
240
- command -v hugo && hugo version || echo "MISSING: hugo"
241
- command -v devbridge && devbridge version || echo "MISSING: devbridge"
236
+ if command -v pnpm >/dev/null 2>&1; then pnpm --version; else echo "MISSING: pnpm"; fi
237
+ if command -v yarn >/dev/null 2>&1; then yarn --version; else echo "MISSING: yarn"; fi
238
+ if command -v hugo >/dev/null 2>&1; then hugo version; else echo "MISSING: hugo"; fi
239
+ if command -v devbridge >/dev/null 2>&1; then devbridge version; else echo "MISSING: devbridge"; fi
242
240
  ```
243
241
 
244
242
  **Install only missing tools** — parse the pre-flight output and install only tools reported as `MISSING`. Skip tools already present:
@@ -250,8 +248,6 @@ command -v devbridge && devbridge version || echo "MISSING: devbridge"
250
248
  | curl | `sudo apt-get update -qq && sudo apt-get install -y -qq curl` |
251
249
  | wget | `sudo apt-get update -qq && sudo apt-get install -y -qq wget` |
252
250
  | build-essential | `sudo apt-get update -qq && sudo apt-get install -y -qq build-essential` |
253
- | lsof | `sudo apt-get update -qq && sudo apt-get install -y -qq lsof` |
254
- | net-tools | `sudo apt-get update -qq && sudo apt-get install -y -qq net-tools` |
255
251
  | make | `sudo apt-get update -qq && sudo apt-get install -y -qq make` |
256
252
  | pnpm | `npm i -g pnpm` |
257
253
  | yarn | `npm i -g yarn` |
@@ -277,7 +273,7 @@ Wait for install to complete, then:
277
273
  **Build** — only if build output doesn't already exist:
278
274
 
279
275
  ```bash
280
- cd /workspace/<dirname> && [ -d <outputDir> ] && echo "SKIP: <outputDir> exists" || <buildCmd>
276
+ cd /workspace/<dirname> && [ -d <outputDir> ] && echo "SKIP: <outputDir> exists" || (umask 022 && <buildCmd>)
281
277
  ```
282
278
 
283
279
  - `cd /workspace/<dirname>/<subAppPath>` for Monorepo sub-apps.
@@ -303,6 +299,20 @@ Replace `<port>`, `<project>`, `<outputDir>` (and `<nodePort>`/`<publicPort>` fo
303
299
 
304
300
  Write the config with `sudo tee`, then reload nginx. If nginx fails, fall back to Python HTTP server (see `references/nginx-templates.md`).
305
301
 
302
+ **Verify nginx is serving** — curl-check the app before proceeding to DevBridge:
303
+
304
+ ```bash
305
+ curl -s -o /dev/null -w "nginx status: %{http_code}\n" http://localhost:<port>
306
+ ```
307
+
308
+ If the status code is not 2xx/3xx:
309
+
310
+ - **403** — likely file permissions: run `chmod -R o+rX /workspace/<project>/<outputDir>` and re-test
311
+ - **000 (connection refused)** — nginx not listening: check `sudo nginx -t` for config errors
312
+ - **Other** — check nginx error log: `sudo tail -20 /var/log/nginx/error.log`
313
+
314
+ > If `curl` is unavailable, check port listening via `/proc`: `cat /proc/net/tcp | awk '{print $2}' | grep -q "$(printf '%04X' <port>)" && echo "port <port> listening" || echo "port <port> NOT listening"`
315
+
306
316
  ### Step 6: Start the App
307
317
 
308
318
  - **Static (SPA/SSG/cross-platform)**: nginx is already serving. Skip.
@@ -325,23 +335,25 @@ Extract the tunnel URL from DevBridge output and return it to the developer. For
325
335
 
326
336
  ## Critical Warnings
327
337
 
328
- | Trap | Why |
329
- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
330
- | 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 |
331
- | Real-name required | `sandbox_connect` fails if `realnameVerified=false`; tell the developer once and stop, don't loop on connect |
332
- | Never expose tunnel details | Do not mention "DevBridge"/"tunnel"/"devbridge" to the developer — say "正在生成访问地址..." and hand over only the URL |
333
- | Login needs `--huaweicloud` | `devbridge auth login --access-key/--secret-key` without `--huaweicloud` falls back to interactive browser login, which fails in the sandbox |
334
- | CLI PATH | The installer only writes `~/.bashrc`; run `export PATH=$PATH:$HOME/.huawei/bin` in the session before using `devbridge` |
335
- | 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 |
336
- | Return the deployment URL | Always hand the public URL from the host log to the developer as the final result |
337
- | Session state persists | `exec_with_session` preserves `cd`, env vars, aliases between calls |
338
- | 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). |
339
- | Destructive commands blocked | `rm -rf /`, `mkfs`, `dd if=`, fork bombs are denied by safety policy |
340
- | Workspace ID = dev_stage_id | Use `dev_stage_id` from `sandbox_connect` as `workspace_id` for terminal exec |
341
- | 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 |
342
- | 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 |
343
- | Upload file for single files | Use `sandbox_upload_file` for individual files — base64 chunked, reliable for small files (<1MB) |
344
- | 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") |
338
+ | Trap | Why |
339
+ | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
340
+ | 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 |
341
+ | Real-name required | `sandbox_connect` fails if `realnameVerified=false`; tell the developer once and stop, don't loop on connect |
342
+ | Never expose tunnel details | Do not mention "DevBridge"/"tunnel"/"devbridge" to the developer — say "正在生成访问地址..." and hand over only the URL |
343
+ | Login needs `--huaweicloud` | `devbridge auth login --access-key/--secret-key` without `--huaweicloud` falls back to interactive browser login, which fails in the sandbox |
344
+ | CLI PATH | The installer only writes `~/.bashrc`; run `export PATH=$PATH:$HOME/.huawei/bin` in the session before using `devbridge` |
345
+ | 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 |
346
+ | Return the deployment URL | Always hand the public URL from the host log to the developer as the final result |
347
+ | Session state persists | `exec_with_session` preserves `cd`, env vars, aliases between calls |
348
+ | 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). |
349
+ | Destructive commands blocked | `rm -rf /`, `mkfs`, `dd if=`, fork bombs are denied by safety policy |
350
+ | Workspace ID = dev_stage_id | Use `dev_stage_id` from `sandbox_connect` as `workspace_id` for terminal exec |
351
+ | 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 |
352
+ | 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 |
353
+ | Upload file for single files | Use `sandbox_upload_file` for individual files — base64 chunked, reliable for small files (<1MB) |
354
+ | 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") |
355
+ | 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. |
356
+ | 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. |
345
357
 
346
358
  ## Node.js in the sandbox
347
359
 
@@ -50,7 +50,6 @@ These tools should be available in the sandbox image. Verify with the pre-flight
50
50
  | curl | 8.x | Tool downloads, health checks |
51
51
  | build-essential | - | Native module compilation |
52
52
  | pnpm | 10.x | Monorepo / workspace installs |
53
- | lsof | 4.95.x | Port conflict detection |
54
53
 
55
54
  ### Install on Demand
56
55
 
@@ -6,7 +6,7 @@ Nginx is the primary serving mechanism for web apps deployed to the sandbox. Pyt
6
6
 
7
7
  ```bash
8
8
  sudo apt-get update -qq && sudo apt-get install -y -qq nginx 2>/dev/null || true
9
- sudo mkdir -p /etc/nginx/sites-enabled
9
+ sudo mkdir -p /etc/nginx/conf.d
10
10
  ```
11
11
 
12
12
  ## Template 1: SPA (with try_files fallback)
@@ -14,7 +14,7 @@ sudo mkdir -p /etc/nginx/sites-enabled
14
14
  Use for: Vite, CRA, Vue CLI, Angular, VitePress, Docusaurus, Taro H5, uni-app H5
15
15
 
16
16
  ```bash
17
- sudo tee /etc/nginx/sites-enabled/app.conf > /dev/null << 'NGINX_EOF'
17
+ sudo tee /etc/nginx/conf.d/app.conf > /dev/null << 'NGINX_EOF'
18
18
  server {
19
19
  listen <port>;
20
20
  root /workspace/<project>/<outputDir>;
@@ -38,7 +38,7 @@ sudo nginx -s reload 2>/dev/null || sudo nginx
38
38
  Use for: Next.js, Nuxt
39
39
 
40
40
  ```bash
41
- sudo tee /etc/nginx/sites-enabled/app.conf > /dev/null << 'NGINX_EOF'
41
+ sudo tee /etc/nginx/conf.d/app.conf > /dev/null << 'NGINX_EOF'
42
42
  server {
43
43
  listen <publicPort>;
44
44
  server_name _;
@@ -65,7 +65,7 @@ sudo nginx -s reload 2>/dev/null || sudo nginx
65
65
  Use for: Hugo, Hexo, plain static HTML
66
66
 
67
67
  ```bash
68
- sudo tee /etc/nginx/sites-enabled/app.conf > /dev/null << 'NGINX_EOF'
68
+ sudo tee /etc/nginx/conf.d/app.conf > /dev/null << 'NGINX_EOF'
69
69
  server {
70
70
  listen <port>;
71
71
  root /workspace/<project>/<outputDir>;
@@ -155,7 +155,9 @@ export async function execWithSession(workspaceId, command, username, timeoutMs)
155
155
 
156
156
  export const UPLOAD_CHUNK_SIZE = 30000;
157
157
 
158
- export const UPLOAD_BATCH_SIZE = 5;
158
+ export const UPLOAD_BATCH_SIZE = 2;
159
+
160
+ export const UPLOAD_MAX_RETRIES = 3;
159
161
 
160
162
  export function splitBase64Chunks(base64, chunkSize = UPLOAD_CHUNK_SIZE) {
161
163
  const chunks = [];
@@ -189,11 +191,21 @@ export async function uploadFileWithSession(workspaceId, localPath, remotePath,
189
191
  const batchNum = Math.floor(batchStart / UPLOAD_BATCH_SIZE) + 1;
190
192
  const totalBatches = Math.ceil(chunks.length / UPLOAD_BATCH_SIZE);
191
193
  const cmd = `printf '%s' '${combinedChunk}' >> "${tmp}"`;
192
- const res = await execWithSession(workspaceId, cmd, username, timeoutMs);
193
- if (res.exitCode !== 0) {
194
- throw new Error(
195
- `sandbox upload: failed writing batch ${batchNum}/${totalBatches}: ${res.stdout || res.error || res.exitCode}`,
196
- );
194
+
195
+ let batchOk = false;
196
+ let lastError;
197
+ for (let retry = 0; retry < UPLOAD_MAX_RETRIES; retry++) {
198
+ const res = await execWithSession(workspaceId, cmd, username, timeoutMs);
199
+ if (res.exitCode === 0) {
200
+ batchOk = true;
201
+ break;
202
+ }
203
+ lastError = res.stdout || res.error || res.exitCode;
204
+ console.error(` upload retry ${retry + 1}/${UPLOAD_MAX_RETRIES} for batch ${batchNum}/${totalBatches}`);
205
+ await new Promise((r) => setTimeout(r, 2000));
206
+ }
207
+ if (!batchOk) {
208
+ throw new Error(`sandbox upload: failed writing batch ${batchNum}/${totalBatches}: ${lastError}`);
197
209
  }
198
210
  if (batchNum % 10 === 0 || batchNum === totalBatches) {
199
211
  console.error(` upload progress: batch ${batchNum}/${totalBatches}`);
@@ -556,14 +568,25 @@ export async function uploadProjectWithSession(
556
568
  uploadLog(`uploadProject: ${localDir} -> ${archiveRemotePath} (archive=${archiveSize} bytes, md5=${expectedMd5})`);
557
569
 
558
570
  let result;
559
- try {
560
- result = await uploadViaHttpTunnel(workspaceId, archivePath, archiveRemotePath, username, timeoutMs, options);
561
- } catch (tunnelError) {
562
- uploadLog(`uploadProject: HTTP tunnel upload failed: ${tunnelError.message}`);
571
+ let tunnelError;
572
+ for (let attempt = 0; attempt < UPLOAD_MAX_RETRIES; attempt++) {
573
+ try {
574
+ await cleanupFileServer(workspaceId, username).catch(() => {});
575
+ result = await uploadViaHttpTunnel(workspaceId, archivePath, archiveRemotePath, username, timeoutMs, options);
576
+ tunnelError = null;
577
+ break;
578
+ } catch (error) {
579
+ tunnelError = error;
580
+ uploadLog(`uploadProject: attempt ${attempt + 1}/${UPLOAD_MAX_RETRIES} failed: ${error.message}`);
581
+ await new Promise((r) => setTimeout(r, 5000));
582
+ }
583
+ }
584
+ if (tunnelError) {
585
+ uploadLog(`uploadProject: all ${UPLOAD_MAX_RETRIES} attempts failed: ${tunnelError.message}`);
563
586
  uploadLog(`uploadProject: NOT falling back to base64 (removed). Rethrowing with diagnostics.`);
564
587
  cleanupLocalArchive(archivePath);
565
588
  throw new Error(
566
- `sandbox upload failed: HTTP tunnel could not transfer the project archive. ` +
589
+ `sandbox upload failed after ${UPLOAD_MAX_RETRIES} attempts: HTTP tunnel could not transfer the project archive. ` +
567
590
  `Archive size: ${(archiveSize / 1024).toFixed(1)}KB. ` +
568
591
  `Root cause: ${tunnelError.message}. ` +
569
592
  `Diagnostic log: ${UPLOAD_LOG_PATH}`,
@@ -648,21 +648,27 @@ export const TOOL_DEFINITIONS = [
648
648
  },
649
649
  {
650
650
  name: 'huaweicloud_voucher_status',
651
- description: '查询激励金代金券领取状态。',
651
+ description: 'Query the Huawei Cloud incentive voucher claim status.',
652
652
  inputSchema: {
653
653
  type: 'object',
654
654
  properties: {
655
- domain_id: { type: 'string' },
655
+ domain_id: {
656
+ type: 'string',
657
+ description: 'Optional. Leave empty in production — account is resolved from IAM automatically.',
658
+ },
656
659
  },
657
660
  },
658
661
  },
659
662
  {
660
663
  name: 'huaweicloud_voucher_claim',
661
- description: '领取激励金代金券。领取前会自动检查是否已领取。',
664
+ description: 'Claim the Huawei Cloud incentive voucher. Automatically checks if already claimed.',
662
665
  inputSchema: {
663
666
  type: 'object',
664
667
  properties: {
665
- domain_id: { type: 'string' },
668
+ domain_id: {
669
+ type: 'string',
670
+ description: 'Optional. Leave empty in production — account is resolved from IAM automatically.',
671
+ },
666
672
  },
667
673
  },
668
674
  },