huaweicloud-devkit 1.0.3-dev.0 → 1.0.3-next.0

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 (96) hide show
  1. package/.agents/plugins/marketplace.json +18 -18
  2. package/README.md +99 -41
  3. package/README.zh-CN.md +99 -41
  4. package/bin/setup.cjs +5 -3
  5. package/integrations/opencode/opencode.json +1 -4
  6. package/package.json +61 -42
  7. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +4 -17
  8. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +4 -17
  9. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +4 -17
  10. package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +30 -0
  11. package/plugins/huaweicloud-core/.mcp.json +2 -5
  12. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +4 -18
  13. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +32 -9
  14. package/plugins/huaweicloud-core/openclaw.plugin.json +20 -0
  15. package/plugins/huaweicloud-core/safety/policy.json +75 -99
  16. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +41 -13
  17. package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +34 -34
  18. package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +18 -18
  19. package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +19 -19
  20. package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +34 -32
  21. package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +30 -30
  22. package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +20 -20
  23. package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +31 -31
  24. package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +21 -20
  25. package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +39 -30
  26. package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +8 -1
  27. package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +6 -0
  28. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +39 -36
  29. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +15 -4
  30. package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +11 -9
  31. package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +13 -13
  32. package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +40 -39
  33. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +12 -10
  34. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +21 -20
  35. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +19 -19
  36. package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +20 -20
  37. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +27 -23
  38. package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +27 -27
  39. package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +65 -39
  40. package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +9 -9
  41. package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +33 -25
  42. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +51 -44
  43. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +19 -19
  44. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +12 -7
  45. package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +13 -12
  46. package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +4 -4
  47. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +63 -58
  48. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +128 -42
  49. package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +26 -26
  50. package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +39 -39
  51. package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +13 -12
  52. package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +20 -19
  53. package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +15 -15
  54. package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +18 -18
  55. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +13 -13
  56. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +3 -3
  57. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +25 -2
  58. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +3 -3
  59. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +27 -1
  60. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +3 -3
  61. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +65 -47
  62. package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +3 -3
  63. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +6 -6
  64. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +15 -1
  65. package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +0 -1
  66. package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +3 -3
  67. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +33 -33
  68. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +3 -3
  69. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +123 -2
  70. package/plugins/huaweicloud-core/src/auth/credentials.mjs +77 -4
  71. package/plugins/huaweicloud-core/src/auth/service.mjs +1 -1
  72. package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
  73. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +25 -7
  74. package/plugins/huaweicloud-core/src/icon-library.mjs +171 -0
  75. package/plugins/huaweicloud-core/src/mcp-server.mjs +30 -3
  76. package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +73 -0
  77. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +94 -0
  78. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +4 -1
  79. package/plugins/huaweicloud-core/src/safety-policy.mjs +122 -68
  80. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +14 -10
  81. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +34 -27
  82. package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
  83. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +521 -8
  84. package/plugins/huaweicloud-core/src/search-market.mjs +53 -14
  85. package/plugins/huaweicloud-core/src/setup-cli.mjs +1741 -215
  86. package/plugins/huaweicloud-core/src/tools.mjs +667 -123
  87. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +72 -38
  88. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +10 -5
  89. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +12 -15
  90. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +12 -16
  91. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +46 -31
  92. package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +267 -0
  93. package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
  94. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +9 -5
  95. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +0 -190
  96. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +0 -124
@@ -1,15 +1,30 @@
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
+ import { spawnSync } from 'node:child_process';
8
9
  import { searchMarketplace } from './search-market.mjs';
9
- import { execWithSession, closeSession, DEFAULT_WORKSPACE_ID } from './sandbox/session-manager.mjs';
10
+ import { getServiceIcon } from './icon-library.mjs';
11
+ import {
12
+ execWithSession,
13
+ execOneShot,
14
+ closeSession,
15
+ uploadFileWithSession,
16
+ uploadProjectWithSession,
17
+ currentWorkspaceId,
18
+ setWorkspaceId,
19
+ } from './sandbox/session-manager.mjs';
10
20
  import { hdkitCheckUser, hdkitSignAgreement, hdkitConnect, hdkitCredentials } from './sandbox/hdkitservice-api.mjs';
11
21
  import { getAuthStatus, syncAuth } from './auth/service.mjs';
12
- import { readGlobalCredentials, writeObsConfig as writeObsConfigFile } from './auth/credentials.mjs';
22
+ import {
23
+ readGlobalCredentials,
24
+ writeObsConfig as writeObsConfigFile,
25
+ setRuntimeCredentials,
26
+ clearRuntimeCredentials,
27
+ } from './auth/credentials.mjs';
13
28
 
14
29
  const __dirname = dirname(fileURLToPath(import.meta.url));
15
30
  const SKILLS_ROOT_DEV = join(__dirname, '..', 'skills');
@@ -25,12 +40,86 @@ function workbuddySkillsDir() {
25
40
  const home = homedir();
26
41
  return join(home, '.workbuddy', 'skills');
27
42
  }
43
+ function dshSkillsDir() {
44
+ const home = process.env.DSH_HOME || join(homedir(), '.dsh');
45
+ return join(home, 'skills');
46
+ }
47
+ function readOfficeaceRegistryInstallDir() {
48
+ if (process.platform !== 'win32') return null;
49
+ try {
50
+ const r = spawnSync('reg', ['query', 'HKCU\\SOFTWARE\\OfficeAce\\OfficeAce', '/v', 'InstallDir'], {
51
+ encoding: 'utf8',
52
+ windowsHide: true,
53
+ timeout: 5000,
54
+ });
55
+ if (r.status === 0) {
56
+ const m = r.stdout.match(/InstallDir\s+REG_SZ\s+(.+)/);
57
+ if (m) return m[1].trim();
58
+ }
59
+ } catch {}
60
+ return null;
61
+ }
62
+
63
+ function officeaceSkillsRoot() {
64
+ const configRoot = process.env.OFFICE_CLAW_CONFIG_ROOT;
65
+ if (configRoot && existsSync(join(configRoot, 'capabilities.json'))) return join(configRoot, 'skills');
66
+ const regDir = readOfficeaceRegistryInstallDir();
67
+ if (regDir) {
68
+ const dir = join(regDir, '.office-claw', 'skills');
69
+ if (existsSync(dir)) return dir;
70
+ }
71
+ if (process.platform === 'win32') {
72
+ const bases = [process.env.ProgramFiles, 'C:\\Program Files', 'D:\\Program Files'];
73
+ if (process.env.LOCALAPPDATA) bases.push(join(process.env.LOCALAPPDATA, 'Programs'));
74
+ for (const base of bases) {
75
+ if (!base) continue;
76
+ const dir = join(base, 'OfficeAce', '.office-claw', 'skills');
77
+ if (existsSync(dir)) return dir;
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+
83
+ function hermesSkillsDir() {
84
+ if (process.env.HERMES_HOME) return join(process.env.HERMES_HOME, 'skills');
85
+ // Hermes on Windows stores under LOCALAPPDATA, not ~/.hermes
86
+ if (process.platform === 'win32' && process.env.LOCALAPPDATA) {
87
+ return join(process.env.LOCALAPPDATA, 'hermes', 'skills');
88
+ }
89
+ const home = homedir();
90
+ return join(home, '.hermes', 'skills');
91
+ }
92
+
93
+ export function listSkillDirs(root) {
94
+ if (!existsSync(root)) return [];
95
+ try {
96
+ return readdirSync(root, { withFileTypes: true })
97
+ .filter((d) => (d.isDirectory() || d.isSymbolicLink()) && existsSync(join(root, d.name, 'SKILL.md')))
98
+ .map((d) => d.name);
99
+ } catch {
100
+ return [];
101
+ }
102
+ }
103
+
104
+ export function findSkillsRoot(candidates) {
105
+ for (const dir of candidates) {
106
+ if (listSkillDirs(dir).length > 0) return dir;
107
+ }
108
+ return null;
109
+ }
110
+
28
111
  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;
112
+ return (
113
+ findSkillsRoot([
114
+ SKILLS_ROOT_DEV,
115
+ dshSkillsDir(),
116
+ codeartsSkillsDir(),
117
+ opencodeSkillsDir(),
118
+ workbuddySkillsDir(),
119
+ officeaceSkillsRoot(),
120
+ hermesSkillsDir(),
121
+ ]) || SKILLS_ROOT_DEV
122
+ );
34
123
  }
35
124
  const SKILLS_ROOT = resolveSkillsRoot();
36
125
 
@@ -90,7 +179,8 @@ export const TOOL_DEFINITIONS = [
90
179
  },
91
180
  {
92
181
  name: 'huaweicloud_list_operations',
93
- description: 'List KooCLI operations for a Huawei Cloud service by running local/read-only hcloud <Service> --help.',
182
+ description:
183
+ 'List KooCLI operations for a Huawei Cloud service by running local/read-only hcloud <Service> --help.',
94
184
  inputSchema: {
95
185
  type: 'object',
96
186
  required: ['service'],
@@ -108,7 +198,8 @@ export const TOOL_DEFINITIONS = [
108
198
  },
109
199
  {
110
200
  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.',
201
+ description:
202
+ 'Run a write-capable hcloud command only after the exact command has been shown and explicitly approved by the user.',
112
203
  inputSchema: {
113
204
  type: 'object',
114
205
  required: ['args', 'approvedCommand', 'approvedByUser'],
@@ -188,7 +279,8 @@ export const TOOL_DEFINITIONS = [
188
279
  },
189
280
  {
190
281
  name: 'huaweicloud_hook_check_deploy_plan',
191
- description: 'Check a structured or textual deployment plan for Huawei Cloud sandbox, exposure, IAM, and cost risks.',
282
+ description:
283
+ 'Check a structured or textual deployment plan for Huawei Cloud sandbox, exposure, IAM, and cost risks.',
192
284
  inputSchema: {
193
285
  type: 'object',
194
286
  required: ['plan'],
@@ -227,19 +319,24 @@ export const TOOL_DEFINITIONS = [
227
319
  },
228
320
  {
229
321
  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.',
322
+ description:
323
+ '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
324
  inputSchema: {
232
325
  type: 'object',
233
326
  required: ['query'],
234
327
  properties: {
235
328
  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.' },
329
+ topic: {
330
+ type: 'string',
331
+ description: 'Optional filter: all | ecs | obs | vpc | iam | rds | cce | modelarts | dew. Defaults to all.',
332
+ },
237
333
  },
238
334
  },
239
335
  },
240
336
  {
241
337
  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.',
338
+ description:
339
+ '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
340
  inputSchema: {
244
341
  type: 'object',
245
342
  required: ['name'],
@@ -250,7 +347,8 @@ export const TOOL_DEFINITIONS = [
250
347
  },
251
348
  {
252
349
  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.',
350
+ description:
351
+ '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
352
  inputSchema: {
255
353
  type: 'object',
256
354
  properties: {},
@@ -258,30 +356,58 @@ export const TOOL_DEFINITIONS = [
258
356
  },
259
357
  {
260
358
  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.',
359
+ description:
360
+ 'Check if a specific Huawei Cloud service is available in a target region. Use before creating resources to prevent failures from regional unavailability.',
262
361
  inputSchema: {
263
362
  type: 'object',
264
363
  required: ['service', 'region'],
265
364
  properties: {
266
- service: { type: 'string', description: 'Service name: ecs, obs, rds, gaussdb, cce, modelarts, functiongraph, etc.' },
365
+ service: {
366
+ type: 'string',
367
+ description: 'Service name: ecs, obs, rds, gaussdb, cce, modelarts, functiongraph, etc.',
368
+ },
267
369
  region: { type: 'string', description: 'Region ID: cn-south-1, cn-north-4, ap-southeast-3, etc.' },
268
370
  },
269
371
  },
270
372
  },
271
373
  {
272
374
  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.',
375
+ description:
376
+ '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
377
  inputSchema: {
275
378
  type: 'object',
276
379
  properties: {
277
380
  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.' },
381
+ category: {
382
+ type: 'string',
383
+ description: 'Optional category filter: computing, storage, network, security, devtools, monitoring, etc.',
384
+ },
385
+ },
386
+ },
387
+ },
388
+ {
389
+ name: 'huaweicloud_get_service_icon',
390
+ description:
391
+ '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.',
392
+ inputSchema: {
393
+ type: 'object',
394
+ properties: {
395
+ service: {
396
+ type: 'string',
397
+ description:
398
+ 'Service name, alias, or Chinese name, e.g. ecs, obs, modelarts, 对象存储, 虚拟私有云. Omit to browse by category only.',
399
+ },
400
+ category: {
401
+ type: 'string',
402
+ description: 'Optional category filter, e.g. 计算, 存储, 网络, 人工智能, 数据库, 安全, 企业应用.',
403
+ },
279
404
  },
280
405
  },
281
406
  },
282
407
  {
283
408
  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.',
409
+ description:
410
+ '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
411
  inputSchema: {
286
412
  type: 'object',
287
413
  properties: {
@@ -291,35 +417,83 @@ export const TOOL_DEFINITIONS = [
291
417
  },
292
418
  {
293
419
  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.',
420
+ description:
421
+ '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
422
  inputSchema: {
296
423
  type: 'object',
297
424
  properties: {
298
- target: { type: 'string', description: 'Agent target to check: opencode, codex, codex-desktop, codearts, workbuddy, or all (default).' },
425
+ target: {
426
+ type: 'string',
427
+ description:
428
+ 'Agent target to check: opencode, codex, codex-desktop, codearts, workbuddy, dsh, officeace, hermes, openclaw, or all (default).',
429
+ },
299
430
  },
300
431
  },
301
432
  },
302
433
  {
303
434
  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.',
435
+ description:
436
+ '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
437
  inputSchema: {
306
438
  type: 'object',
307
439
  properties: {
308
- target: { type: 'string', description: 'Agent target to report after sync: opencode, codex, codex-desktop, codearts, workbuddy, or all (default).' },
440
+ target: {
441
+ type: 'string',
442
+ description:
443
+ 'Agent target to report after sync: opencode, codex, codex-desktop, codearts, workbuddy, dsh, officeace, hermes, or all (default).',
444
+ },
445
+ },
446
+ },
447
+ },
448
+ {
449
+ name: 'huaweicloud_auth_init',
450
+ description:
451
+ 'Set or clear runtime Huawei Cloud credentials (AK/SK) for this MCP session. Runtime credentials take highest priority over environment variables and config files for all subsequent API calls. Use when switching accounts within the same Agent session — call with AK/SK to switch, or with clear=true to fall back to env/file credentials.',
452
+ inputSchema: {
453
+ type: 'object',
454
+ properties: {
455
+ ak: { type: 'string', description: 'Huawei Cloud Access Key (required unless clear=true)' },
456
+ sk: { type: 'string', description: 'Huawei Cloud Secret Key (required unless clear=true)' },
457
+ region: { type: 'string', description: 'Default region (optional)' },
458
+ clear: { type: 'boolean', description: 'Set to true to clear runtime credentials and revert to env/file' },
309
459
  },
310
460
  },
311
461
  },
312
462
  {
313
463
  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.',
464
+ description:
465
+ 'Execute a command on a workspace terminal with session reuse (state persists across calls). Shell state (cd, env vars, aliases) carries over between calls. Use for interactive work and command sequences that need shared state. NOT for long-running commands (>30s) — prefer exec_one_shot for those.',
466
+ inputSchema: {
467
+ type: 'object',
468
+ required: ['command'],
469
+ properties: {
470
+ command: { type: 'string', description: 'The shell command to execute on the remote workspace' },
471
+ workspace_id: {
472
+ type: 'string',
473
+ description:
474
+ 'Workspace ID from huaweicloud_sandbox_connect return value. Required - must be passed explicitly when HW_WORKSPACE_ID is not set.',
475
+ },
476
+ username: { type: 'string', description: 'Login username for the remote terminal (default: root)' },
477
+ timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 120000)' },
478
+ },
479
+ },
480
+ },
481
+ {
482
+ name: 'huaweicloud_sandbox_exec_one_shot',
483
+ description:
484
+ 'Execute a command on a workspace terminal with a fresh connection per call (no session state carries over). Each invocation opens a new WebSocket connection, executes one command, then disconnects. Use for long-running build/deploy/install commands (>30s) that do not need shell state persistence between calls. More stable than session-based execution for heavy workloads.',
315
485
  inputSchema: {
316
486
  type: 'object',
317
487
  required: ['command'],
318
488
  properties: {
319
489
  command: { type: 'string', description: 'The shell command to execute on the remote workspace' },
320
- workspace_id: { type: 'string', description: 'The workspace ID' },
490
+ workspace_id: {
491
+ type: 'string',
492
+ description:
493
+ 'Workspace ID from huaweicloud_sandbox_connect return value. Required - must be passed explicitly when HW_WORKSPACE_ID is not set.',
494
+ },
321
495
  username: { type: 'string', description: 'Login username for the remote terminal (default: root)' },
322
- timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 30000)' },
496
+ timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 120000)' },
323
497
  },
324
498
  },
325
499
  },
@@ -329,14 +503,72 @@ export const TOOL_DEFINITIONS = [
329
503
  inputSchema: {
330
504
  type: 'object',
331
505
  properties: {
332
- workspace_id: { type: 'string', description: 'The workspace ID' },
506
+ workspace_id: {
507
+ type: 'string',
508
+ description:
509
+ 'Workspace ID from huaweicloud_sandbox_connect return value. Required - must be passed explicitly when HW_WORKSPACE_ID is not set.',
510
+ },
333
511
  username: { type: 'string', description: 'Login username (default: root)' },
334
512
  },
335
513
  },
336
514
  },
515
+ {
516
+ name: 'huaweicloud_sandbox_upload_file',
517
+ description:
518
+ '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.',
519
+ inputSchema: {
520
+ type: 'object',
521
+ required: ['local_path', 'remote_path'],
522
+ properties: {
523
+ local_path: { type: 'string', description: 'Absolute path to the local file to upload.' },
524
+ remote_path: { type: 'string', description: 'Target path in the sandbox, e.g. /workspace/<repo>/index.html.' },
525
+ workspace_id: {
526
+ type: 'string',
527
+ description:
528
+ 'Workspace ID from huaweicloud_sandbox_connect return value. Required - must be passed explicitly when HW_WORKSPACE_ID is not set.',
529
+ },
530
+ username: { type: 'string', description: 'Login username (default: root)' },
531
+ timeout_ms: { type: 'number', description: 'Per-command execution timeout in milliseconds (default: 120000)' },
532
+ },
533
+ },
534
+ },
535
+ {
536
+ name: 'huaweicloud_sandbox_upload_project',
537
+ description:
538
+ 'Package a local project directory and upload it to a sandbox workspace via HTTP tunnel. Falls back to base64 chunking if tunnel fails. Creates a tar.gz archive, uploads it, and extracts it on the sandbox by default.',
539
+ inputSchema: {
540
+ type: 'object',
541
+ required: ['local_dir'],
542
+ properties: {
543
+ local_dir: { type: 'string', description: 'Local project directory to upload.' },
544
+ remote_dir: {
545
+ type: 'string',
546
+ description:
547
+ 'Remote parent directory where project will be extracted (default: /workspace). Final layout: <remote_dir>/<dirname>/',
548
+ },
549
+ workspace_id: {
550
+ type: 'string',
551
+ description:
552
+ 'Workspace ID from huaweicloud_sandbox_connect return value. Required - must be passed explicitly when HW_WORKSPACE_ID is not set.',
553
+ },
554
+ username: { type: 'string', description: 'Login username (default: root)' },
555
+ exclude: {
556
+ type: 'array',
557
+ items: { type: 'string' },
558
+ description: 'Patterns to exclude from archive (default: .git, node_modules, __pycache__, .venv)',
559
+ },
560
+ extract: {
561
+ type: 'boolean',
562
+ description: 'Extract tar.gz on sandbox after upload (default: true)',
563
+ },
564
+ timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 300000)' },
565
+ },
566
+ },
567
+ },
337
568
  {
338
569
  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.',
570
+ description:
571
+ '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
572
  inputSchema: {
341
573
  type: 'object',
342
574
  properties: {},
@@ -344,7 +576,8 @@ export const TOOL_DEFINITIONS = [
344
576
  },
345
577
  {
346
578
  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.',
579
+ description:
580
+ "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
581
  inputSchema: {
349
582
  type: 'object',
350
583
  properties: {},
@@ -352,11 +585,16 @@ export const TOOL_DEFINITIONS = [
352
585
  },
353
586
  {
354
587
  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.',
588
+ description:
589
+ '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
590
  inputSchema: {
357
591
  type: 'object',
358
592
  properties: {
359
- source: { type: 'string', description: 'Source identifier (default: WEB). Options: VSCODE, CLI, WEB, WEBVNC, WEBPTY, WEBIDE, CURSOR, etc.' },
593
+ source: {
594
+ type: 'string',
595
+ description:
596
+ 'Source identifier (default: WEB). Options: VSCODE, CLI, WEB, WEBVNC, WEBPTY, WEBIDE, CURSOR, etc.',
597
+ },
360
598
  template_id: { type: 'string', description: 'Template ID; overrides server default (only for new sandbox)' },
361
599
  flavor_id: { type: 'string', description: 'Flavor ID; overrides server default (only for new sandbox)' },
362
600
  env: { type: 'object', description: 'Environment variables to set in the sandbox (only for new sandbox)' },
@@ -376,7 +614,8 @@ export const TOOL_DEFINITIONS = [
376
614
  },
377
615
  {
378
616
  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.',
617
+ description:
618
+ 'Configure temporary AK/SK for a sandbox via hdkitservice. Injects temporary credentials into the sandbox. The sandbox must be in RUNNING state.',
380
619
  inputSchema: {
381
620
  type: 'object',
382
621
  properties: {
@@ -386,7 +625,6 @@ export const TOOL_DEFINITIONS = [
386
625
  },
387
626
  },
388
627
  },
389
-
390
628
  ];
391
629
 
392
630
  export async function callTool(name, args = {}) {
@@ -424,35 +662,124 @@ export async function callTool(name, args = {}) {
424
662
  return listRegions();
425
663
  case 'huaweicloud_get_regional_availability':
426
664
  return getRegionalAvailability(args.service || '', args.region || '');
427
- case 'huaweicloud_explain_error':
665
+ case 'huaweicloud_explain_error':
428
666
  return explainError(args);
429
667
  case 'huaweicloud_search_marketplace':
430
668
  return searchMarketplace(args.query || '', args.category || '');
669
+ case 'huaweicloud_get_service_icon':
670
+ return getServiceIcon(args.service || '', args.category || '');
431
671
  case 'huaweicloud_setup_obs_config':
432
672
  return setupObsConfig(args.profile);
433
673
  case 'huaweicloud_auth_status':
434
674
  return getAuthStatus(args.target || 'all');
435
675
  case 'huaweicloud_auth_sync':
436
676
  return syncAuth(args.target || 'all');
677
+ case 'huaweicloud_auth_init':
678
+ if (args.clear) {
679
+ clearRuntimeCredentials();
680
+ return { status: 'cleared', message: 'Runtime credentials cleared. Fallback to env/file.' };
681
+ }
682
+ if (!args.ak || !args.sk) {
683
+ throw new Error('ak and sk are required. Set clear=true to clear runtime credentials.');
684
+ }
685
+ setRuntimeCredentials(args.ak, args.sk, undefined, args.region);
686
+ return { status: 'ok', message: 'Runtime credentials set for this MCP session.' };
437
687
  case 'huaweicloud_sandbox_exec_with_session': {
438
- const sandboxWsId2 = args.workspace_id || DEFAULT_WORKSPACE_ID;
688
+ const sandboxWsId2 = args.workspace_id || currentWorkspaceId;
689
+ if (!sandboxWsId2) {
690
+ throw new Error(
691
+ 'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
692
+ 'or set HW_WORKSPACE_ID environment variable before starting the agent.',
693
+ );
694
+ }
439
695
  const sandboxUser2 = args.username || 'root';
440
- const sandboxTimeout2 = args.timeout_ms || 30000;
696
+ const sandboxTimeout2 = args.timeout_ms || 120000;
441
697
  const sandboxResult2 = await execWithSession(sandboxWsId2, args.command, sandboxUser2, sandboxTimeout2);
442
698
  return { stdout: sandboxResult2.stdout, exitCode: sandboxResult2.exitCode };
443
699
  }
444
- case 'huaweicloud_sandbox_close_session': {
445
- const sandboxWsId3 = args.workspace_id || DEFAULT_WORKSPACE_ID;
700
+ case 'huaweicloud_sandbox_exec_one_shot': {
701
+ const sandboxWsId3 = args.workspace_id || currentWorkspaceId;
702
+ if (!sandboxWsId3) {
703
+ throw new Error(
704
+ 'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
705
+ 'or set HW_WORKSPACE_ID environment variable before starting the agent.',
706
+ );
707
+ }
446
708
  const sandboxUser3 = args.username || 'root';
447
- const closed = await closeSession(sandboxWsId3, sandboxUser3);
709
+ const sandboxTimeout3 = args.timeout_ms || 120000;
710
+ const sandboxResult3 = await execOneShot(sandboxWsId3, args.command, sandboxUser3, sandboxTimeout3);
711
+ return { stdout: sandboxResult3.stdout, exitCode: sandboxResult3.exitCode };
712
+ }
713
+ case 'huaweicloud_sandbox_close_session': {
714
+ const sandboxWsId4 = args.workspace_id || currentWorkspaceId;
715
+ if (!sandboxWsId4) {
716
+ throw new Error(
717
+ 'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
718
+ 'or set HW_WORKSPACE_ID environment variable before starting the agent.',
719
+ );
720
+ }
721
+ const sandboxUser4 = args.username || 'root';
722
+ const closed = await closeSession(sandboxWsId4, sandboxUser4);
448
723
  return closed ? 'ok' : 'not_connected';
449
724
  }
725
+ case 'huaweicloud_sandbox_upload_file': {
726
+ if (!args.local_path || !args.remote_path) {
727
+ throw new Error('local_path and remote_path are required.');
728
+ }
729
+ const sandboxWsId5 = args.workspace_id || currentWorkspaceId;
730
+ if (!sandboxWsId5) {
731
+ throw new Error(
732
+ 'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
733
+ 'or set HW_WORKSPACE_ID environment variable before starting the agent.',
734
+ );
735
+ }
736
+ const sandboxUser5 = args.username || 'root';
737
+ const sandboxTimeout5 = args.timeout_ms || 120000;
738
+ return await uploadFileWithSession(
739
+ sandboxWsId5,
740
+ args.local_path,
741
+ args.remote_path,
742
+ sandboxUser5,
743
+ sandboxTimeout5,
744
+ );
745
+ }
746
+ case 'huaweicloud_sandbox_upload_project': {
747
+ if (!args.local_dir) {
748
+ throw new Error('local_dir is required.');
749
+ }
750
+ const sandboxWsId6 = args.workspace_id || currentWorkspaceId;
751
+ if (!sandboxWsId6) {
752
+ throw new Error(
753
+ 'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
754
+ 'or set HW_WORKSPACE_ID environment variable before starting the agent.',
755
+ );
756
+ }
757
+ const sandboxUser6 = args.username || 'root';
758
+ const sandboxTimeout6 = args.timeout_ms || 120000;
759
+ return await uploadProjectWithSession(
760
+ sandboxWsId6,
761
+ args.local_dir,
762
+ args.remote_dir,
763
+ sandboxUser6,
764
+ sandboxTimeout6,
765
+ {
766
+ exclude: args.exclude,
767
+ extract: args.extract,
768
+ },
769
+ );
770
+ }
450
771
  case 'huaweicloud_sandbox_check_user':
451
772
  return await hdkitCheckUser();
452
773
  case 'huaweicloud_sandbox_sign_agreement':
453
774
  return await hdkitSignAgreement();
454
- case 'huaweicloud_sandbox_connect':
455
- return await hdkitConnect(args);
775
+ case 'huaweicloud_sandbox_connect': {
776
+ const connectResult = await hdkitConnect(args);
777
+ const devStageId = connectResult?.dev_stage_id || connectResult?.devStageId;
778
+ if (devStageId) {
779
+ setWorkspaceId(devStageId);
780
+ }
781
+ return connectResult;
782
+ }
456
783
  case 'huaweicloud_sandbox_credentials':
457
784
  return await hdkitCredentials(args.session_id, args.dev_stage_id, args.enable_sts !== false);
458
785
  default:
@@ -465,11 +792,12 @@ function hookResult(result) {
465
792
  ok: result.decision !== 'deny',
466
793
  decision: result.decision,
467
794
  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.',
795
+ nextStep:
796
+ result.decision === 'deny'
797
+ ? 'Revise the command, artifact, or deployment plan before execution.'
798
+ : result.decision === 'warn'
799
+ ? 'Review the warnings with the user before proceeding.'
800
+ : 'No Huawei Cloud hook risk rule matched.',
473
801
  };
474
802
  }
475
803
 
@@ -490,7 +818,8 @@ export async function runVersionCheck(options = {}) {
490
818
  : isSpawnError
491
819
  ? '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
820
  : '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.',
821
+ authHint:
822
+ '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
823
  };
495
824
  }
496
825
 
@@ -509,7 +838,8 @@ async function showProfileRedacted(profile) {
509
838
  ok: false,
510
839
  blockedByPolicy: true,
511
840
  reason: result.reason,
512
- safeAlternative: 'Use huaweicloud_show_profile_redacted so profile output is returned through the redaction pipeline.',
841
+ safeAlternative:
842
+ 'Use huaweicloud_show_profile_redacted so profile output is returned through the redaction pipeline.',
513
843
  };
514
844
  }
515
845
  return {
@@ -551,7 +881,12 @@ async function setupObsConfig(profile) {
551
881
  async function setupObsConfigFromHcloud(profile) {
552
882
  const obsConfigPath = join(homedir(), '.obsutilconfig');
553
883
  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.' };
884
+ return {
885
+ ok: true,
886
+ existed: true,
887
+ path: obsConfigPath,
888
+ note: 'OBS config already exists. Delete ~/.obsutilconfig first if you need to re-sync.',
889
+ };
555
890
  }
556
891
 
557
892
  const args = ['configure', 'show'];
@@ -567,9 +902,9 @@ async function setupObsConfigFromHcloud(profile) {
567
902
  };
568
903
  }
569
904
 
570
- let accessKeyId = '';
571
- let secretAccessKey = '';
572
- let region = '';
905
+ let accessKeyId;
906
+ let secretAccessKey;
907
+ let region;
573
908
 
574
909
  try {
575
910
  const parsed = typeof result.stdout === 'string' ? JSON.parse(result.stdout) : result.stdout;
@@ -630,12 +965,16 @@ async function setupObsConfigFromHcloud(profile) {
630
965
  const SERVICE_EXAMPLES = {
631
966
  ECS: { list: 'ECS ListServersDetails', create: 'ECS CreateServers', show: 'IMS GlanceShowImage' },
632
967
  VPC: { list: 'VPC ListVpcs', create: 'VPC CreateVpc', show: 'VPC ShowVpc' },
633
- FunctionGraph: { list: 'FunctionGraph ListFunctions', create: 'FunctionGraph CreateFunction', show: 'FunctionGraph ShowFunctionConfig' },
968
+ FUNCTIONGRAPH: {
969
+ list: 'FunctionGraph ListFunctions',
970
+ create: 'FunctionGraph CreateFunction',
971
+ show: 'FunctionGraph ShowFunctionConfig',
972
+ },
634
973
  APIG: { list: 'APIG ListInstancesV2', create: 'APIG CreateInstanceV2', show: 'APIG ShowDetailsOfInstanceV2' },
635
974
  OBS: { list: 'OBS ls', create: 'OBS mb obs://<bucket>', show: 'OBS stat obs://<bucket>/<key>' },
636
975
  RDS: { list: 'RDS ListInstances', create: 'RDS CreateInstance', show: 'RDS ShowInstance' },
637
976
  CES: { list: 'CES ListAlarms', create: 'CES CreateAlarm', show: 'CES ListMetrics' },
638
- GaussDB: { list: 'GaussDB ListInstances', create: 'GaussDB CreateInstance', show: 'GaussDB ShowInstance' },
977
+ GAUSSDB: { list: 'GaussDB ListInstances', create: 'GaussDB CreateInstance', show: 'GaussDB ShowInstance' },
639
978
  DDS: { list: 'DDS ListInstances', create: 'DDS CreateInstance', show: 'DDS ShowInstance' },
640
979
  DCS: { list: 'DCS ListInstances', create: 'DCS CreateInstance', show: 'DCS ShowInstance' },
641
980
  };
@@ -662,7 +1001,9 @@ async function listOperations(service, options = {}) {
662
1001
  service: serviceName,
663
1002
  command: isObs ? 'hcloud obs help' : `hcloud ${svc} --help`,
664
1003
  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.` },
1004
+ examples: SERVICE_EXAMPLES[serviceName.toUpperCase()] || {
1005
+ note: `No cached examples for ${serviceName}. Use the help text above to discover available operations.`,
1006
+ },
666
1007
  result,
667
1008
  };
668
1009
  }
@@ -691,41 +1032,131 @@ async function runApprovedCommand(args = {}) {
691
1032
  function serviceCatalog(intent = '') {
692
1033
  const it = String(intent).toLowerCase();
693
1034
  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'] },
1035
+ {
1036
+ keywords: ['ecs', 'server', 'vm', 'instance', 'compute', 'flavor', 'image'],
1037
+ skills: ['huawei-ecs'],
1038
+ services: ['ECS'],
1039
+ },
1040
+ {
1041
+ keywords: ['vpc', 'subnet', 'network', 'security group', 'eip', 'nat', 'vpn', 'bandwidth'],
1042
+ skills: ['huawei-vpc'],
1043
+ services: ['VPC', 'EIP'],
1044
+ },
1045
+ {
1046
+ keywords: ['obs', 'bucket', 'storage', 'object', 'static website', 'static site', 'hosting'],
1047
+ skills: ['huawei-obs'],
1048
+ services: ['OBS'],
1049
+ },
1050
+ {
1051
+ keywords: ['functiongraph', 'serverless', 'function', 'lambda', 'trigger', 'faas'],
1052
+ skills: ['huawei-functiongraph'],
1053
+ services: ['FunctionGraph'],
1054
+ },
1055
+ {
1056
+ keywords: ['cce', 'kubernetes', 'k8s', 'container', 'cluster', 'node pool', 'swr', 'docker', 'image registry'],
1057
+ skills: ['huawei-cce'],
1058
+ services: ['CCE', 'SWR'],
1059
+ },
699
1060
  { keywords: ['apig', 'api gateway', 'publish', 'throttle'], skills: ['huawei-apig'], services: ['APIG'] },
700
1061
  { 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'] },
1062
+ {
1063
+ keywords: ['gaussdb', 'distributed', 'sharding', 'opengauss'],
1064
+ skills: ['huawei-gaussdb'],
1065
+ services: ['GaussDB'],
1066
+ },
1067
+ {
1068
+ keywords: ['iam', 'permission', 'policy', 'role', 'user', 'ak/sk', 'access key', 'agency'],
1069
+ skills: ['huawei-iam'],
1070
+ services: ['IAM'],
1071
+ },
1072
+ {
1073
+ keywords: ['dew', 'secret', 'kms', 'encrypt', 'decrypt', 'certificate', 'csms'],
1074
+ skills: ['huawei-dew'],
1075
+ services: ['CSMS', 'KMS'],
1076
+ },
1077
+ {
1078
+ keywords: ['modelarts', 'ai', 'model', 'training', 'inference', 'machine learning'],
1079
+ skills: ['huawei-modelarts'],
1080
+ services: ['ModelArts'],
1081
+ },
1082
+ {
1083
+ keywords: ['billing', 'cost', 'bill', 'budget', 'expense', 'bss'],
1084
+ skills: ['huawei-billing'],
1085
+ services: ['BSS'],
1086
+ },
1087
+ {
1088
+ keywords: ['waf', 'aad', 'ddos', 'firewall', 'web protection'],
1089
+ skills: ['huawei-waf-aad'],
1090
+ services: ['WAF', 'AAD'],
1091
+ },
1092
+ {
1093
+ keywords: ['smn', 'dms', 'notification', 'message', 'kafka', 'rabbitmq'],
1094
+ skills: ['huawei-smn-dms'],
1095
+ services: ['SMN', 'DMS'],
1096
+ },
1097
+ {
1098
+ keywords: ['ces', 'monitor', 'alarm', 'metric', 'dashboard', 'cloud eye'],
1099
+ skills: ['huawei-cloud-eye'],
1100
+ services: ['CES'],
1101
+ },
709
1102
  { keywords: ['cts', 'audit', 'trace', 'tracker'], skills: ['huawei-cts'], services: ['CTS'] },
710
1103
  { 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
- ];
1104
+ {
1105
+ keywords: ['deployment', 'deploy', 'ci/cd', 'pipeline', 'release'],
1106
+ skills: ['huawei-deployment'],
1107
+ services: ['CloudDeploy'],
1108
+ },
1109
+ {
1110
+ keywords: [
1111
+ 'sandbox',
1112
+ 'devstation',
1113
+ 'workspace',
1114
+ 'terminal',
1115
+ 'preview',
1116
+ 'hwlink',
1117
+ 'website',
1118
+ 'web app',
1119
+ 'webapp',
1120
+ 'hosting',
1121
+ '网站',
1122
+ '网页',
1123
+ '静态',
1124
+ ],
1125
+ skills: ['huawei-sandbox'],
1126
+ services: ['Sandbox', 'DevStation'],
1127
+ },
1128
+ {
1129
+ keywords: ['dds', 'dcs', 'mongodb', 'redis', 'memcached', 'cache', 'document db'],
1130
+ skills: ['huawei-dds-dcs'],
1131
+ services: ['DDS', 'DCS'],
1132
+ },
1133
+ ];
715
1134
  const matched = [];
716
1135
  const tokens = it.split(/[\s,./-]+/).filter((t) => t.length > 0);
1136
+ const cjk = /[\u4e00-\u9fff]/;
717
1137
  for (const route of routeMap) {
718
- if (route.keywords.some((kw) => tokens.includes(kw))) {
1138
+ if (route.keywords.some((kw) => (kw.includes(' ') || cjk.test(kw) ? it.includes(kw) : tokens.includes(kw)))) {
719
1139
  matched.push(route);
720
1140
  }
721
1141
  }
722
1142
  const recommendedSkills = [...new Set(matched.flatMap((r) => r.skills))];
723
1143
  const recommendedServices = [...new Set(matched.flatMap((r) => r.services))].slice(0, 5);
724
1144
 
1145
+ // Deployment intent (deploy/host/publish a web app or static website) must never
1146
+ // default to a storage/other service — recommend the sandbox first.
1147
+ const deploymentIntent = /deploy|host|hosting|publish|website|web app|preview|部署|托管|发布|网站|网页/.test(it);
1148
+ if (deploymentIntent && recommendedSkills.includes('huawei-sandbox')) {
1149
+ const idx = recommendedSkills.indexOf('huawei-sandbox');
1150
+ recommendedSkills.splice(idx, 1);
1151
+ recommendedSkills.unshift('huawei-sandbox');
1152
+ }
1153
+
725
1154
  return {
726
1155
  intent,
727
1156
  recommendedSkills: recommendedSkills.length ? recommendedSkills : ['Use huaweicloud-core to route intent.'],
728
- recommendedServices: recommendedServices.length ? recommendedServices : ['Run hcloud --help to list available services.'],
1157
+ recommendedServices: recommendedServices.length
1158
+ ? recommendedServices
1159
+ : ['Run hcloud --help to list available services.'],
729
1160
  capabilityOrder: [
730
1161
  'Huawei Cloud Skills for task-specific workflows and examples',
731
1162
  'KooCLI hcloud for local authenticated operations and quick inspection',
@@ -751,32 +1182,38 @@ function explainError({ service = 'unknown', errorCode = '', message = '', reque
751
1182
  const svc = String(service).toLowerCase();
752
1183
 
753
1184
  const SERVICE_ALIASES = {
754
- 'functiongraph': 'FSS',
755
- 'fgs': 'FSS',
1185
+ functiongraph: 'FSS',
1186
+ fgs: 'FSS',
756
1187
  };
757
1188
  const patternKey = SERVICE_ALIASES[svc] || service;
758
1189
 
759
1190
  const hwErrorPatterns = {
760
1191
  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.',
1192
+ InvalidAccessKeyId:
1193
+ 'OBS uses AK/SK directly (not IAM tokens). Verify AK/SK validity, OBS endpoint, and OBS permissions.',
1194
+ UserRestricted:
1195
+ '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
1196
  },
764
1197
  APIG: {
765
1198
  '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.',
1199
+ 'APIC.7242':
1200
+ 'The EIP binding method depends on loadbalancer_provider. Use AddIngressEipV2 for elb, AddEipV2 for lvs.',
767
1201
  'APIC.7256': 'Bandwidth minimum is 5 Mbps. Use --bandwidth_size=5 or higher.',
768
1202
  'APIC.7310': 'available_zone_ids must use AZ codes (e.g. ap-southeast-3a), NOT UUIDs from ListAvailableZonesV2.',
769
1203
  },
770
1204
  FSS: {
771
- 'FSS.0403': 'Missing FunctionGraph IAM permissions. Attach FunctionGraph FullAccess role or grant specific actions.',
1205
+ 'FSS.0403':
1206
+ 'Missing FunctionGraph IAM permissions. Attach FunctionGraph FullAccess role or grant specific actions.',
772
1207
  '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.',
1208
+ 'FSS.1417':
1209
+ 'event_data field validation failed. Check parameter format: use dotted key=value, verify required hidden-optional fields.',
774
1210
  },
775
1211
  VPC: {
776
1212
  'VPC.0301': 'Bandwidth name is required for PER type EIPs, even though --help marks it optional.',
777
1213
  },
778
1214
  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.',
1215
+ 'APIGW.0802':
1216
+ '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
1217
  },
781
1218
  };
782
1219
  if (hwErrorPatterns[patternKey] && hwErrorPatterns[patternKey][errorCode]) {
@@ -791,19 +1228,30 @@ function explainError({ service = 'unknown', errorCode = '', message = '', reque
791
1228
 
792
1229
  if (/auth|token|credential|ak|sk|401|403|unauthorized|forbidden|Incorrect IAM/i.test(combined)) {
793
1230
  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.');
1231
+ suggestions.push(
1232
+ '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.',
1233
+ );
795
1234
  } else {
796
1235
  suggestions.push('Check KooCLI profile, region, project_id, and IAM permissions without printing secrets.');
797
1236
  }
798
1237
  }
799
1238
  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.'));
1239
+ suggestions.push(
1240
+ 'APIGW.' +
1241
+ (errorCode.match(/APIGW\.(\d+)/i) || [])[1] +
1242
+ ': API Gateway layer error. ' +
1243
+ (errorCode === 'APIGW.0802'
1244
+ ? 'IAM user has no region permissions — check IAM console → User → Permissions → add target region.'
1245
+ : 'Verify the API request, region endpoint, and IAM permissions.'),
1246
+ );
801
1247
  }
802
1248
  if (/region|endpoint|project/i.test(combined)) {
803
1249
  suggestions.push('Confirm the service endpoint, region, and project_id match the target resource.');
804
1250
  }
805
1251
  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.');
1252
+ suggestions.push(
1253
+ 'Check quota and resource limits before retrying a create or scale operation. Consider switching accounts or requesting a quota increase.',
1254
+ );
807
1255
  }
808
1256
  if (/not.?found|404/i.test(combined)) {
809
1257
  if (/list.?regions/i.test(svc)) {
@@ -813,7 +1261,9 @@ function explainError({ service = 'unknown', errorCode = '', message = '', reque
813
1261
  }
814
1262
  }
815
1263
  if (!suggestions.length) {
816
- suggestions.push('Collect service name, operation, region, project_id, request_id, and the full redacted error message.');
1264
+ suggestions.push(
1265
+ 'Collect service name, operation, region, project_id, request_id, and the full redacted error message.',
1266
+ );
817
1267
  }
818
1268
 
819
1269
  if (requestId) {
@@ -821,8 +1271,7 @@ function explainError({ service = 'unknown', errorCode = '', message = '', reque
821
1271
  }
822
1272
 
823
1273
  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());
1274
+ return !arr.slice(0, i).some((prev) => prev.substring(0, 50).toLowerCase() === s.substring(0, 50).toLowerCase());
826
1275
  });
827
1276
 
828
1277
  return {
@@ -839,9 +1288,7 @@ async function searchDocs(query, topic = 'all') {
839
1288
  const results = [];
840
1289
  try {
841
1290
  if (existsSync(SKILLS_ROOT)) {
842
- const dirs = readdirSync(SKILLS_ROOT, { withFileTypes: true })
843
- .filter((d) => d.isDirectory())
844
- .map((d) => d.name);
1291
+ const dirs = listSkillDirs(SKILLS_ROOT);
845
1292
  for (const dir of dirs) {
846
1293
  const skillPath = join(SKILLS_ROOT, dir, 'SKILL.md');
847
1294
  if (!existsSync(skillPath)) continue;
@@ -864,11 +1311,16 @@ async function searchDocs(query, topic = 'all') {
864
1311
  const nameLower = name.toLowerCase();
865
1312
  const contentLower = content.toLowerCase();
866
1313
  const relevance = tokens.reduce((score, token) => {
867
- return score + (descLower.includes(token) ? 3 : 0) + (nameLower.includes(token) ? 2 : 0) + (contentLower.includes(token) ? 1 : 0);
1314
+ return (
1315
+ score +
1316
+ (descLower.includes(token) ? 3 : 0) +
1317
+ (nameLower.includes(token) ? 2 : 0) +
1318
+ (contentLower.includes(token) ? 1 : 0)
1319
+ );
868
1320
  }, 0);
869
1321
  if (relevance > 0) {
870
1322
  results.push({
871
- source: "skills/" + dir + "/SKILL.md",
1323
+ source: 'skills/' + dir + '/SKILL.md',
872
1324
  name,
873
1325
  snippet: description.substring(0, 200),
874
1326
  relevance,
@@ -888,9 +1340,7 @@ async function retrieveSkill(name) {
888
1340
  if (!skillName) return { ok: false, error: 'Skill name is required.' };
889
1341
  const skillPath = join(SKILLS_ROOT, skillName, 'SKILL.md');
890
1342
  if (!existsSync(skillPath)) {
891
- const dirs = existsSync(SKILLS_ROOT)
892
- ? readdirSync(SKILLS_ROOT, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name)
893
- : [];
1343
+ const dirs = listSkillDirs(SKILLS_ROOT);
894
1344
  return { ok: false, error: 'Skill "' + skillName + '" not found. Available: ' + dirs.join(', ') };
895
1345
  }
896
1346
  const content = readFileSync(skillPath, 'utf8');
@@ -903,7 +1353,8 @@ async function retrieveSkill(name) {
903
1353
  });
904
1354
  }
905
1355
  const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
906
- let version = 1, description = '';
1356
+ let version = 1,
1357
+ description = '';
907
1358
  if (frontmatter) {
908
1359
  const fm = frontmatter[1];
909
1360
  const vm = fm.match(/^version:\s*(.+)$/m);
@@ -915,8 +1366,10 @@ async function retrieveSkill(name) {
915
1366
  }
916
1367
 
917
1368
  async function listRegions() {
918
- const result = await runHcloud(['IAM', 'KeystoneListRegions'], { timeoutMs: 30000, maxRetries: 0 })
919
- .catch((err) => ({ ok: false, error: err.message }));
1369
+ const result = await runHcloud(['IAM', 'KeystoneListRegions'], { timeoutMs: 30000, maxRetries: 0 }).catch((err) => ({
1370
+ ok: false,
1371
+ error: err.message,
1372
+ }));
920
1373
  if (!result.ok) {
921
1374
  return {
922
1375
  ok: false,
@@ -936,7 +1389,7 @@ async function listRegions() {
936
1389
  note: 'hcloud unavailable. Showing static region list. For the complete list, visit the fallback URL.',
937
1390
  };
938
1391
  }
939
- let regions = [];
1392
+ let regions;
940
1393
  try {
941
1394
  const parsed = typeof result.stdout === 'string' ? JSON.parse(result.stdout) : result.stdout;
942
1395
  const rawRegions = parsed.regions || [];
@@ -954,37 +1407,128 @@ async function listRegions() {
954
1407
  }
955
1408
 
956
1409
  async function getRegionalAvailability(service, region) {
957
- const svc = String(service || '').toLowerCase().trim();
958
- const reg = String(region || '').toLowerCase().trim();
1410
+ const svc = String(service || '')
1411
+ .toLowerCase()
1412
+ .trim();
1413
+ const reg = String(region || '')
1414
+ .toLowerCase()
1415
+ .trim();
959
1416
  if (!svc || !reg) return { ok: false, error: 'Both service and region are required.' };
960
1417
  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'],
1418
+ ecs: [
1419
+ 'cn-south-1',
1420
+ 'cn-north-4',
1421
+ 'cn-north-1',
1422
+ 'cn-east-3',
1423
+ 'cn-east-2',
1424
+ 'ap-southeast-3',
1425
+ 'ap-southeast-2',
1426
+ 'ap-southeast-1',
1427
+ 'ap-southeast-4',
1428
+ 'af-south-1',
1429
+ 'tr-west-1',
1430
+ 'sa-brazil-1',
1431
+ 'la-north-2',
1432
+ 'na-mexico-1',
1433
+ 'me-east-1',
1434
+ ],
1435
+ obs: [
1436
+ 'cn-south-1',
1437
+ 'cn-north-4',
1438
+ 'cn-north-1',
1439
+ 'cn-east-3',
1440
+ 'cn-east-2',
1441
+ 'ap-southeast-3',
1442
+ 'ap-southeast-2',
1443
+ 'ap-southeast-1',
1444
+ 'af-south-1',
1445
+ ],
1446
+ vpc: [
1447
+ 'cn-south-1',
1448
+ 'cn-north-4',
1449
+ 'cn-north-1',
1450
+ 'cn-east-3',
1451
+ 'cn-east-2',
1452
+ 'ap-southeast-3',
1453
+ 'ap-southeast-2',
1454
+ 'ap-southeast-1',
1455
+ 'ap-southeast-4',
1456
+ 'af-south-1',
1457
+ 'tr-west-1',
1458
+ 'sa-brazil-1',
1459
+ 'la-north-2',
1460
+ 'me-east-1',
1461
+ ],
964
1462
  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'],
1463
+ rds: [
1464
+ 'cn-south-1',
1465
+ 'cn-north-4',
1466
+ 'cn-north-1',
1467
+ 'cn-east-3',
1468
+ 'cn-east-2',
1469
+ 'ap-southeast-3',
1470
+ 'ap-southeast-2',
1471
+ 'ap-southeast-1',
1472
+ ],
1473
+ gaussdb: ['cn-south-1', 'cn-north-4', 'cn-east-3'],
1474
+ cce: [
1475
+ 'cn-south-1',
1476
+ 'cn-north-4',
1477
+ 'cn-north-1',
1478
+ 'cn-east-3',
1479
+ 'cn-east-2',
1480
+ 'ap-southeast-3',
1481
+ 'ap-southeast-2',
1482
+ 'ap-southeast-1',
1483
+ ],
1484
+ modelarts: ['cn-south-1', 'cn-north-4', 'cn-east-3'],
1485
+ functiongraph: [
1486
+ 'cn-south-1',
1487
+ 'cn-north-4',
1488
+ 'cn-north-1',
1489
+ 'cn-east-3',
1490
+ 'cn-east-2',
1491
+ 'ap-southeast-3',
1492
+ 'ap-southeast-2',
1493
+ 'ap-southeast-1',
1494
+ ],
1495
+ dew: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1496
+ smn: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1497
+ ces: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1498
+ cts: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1499
+ apig: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1500
+ cbr: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1501
+ dds: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1502
+ dcs: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
978
1503
  };
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.' };
1504
+ if (!known[svc])
1505
+ return {
1506
+ ok: false,
1507
+ service: svc,
1508
+ region: reg,
1509
+ available: false,
1510
+ note:
1511
+ 'Service ' +
1512
+ svc +
1513
+ ' is not in the regional availability cache. Run hcloud ' +
1514
+ svc.toUpperCase() +
1515
+ ' --help to verify, or check https://developer.huaweicloud.com/endpoint.',
1516
+ };
980
1517
  const available = known[svc].includes(reg) || known[svc].includes('global');
981
1518
  return {
982
- ok: true, service: svc, region: reg, available,
1519
+ ok: true,
1520
+ service: svc,
1521
+ region: reg,
1522
+ available,
983
1523
  note: available
984
1524
  ? svc + ' is available in ' + reg + '.'
985
- : svc + ' availability in ' + reg + ' could not be confirmed. Verify at https://developer.huaweicloud.com/endpoint.',
1525
+ : svc +
1526
+ ' availability in ' +
1527
+ reg +
1528
+ ' could not be confirmed. Verify at https://developer.huaweicloud.com/endpoint.',
986
1529
  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.',
1530
+ disclaimer:
1531
+ '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
1532
  };
989
1533
  }
990
1534