huaweicloud-devkit 1.0.2-dev.9 → 1.0.2-next.10

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 (99) hide show
  1. package/.agents/plugins/marketplace.json +20 -20
  2. package/LICENSE +201 -201
  3. package/README.md +178 -138
  4. package/README.zh-CN.md +176 -138
  5. package/bin/setup.cjs +10 -8
  6. package/integrations/opencode/commands/huaweicloud-doctor.md +14 -14
  7. package/integrations/opencode/opencode.json +11 -14
  8. package/integrations/opencode/skills/huaweicloud-core/SKILL.md +32 -32
  9. package/package.json +60 -44
  10. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +31 -44
  11. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +30 -43
  12. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +31 -44
  13. package/plugins/huaweicloud-core/.mcp.json +13 -15
  14. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +31 -44
  15. package/plugins/huaweicloud-core/hooks/hooks.json +26 -26
  16. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +136 -136
  17. package/plugins/huaweicloud-core/safety/policy.json +76 -100
  18. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +227 -199
  19. package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +164 -164
  20. package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +48 -48
  21. package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +50 -50
  22. package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +103 -101
  23. package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +90 -90
  24. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +191 -190
  25. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +171 -124
  26. package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +55 -55
  27. package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +70 -70
  28. package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +57 -56
  29. package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +106 -97
  30. package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +36 -29
  31. package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +29 -23
  32. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +188 -185
  33. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +144 -133
  34. package/plugins/huaweicloud-core/skills/huawei-ecs/references/evs.md +31 -31
  35. package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +57 -55
  36. package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +52 -52
  37. package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +132 -131
  38. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +60 -58
  39. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +72 -71
  40. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/deploy-workflow.md +119 -119
  41. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +142 -142
  42. package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +56 -56
  43. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +69 -65
  44. package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +111 -111
  45. package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +148 -122
  46. package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +56 -56
  47. package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +65 -65
  48. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +134 -127
  49. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +49 -49
  50. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +31 -26
  51. package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +101 -100
  52. package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +40 -40
  53. package/plugins/huaweicloud-core/skills/huawei-obs/references/static-website.md +56 -56
  54. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +229 -224
  55. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +173 -134
  56. package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +69 -69
  57. package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +84 -84
  58. package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +77 -76
  59. package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +60 -59
  60. package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +50 -50
  61. package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +63 -63
  62. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +56 -56
  63. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +4 -4
  64. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +56 -47
  65. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +4 -4
  66. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +138 -128
  67. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +4 -4
  68. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +111 -96
  69. package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +4 -4
  70. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +20 -20
  71. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +30 -28
  72. package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +59 -60
  73. package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +4 -4
  74. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +83 -83
  75. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +4 -4
  76. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +54 -1
  77. package/plugins/huaweicloud-core/src/auth/credentials.mjs +103 -95
  78. package/plugins/huaweicloud-core/src/auth/service.mjs +63 -63
  79. package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
  80. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +283 -265
  81. package/plugins/huaweicloud-core/src/icon-library.mjs +171 -0
  82. package/plugins/huaweicloud-core/src/mcp-server.mjs +130 -129
  83. package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +68 -0
  84. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +94 -0
  85. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +140 -137
  86. package/plugins/huaweicloud-core/src/safety-policy.mjs +356 -302
  87. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +86 -87
  88. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +150 -153
  89. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +196 -105
  90. package/plugins/huaweicloud-core/src/search-market.mjs +208 -169
  91. package/plugins/huaweicloud-core/src/setup-cli.mjs +2622 -1594
  92. package/plugins/huaweicloud-core/src/tools.mjs +463 -111
  93. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +447 -427
  94. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +128 -132
  95. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +215 -227
  96. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +189 -202
  97. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +164 -158
  98. package/plugins/huaweicloud-core/src/ws-exec/index.js +19 -19
  99. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +338 -338
@@ -1,12 +1,18 @@
1
1
  import { planHcloudCommand, runHcloud } from './hcloud-cli.mjs';
2
2
  import { classifyTextCommand, redactSecrets } from './safety-policy.mjs';
3
3
  import { evaluateArtifacts, evaluateCommandRisk, evaluateDeployPlan } from './risk-rule-engine.mjs';
4
- import { readFileSync, readdirSync, existsSync, writeFileSync, mkdirSync } from 'node:fs';
4
+ import { readFileSync, readdirSync, existsSync, writeFileSync } from 'node:fs';
5
5
  import { join, dirname } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { homedir } from 'node:os';
8
8
  import { searchMarketplace } from './search-market.mjs';
9
- import { execWithSession, closeSession, DEFAULT_WORKSPACE_ID } from './sandbox/session-manager.mjs';
9
+ import { getServiceIcon } from './icon-library.mjs';
10
+ import {
11
+ execWithSession,
12
+ closeSession,
13
+ uploadFileWithSession,
14
+ DEFAULT_WORKSPACE_ID,
15
+ } from './sandbox/session-manager.mjs';
10
16
  import { hdkitCheckUser, hdkitSignAgreement, hdkitConnect, hdkitCredentials } from './sandbox/hdkitservice-api.mjs';
11
17
  import { getAuthStatus, syncAuth } from './auth/service.mjs';
12
18
  import { readGlobalCredentials, writeObsConfig as writeObsConfigFile } from './auth/credentials.mjs';
@@ -25,12 +31,53 @@ function workbuddySkillsDir() {
25
31
  const home = homedir();
26
32
  return join(home, '.workbuddy', 'skills');
27
33
  }
34
+ function dshSkillsDir() {
35
+ const home = process.env.DSH_HOME || join(homedir(), '.dsh');
36
+ return join(home, 'skills');
37
+ }
38
+ function officeaceSkillsRoot() {
39
+ if (process.env.OFFICEACE_HOME) return join(process.env.OFFICEACE_HOME, 'skills');
40
+ const dotDir = join(homedir(), '.office-claw');
41
+ const dotCapFile = join(dotDir, 'capabilities.json');
42
+ if (existsSync(dotCapFile)) return join(dotDir, 'skills');
43
+ if (process.platform === 'win32') {
44
+ for (const base of [process.env.ProgramFiles, 'C:\\Program Files', 'D:\\Program Files']) {
45
+ if (!base) continue;
46
+ const dir = join(base, 'OfficeAce', '.office-claw');
47
+ if (existsSync(join(dir, 'capabilities.json'))) return join(dir, 'skills');
48
+ }
49
+ }
50
+ return join(dotDir, 'skills');
51
+ }
52
+ export function listSkillDirs(root) {
53
+ if (!existsSync(root)) return [];
54
+ try {
55
+ return readdirSync(root, { withFileTypes: true })
56
+ .filter((d) => (d.isDirectory() || d.isSymbolicLink()) && existsSync(join(root, d.name, 'SKILL.md')))
57
+ .map((d) => d.name);
58
+ } catch {
59
+ return [];
60
+ }
61
+ }
62
+
63
+ export function findSkillsRoot(candidates) {
64
+ for (const dir of candidates) {
65
+ if (listSkillDirs(dir).length > 0) return dir;
66
+ }
67
+ return null;
68
+ }
69
+
28
70
  function resolveSkillsRoot() {
29
- if (existsSync(SKILLS_ROOT_DEV)) return SKILLS_ROOT_DEV;
30
- if (existsSync(codeartsSkillsDir())) return codeartsSkillsDir();
31
- if (existsSync(opencodeSkillsDir())) return opencodeSkillsDir();
32
- if (existsSync(workbuddySkillsDir())) return workbuddySkillsDir();
33
- return SKILLS_ROOT_DEV;
71
+ return (
72
+ findSkillsRoot([
73
+ SKILLS_ROOT_DEV,
74
+ dshSkillsDir(),
75
+ codeartsSkillsDir(),
76
+ opencodeSkillsDir(),
77
+ workbuddySkillsDir(),
78
+ officeaceSkillsRoot(),
79
+ ]) || SKILLS_ROOT_DEV
80
+ );
34
81
  }
35
82
  const SKILLS_ROOT = resolveSkillsRoot();
36
83
 
@@ -90,7 +137,8 @@ export const TOOL_DEFINITIONS = [
90
137
  },
91
138
  {
92
139
  name: 'huaweicloud_list_operations',
93
- description: 'List KooCLI operations for a Huawei Cloud service by running local/read-only hcloud <Service> --help.',
140
+ description:
141
+ 'List KooCLI operations for a Huawei Cloud service by running local/read-only hcloud <Service> --help.',
94
142
  inputSchema: {
95
143
  type: 'object',
96
144
  required: ['service'],
@@ -108,7 +156,8 @@ export const TOOL_DEFINITIONS = [
108
156
  },
109
157
  {
110
158
  name: 'huaweicloud_run_approved_command',
111
- description: 'Run a write-capable hcloud command only after the exact command has been shown and explicitly approved by the user.',
159
+ description:
160
+ 'Run a write-capable hcloud command only after the exact command has been shown and explicitly approved by the user.',
112
161
  inputSchema: {
113
162
  type: 'object',
114
163
  required: ['args', 'approvedCommand', 'approvedByUser'],
@@ -188,7 +237,8 @@ export const TOOL_DEFINITIONS = [
188
237
  },
189
238
  {
190
239
  name: 'huaweicloud_hook_check_deploy_plan',
191
- description: 'Check a structured or textual deployment plan for Huawei Cloud sandbox, exposure, IAM, and cost risks.',
240
+ description:
241
+ 'Check a structured or textual deployment plan for Huawei Cloud sandbox, exposure, IAM, and cost risks.',
192
242
  inputSchema: {
193
243
  type: 'object',
194
244
  required: ['plan'],
@@ -227,19 +277,24 @@ export const TOOL_DEFINITIONS = [
227
277
  },
228
278
  {
229
279
  name: 'huaweicloud_search_docs',
230
- description: 'Search across Huawei Cloud SKILL.md files and local documentation. Returns top 10 relevant results with source, name, snippet, and relevance score. Use when the agent needs to discover which skill covers a topic, or when uncertain about API parameters, quotas, or limitations.',
280
+ description:
281
+ 'Search across Huawei Cloud SKILL.md files and local documentation. Returns top 10 relevant results with source, name, snippet, and relevance score. Use when the agent needs to discover which skill covers a topic, or when uncertain about API parameters, quotas, or limitations.',
231
282
  inputSchema: {
232
283
  type: 'object',
233
284
  required: ['query'],
234
285
  properties: {
235
286
  query: { type: 'string', description: 'Search query across skill descriptions and documentation.' },
236
- topic: { type: 'string', description: 'Optional filter: all | ecs | obs | vpc | iam | rds | cce | modelarts | dew. Defaults to all.' },
287
+ topic: {
288
+ type: 'string',
289
+ description: 'Optional filter: all | ecs | obs | vpc | iam | rds | cce | modelarts | dew. Defaults to all.',
290
+ },
237
291
  },
238
292
  },
239
293
  },
240
294
  {
241
295
  name: 'huaweicloud_retrieve_skill',
242
- description: 'Retrieve a full SKILL.md by skill name. Returns the complete skill content plus list of reference files. Use when the agent has identified which skill to load and needs the full procedure.',
296
+ description:
297
+ 'Retrieve a full SKILL.md by skill name. Returns the complete skill content plus list of reference files. Use when the agent has identified which skill to load and needs the full procedure.',
243
298
  inputSchema: {
244
299
  type: 'object',
245
300
  required: ['name'],
@@ -250,7 +305,8 @@ export const TOOL_DEFINITIONS = [
250
305
  },
251
306
  {
252
307
  name: 'huaweicloud_list_regions',
253
- description: 'List available Huawei Cloud regions. Returns region IDs, display names, and endpoints. Use when the agent needs to discover available regions before creating resources.',
308
+ description:
309
+ 'List available Huawei Cloud regions. Returns region IDs, display names, and endpoints. Use when the agent needs to discover available regions before creating resources.',
254
310
  inputSchema: {
255
311
  type: 'object',
256
312
  properties: {},
@@ -258,30 +314,58 @@ export const TOOL_DEFINITIONS = [
258
314
  },
259
315
  {
260
316
  name: 'huaweicloud_get_regional_availability',
261
- description: 'Check if a specific Huawei Cloud service is available in a target region. Use before creating resources to prevent failures from regional unavailability.',
317
+ description:
318
+ 'Check if a specific Huawei Cloud service is available in a target region. Use before creating resources to prevent failures from regional unavailability.',
262
319
  inputSchema: {
263
320
  type: 'object',
264
321
  required: ['service', 'region'],
265
322
  properties: {
266
- service: { type: 'string', description: 'Service name: ecs, obs, rds, gaussdb, cce, modelarts, functiongraph, etc.' },
323
+ service: {
324
+ type: 'string',
325
+ description: 'Service name: ecs, obs, rds, gaussdb, cce, modelarts, functiongraph, etc.',
326
+ },
267
327
  region: { type: 'string', description: 'Region ID: cn-south-1, cn-north-4, ap-southeast-3, etc.' },
268
328
  },
269
329
  },
270
330
  },
271
331
  {
272
332
  name: 'huaweicloud_search_marketplace',
273
- description: 'Search the Huawei Cloud agent skill marketplace for available skills. Returns scored results with names, categories, and descriptions. Use when built-in skills are insufficient or the user asks what skills exist.',
333
+ description:
334
+ 'Search the Huawei Cloud agent skill marketplace for available skills. Returns scored results with names, categories, and descriptions. Use when built-in skills are insufficient or the user asks what skills exist.',
274
335
  inputSchema: {
275
336
  type: 'object',
276
337
  properties: {
277
338
  query: { type: 'string', description: 'Search query across skill name, description, triggers, and service.' },
278
- category: { type: 'string', description: 'Optional category filter: computing, storage, network, security, devtools, monitoring, etc.' },
339
+ category: {
340
+ type: 'string',
341
+ description: 'Optional category filter: computing, storage, network, security, devtools, monitoring, etc.',
342
+ },
343
+ },
344
+ },
345
+ },
346
+ {
347
+ name: 'huaweicloud_get_service_icon',
348
+ description:
349
+ 'Find the official Huawei Cloud service logo from the Huawei Cloud Icons library (open.huaweicloud.com/openplatform/icons.html). Returns top 5 matches with CDN logo URLs, local paths, category, aliases, and product page links. Provide service (e.g. ecs, obs, modelarts, 对象存储) or category (e.g. 计算, 存储, 人工智能) to browse. Use when generating PPT, architecture diagrams (draw.io), or frontend pages that need official Huawei Cloud service logos.',
350
+ inputSchema: {
351
+ type: 'object',
352
+ properties: {
353
+ service: {
354
+ type: 'string',
355
+ description:
356
+ 'Service name, alias, or Chinese name, e.g. ecs, obs, modelarts, 对象存储, 虚拟私有云. Omit to browse by category only.',
357
+ },
358
+ category: {
359
+ type: 'string',
360
+ description: 'Optional category filter, e.g. 计算, 存储, 网络, 人工智能, 数据库, 安全, 企业应用.',
361
+ },
279
362
  },
280
363
  },
281
364
  },
282
365
  {
283
366
  name: 'huaweicloud_setup_obs_config',
284
- description: 'Synchronize KooCLI credentials to OBS config (~/.obsutilconfig). KooCLI and OBS use separate credential stores — hcloud commands work fine but OBS commands fail with "Please set ak, sk" unless this sync is done. Run this once to enable OBS operations; re-run after changing hcloud credentials.',
367
+ description:
368
+ 'Synchronize KooCLI credentials to OBS config (~/.obsutilconfig). KooCLI and OBS use separate credential stores — hcloud commands work fine but OBS commands fail with "Please set ak, sk" unless this sync is done. Run this once to enable OBS operations; re-run after changing hcloud credentials.',
285
369
  inputSchema: {
286
370
  type: 'object',
287
371
  properties: {
@@ -291,27 +375,38 @@ export const TOOL_DEFINITIONS = [
291
375
  },
292
376
  {
293
377
  name: 'huaweicloud_auth_status',
294
- description: 'Check unified Huawei Cloud authentication status across the global credential vault, OBS, KooCLI, and all supported agent MCP registrations. Returns only redacted/status information, never credentials.',
378
+ description:
379
+ 'Check unified Huawei Cloud authentication status across the global credential vault, OBS, KooCLI, and all supported agent MCP registrations. Returns only redacted/status information, never credentials.',
295
380
  inputSchema: {
296
381
  type: 'object',
297
382
  properties: {
298
- target: { type: 'string', description: 'Agent target to check: opencode, codex, codex-desktop, codearts, workbuddy, or all (default).' },
383
+ target: {
384
+ type: 'string',
385
+ description:
386
+ 'Agent target to check: opencode, codex, codex-desktop, codearts, workbuddy, dsh, officeace, or all (default).',
387
+ },
299
388
  },
300
389
  },
301
390
  },
302
391
  {
303
392
  name: 'huaweicloud_auth_sync',
304
- description: 'Synchronize credentials from the global Huawei Cloud credential vault to OBS and report agent registration status. Does not write secrets into any agent config.',
393
+ description:
394
+ 'Synchronize credentials from the global Huawei Cloud credential vault to OBS and report agent registration status. Does not write secrets into any agent config.',
305
395
  inputSchema: {
306
396
  type: 'object',
307
397
  properties: {
308
- target: { type: 'string', description: 'Agent target to report after sync: opencode, codex, codex-desktop, codearts, workbuddy, or all (default).' },
398
+ target: {
399
+ type: 'string',
400
+ description:
401
+ 'Agent target to report after sync: opencode, codex, codex-desktop, codearts, workbuddy, dsh, or all (default).',
402
+ },
309
403
  },
310
404
  },
311
405
  },
312
406
  {
313
407
  name: 'huaweicloud_sandbox_exec_with_session',
314
- description: 'Execute a command on a workspace terminal with session reuse (state persists across calls). Shell state (cd, env vars, aliases) carries over between calls.',
408
+ description:
409
+ 'Execute a command on a workspace terminal with session reuse (state persists across calls). Shell state (cd, env vars, aliases) carries over between calls.',
315
410
  inputSchema: {
316
411
  type: 'object',
317
412
  required: ['command'],
@@ -334,9 +429,26 @@ export const TOOL_DEFINITIONS = [
334
429
  },
335
430
  },
336
431
  },
432
+ {
433
+ name: 'huaweicloud_sandbox_upload_file',
434
+ description:
435
+ 'Upload a local file into the sandbox workspace. Base64-encodes the file, writes it in small chunks through the terminal session (the exec channel is fragile for large single commands), then decodes and verifies the md5 checksum. Use this instead of embedding large file content directly in a command.',
436
+ inputSchema: {
437
+ type: 'object',
438
+ required: ['local_path', 'remote_path'],
439
+ properties: {
440
+ local_path: { type: 'string', description: 'Absolute path to the local file to upload.' },
441
+ remote_path: { type: 'string', description: 'Target path in the sandbox, e.g. /workspace/<repo>/index.html.' },
442
+ workspace_id: { type: 'string', description: 'The workspace ID' },
443
+ username: { type: 'string', description: 'Login username (default: root)' },
444
+ timeout_ms: { type: 'number', description: 'Per-command execution timeout in milliseconds (default: 30000)' },
445
+ },
446
+ },
447
+ },
337
448
  {
338
449
  name: 'huaweicloud_sandbox_check_user',
339
- description: 'Check if the current user has completed real-name verification and signed the required agreements. Returns realname_verified and agreement_signed status.',
450
+ description:
451
+ 'Check if the current user has completed real-name verification and signed the required agreements. Returns 200 {realnameVerified, agreementSigned} when all good; throws 403 HDKIT_NOT_REALNAME / HDKIT_NOT_AGREEMENT / HDKIT_NOT_REALNAME_AND_AGREEMENT to indicate what is missing. Never signs anything itself.',
340
452
  inputSchema: {
341
453
  type: 'object',
342
454
  properties: {},
@@ -344,7 +456,8 @@ export const TOOL_DEFINITIONS = [
344
456
  },
345
457
  {
346
458
  name: 'huaweicloud_sandbox_sign_agreement',
347
- description: 'Sign all unsigned or outdated agreements for the current user. Required before huaweicloud_sandbox_connect if check-user returns agreement_signed=false.',
459
+ description:
460
+ "Sign all unsigned or outdated agreements for the current user. Required before huaweicloud_sandbox_connect if check-user returns agreementSigned=false. CRITICAL: only call after the user explicitly consents to signing — never sign agreements on the user's behalf without their explicit request.",
348
461
  inputSchema: {
349
462
  type: 'object',
350
463
  properties: {},
@@ -352,11 +465,16 @@ export const TOOL_DEFINITIONS = [
352
465
  },
353
466
  {
354
467
  name: 'huaweicloud_sandbox_connect',
355
- description: 'Connect to a sandbox via hdkitservice. One user one instance - reuses existing sandbox if available, otherwise creates a new one. Returns session_id, dev_stage_id, connection_id, and connection_address.',
468
+ description:
469
+ 'Connect to a sandbox via hdkitservice. One user one instance - reuses existing sandbox if available, otherwise creates a new one. Returns session_id, dev_stage_id, connection_id, and connection_address.',
356
470
  inputSchema: {
357
471
  type: 'object',
358
472
  properties: {
359
- source: { type: 'string', description: 'Source identifier (default: WEB). Options: VSCODE, CLI, WEB, WEBVNC, WEBPTY, WEBIDE, CURSOR, etc.' },
473
+ source: {
474
+ type: 'string',
475
+ description:
476
+ 'Source identifier (default: WEB). Options: VSCODE, CLI, WEB, WEBVNC, WEBPTY, WEBIDE, CURSOR, etc.',
477
+ },
360
478
  template_id: { type: 'string', description: 'Template ID; overrides server default (only for new sandbox)' },
361
479
  flavor_id: { type: 'string', description: 'Flavor ID; overrides server default (only for new sandbox)' },
362
480
  env: { type: 'object', description: 'Environment variables to set in the sandbox (only for new sandbox)' },
@@ -376,7 +494,8 @@ export const TOOL_DEFINITIONS = [
376
494
  },
377
495
  {
378
496
  name: 'huaweicloud_sandbox_credentials',
379
- description: 'Configure temporary AK/SK for a sandbox via hdkitservice. Injects temporary credentials into the sandbox. The sandbox must be in RUNNING state.',
497
+ description:
498
+ 'Configure temporary AK/SK for a sandbox via hdkitservice. Injects temporary credentials into the sandbox. The sandbox must be in RUNNING state.',
380
499
  inputSchema: {
381
500
  type: 'object',
382
501
  properties: {
@@ -386,7 +505,6 @@ export const TOOL_DEFINITIONS = [
386
505
  },
387
506
  },
388
507
  },
389
-
390
508
  ];
391
509
 
392
510
  export async function callTool(name, args = {}) {
@@ -424,10 +542,12 @@ export async function callTool(name, args = {}) {
424
542
  return listRegions();
425
543
  case 'huaweicloud_get_regional_availability':
426
544
  return getRegionalAvailability(args.service || '', args.region || '');
427
- case 'huaweicloud_explain_error':
545
+ case 'huaweicloud_explain_error':
428
546
  return explainError(args);
429
547
  case 'huaweicloud_search_marketplace':
430
548
  return searchMarketplace(args.query || '', args.category || '');
549
+ case 'huaweicloud_get_service_icon':
550
+ return getServiceIcon(args.service || '', args.category || '');
431
551
  case 'huaweicloud_setup_obs_config':
432
552
  return setupObsConfig(args.profile);
433
553
  case 'huaweicloud_auth_status':
@@ -447,6 +567,21 @@ export async function callTool(name, args = {}) {
447
567
  const closed = await closeSession(sandboxWsId3, sandboxUser3);
448
568
  return closed ? 'ok' : 'not_connected';
449
569
  }
570
+ case 'huaweicloud_sandbox_upload_file': {
571
+ if (!args.local_path || !args.remote_path) {
572
+ throw new Error('local_path and remote_path are required.');
573
+ }
574
+ const sandboxWsId4 = args.workspace_id || DEFAULT_WORKSPACE_ID;
575
+ const sandboxUser4 = args.username || 'root';
576
+ const sandboxTimeout4 = args.timeout_ms || 30000;
577
+ return await uploadFileWithSession(
578
+ sandboxWsId4,
579
+ args.local_path,
580
+ args.remote_path,
581
+ sandboxUser4,
582
+ sandboxTimeout4,
583
+ );
584
+ }
450
585
  case 'huaweicloud_sandbox_check_user':
451
586
  return await hdkitCheckUser();
452
587
  case 'huaweicloud_sandbox_sign_agreement':
@@ -465,11 +600,12 @@ function hookResult(result) {
465
600
  ok: result.decision !== 'deny',
466
601
  decision: result.decision,
467
602
  findings: result.findings,
468
- nextStep: result.decision === 'deny'
469
- ? 'Revise the command, artifact, or deployment plan before execution.'
470
- : result.decision === 'warn'
471
- ? 'Review the warnings with the user before proceeding.'
472
- : 'No Huawei Cloud hook risk rule matched.',
603
+ nextStep:
604
+ result.decision === 'deny'
605
+ ? 'Revise the command, artifact, or deployment plan before execution.'
606
+ : result.decision === 'warn'
607
+ ? 'Review the warnings with the user before proceeding.'
608
+ : 'No Huawei Cloud hook risk rule matched.',
473
609
  };
474
610
  }
475
611
 
@@ -490,7 +626,8 @@ export async function runVersionCheck(options = {}) {
490
626
  : isSpawnError
491
627
  ? 'hcloud executable not found. Set HCLOUD_BIN to the full hcloud path, or install KooCLI: npx huaweicloud-devkit install-hcloud. Then restart the agent.'
492
628
  : 'Install Huawei Cloud KooCLI: npx huaweicloud-devkit install-hcloud. Configure credentials outside the agent conversation.',
493
- authHint: 'If hcloud is installed but commands fail with "配置文件中不存在配置项", run `hcloud configure set --cli-access-key=<AK> --cli-secret-key=<SK> --cli-region=<region>` outside agent chat to configure credentials.',
629
+ authHint:
630
+ 'If hcloud is installed but commands fail with "配置文件中不存在配置项", run `hcloud configure set --cli-access-key=<AK> --cli-secret-key=<SK> --cli-region=<region>` outside agent chat to configure credentials.',
494
631
  };
495
632
  }
496
633
 
@@ -509,7 +646,8 @@ async function showProfileRedacted(profile) {
509
646
  ok: false,
510
647
  blockedByPolicy: true,
511
648
  reason: result.reason,
512
- safeAlternative: 'Use huaweicloud_show_profile_redacted so profile output is returned through the redaction pipeline.',
649
+ safeAlternative:
650
+ 'Use huaweicloud_show_profile_redacted so profile output is returned through the redaction pipeline.',
513
651
  };
514
652
  }
515
653
  return {
@@ -551,7 +689,12 @@ async function setupObsConfig(profile) {
551
689
  async function setupObsConfigFromHcloud(profile) {
552
690
  const obsConfigPath = join(homedir(), '.obsutilconfig');
553
691
  if (existsSync(obsConfigPath)) {
554
- return { ok: true, existed: true, path: obsConfigPath, note: 'OBS config already exists. Delete ~/.obsutilconfig first if you need to re-sync.' };
692
+ return {
693
+ ok: true,
694
+ existed: true,
695
+ path: obsConfigPath,
696
+ note: 'OBS config already exists. Delete ~/.obsutilconfig first if you need to re-sync.',
697
+ };
555
698
  }
556
699
 
557
700
  const args = ['configure', 'show'];
@@ -567,9 +710,9 @@ async function setupObsConfigFromHcloud(profile) {
567
710
  };
568
711
  }
569
712
 
570
- let accessKeyId = '';
571
- let secretAccessKey = '';
572
- let region = '';
713
+ let accessKeyId;
714
+ let secretAccessKey;
715
+ let region;
573
716
 
574
717
  try {
575
718
  const parsed = typeof result.stdout === 'string' ? JSON.parse(result.stdout) : result.stdout;
@@ -630,7 +773,11 @@ async function setupObsConfigFromHcloud(profile) {
630
773
  const SERVICE_EXAMPLES = {
631
774
  ECS: { list: 'ECS ListServersDetails', create: 'ECS CreateServers', show: 'IMS GlanceShowImage' },
632
775
  VPC: { list: 'VPC ListVpcs', create: 'VPC CreateVpc', show: 'VPC ShowVpc' },
633
- FunctionGraph: { list: 'FunctionGraph ListFunctions', create: 'FunctionGraph CreateFunction', show: 'FunctionGraph ShowFunctionConfig' },
776
+ FunctionGraph: {
777
+ list: 'FunctionGraph ListFunctions',
778
+ create: 'FunctionGraph CreateFunction',
779
+ show: 'FunctionGraph ShowFunctionConfig',
780
+ },
634
781
  APIG: { list: 'APIG ListInstancesV2', create: 'APIG CreateInstanceV2', show: 'APIG ShowDetailsOfInstanceV2' },
635
782
  OBS: { list: 'OBS ls', create: 'OBS mb obs://<bucket>', show: 'OBS stat obs://<bucket>/<key>' },
636
783
  RDS: { list: 'RDS ListInstances', create: 'RDS CreateInstance', show: 'RDS ShowInstance' },
@@ -662,7 +809,9 @@ async function listOperations(service, options = {}) {
662
809
  service: serviceName,
663
810
  command: isObs ? 'hcloud obs help' : `hcloud ${svc} --help`,
664
811
  selectionRule: 'Use this help text to select the exact KooCLI operation name before planning any service command.',
665
- examples: SERVICE_EXAMPLES[serviceName.toUpperCase()] || { note: `No cached examples for ${serviceName}. Use the help text above to discover available operations.` },
812
+ examples: SERVICE_EXAMPLES[serviceName.toUpperCase()] || {
813
+ note: `No cached examples for ${serviceName}. Use the help text above to discover available operations.`,
814
+ },
666
815
  result,
667
816
  };
668
817
  }
@@ -691,41 +840,131 @@ async function runApprovedCommand(args = {}) {
691
840
  function serviceCatalog(intent = '') {
692
841
  const it = String(intent).toLowerCase();
693
842
  const routeMap = [
694
- { keywords: ['ecs', 'server', 'vm', 'instance', 'compute', 'flavor', 'image'], skills: ['huawei-ecs'], services: ['ECS'] },
695
- { keywords: ['vpc', 'subnet', 'network', 'security group', 'eip', 'nat', 'vpn', 'bandwidth'], skills: ['huawei-vpc'], services: ['VPC', 'EIP'] },
696
- { keywords: ['obs', 'bucket', 'storage', 'object', 'static website', 'static site', 'hosting'], skills: ['huawei-obs'], services: ['OBS'] },
697
- { keywords: ['functiongraph', 'serverless', 'function', 'lambda', 'trigger', 'faas'], skills: ['huawei-functiongraph'], services: ['FunctionGraph'] },
698
- { keywords: ['cce', 'kubernetes', 'k8s', 'container', 'cluster', 'node pool', 'swr', 'docker', 'image registry'], skills: ['huawei-cce'], services: ['CCE', 'SWR'] },
843
+ {
844
+ keywords: ['ecs', 'server', 'vm', 'instance', 'compute', 'flavor', 'image'],
845
+ skills: ['huawei-ecs'],
846
+ services: ['ECS'],
847
+ },
848
+ {
849
+ keywords: ['vpc', 'subnet', 'network', 'security group', 'eip', 'nat', 'vpn', 'bandwidth'],
850
+ skills: ['huawei-vpc'],
851
+ services: ['VPC', 'EIP'],
852
+ },
853
+ {
854
+ keywords: ['obs', 'bucket', 'storage', 'object', 'static website', 'static site', 'hosting'],
855
+ skills: ['huawei-obs'],
856
+ services: ['OBS'],
857
+ },
858
+ {
859
+ keywords: ['functiongraph', 'serverless', 'function', 'lambda', 'trigger', 'faas'],
860
+ skills: ['huawei-functiongraph'],
861
+ services: ['FunctionGraph'],
862
+ },
863
+ {
864
+ keywords: ['cce', 'kubernetes', 'k8s', 'container', 'cluster', 'node pool', 'swr', 'docker', 'image registry'],
865
+ skills: ['huawei-cce'],
866
+ services: ['CCE', 'SWR'],
867
+ },
699
868
  { keywords: ['apig', 'api gateway', 'publish', 'throttle'], skills: ['huawei-apig'], services: ['APIG'] },
700
869
  { keywords: ['rds', 'mysql', 'postgresql', 'database', 'db'], skills: ['huawei-rds'], services: ['RDS'] },
701
- { keywords: ['gaussdb', 'distributed', 'sharding', 'opengauss'], skills: ['huawei-gaussdb'], services: ['GaussDB'] },
702
- { keywords: ['iam', 'permission', 'policy', 'role', 'user', 'ak/sk', 'access key', 'agency'], skills: ['huawei-iam'], services: ['IAM'] },
703
- { keywords: ['dew', 'secret', 'kms', 'encrypt', 'decrypt', 'certificate', 'csms'], skills: ['huawei-dew'], services: ['CSMS', 'KMS'] },
704
- { keywords: ['modelarts', 'ai', 'model', 'training', 'inference', 'machine learning'], skills: ['huawei-modelarts'], services: ['ModelArts'] },
705
- { keywords: ['billing', 'cost', 'bill', 'budget', 'expense', 'bss'], skills: ['huawei-billing'], services: ['BSS'] },
706
- { keywords: ['waf', 'aad', 'ddos', 'firewall', 'web protection'], skills: ['huawei-waf-aad'], services: ['WAF', 'AAD'] },
707
- { keywords: ['smn', 'dms', 'notification', 'message', 'kafka', 'rabbitmq'], skills: ['huawei-smn-dms'], services: ['SMN', 'DMS'] },
708
- { keywords: ['ces', 'monitor', 'alarm', 'metric', 'dashboard', 'cloud eye'], skills: ['huawei-cloud-eye'], services: ['CES'] },
870
+ {
871
+ keywords: ['gaussdb', 'distributed', 'sharding', 'opengauss'],
872
+ skills: ['huawei-gaussdb'],
873
+ services: ['GaussDB'],
874
+ },
875
+ {
876
+ keywords: ['iam', 'permission', 'policy', 'role', 'user', 'ak/sk', 'access key', 'agency'],
877
+ skills: ['huawei-iam'],
878
+ services: ['IAM'],
879
+ },
880
+ {
881
+ keywords: ['dew', 'secret', 'kms', 'encrypt', 'decrypt', 'certificate', 'csms'],
882
+ skills: ['huawei-dew'],
883
+ services: ['CSMS', 'KMS'],
884
+ },
885
+ {
886
+ keywords: ['modelarts', 'ai', 'model', 'training', 'inference', 'machine learning'],
887
+ skills: ['huawei-modelarts'],
888
+ services: ['ModelArts'],
889
+ },
890
+ {
891
+ keywords: ['billing', 'cost', 'bill', 'budget', 'expense', 'bss'],
892
+ skills: ['huawei-billing'],
893
+ services: ['BSS'],
894
+ },
895
+ {
896
+ keywords: ['waf', 'aad', 'ddos', 'firewall', 'web protection'],
897
+ skills: ['huawei-waf-aad'],
898
+ services: ['WAF', 'AAD'],
899
+ },
900
+ {
901
+ keywords: ['smn', 'dms', 'notification', 'message', 'kafka', 'rabbitmq'],
902
+ skills: ['huawei-smn-dms'],
903
+ services: ['SMN', 'DMS'],
904
+ },
905
+ {
906
+ keywords: ['ces', 'monitor', 'alarm', 'metric', 'dashboard', 'cloud eye'],
907
+ skills: ['huawei-cloud-eye'],
908
+ services: ['CES'],
909
+ },
709
910
  { keywords: ['cts', 'audit', 'trace', 'tracker'], skills: ['huawei-cts'], services: ['CTS'] },
710
911
  { keywords: ['cbr', 'backup', 'restore', 'vault', 'snapshot'], skills: ['huawei-cbr'], services: ['CBR'] },
711
- { keywords: ['deployment', 'deploy', 'ci/cd', 'pipeline', 'release'], skills: ['huawei-deployment'], services: ['CloudDeploy'] },
712
- { keywords: ['sandbox', 'devstation', 'workspace', 'terminal', 'preview', 'hwlink'], skills: ['huawei-sandbox'], services: ['Sandbox', 'DevStation'] },
713
- { keywords: ['dds', 'dcs', 'mongodb', 'redis', 'memcached', 'cache', 'document db'], skills: ['huawei-dds-dcs'], services: ['DDS', 'DCS'] },
714
- ];
912
+ {
913
+ keywords: ['deployment', 'deploy', 'ci/cd', 'pipeline', 'release'],
914
+ skills: ['huawei-deployment'],
915
+ services: ['CloudDeploy'],
916
+ },
917
+ {
918
+ keywords: [
919
+ 'sandbox',
920
+ 'devstation',
921
+ 'workspace',
922
+ 'terminal',
923
+ 'preview',
924
+ 'hwlink',
925
+ 'website',
926
+ 'web app',
927
+ 'webapp',
928
+ 'hosting',
929
+ '网站',
930
+ '网页',
931
+ '静态',
932
+ ],
933
+ skills: ['huawei-sandbox'],
934
+ services: ['Sandbox', 'DevStation'],
935
+ },
936
+ {
937
+ keywords: ['dds', 'dcs', 'mongodb', 'redis', 'memcached', 'cache', 'document db'],
938
+ skills: ['huawei-dds-dcs'],
939
+ services: ['DDS', 'DCS'],
940
+ },
941
+ ];
715
942
  const matched = [];
716
943
  const tokens = it.split(/[\s,./-]+/).filter((t) => t.length > 0);
944
+ const cjk = /[\u4e00-\u9fff]/;
717
945
  for (const route of routeMap) {
718
- if (route.keywords.some((kw) => tokens.includes(kw))) {
946
+ if (route.keywords.some((kw) => (kw.includes(' ') || cjk.test(kw) ? it.includes(kw) : tokens.includes(kw)))) {
719
947
  matched.push(route);
720
948
  }
721
949
  }
722
950
  const recommendedSkills = [...new Set(matched.flatMap((r) => r.skills))];
723
951
  const recommendedServices = [...new Set(matched.flatMap((r) => r.services))].slice(0, 5);
724
952
 
953
+ // Deployment intent (deploy/host/publish a web app or static website) must never
954
+ // default to a storage/other service — recommend the sandbox first.
955
+ const deploymentIntent = /deploy|host|hosting|publish|website|web app|preview|部署|托管|发布|网站|网页/.test(it);
956
+ if (deploymentIntent && recommendedSkills.includes('huawei-sandbox')) {
957
+ const idx = recommendedSkills.indexOf('huawei-sandbox');
958
+ recommendedSkills.splice(idx, 1);
959
+ recommendedSkills.unshift('huawei-sandbox');
960
+ }
961
+
725
962
  return {
726
963
  intent,
727
964
  recommendedSkills: recommendedSkills.length ? recommendedSkills : ['Use huaweicloud-core to route intent.'],
728
- recommendedServices: recommendedServices.length ? recommendedServices : ['Run hcloud --help to list available services.'],
965
+ recommendedServices: recommendedServices.length
966
+ ? recommendedServices
967
+ : ['Run hcloud --help to list available services.'],
729
968
  capabilityOrder: [
730
969
  'Huawei Cloud Skills for task-specific workflows and examples',
731
970
  'KooCLI hcloud for local authenticated operations and quick inspection',
@@ -751,32 +990,38 @@ function explainError({ service = 'unknown', errorCode = '', message = '', reque
751
990
  const svc = String(service).toLowerCase();
752
991
 
753
992
  const SERVICE_ALIASES = {
754
- 'functiongraph': 'FSS',
755
- 'fgs': 'FSS',
993
+ functiongraph: 'FSS',
994
+ fgs: 'FSS',
756
995
  };
757
996
  const patternKey = SERVICE_ALIASES[svc] || service;
758
997
 
759
998
  const hwErrorPatterns = {
760
999
  OBS: {
761
- 'InvalidAccessKeyId': 'OBS uses AK/SK directly (not IAM tokens). Verify AK/SK validity, OBS endpoint, and OBS permissions.',
762
- 'UserRestricted': 'This IAM user is restricted from OBS operations (403). Check IAM console → Users → Permissions: grant OBS bucket/object actions, or use an unrestricted account. If the account is a sub-account, the main account may have imposed restrictions.',
1000
+ InvalidAccessKeyId:
1001
+ 'OBS uses AK/SK directly (not IAM tokens). Verify AK/SK validity, OBS endpoint, and OBS permissions.',
1002
+ UserRestricted:
1003
+ 'This IAM user is restricted from OBS operations (403). Check IAM console → Users → Permissions: grant OBS bucket/object actions, or use an unrestricted account. If the account is a sub-account, the main account may have imposed restrictions.',
763
1004
  },
764
1005
  APIG: {
765
1006
  'APIC.7241': 'The enterprise_project_id is required for enterprise accounts. Add --enterprise_project_id=0.',
766
- 'APIC.7242': 'The EIP binding method depends on loadbalancer_provider. Use AddIngressEipV2 for elb, AddEipV2 for lvs.',
1007
+ 'APIC.7242':
1008
+ 'The EIP binding method depends on loadbalancer_provider. Use AddIngressEipV2 for elb, AddEipV2 for lvs.',
767
1009
  'APIC.7256': 'Bandwidth minimum is 5 Mbps. Use --bandwidth_size=5 or higher.',
768
1010
  'APIC.7310': 'available_zone_ids must use AZ codes (e.g. ap-southeast-3a), NOT UUIDs from ListAvailableZonesV2.',
769
1011
  },
770
1012
  FSS: {
771
- 'FSS.0403': 'Missing FunctionGraph IAM permissions. Attach FunctionGraph FullAccess role or grant specific actions.',
1013
+ 'FSS.0403':
1014
+ 'Missing FunctionGraph IAM permissions. Attach FunctionGraph FullAccess role or grant specific actions.',
772
1015
  'FSS.1078': '--code_filename is filename-only (no path). cd to the file directory before running the command.',
773
- 'FSS.1417': 'event_data field validation failed. Check parameter format: use dotted key=value, verify required hidden-optional fields.',
1016
+ 'FSS.1417':
1017
+ 'event_data field validation failed. Check parameter format: use dotted key=value, verify required hidden-optional fields.',
774
1018
  },
775
1019
  VPC: {
776
1020
  'VPC.0301': 'Bandwidth name is required for PER type EIPs, even though --help marks it optional.',
777
1021
  },
778
1022
  APIGW: {
779
- 'APIGW.0802': 'The current IAM user has no permissions in the requested region. Go to IAM console → Users → Permissions → add the target region, or switch to a different region.',
1023
+ 'APIGW.0802':
1024
+ 'The current IAM user has no permissions in the requested region. Go to IAM console → Users → Permissions → add the target region, or switch to a different region.',
780
1025
  },
781
1026
  };
782
1027
  if (hwErrorPatterns[patternKey] && hwErrorPatterns[patternKey][errorCode]) {
@@ -791,19 +1036,30 @@ function explainError({ service = 'unknown', errorCode = '', message = '', reque
791
1036
 
792
1037
  if (/auth|token|credential|ak|sk|401|403|unauthorized|forbidden|Incorrect IAM/i.test(combined)) {
793
1038
  if (svc === 'obs') {
794
- suggestions.push('OBS uses AK/SK directly, not IAM tokens. Verify AK/SK validity and OBS bucket permissions via hcloud configure list or the Huawei Cloud console.');
1039
+ suggestions.push(
1040
+ 'OBS uses AK/SK directly, not IAM tokens. Verify AK/SK validity and OBS bucket permissions via hcloud configure list or the Huawei Cloud console.',
1041
+ );
795
1042
  } else {
796
1043
  suggestions.push('Check KooCLI profile, region, project_id, and IAM permissions without printing secrets.');
797
1044
  }
798
1045
  }
799
1046
  if (/APIGW\.(\d+)/i.test(errorCode)) {
800
- suggestions.push('APIGW.' + (errorCode.match(/APIGW\.(\d+)/i) || [])[1] + ': API Gateway layer error. ' + (errorCode === 'APIGW.0802' ? 'IAM user has no region permissions — check IAM console → User → Permissions → add target region.' : 'Verify the API request, region endpoint, and IAM permissions.'));
1047
+ suggestions.push(
1048
+ 'APIGW.' +
1049
+ (errorCode.match(/APIGW\.(\d+)/i) || [])[1] +
1050
+ ': API Gateway layer error. ' +
1051
+ (errorCode === 'APIGW.0802'
1052
+ ? 'IAM user has no region permissions — check IAM console → User → Permissions → add target region.'
1053
+ : 'Verify the API request, region endpoint, and IAM permissions.'),
1054
+ );
801
1055
  }
802
1056
  if (/region|endpoint|project/i.test(combined)) {
803
1057
  suggestions.push('Confirm the service endpoint, region, and project_id match the target resource.');
804
1058
  }
805
1059
  if (/quota|limit|insufficient|reach the limit/i.test(combined)) {
806
- suggestions.push('Check quota and resource limits before retrying a create or scale operation. Consider switching accounts or requesting a quota increase.');
1060
+ suggestions.push(
1061
+ 'Check quota and resource limits before retrying a create or scale operation. Consider switching accounts or requesting a quota increase.',
1062
+ );
807
1063
  }
808
1064
  if (/not.?found|404/i.test(combined)) {
809
1065
  if (/list.?regions/i.test(svc)) {
@@ -813,7 +1069,9 @@ function explainError({ service = 'unknown', errorCode = '', message = '', reque
813
1069
  }
814
1070
  }
815
1071
  if (!suggestions.length) {
816
- suggestions.push('Collect service name, operation, region, project_id, request_id, and the full redacted error message.');
1072
+ suggestions.push(
1073
+ 'Collect service name, operation, region, project_id, request_id, and the full redacted error message.',
1074
+ );
817
1075
  }
818
1076
 
819
1077
  if (requestId) {
@@ -821,8 +1079,7 @@ function explainError({ service = 'unknown', errorCode = '', message = '', reque
821
1079
  }
822
1080
 
823
1081
  const uniqueSuggestions = suggestions.filter((s, i, arr) => {
824
- return !arr.slice(0, i).some((prev) =>
825
- prev.substring(0, 50).toLowerCase() === s.substring(0, 50).toLowerCase());
1082
+ return !arr.slice(0, i).some((prev) => prev.substring(0, 50).toLowerCase() === s.substring(0, 50).toLowerCase());
826
1083
  });
827
1084
 
828
1085
  return {
@@ -839,9 +1096,7 @@ async function searchDocs(query, topic = 'all') {
839
1096
  const results = [];
840
1097
  try {
841
1098
  if (existsSync(SKILLS_ROOT)) {
842
- const dirs = readdirSync(SKILLS_ROOT, { withFileTypes: true })
843
- .filter((d) => d.isDirectory())
844
- .map((d) => d.name);
1099
+ const dirs = listSkillDirs(SKILLS_ROOT);
845
1100
  for (const dir of dirs) {
846
1101
  const skillPath = join(SKILLS_ROOT, dir, 'SKILL.md');
847
1102
  if (!existsSync(skillPath)) continue;
@@ -864,11 +1119,16 @@ async function searchDocs(query, topic = 'all') {
864
1119
  const nameLower = name.toLowerCase();
865
1120
  const contentLower = content.toLowerCase();
866
1121
  const relevance = tokens.reduce((score, token) => {
867
- return score + (descLower.includes(token) ? 3 : 0) + (nameLower.includes(token) ? 2 : 0) + (contentLower.includes(token) ? 1 : 0);
1122
+ return (
1123
+ score +
1124
+ (descLower.includes(token) ? 3 : 0) +
1125
+ (nameLower.includes(token) ? 2 : 0) +
1126
+ (contentLower.includes(token) ? 1 : 0)
1127
+ );
868
1128
  }, 0);
869
1129
  if (relevance > 0) {
870
1130
  results.push({
871
- source: "skills/" + dir + "/SKILL.md",
1131
+ source: 'skills/' + dir + '/SKILL.md',
872
1132
  name,
873
1133
  snippet: description.substring(0, 200),
874
1134
  relevance,
@@ -888,9 +1148,7 @@ async function retrieveSkill(name) {
888
1148
  if (!skillName) return { ok: false, error: 'Skill name is required.' };
889
1149
  const skillPath = join(SKILLS_ROOT, skillName, 'SKILL.md');
890
1150
  if (!existsSync(skillPath)) {
891
- const dirs = existsSync(SKILLS_ROOT)
892
- ? readdirSync(SKILLS_ROOT, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name)
893
- : [];
1151
+ const dirs = listSkillDirs(SKILLS_ROOT);
894
1152
  return { ok: false, error: 'Skill "' + skillName + '" not found. Available: ' + dirs.join(', ') };
895
1153
  }
896
1154
  const content = readFileSync(skillPath, 'utf8');
@@ -903,7 +1161,8 @@ async function retrieveSkill(name) {
903
1161
  });
904
1162
  }
905
1163
  const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
906
- let version = 1, description = '';
1164
+ let version = 1,
1165
+ description = '';
907
1166
  if (frontmatter) {
908
1167
  const fm = frontmatter[1];
909
1168
  const vm = fm.match(/^version:\s*(.+)$/m);
@@ -915,8 +1174,10 @@ async function retrieveSkill(name) {
915
1174
  }
916
1175
 
917
1176
  async function listRegions() {
918
- const result = await runHcloud(['IAM', 'KeystoneListRegions'], { timeoutMs: 30000, maxRetries: 0 })
919
- .catch((err) => ({ ok: false, error: err.message }));
1177
+ const result = await runHcloud(['IAM', 'KeystoneListRegions'], { timeoutMs: 30000, maxRetries: 0 }).catch((err) => ({
1178
+ ok: false,
1179
+ error: err.message,
1180
+ }));
920
1181
  if (!result.ok) {
921
1182
  return {
922
1183
  ok: false,
@@ -936,7 +1197,7 @@ async function listRegions() {
936
1197
  note: 'hcloud unavailable. Showing static region list. For the complete list, visit the fallback URL.',
937
1198
  };
938
1199
  }
939
- let regions = [];
1200
+ let regions;
940
1201
  try {
941
1202
  const parsed = typeof result.stdout === 'string' ? JSON.parse(result.stdout) : result.stdout;
942
1203
  const rawRegions = parsed.regions || [];
@@ -954,37 +1215,128 @@ async function listRegions() {
954
1215
  }
955
1216
 
956
1217
  async function getRegionalAvailability(service, region) {
957
- const svc = String(service || '').toLowerCase().trim();
958
- const reg = String(region || '').toLowerCase().trim();
1218
+ const svc = String(service || '')
1219
+ .toLowerCase()
1220
+ .trim();
1221
+ const reg = String(region || '')
1222
+ .toLowerCase()
1223
+ .trim();
959
1224
  if (!svc || !reg) return { ok: false, error: 'Both service and region are required.' };
960
1225
  const known = {
961
- ecs: ['cn-south-1','cn-north-4','cn-north-1','cn-east-3','cn-east-2','ap-southeast-3','ap-southeast-2','ap-southeast-1','ap-southeast-4','af-south-1','tr-west-1','sa-brazil-1','la-north-2','na-mexico-1','me-east-1'],
962
- obs: ['cn-south-1','cn-north-4','cn-north-1','cn-east-3','cn-east-2','ap-southeast-3','ap-southeast-2','ap-southeast-1','af-south-1'],
963
- vpc: ['cn-south-1','cn-north-4','cn-north-1','cn-east-3','cn-east-2','ap-southeast-3','ap-southeast-2','ap-southeast-1','ap-southeast-4','af-south-1','tr-west-1','sa-brazil-1','la-north-2','me-east-1'],
1226
+ ecs: [
1227
+ 'cn-south-1',
1228
+ 'cn-north-4',
1229
+ 'cn-north-1',
1230
+ 'cn-east-3',
1231
+ 'cn-east-2',
1232
+ 'ap-southeast-3',
1233
+ 'ap-southeast-2',
1234
+ 'ap-southeast-1',
1235
+ 'ap-southeast-4',
1236
+ 'af-south-1',
1237
+ 'tr-west-1',
1238
+ 'sa-brazil-1',
1239
+ 'la-north-2',
1240
+ 'na-mexico-1',
1241
+ 'me-east-1',
1242
+ ],
1243
+ obs: [
1244
+ 'cn-south-1',
1245
+ 'cn-north-4',
1246
+ 'cn-north-1',
1247
+ 'cn-east-3',
1248
+ 'cn-east-2',
1249
+ 'ap-southeast-3',
1250
+ 'ap-southeast-2',
1251
+ 'ap-southeast-1',
1252
+ 'af-south-1',
1253
+ ],
1254
+ vpc: [
1255
+ 'cn-south-1',
1256
+ 'cn-north-4',
1257
+ 'cn-north-1',
1258
+ 'cn-east-3',
1259
+ 'cn-east-2',
1260
+ 'ap-southeast-3',
1261
+ 'ap-southeast-2',
1262
+ 'ap-southeast-1',
1263
+ 'ap-southeast-4',
1264
+ 'af-south-1',
1265
+ 'tr-west-1',
1266
+ 'sa-brazil-1',
1267
+ 'la-north-2',
1268
+ 'me-east-1',
1269
+ ],
964
1270
  iam: ['global'],
965
- rds: ['cn-south-1','cn-north-4','cn-north-1','cn-east-3','cn-east-2','ap-southeast-3','ap-southeast-2','ap-southeast-1'],
966
- gaussdb: ['cn-south-1','cn-north-4','cn-east-3'],
967
- cce: ['cn-south-1','cn-north-4','cn-north-1','cn-east-3','cn-east-2','ap-southeast-3','ap-southeast-2','ap-southeast-1'],
968
- modelarts: ['cn-south-1','cn-north-4','cn-east-3'],
969
- functiongraph: ['cn-south-1','cn-north-4','cn-north-1','cn-east-3','cn-east-2','ap-southeast-3','ap-southeast-2','ap-southeast-1'],
970
- dew: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
971
- smn: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
972
- ces: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
973
- cts: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
974
- apig: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
975
- cbr: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
976
- dds: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
977
- dcs: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
1271
+ rds: [
1272
+ 'cn-south-1',
1273
+ 'cn-north-4',
1274
+ 'cn-north-1',
1275
+ 'cn-east-3',
1276
+ 'cn-east-2',
1277
+ 'ap-southeast-3',
1278
+ 'ap-southeast-2',
1279
+ 'ap-southeast-1',
1280
+ ],
1281
+ gaussdb: ['cn-south-1', 'cn-north-4', 'cn-east-3'],
1282
+ cce: [
1283
+ 'cn-south-1',
1284
+ 'cn-north-4',
1285
+ 'cn-north-1',
1286
+ 'cn-east-3',
1287
+ 'cn-east-2',
1288
+ 'ap-southeast-3',
1289
+ 'ap-southeast-2',
1290
+ 'ap-southeast-1',
1291
+ ],
1292
+ modelarts: ['cn-south-1', 'cn-north-4', 'cn-east-3'],
1293
+ functiongraph: [
1294
+ 'cn-south-1',
1295
+ 'cn-north-4',
1296
+ 'cn-north-1',
1297
+ 'cn-east-3',
1298
+ 'cn-east-2',
1299
+ 'ap-southeast-3',
1300
+ 'ap-southeast-2',
1301
+ 'ap-southeast-1',
1302
+ ],
1303
+ dew: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1304
+ smn: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1305
+ ces: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1306
+ cts: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1307
+ apig: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1308
+ cbr: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1309
+ dds: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1310
+ dcs: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
978
1311
  };
979
- if (!known[svc]) return { ok: false, service: svc, region: reg, available: false, note: 'Service ' + svc + ' is not in the regional availability cache. Run hcloud ' + svc.toUpperCase() + ' --help to verify, or check https://developer.huaweicloud.com/endpoint.' };
1312
+ if (!known[svc])
1313
+ return {
1314
+ ok: false,
1315
+ service: svc,
1316
+ region: reg,
1317
+ available: false,
1318
+ note:
1319
+ 'Service ' +
1320
+ svc +
1321
+ ' is not in the regional availability cache. Run hcloud ' +
1322
+ svc.toUpperCase() +
1323
+ ' --help to verify, or check https://developer.huaweicloud.com/endpoint.',
1324
+ };
980
1325
  const available = known[svc].includes(reg) || known[svc].includes('global');
981
1326
  return {
982
- ok: true, service: svc, region: reg, available,
1327
+ ok: true,
1328
+ service: svc,
1329
+ region: reg,
1330
+ available,
983
1331
  note: available
984
1332
  ? svc + ' is available in ' + reg + '.'
985
- : svc + ' availability in ' + reg + ' could not be confirmed. Verify at https://developer.huaweicloud.com/endpoint.',
1333
+ : svc +
1334
+ ' availability in ' +
1335
+ reg +
1336
+ ' could not be confirmed. Verify at https://developer.huaweicloud.com/endpoint.',
986
1337
  sourcedFrom: 'static cache, update via npm package upgrade',
987
- disclaimer: 'This result reflects service-level availability only. It does NOT guarantee that your IAM user has permissions in this region. Account-level restrictions (e.g., APIGW.0802) may block actual API calls even when the service is available.',
1338
+ disclaimer:
1339
+ 'This result reflects service-level availability only. It does NOT guarantee that your IAM user has permissions in this region. Account-level restrictions (e.g., APIGW.0802) may block actual API calls even when the service is available.',
988
1340
  };
989
1341
  }
990
1342