huaweicloud-devkit 1.0.2-next.2 → 1.0.2-next.21

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 +81 -51
  3. package/README.zh-CN.md +81 -49
  4. package/bin/setup.cjs +5 -3
  5. package/integrations/opencode/opencode.json +1 -4
  6. package/package.json +61 -44
  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-cloud-find-skills/SKILL.md +13 -12
  23. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +64 -17
  24. package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +20 -20
  25. package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +31 -31
  26. package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +21 -20
  27. package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +39 -30
  28. package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +8 -1
  29. package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +6 -0
  30. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +39 -36
  31. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +15 -4
  32. package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +11 -9
  33. package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +13 -13
  34. package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +40 -39
  35. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +12 -10
  36. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +21 -20
  37. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +19 -19
  38. package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +20 -20
  39. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +27 -23
  40. package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +27 -27
  41. package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +65 -39
  42. package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +9 -9
  43. package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +25 -25
  44. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +45 -44
  45. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +19 -19
  46. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +12 -7
  47. package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +13 -12
  48. package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +4 -4
  49. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +63 -58
  50. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +127 -42
  51. package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +26 -26
  52. package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +39 -39
  53. package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +13 -12
  54. package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +20 -19
  55. package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +15 -15
  56. package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +18 -18
  57. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +13 -13
  58. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +3 -3
  59. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +17 -1
  60. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +3 -3
  61. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +27 -1
  62. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +3 -3
  63. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +56 -53
  64. package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +3 -3
  65. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +6 -6
  66. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +15 -1
  67. package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +0 -1
  68. package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +3 -3
  69. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +33 -33
  70. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +3 -3
  71. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +108 -5
  72. package/plugins/huaweicloud-core/src/auth/credentials.mjs +77 -4
  73. package/plugins/huaweicloud-core/src/auth/service.mjs +1 -1
  74. package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
  75. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +25 -7
  76. package/plugins/huaweicloud-core/src/icon-library.mjs +171 -0
  77. package/plugins/huaweicloud-core/src/mcp-server.mjs +30 -3
  78. package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +20 -102
  79. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +11 -4
  80. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +4 -1
  81. package/plugins/huaweicloud-core/src/safety-policy.mjs +122 -68
  82. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +9 -9
  83. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +24 -26
  84. package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
  85. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +510 -10
  86. package/plugins/huaweicloud-core/src/search-market.mjs +46 -12
  87. package/plugins/huaweicloud-core/src/setup-cli.mjs +1251 -181
  88. package/plugins/huaweicloud-core/src/tools.mjs +600 -122
  89. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +72 -38
  90. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +10 -5
  91. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +12 -15
  92. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +12 -16
  93. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +46 -31
  94. package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +267 -0
  95. package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
  96. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +9 -5
@@ -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');
@@ -29,13 +44,82 @@ function dshSkillsDir() {
29
44
  const home = process.env.DSH_HOME || join(homedir(), '.dsh');
30
45
  return join(home, 'skills');
31
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
+
32
111
  function resolveSkillsRoot() {
33
- if (existsSync(SKILLS_ROOT_DEV)) return SKILLS_ROOT_DEV;
34
- if (existsSync(dshSkillsDir())) return dshSkillsDir();
35
- if (existsSync(codeartsSkillsDir())) return codeartsSkillsDir();
36
- if (existsSync(opencodeSkillsDir())) return opencodeSkillsDir();
37
- if (existsSync(workbuddySkillsDir())) return workbuddySkillsDir();
38
- 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
+ );
39
123
  }
40
124
  const SKILLS_ROOT = resolveSkillsRoot();
41
125
 
@@ -95,7 +179,8 @@ export const TOOL_DEFINITIONS = [
95
179
  },
96
180
  {
97
181
  name: 'huaweicloud_list_operations',
98
- 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.',
99
184
  inputSchema: {
100
185
  type: 'object',
101
186
  required: ['service'],
@@ -113,7 +198,8 @@ export const TOOL_DEFINITIONS = [
113
198
  },
114
199
  {
115
200
  name: 'huaweicloud_run_approved_command',
116
- 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.',
117
203
  inputSchema: {
118
204
  type: 'object',
119
205
  required: ['args', 'approvedCommand', 'approvedByUser'],
@@ -193,7 +279,8 @@ export const TOOL_DEFINITIONS = [
193
279
  },
194
280
  {
195
281
  name: 'huaweicloud_hook_check_deploy_plan',
196
- 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.',
197
284
  inputSchema: {
198
285
  type: 'object',
199
286
  required: ['plan'],
@@ -232,19 +319,24 @@ export const TOOL_DEFINITIONS = [
232
319
  },
233
320
  {
234
321
  name: 'huaweicloud_search_docs',
235
- 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.',
236
324
  inputSchema: {
237
325
  type: 'object',
238
326
  required: ['query'],
239
327
  properties: {
240
328
  query: { type: 'string', description: 'Search query across skill descriptions and documentation.' },
241
- 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
+ },
242
333
  },
243
334
  },
244
335
  },
245
336
  {
246
337
  name: 'huaweicloud_retrieve_skill',
247
- 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.',
248
340
  inputSchema: {
249
341
  type: 'object',
250
342
  required: ['name'],
@@ -255,7 +347,8 @@ export const TOOL_DEFINITIONS = [
255
347
  },
256
348
  {
257
349
  name: 'huaweicloud_list_regions',
258
- 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.',
259
352
  inputSchema: {
260
353
  type: 'object',
261
354
  properties: {},
@@ -263,30 +356,58 @@ export const TOOL_DEFINITIONS = [
263
356
  },
264
357
  {
265
358
  name: 'huaweicloud_get_regional_availability',
266
- 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.',
267
361
  inputSchema: {
268
362
  type: 'object',
269
363
  required: ['service', 'region'],
270
364
  properties: {
271
- 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
+ },
272
369
  region: { type: 'string', description: 'Region ID: cn-south-1, cn-north-4, ap-southeast-3, etc.' },
273
370
  },
274
371
  },
275
372
  },
276
373
  {
277
374
  name: 'huaweicloud_search_marketplace',
278
- 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.',
279
377
  inputSchema: {
280
378
  type: 'object',
281
379
  properties: {
282
380
  query: { type: 'string', description: 'Search query across skill name, description, triggers, and service.' },
283
- 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
+ },
284
404
  },
285
405
  },
286
406
  },
287
407
  {
288
408
  name: 'huaweicloud_setup_obs_config',
289
- 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.',
290
411
  inputSchema: {
291
412
  type: 'object',
292
413
  properties: {
@@ -296,27 +417,52 @@ export const TOOL_DEFINITIONS = [
296
417
  },
297
418
  {
298
419
  name: 'huaweicloud_auth_status',
299
- 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.',
300
422
  inputSchema: {
301
423
  type: 'object',
302
424
  properties: {
303
- target: { type: 'string', description: 'Agent target to check: opencode, codex, codex-desktop, codearts, workbuddy, dsh, 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
+ },
304
430
  },
305
431
  },
306
432
  },
307
433
  {
308
434
  name: 'huaweicloud_auth_sync',
309
- 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.',
437
+ inputSchema: {
438
+ type: 'object',
439
+ properties: {
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.',
310
452
  inputSchema: {
311
453
  type: 'object',
312
454
  properties: {
313
- target: { type: 'string', description: 'Agent target to report after sync: opencode, codex, codex-desktop, codearts, workbuddy, dsh, or all (default).' },
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' },
314
459
  },
315
460
  },
316
461
  },
317
462
  {
318
463
  name: 'huaweicloud_sandbox_exec_with_session',
319
- 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.',
320
466
  inputSchema: {
321
467
  type: 'object',
322
468
  required: ['command'],
@@ -324,7 +470,22 @@ export const TOOL_DEFINITIONS = [
324
470
  command: { type: 'string', description: 'The shell command to execute on the remote workspace' },
325
471
  workspace_id: { type: 'string', description: 'The workspace ID' },
326
472
  username: { type: 'string', description: 'Login username for the remote terminal (default: root)' },
327
- timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 30000)' },
473
+ timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 120000)' },
474
+ },
475
+ },
476
+ },
477
+ {
478
+ name: 'huaweicloud_sandbox_exec_one_shot',
479
+ description:
480
+ '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.',
481
+ inputSchema: {
482
+ type: 'object',
483
+ required: ['command'],
484
+ properties: {
485
+ command: { type: 'string', description: 'The shell command to execute on the remote workspace' },
486
+ workspace_id: { type: 'string', description: 'The workspace ID' },
487
+ username: { type: 'string', description: 'Login username for the remote terminal (default: root)' },
488
+ timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 120000)' },
328
489
  },
329
490
  },
330
491
  },
@@ -339,9 +500,55 @@ export const TOOL_DEFINITIONS = [
339
500
  },
340
501
  },
341
502
  },
503
+ {
504
+ name: 'huaweicloud_sandbox_upload_file',
505
+ description:
506
+ '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.',
507
+ inputSchema: {
508
+ type: 'object',
509
+ required: ['local_path', 'remote_path'],
510
+ properties: {
511
+ local_path: { type: 'string', description: 'Absolute path to the local file to upload.' },
512
+ remote_path: { type: 'string', description: 'Target path in the sandbox, e.g. /workspace/<repo>/index.html.' },
513
+ workspace_id: { type: 'string', description: 'The workspace ID' },
514
+ username: { type: 'string', description: 'Login username (default: root)' },
515
+ timeout_ms: { type: 'number', description: 'Per-command execution timeout in milliseconds (default: 120000)' },
516
+ },
517
+ },
518
+ },
519
+ {
520
+ name: 'huaweicloud_sandbox_upload_project',
521
+ description:
522
+ '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.',
523
+ inputSchema: {
524
+ type: 'object',
525
+ required: ['local_dir'],
526
+ properties: {
527
+ local_dir: { type: 'string', description: 'Local project directory to upload.' },
528
+ remote_dir: {
529
+ type: 'string',
530
+ description:
531
+ 'Remote parent directory where project will be extracted (default: /workspace). Final layout: <remote_dir>/<dirname>/',
532
+ },
533
+ workspace_id: { type: 'string', description: 'Workspace ID (defaults to HW_WORKSPACE_ID env var)' },
534
+ username: { type: 'string', description: 'Login username (default: root)' },
535
+ exclude: {
536
+ type: 'array',
537
+ items: { type: 'string' },
538
+ description: 'Patterns to exclude from archive (default: .git, node_modules, __pycache__, .venv)',
539
+ },
540
+ extract: {
541
+ type: 'boolean',
542
+ description: 'Extract tar.gz on sandbox after upload (default: true)',
543
+ },
544
+ timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 300000)' },
545
+ },
546
+ },
547
+ },
342
548
  {
343
549
  name: 'huaweicloud_sandbox_check_user',
344
- description: 'Check if the current user has completed real-name verification and signed the required agreements. Returns realnameVerified and agreementSigned status.',
550
+ description:
551
+ '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.',
345
552
  inputSchema: {
346
553
  type: 'object',
347
554
  properties: {},
@@ -349,7 +556,8 @@ export const TOOL_DEFINITIONS = [
349
556
  },
350
557
  {
351
558
  name: 'huaweicloud_sandbox_sign_agreement',
352
- description: '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.',
559
+ description:
560
+ "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.",
353
561
  inputSchema: {
354
562
  type: 'object',
355
563
  properties: {},
@@ -357,11 +565,16 @@ export const TOOL_DEFINITIONS = [
357
565
  },
358
566
  {
359
567
  name: 'huaweicloud_sandbox_connect',
360
- 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.',
568
+ description:
569
+ '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.',
361
570
  inputSchema: {
362
571
  type: 'object',
363
572
  properties: {
364
- source: { type: 'string', description: 'Source identifier (default: WEB). Options: VSCODE, CLI, WEB, WEBVNC, WEBPTY, WEBIDE, CURSOR, etc.' },
573
+ source: {
574
+ type: 'string',
575
+ description:
576
+ 'Source identifier (default: WEB). Options: VSCODE, CLI, WEB, WEBVNC, WEBPTY, WEBIDE, CURSOR, etc.',
577
+ },
365
578
  template_id: { type: 'string', description: 'Template ID; overrides server default (only for new sandbox)' },
366
579
  flavor_id: { type: 'string', description: 'Flavor ID; overrides server default (only for new sandbox)' },
367
580
  env: { type: 'object', description: 'Environment variables to set in the sandbox (only for new sandbox)' },
@@ -381,7 +594,8 @@ export const TOOL_DEFINITIONS = [
381
594
  },
382
595
  {
383
596
  name: 'huaweicloud_sandbox_credentials',
384
- description: 'Configure temporary AK/SK for a sandbox via hdkitservice. Injects temporary credentials into the sandbox. The sandbox must be in RUNNING state.',
597
+ description:
598
+ 'Configure temporary AK/SK for a sandbox via hdkitservice. Injects temporary credentials into the sandbox. The sandbox must be in RUNNING state.',
385
599
  inputSchema: {
386
600
  type: 'object',
387
601
  properties: {
@@ -391,7 +605,6 @@ export const TOOL_DEFINITIONS = [
391
605
  },
392
606
  },
393
607
  },
394
-
395
608
  ];
396
609
 
397
610
  export async function callTool(name, args = {}) {
@@ -429,35 +642,93 @@ export async function callTool(name, args = {}) {
429
642
  return listRegions();
430
643
  case 'huaweicloud_get_regional_availability':
431
644
  return getRegionalAvailability(args.service || '', args.region || '');
432
- case 'huaweicloud_explain_error':
645
+ case 'huaweicloud_explain_error':
433
646
  return explainError(args);
434
647
  case 'huaweicloud_search_marketplace':
435
648
  return searchMarketplace(args.query || '', args.category || '');
649
+ case 'huaweicloud_get_service_icon':
650
+ return getServiceIcon(args.service || '', args.category || '');
436
651
  case 'huaweicloud_setup_obs_config':
437
652
  return setupObsConfig(args.profile);
438
653
  case 'huaweicloud_auth_status':
439
654
  return getAuthStatus(args.target || 'all');
440
655
  case 'huaweicloud_auth_sync':
441
656
  return syncAuth(args.target || 'all');
657
+ case 'huaweicloud_auth_init':
658
+ if (args.clear) {
659
+ clearRuntimeCredentials();
660
+ return { status: 'cleared', message: 'Runtime credentials cleared. Fallback to env/file.' };
661
+ }
662
+ if (!args.ak || !args.sk) {
663
+ throw new Error('ak and sk are required. Set clear=true to clear runtime credentials.');
664
+ }
665
+ setRuntimeCredentials(args.ak, args.sk, undefined, args.region);
666
+ return { status: 'ok', message: 'Runtime credentials set for this MCP session.' };
442
667
  case 'huaweicloud_sandbox_exec_with_session': {
443
- const sandboxWsId2 = args.workspace_id || DEFAULT_WORKSPACE_ID;
668
+ const sandboxWsId2 = args.workspace_id || currentWorkspaceId;
444
669
  const sandboxUser2 = args.username || 'root';
445
- const sandboxTimeout2 = args.timeout_ms || 30000;
670
+ const sandboxTimeout2 = args.timeout_ms || 120000;
446
671
  const sandboxResult2 = await execWithSession(sandboxWsId2, args.command, sandboxUser2, sandboxTimeout2);
447
672
  return { stdout: sandboxResult2.stdout, exitCode: sandboxResult2.exitCode };
448
673
  }
449
- case 'huaweicloud_sandbox_close_session': {
450
- const sandboxWsId3 = args.workspace_id || DEFAULT_WORKSPACE_ID;
674
+ case 'huaweicloud_sandbox_exec_one_shot': {
675
+ const sandboxWsId3 = args.workspace_id || currentWorkspaceId;
451
676
  const sandboxUser3 = args.username || 'root';
452
- const closed = await closeSession(sandboxWsId3, sandboxUser3);
677
+ const sandboxTimeout3 = args.timeout_ms || 120000;
678
+ const sandboxResult3 = await execOneShot(sandboxWsId3, args.command, sandboxUser3, sandboxTimeout3);
679
+ return { stdout: sandboxResult3.stdout, exitCode: sandboxResult3.exitCode };
680
+ }
681
+ case 'huaweicloud_sandbox_close_session': {
682
+ const sandboxWsId4 = args.workspace_id || currentWorkspaceId;
683
+ const sandboxUser4 = args.username || 'root';
684
+ const closed = await closeSession(sandboxWsId4, sandboxUser4);
453
685
  return closed ? 'ok' : 'not_connected';
454
686
  }
687
+ case 'huaweicloud_sandbox_upload_file': {
688
+ if (!args.local_path || !args.remote_path) {
689
+ throw new Error('local_path and remote_path are required.');
690
+ }
691
+ const sandboxWsId5 = args.workspace_id || currentWorkspaceId;
692
+ const sandboxUser5 = args.username || 'root';
693
+ const sandboxTimeout5 = args.timeout_ms || 120000;
694
+ return await uploadFileWithSession(
695
+ sandboxWsId5,
696
+ args.local_path,
697
+ args.remote_path,
698
+ sandboxUser5,
699
+ sandboxTimeout5,
700
+ );
701
+ }
702
+ case 'huaweicloud_sandbox_upload_project': {
703
+ if (!args.local_dir) {
704
+ throw new Error('local_dir is required.');
705
+ }
706
+ const sandboxWsId6 = args.workspace_id || currentWorkspaceId;
707
+ const sandboxUser6 = args.username || 'root';
708
+ const sandboxTimeout6 = args.timeout_ms || 120000;
709
+ return await uploadProjectWithSession(
710
+ sandboxWsId6,
711
+ args.local_dir,
712
+ args.remote_dir,
713
+ sandboxUser6,
714
+ sandboxTimeout6,
715
+ {
716
+ exclude: args.exclude,
717
+ extract: args.extract,
718
+ },
719
+ );
720
+ }
455
721
  case 'huaweicloud_sandbox_check_user':
456
722
  return await hdkitCheckUser();
457
723
  case 'huaweicloud_sandbox_sign_agreement':
458
724
  return await hdkitSignAgreement();
459
- case 'huaweicloud_sandbox_connect':
460
- return await hdkitConnect(args);
725
+ case 'huaweicloud_sandbox_connect': {
726
+ const connectResult = await hdkitConnect(args);
727
+ if (connectResult?.dev_stage_id) {
728
+ setWorkspaceId(connectResult.dev_stage_id);
729
+ }
730
+ return connectResult;
731
+ }
461
732
  case 'huaweicloud_sandbox_credentials':
462
733
  return await hdkitCredentials(args.session_id, args.dev_stage_id, args.enable_sts !== false);
463
734
  default:
@@ -470,11 +741,12 @@ function hookResult(result) {
470
741
  ok: result.decision !== 'deny',
471
742
  decision: result.decision,
472
743
  findings: result.findings,
473
- nextStep: result.decision === 'deny'
474
- ? 'Revise the command, artifact, or deployment plan before execution.'
475
- : result.decision === 'warn'
476
- ? 'Review the warnings with the user before proceeding.'
477
- : 'No Huawei Cloud hook risk rule matched.',
744
+ nextStep:
745
+ result.decision === 'deny'
746
+ ? 'Revise the command, artifact, or deployment plan before execution.'
747
+ : result.decision === 'warn'
748
+ ? 'Review the warnings with the user before proceeding.'
749
+ : 'No Huawei Cloud hook risk rule matched.',
478
750
  };
479
751
  }
480
752
 
@@ -495,7 +767,8 @@ export async function runVersionCheck(options = {}) {
495
767
  : isSpawnError
496
768
  ? 'hcloud executable not found. Set HCLOUD_BIN to the full hcloud path, or install KooCLI: npx huaweicloud-devkit install-hcloud. Then restart the agent.'
497
769
  : 'Install Huawei Cloud KooCLI: npx huaweicloud-devkit install-hcloud. Configure credentials outside the agent conversation.',
498
- 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.',
770
+ authHint:
771
+ '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.',
499
772
  };
500
773
  }
501
774
 
@@ -514,7 +787,8 @@ async function showProfileRedacted(profile) {
514
787
  ok: false,
515
788
  blockedByPolicy: true,
516
789
  reason: result.reason,
517
- safeAlternative: 'Use huaweicloud_show_profile_redacted so profile output is returned through the redaction pipeline.',
790
+ safeAlternative:
791
+ 'Use huaweicloud_show_profile_redacted so profile output is returned through the redaction pipeline.',
518
792
  };
519
793
  }
520
794
  return {
@@ -556,7 +830,12 @@ async function setupObsConfig(profile) {
556
830
  async function setupObsConfigFromHcloud(profile) {
557
831
  const obsConfigPath = join(homedir(), '.obsutilconfig');
558
832
  if (existsSync(obsConfigPath)) {
559
- return { ok: true, existed: true, path: obsConfigPath, note: 'OBS config already exists. Delete ~/.obsutilconfig first if you need to re-sync.' };
833
+ return {
834
+ ok: true,
835
+ existed: true,
836
+ path: obsConfigPath,
837
+ note: 'OBS config already exists. Delete ~/.obsutilconfig first if you need to re-sync.',
838
+ };
560
839
  }
561
840
 
562
841
  const args = ['configure', 'show'];
@@ -572,9 +851,9 @@ async function setupObsConfigFromHcloud(profile) {
572
851
  };
573
852
  }
574
853
 
575
- let accessKeyId = '';
576
- let secretAccessKey = '';
577
- let region = '';
854
+ let accessKeyId;
855
+ let secretAccessKey;
856
+ let region;
578
857
 
579
858
  try {
580
859
  const parsed = typeof result.stdout === 'string' ? JSON.parse(result.stdout) : result.stdout;
@@ -635,12 +914,16 @@ async function setupObsConfigFromHcloud(profile) {
635
914
  const SERVICE_EXAMPLES = {
636
915
  ECS: { list: 'ECS ListServersDetails', create: 'ECS CreateServers', show: 'IMS GlanceShowImage' },
637
916
  VPC: { list: 'VPC ListVpcs', create: 'VPC CreateVpc', show: 'VPC ShowVpc' },
638
- FunctionGraph: { list: 'FunctionGraph ListFunctions', create: 'FunctionGraph CreateFunction', show: 'FunctionGraph ShowFunctionConfig' },
917
+ FUNCTIONGRAPH: {
918
+ list: 'FunctionGraph ListFunctions',
919
+ create: 'FunctionGraph CreateFunction',
920
+ show: 'FunctionGraph ShowFunctionConfig',
921
+ },
639
922
  APIG: { list: 'APIG ListInstancesV2', create: 'APIG CreateInstanceV2', show: 'APIG ShowDetailsOfInstanceV2' },
640
923
  OBS: { list: 'OBS ls', create: 'OBS mb obs://<bucket>', show: 'OBS stat obs://<bucket>/<key>' },
641
924
  RDS: { list: 'RDS ListInstances', create: 'RDS CreateInstance', show: 'RDS ShowInstance' },
642
925
  CES: { list: 'CES ListAlarms', create: 'CES CreateAlarm', show: 'CES ListMetrics' },
643
- GaussDB: { list: 'GaussDB ListInstances', create: 'GaussDB CreateInstance', show: 'GaussDB ShowInstance' },
926
+ GAUSSDB: { list: 'GaussDB ListInstances', create: 'GaussDB CreateInstance', show: 'GaussDB ShowInstance' },
644
927
  DDS: { list: 'DDS ListInstances', create: 'DDS CreateInstance', show: 'DDS ShowInstance' },
645
928
  DCS: { list: 'DCS ListInstances', create: 'DCS CreateInstance', show: 'DCS ShowInstance' },
646
929
  };
@@ -667,7 +950,9 @@ async function listOperations(service, options = {}) {
667
950
  service: serviceName,
668
951
  command: isObs ? 'hcloud obs help' : `hcloud ${svc} --help`,
669
952
  selectionRule: 'Use this help text to select the exact KooCLI operation name before planning any service command.',
670
- examples: SERVICE_EXAMPLES[serviceName.toUpperCase()] || { note: `No cached examples for ${serviceName}. Use the help text above to discover available operations.` },
953
+ examples: SERVICE_EXAMPLES[serviceName.toUpperCase()] || {
954
+ note: `No cached examples for ${serviceName}. Use the help text above to discover available operations.`,
955
+ },
671
956
  result,
672
957
  };
673
958
  }
@@ -696,32 +981,110 @@ async function runApprovedCommand(args = {}) {
696
981
  function serviceCatalog(intent = '') {
697
982
  const it = String(intent).toLowerCase();
698
983
  const routeMap = [
699
- { keywords: ['ecs', 'server', 'vm', 'instance', 'compute', 'flavor', 'image'], skills: ['huawei-ecs'], services: ['ECS'] },
700
- { keywords: ['vpc', 'subnet', 'network', 'security group', 'eip', 'nat', 'vpn', 'bandwidth'], skills: ['huawei-vpc'], services: ['VPC', 'EIP'] },
701
- { keywords: ['obs', 'bucket', 'storage', 'object', 'static website', 'static site', 'hosting'], skills: ['huawei-obs'], services: ['OBS'] },
702
- { keywords: ['functiongraph', 'serverless', 'function', 'lambda', 'trigger', 'faas'], skills: ['huawei-functiongraph'], services: ['FunctionGraph'] },
703
- { keywords: ['cce', 'kubernetes', 'k8s', 'container', 'cluster', 'node pool', 'swr', 'docker', 'image registry'], skills: ['huawei-cce'], services: ['CCE', 'SWR'] },
984
+ {
985
+ keywords: ['ecs', 'server', 'vm', 'instance', 'compute', 'flavor', 'image'],
986
+ skills: ['huawei-ecs'],
987
+ services: ['ECS'],
988
+ },
989
+ {
990
+ keywords: ['vpc', 'subnet', 'network', 'security group', 'eip', 'nat', 'vpn', 'bandwidth'],
991
+ skills: ['huawei-vpc'],
992
+ services: ['VPC', 'EIP'],
993
+ },
994
+ {
995
+ keywords: ['obs', 'bucket', 'storage', 'object', 'static website', 'static site', 'hosting'],
996
+ skills: ['huawei-obs'],
997
+ services: ['OBS'],
998
+ },
999
+ {
1000
+ keywords: ['functiongraph', 'serverless', 'function', 'lambda', 'trigger', 'faas'],
1001
+ skills: ['huawei-functiongraph'],
1002
+ services: ['FunctionGraph'],
1003
+ },
1004
+ {
1005
+ keywords: ['cce', 'kubernetes', 'k8s', 'container', 'cluster', 'node pool', 'swr', 'docker', 'image registry'],
1006
+ skills: ['huawei-cce'],
1007
+ services: ['CCE', 'SWR'],
1008
+ },
704
1009
  { keywords: ['apig', 'api gateway', 'publish', 'throttle'], skills: ['huawei-apig'], services: ['APIG'] },
705
1010
  { keywords: ['rds', 'mysql', 'postgresql', 'database', 'db'], skills: ['huawei-rds'], services: ['RDS'] },
706
- { keywords: ['gaussdb', 'distributed', 'sharding', 'opengauss'], skills: ['huawei-gaussdb'], services: ['GaussDB'] },
707
- { keywords: ['iam', 'permission', 'policy', 'role', 'user', 'ak/sk', 'access key', 'agency'], skills: ['huawei-iam'], services: ['IAM'] },
708
- { keywords: ['dew', 'secret', 'kms', 'encrypt', 'decrypt', 'certificate', 'csms'], skills: ['huawei-dew'], services: ['CSMS', 'KMS'] },
709
- { keywords: ['modelarts', 'ai', 'model', 'training', 'inference', 'machine learning'], skills: ['huawei-modelarts'], services: ['ModelArts'] },
710
- { keywords: ['billing', 'cost', 'bill', 'budget', 'expense', 'bss'], skills: ['huawei-billing'], services: ['BSS'] },
711
- { keywords: ['waf', 'aad', 'ddos', 'firewall', 'web protection'], skills: ['huawei-waf-aad'], services: ['WAF', 'AAD'] },
712
- { keywords: ['smn', 'dms', 'notification', 'message', 'kafka', 'rabbitmq'], skills: ['huawei-smn-dms'], services: ['SMN', 'DMS'] },
713
- { keywords: ['ces', 'monitor', 'alarm', 'metric', 'dashboard', 'cloud eye'], skills: ['huawei-cloud-eye'], services: ['CES'] },
1011
+ {
1012
+ keywords: ['gaussdb', 'distributed', 'sharding', 'opengauss'],
1013
+ skills: ['huawei-gaussdb'],
1014
+ services: ['GaussDB'],
1015
+ },
1016
+ {
1017
+ keywords: ['iam', 'permission', 'policy', 'role', 'user', 'ak/sk', 'access key', 'agency'],
1018
+ skills: ['huawei-iam'],
1019
+ services: ['IAM'],
1020
+ },
1021
+ {
1022
+ keywords: ['dew', 'secret', 'kms', 'encrypt', 'decrypt', 'certificate', 'csms'],
1023
+ skills: ['huawei-dew'],
1024
+ services: ['CSMS', 'KMS'],
1025
+ },
1026
+ {
1027
+ keywords: ['modelarts', 'ai', 'model', 'training', 'inference', 'machine learning'],
1028
+ skills: ['huawei-modelarts'],
1029
+ services: ['ModelArts'],
1030
+ },
1031
+ {
1032
+ keywords: ['billing', 'cost', 'bill', 'budget', 'expense', 'bss'],
1033
+ skills: ['huawei-billing'],
1034
+ services: ['BSS'],
1035
+ },
1036
+ {
1037
+ keywords: ['waf', 'aad', 'ddos', 'firewall', 'web protection'],
1038
+ skills: ['huawei-waf-aad'],
1039
+ services: ['WAF', 'AAD'],
1040
+ },
1041
+ {
1042
+ keywords: ['smn', 'dms', 'notification', 'message', 'kafka', 'rabbitmq'],
1043
+ skills: ['huawei-smn-dms'],
1044
+ services: ['SMN', 'DMS'],
1045
+ },
1046
+ {
1047
+ keywords: ['ces', 'monitor', 'alarm', 'metric', 'dashboard', 'cloud eye'],
1048
+ skills: ['huawei-cloud-eye'],
1049
+ services: ['CES'],
1050
+ },
714
1051
  { keywords: ['cts', 'audit', 'trace', 'tracker'], skills: ['huawei-cts'], services: ['CTS'] },
715
1052
  { keywords: ['cbr', 'backup', 'restore', 'vault', 'snapshot'], skills: ['huawei-cbr'], services: ['CBR'] },
716
- { keywords: ['deployment', 'deploy', 'ci/cd', 'pipeline', 'release'], skills: ['huawei-deployment'], services: ['CloudDeploy'] },
717
- { keywords: ['sandbox', 'devstation', 'workspace', 'terminal', 'preview', 'hwlink', 'website', 'web app', 'webapp', 'hosting', '网站', '网页', '静态'], skills: ['huawei-sandbox'], services: ['Sandbox', 'DevStation'] },
718
- { keywords: ['dds', 'dcs', 'mongodb', 'redis', 'memcached', 'cache', 'document db'], skills: ['huawei-dds-dcs'], services: ['DDS', 'DCS'] },
719
- ];
1053
+ {
1054
+ keywords: ['deployment', 'deploy', 'ci/cd', 'pipeline', 'release'],
1055
+ skills: ['huawei-deployment'],
1056
+ services: ['CloudDeploy'],
1057
+ },
1058
+ {
1059
+ keywords: [
1060
+ 'sandbox',
1061
+ 'devstation',
1062
+ 'workspace',
1063
+ 'terminal',
1064
+ 'preview',
1065
+ 'hwlink',
1066
+ 'website',
1067
+ 'web app',
1068
+ 'webapp',
1069
+ 'hosting',
1070
+ '网站',
1071
+ '网页',
1072
+ '静态',
1073
+ ],
1074
+ skills: ['huawei-sandbox'],
1075
+ services: ['Sandbox', 'DevStation'],
1076
+ },
1077
+ {
1078
+ keywords: ['dds', 'dcs', 'mongodb', 'redis', 'memcached', 'cache', 'document db'],
1079
+ skills: ['huawei-dds-dcs'],
1080
+ services: ['DDS', 'DCS'],
1081
+ },
1082
+ ];
720
1083
  const matched = [];
721
1084
  const tokens = it.split(/[\s,./-]+/).filter((t) => t.length > 0);
722
1085
  const cjk = /[\u4e00-\u9fff]/;
723
1086
  for (const route of routeMap) {
724
- if (route.keywords.some((kw) => (kw.includes(' ') || cjk.test(kw)) ? it.includes(kw) : tokens.includes(kw))) {
1087
+ if (route.keywords.some((kw) => (kw.includes(' ') || cjk.test(kw) ? it.includes(kw) : tokens.includes(kw)))) {
725
1088
  matched.push(route);
726
1089
  }
727
1090
  }
@@ -740,7 +1103,9 @@ function serviceCatalog(intent = '') {
740
1103
  return {
741
1104
  intent,
742
1105
  recommendedSkills: recommendedSkills.length ? recommendedSkills : ['Use huaweicloud-core to route intent.'],
743
- recommendedServices: recommendedServices.length ? recommendedServices : ['Run hcloud --help to list available services.'],
1106
+ recommendedServices: recommendedServices.length
1107
+ ? recommendedServices
1108
+ : ['Run hcloud --help to list available services.'],
744
1109
  capabilityOrder: [
745
1110
  'Huawei Cloud Skills for task-specific workflows and examples',
746
1111
  'KooCLI hcloud for local authenticated operations and quick inspection',
@@ -766,32 +1131,38 @@ function explainError({ service = 'unknown', errorCode = '', message = '', reque
766
1131
  const svc = String(service).toLowerCase();
767
1132
 
768
1133
  const SERVICE_ALIASES = {
769
- 'functiongraph': 'FSS',
770
- 'fgs': 'FSS',
1134
+ functiongraph: 'FSS',
1135
+ fgs: 'FSS',
771
1136
  };
772
1137
  const patternKey = SERVICE_ALIASES[svc] || service;
773
1138
 
774
1139
  const hwErrorPatterns = {
775
1140
  OBS: {
776
- 'InvalidAccessKeyId': 'OBS uses AK/SK directly (not IAM tokens). Verify AK/SK validity, OBS endpoint, and OBS permissions.',
777
- '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.',
1141
+ InvalidAccessKeyId:
1142
+ 'OBS uses AK/SK directly (not IAM tokens). Verify AK/SK validity, OBS endpoint, and OBS permissions.',
1143
+ UserRestricted:
1144
+ '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.',
778
1145
  },
779
1146
  APIG: {
780
1147
  'APIC.7241': 'The enterprise_project_id is required for enterprise accounts. Add --enterprise_project_id=0.',
781
- 'APIC.7242': 'The EIP binding method depends on loadbalancer_provider. Use AddIngressEipV2 for elb, AddEipV2 for lvs.',
1148
+ 'APIC.7242':
1149
+ 'The EIP binding method depends on loadbalancer_provider. Use AddIngressEipV2 for elb, AddEipV2 for lvs.',
782
1150
  'APIC.7256': 'Bandwidth minimum is 5 Mbps. Use --bandwidth_size=5 or higher.',
783
1151
  'APIC.7310': 'available_zone_ids must use AZ codes (e.g. ap-southeast-3a), NOT UUIDs from ListAvailableZonesV2.',
784
1152
  },
785
1153
  FSS: {
786
- 'FSS.0403': 'Missing FunctionGraph IAM permissions. Attach FunctionGraph FullAccess role or grant specific actions.',
1154
+ 'FSS.0403':
1155
+ 'Missing FunctionGraph IAM permissions. Attach FunctionGraph FullAccess role or grant specific actions.',
787
1156
  'FSS.1078': '--code_filename is filename-only (no path). cd to the file directory before running the command.',
788
- 'FSS.1417': 'event_data field validation failed. Check parameter format: use dotted key=value, verify required hidden-optional fields.',
1157
+ 'FSS.1417':
1158
+ 'event_data field validation failed. Check parameter format: use dotted key=value, verify required hidden-optional fields.',
789
1159
  },
790
1160
  VPC: {
791
1161
  'VPC.0301': 'Bandwidth name is required for PER type EIPs, even though --help marks it optional.',
792
1162
  },
793
1163
  APIGW: {
794
- '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.',
1164
+ 'APIGW.0802':
1165
+ '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.',
795
1166
  },
796
1167
  };
797
1168
  if (hwErrorPatterns[patternKey] && hwErrorPatterns[patternKey][errorCode]) {
@@ -806,19 +1177,30 @@ function explainError({ service = 'unknown', errorCode = '', message = '', reque
806
1177
 
807
1178
  if (/auth|token|credential|ak|sk|401|403|unauthorized|forbidden|Incorrect IAM/i.test(combined)) {
808
1179
  if (svc === 'obs') {
809
- 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.');
1180
+ suggestions.push(
1181
+ '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.',
1182
+ );
810
1183
  } else {
811
1184
  suggestions.push('Check KooCLI profile, region, project_id, and IAM permissions without printing secrets.');
812
1185
  }
813
1186
  }
814
1187
  if (/APIGW\.(\d+)/i.test(errorCode)) {
815
- 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.'));
1188
+ suggestions.push(
1189
+ 'APIGW.' +
1190
+ (errorCode.match(/APIGW\.(\d+)/i) || [])[1] +
1191
+ ': API Gateway layer error. ' +
1192
+ (errorCode === 'APIGW.0802'
1193
+ ? 'IAM user has no region permissions — check IAM console → User → Permissions → add target region.'
1194
+ : 'Verify the API request, region endpoint, and IAM permissions.'),
1195
+ );
816
1196
  }
817
1197
  if (/region|endpoint|project/i.test(combined)) {
818
1198
  suggestions.push('Confirm the service endpoint, region, and project_id match the target resource.');
819
1199
  }
820
1200
  if (/quota|limit|insufficient|reach the limit/i.test(combined)) {
821
- suggestions.push('Check quota and resource limits before retrying a create or scale operation. Consider switching accounts or requesting a quota increase.');
1201
+ suggestions.push(
1202
+ 'Check quota and resource limits before retrying a create or scale operation. Consider switching accounts or requesting a quota increase.',
1203
+ );
822
1204
  }
823
1205
  if (/not.?found|404/i.test(combined)) {
824
1206
  if (/list.?regions/i.test(svc)) {
@@ -828,7 +1210,9 @@ function explainError({ service = 'unknown', errorCode = '', message = '', reque
828
1210
  }
829
1211
  }
830
1212
  if (!suggestions.length) {
831
- suggestions.push('Collect service name, operation, region, project_id, request_id, and the full redacted error message.');
1213
+ suggestions.push(
1214
+ 'Collect service name, operation, region, project_id, request_id, and the full redacted error message.',
1215
+ );
832
1216
  }
833
1217
 
834
1218
  if (requestId) {
@@ -836,8 +1220,7 @@ function explainError({ service = 'unknown', errorCode = '', message = '', reque
836
1220
  }
837
1221
 
838
1222
  const uniqueSuggestions = suggestions.filter((s, i, arr) => {
839
- return !arr.slice(0, i).some((prev) =>
840
- prev.substring(0, 50).toLowerCase() === s.substring(0, 50).toLowerCase());
1223
+ return !arr.slice(0, i).some((prev) => prev.substring(0, 50).toLowerCase() === s.substring(0, 50).toLowerCase());
841
1224
  });
842
1225
 
843
1226
  return {
@@ -854,9 +1237,7 @@ async function searchDocs(query, topic = 'all') {
854
1237
  const results = [];
855
1238
  try {
856
1239
  if (existsSync(SKILLS_ROOT)) {
857
- const dirs = readdirSync(SKILLS_ROOT, { withFileTypes: true })
858
- .filter((d) => d.isDirectory())
859
- .map((d) => d.name);
1240
+ const dirs = listSkillDirs(SKILLS_ROOT);
860
1241
  for (const dir of dirs) {
861
1242
  const skillPath = join(SKILLS_ROOT, dir, 'SKILL.md');
862
1243
  if (!existsSync(skillPath)) continue;
@@ -879,11 +1260,16 @@ async function searchDocs(query, topic = 'all') {
879
1260
  const nameLower = name.toLowerCase();
880
1261
  const contentLower = content.toLowerCase();
881
1262
  const relevance = tokens.reduce((score, token) => {
882
- return score + (descLower.includes(token) ? 3 : 0) + (nameLower.includes(token) ? 2 : 0) + (contentLower.includes(token) ? 1 : 0);
1263
+ return (
1264
+ score +
1265
+ (descLower.includes(token) ? 3 : 0) +
1266
+ (nameLower.includes(token) ? 2 : 0) +
1267
+ (contentLower.includes(token) ? 1 : 0)
1268
+ );
883
1269
  }, 0);
884
1270
  if (relevance > 0) {
885
1271
  results.push({
886
- source: "skills/" + dir + "/SKILL.md",
1272
+ source: 'skills/' + dir + '/SKILL.md',
887
1273
  name,
888
1274
  snippet: description.substring(0, 200),
889
1275
  relevance,
@@ -903,9 +1289,7 @@ async function retrieveSkill(name) {
903
1289
  if (!skillName) return { ok: false, error: 'Skill name is required.' };
904
1290
  const skillPath = join(SKILLS_ROOT, skillName, 'SKILL.md');
905
1291
  if (!existsSync(skillPath)) {
906
- const dirs = existsSync(SKILLS_ROOT)
907
- ? readdirSync(SKILLS_ROOT, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name)
908
- : [];
1292
+ const dirs = listSkillDirs(SKILLS_ROOT);
909
1293
  return { ok: false, error: 'Skill "' + skillName + '" not found. Available: ' + dirs.join(', ') };
910
1294
  }
911
1295
  const content = readFileSync(skillPath, 'utf8');
@@ -918,7 +1302,8 @@ async function retrieveSkill(name) {
918
1302
  });
919
1303
  }
920
1304
  const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
921
- let version = 1, description = '';
1305
+ let version = 1,
1306
+ description = '';
922
1307
  if (frontmatter) {
923
1308
  const fm = frontmatter[1];
924
1309
  const vm = fm.match(/^version:\s*(.+)$/m);
@@ -930,8 +1315,10 @@ async function retrieveSkill(name) {
930
1315
  }
931
1316
 
932
1317
  async function listRegions() {
933
- const result = await runHcloud(['IAM', 'KeystoneListRegions'], { timeoutMs: 30000, maxRetries: 0 })
934
- .catch((err) => ({ ok: false, error: err.message }));
1318
+ const result = await runHcloud(['IAM', 'KeystoneListRegions'], { timeoutMs: 30000, maxRetries: 0 }).catch((err) => ({
1319
+ ok: false,
1320
+ error: err.message,
1321
+ }));
935
1322
  if (!result.ok) {
936
1323
  return {
937
1324
  ok: false,
@@ -951,7 +1338,7 @@ async function listRegions() {
951
1338
  note: 'hcloud unavailable. Showing static region list. For the complete list, visit the fallback URL.',
952
1339
  };
953
1340
  }
954
- let regions = [];
1341
+ let regions;
955
1342
  try {
956
1343
  const parsed = typeof result.stdout === 'string' ? JSON.parse(result.stdout) : result.stdout;
957
1344
  const rawRegions = parsed.regions || [];
@@ -969,37 +1356,128 @@ async function listRegions() {
969
1356
  }
970
1357
 
971
1358
  async function getRegionalAvailability(service, region) {
972
- const svc = String(service || '').toLowerCase().trim();
973
- const reg = String(region || '').toLowerCase().trim();
1359
+ const svc = String(service || '')
1360
+ .toLowerCase()
1361
+ .trim();
1362
+ const reg = String(region || '')
1363
+ .toLowerCase()
1364
+ .trim();
974
1365
  if (!svc || !reg) return { ok: false, error: 'Both service and region are required.' };
975
1366
  const known = {
976
- 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'],
977
- 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'],
978
- 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'],
1367
+ ecs: [
1368
+ 'cn-south-1',
1369
+ 'cn-north-4',
1370
+ 'cn-north-1',
1371
+ 'cn-east-3',
1372
+ 'cn-east-2',
1373
+ 'ap-southeast-3',
1374
+ 'ap-southeast-2',
1375
+ 'ap-southeast-1',
1376
+ 'ap-southeast-4',
1377
+ 'af-south-1',
1378
+ 'tr-west-1',
1379
+ 'sa-brazil-1',
1380
+ 'la-north-2',
1381
+ 'na-mexico-1',
1382
+ 'me-east-1',
1383
+ ],
1384
+ obs: [
1385
+ 'cn-south-1',
1386
+ 'cn-north-4',
1387
+ 'cn-north-1',
1388
+ 'cn-east-3',
1389
+ 'cn-east-2',
1390
+ 'ap-southeast-3',
1391
+ 'ap-southeast-2',
1392
+ 'ap-southeast-1',
1393
+ 'af-south-1',
1394
+ ],
1395
+ vpc: [
1396
+ 'cn-south-1',
1397
+ 'cn-north-4',
1398
+ 'cn-north-1',
1399
+ 'cn-east-3',
1400
+ 'cn-east-2',
1401
+ 'ap-southeast-3',
1402
+ 'ap-southeast-2',
1403
+ 'ap-southeast-1',
1404
+ 'ap-southeast-4',
1405
+ 'af-south-1',
1406
+ 'tr-west-1',
1407
+ 'sa-brazil-1',
1408
+ 'la-north-2',
1409
+ 'me-east-1',
1410
+ ],
979
1411
  iam: ['global'],
980
- 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'],
981
- gaussdb: ['cn-south-1','cn-north-4','cn-east-3'],
982
- 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'],
983
- modelarts: ['cn-south-1','cn-north-4','cn-east-3'],
984
- 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'],
985
- dew: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
986
- smn: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
987
- ces: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
988
- cts: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
989
- apig: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
990
- cbr: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
991
- dds: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
992
- dcs: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
1412
+ rds: [
1413
+ 'cn-south-1',
1414
+ 'cn-north-4',
1415
+ 'cn-north-1',
1416
+ 'cn-east-3',
1417
+ 'cn-east-2',
1418
+ 'ap-southeast-3',
1419
+ 'ap-southeast-2',
1420
+ 'ap-southeast-1',
1421
+ ],
1422
+ gaussdb: ['cn-south-1', 'cn-north-4', 'cn-east-3'],
1423
+ cce: [
1424
+ 'cn-south-1',
1425
+ 'cn-north-4',
1426
+ 'cn-north-1',
1427
+ 'cn-east-3',
1428
+ 'cn-east-2',
1429
+ 'ap-southeast-3',
1430
+ 'ap-southeast-2',
1431
+ 'ap-southeast-1',
1432
+ ],
1433
+ modelarts: ['cn-south-1', 'cn-north-4', 'cn-east-3'],
1434
+ functiongraph: [
1435
+ 'cn-south-1',
1436
+ 'cn-north-4',
1437
+ 'cn-north-1',
1438
+ 'cn-east-3',
1439
+ 'cn-east-2',
1440
+ 'ap-southeast-3',
1441
+ 'ap-southeast-2',
1442
+ 'ap-southeast-1',
1443
+ ],
1444
+ dew: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1445
+ smn: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1446
+ ces: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1447
+ cts: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1448
+ apig: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1449
+ cbr: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1450
+ dds: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
1451
+ dcs: ['cn-south-1', 'cn-north-4', 'cn-east-3', 'ap-southeast-3'],
993
1452
  };
994
- 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.' };
1453
+ if (!known[svc])
1454
+ return {
1455
+ ok: false,
1456
+ service: svc,
1457
+ region: reg,
1458
+ available: false,
1459
+ note:
1460
+ 'Service ' +
1461
+ svc +
1462
+ ' is not in the regional availability cache. Run hcloud ' +
1463
+ svc.toUpperCase() +
1464
+ ' --help to verify, or check https://developer.huaweicloud.com/endpoint.',
1465
+ };
995
1466
  const available = known[svc].includes(reg) || known[svc].includes('global');
996
1467
  return {
997
- ok: true, service: svc, region: reg, available,
1468
+ ok: true,
1469
+ service: svc,
1470
+ region: reg,
1471
+ available,
998
1472
  note: available
999
1473
  ? svc + ' is available in ' + reg + '.'
1000
- : svc + ' availability in ' + reg + ' could not be confirmed. Verify at https://developer.huaweicloud.com/endpoint.',
1474
+ : svc +
1475
+ ' availability in ' +
1476
+ reg +
1477
+ ' could not be confirmed. Verify at https://developer.huaweicloud.com/endpoint.',
1001
1478
  sourcedFrom: 'static cache, update via npm package upgrade',
1002
- 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.',
1479
+ disclaimer:
1480
+ '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.',
1003
1481
  };
1004
1482
  }
1005
1483