huaweicloud-devkit 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +20 -0
- package/LICENSE +201 -0
- package/README.md +148 -0
- package/bin/setup.cjs +8 -0
- package/integrations/opencode/commands/huaweicloud-doctor.md +14 -0
- package/integrations/opencode/opencode.json +14 -0
- package/integrations/opencode/skills/huaweicloud-core/SKILL.md +30 -0
- package/package.json +43 -0
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +44 -0
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +43 -0
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +44 -0
- package/plugins/huaweicloud-core/.mcp.json +13 -0
- package/plugins/huaweicloud-core/hooks/hooks.json +26 -0
- package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +88 -0
- package/plugins/huaweicloud-core/safety/policy.json +86 -0
- package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +144 -0
- package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +48 -0
- package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +50 -0
- package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +101 -0
- package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +73 -0
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +190 -0
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +124 -0
- package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +55 -0
- package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +70 -0
- package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +56 -0
- package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +97 -0
- package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +29 -0
- package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +23 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +131 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +92 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/evs.md +31 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +28 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +52 -0
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +132 -0
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +58 -0
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +71 -0
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/deploy-workflow.md +119 -0
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +142 -0
- package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +56 -0
- package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +65 -0
- package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +111 -0
- package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +122 -0
- package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +56 -0
- package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +65 -0
- package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +118 -0
- package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +49 -0
- package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +26 -0
- package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +100 -0
- package/plugins/huaweicloud-core/skills/huawei-obs/references/static-website.md +56 -0
- package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +181 -0
- package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +69 -0
- package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +77 -0
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +52 -0
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +59 -0
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +50 -0
- package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +63 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +56 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +4 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +47 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +4 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +125 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +4 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +94 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +4 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +20 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +28 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +46 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +4 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +83 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +4 -0
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +265 -0
- package/plugins/huaweicloud-core/src/mcp-server.mjs +93 -0
- package/plugins/huaweicloud-core/src/safety-policy.mjs +289 -0
- package/plugins/huaweicloud-core/src/search-market.mjs +169 -0
- package/plugins/huaweicloud-core/src/setup-cli.mjs +628 -0
- package/plugins/huaweicloud-core/src/tools.mjs +672 -0
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
import { planHcloudCommand, runHcloud } from './hcloud-cli.mjs';
|
|
2
|
+
import { classifyTextCommand, redactSecrets } from './safety-policy.mjs';
|
|
3
|
+
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { searchMarketplace } from './search-market.mjs';
|
|
8
|
+
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const SKILLS_ROOT_DEV = join(__dirname, '..', 'skills');
|
|
11
|
+
function opencodeSkillsDir() {
|
|
12
|
+
const home = homedir();
|
|
13
|
+
return join(home, '.config', 'opencode', 'skills');
|
|
14
|
+
}
|
|
15
|
+
function resolveSkillsRoot() {
|
|
16
|
+
if (existsSync(SKILLS_ROOT_DEV)) return SKILLS_ROOT_DEV;
|
|
17
|
+
if (existsSync(opencodeSkillsDir())) return opencodeSkillsDir();
|
|
18
|
+
return SKILLS_ROOT_DEV;
|
|
19
|
+
}
|
|
20
|
+
const SKILLS_ROOT = resolveSkillsRoot();
|
|
21
|
+
|
|
22
|
+
export const TOOL_DEFINITIONS = [
|
|
23
|
+
{
|
|
24
|
+
name: 'huaweicloud_check_cli',
|
|
25
|
+
description: 'Check whether Huawei Cloud KooCLI hcloud is installed. Returns redacted output.',
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'huaweicloud_plan_cli_command',
|
|
33
|
+
description: 'Classify and plan a Huawei Cloud hcloud command without executing it.',
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
required: ['args'],
|
|
37
|
+
properties: {
|
|
38
|
+
args: {
|
|
39
|
+
type: 'array',
|
|
40
|
+
items: { type: 'string' },
|
|
41
|
+
description: 'hcloud arguments, excluding the hcloud executable.',
|
|
42
|
+
},
|
|
43
|
+
allowWrites: {
|
|
44
|
+
type: 'boolean',
|
|
45
|
+
description: 'Only true after explicit user approval for this exact operation.',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'huaweicloud_run_readonly_command',
|
|
52
|
+
description: 'Run a read-only hcloud command through the toolkit safety policy and redact output.',
|
|
53
|
+
inputSchema: {
|
|
54
|
+
type: 'object',
|
|
55
|
+
required: ['args'],
|
|
56
|
+
properties: {
|
|
57
|
+
args: {
|
|
58
|
+
type: 'array',
|
|
59
|
+
items: { type: 'string' },
|
|
60
|
+
},
|
|
61
|
+
timeoutMs: {
|
|
62
|
+
type: 'number',
|
|
63
|
+
description: 'Optional timeout in milliseconds. Defaults to 60000.',
|
|
64
|
+
},
|
|
65
|
+
maxRetries: {
|
|
66
|
+
type: 'number',
|
|
67
|
+
description: 'Optional retry count for transient network errors. Defaults to 1.',
|
|
68
|
+
},
|
|
69
|
+
cwd: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
description: 'Optional working directory for the hcloud process.',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'huaweicloud_list_operations',
|
|
78
|
+
description: 'List KooCLI operations for a Huawei Cloud service by running local/read-only hcloud <Service> --help.',
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
required: ['service'],
|
|
82
|
+
properties: {
|
|
83
|
+
service: {
|
|
84
|
+
type: 'string',
|
|
85
|
+
description: 'KooCLI service name, such as ECS, VPC, IMS, OBS, RDS, or CDN.',
|
|
86
|
+
},
|
|
87
|
+
timeoutMs: {
|
|
88
|
+
type: 'number',
|
|
89
|
+
description: 'Optional timeout in milliseconds. Defaults to 60000.',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'huaweicloud_run_approved_command',
|
|
96
|
+
description: 'Run a write-capable hcloud command only after the exact command has been shown and explicitly approved by the user.',
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
required: ['args', 'approvedCommand', 'approvedByUser'],
|
|
100
|
+
properties: {
|
|
101
|
+
args: {
|
|
102
|
+
type: 'array',
|
|
103
|
+
items: { type: 'string' },
|
|
104
|
+
description: 'hcloud arguments, excluding the hcloud executable.',
|
|
105
|
+
},
|
|
106
|
+
approvedCommand: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
description: 'The exact command string previously shown to the user.',
|
|
109
|
+
},
|
|
110
|
+
approvedByUser: {
|
|
111
|
+
type: 'boolean',
|
|
112
|
+
description: 'Must be true only after the user explicitly approves this exact command.',
|
|
113
|
+
},
|
|
114
|
+
timeoutMs: {
|
|
115
|
+
type: 'number',
|
|
116
|
+
description: 'Optional timeout in milliseconds. Defaults to 60000.',
|
|
117
|
+
},
|
|
118
|
+
maxRetries: {
|
|
119
|
+
type: 'number',
|
|
120
|
+
description: 'Optional retry count for transient network errors. Defaults to 1.',
|
|
121
|
+
},
|
|
122
|
+
cwd: {
|
|
123
|
+
type: 'string',
|
|
124
|
+
description: 'Optional working directory for the hcloud process.',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'huaweicloud_show_profile_redacted',
|
|
131
|
+
description: 'Inspect a KooCLI profile through hcloud configure show and return only redacted output.',
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: 'object',
|
|
134
|
+
properties: {
|
|
135
|
+
profile: {
|
|
136
|
+
type: 'string',
|
|
137
|
+
description: 'Optional KooCLI profile name.',
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'huaweicloud_service_catalog',
|
|
144
|
+
description: 'Return the recommended capability sources for Huawei Cloud agent tasks.',
|
|
145
|
+
inputSchema: {
|
|
146
|
+
type: 'object',
|
|
147
|
+
properties: {
|
|
148
|
+
intent: {
|
|
149
|
+
type: 'string',
|
|
150
|
+
description: 'Developer intent to route, such as deploy app, use API, debug error, or inspect resources.',
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'huaweicloud_explain_error',
|
|
157
|
+
description: 'Explain a Huawei Cloud CLI, API, SDK, or agent workflow error and suggest next diagnostic steps.',
|
|
158
|
+
inputSchema: {
|
|
159
|
+
type: 'object',
|
|
160
|
+
properties: {
|
|
161
|
+
service: { type: 'string' },
|
|
162
|
+
errorCode: { type: 'string' },
|
|
163
|
+
message: { type: 'string' },
|
|
164
|
+
requestId: { type: 'string' },
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: 'huaweicloud_search_docs',
|
|
170
|
+
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.',
|
|
171
|
+
inputSchema: {
|
|
172
|
+
type: 'object',
|
|
173
|
+
required: ['query'],
|
|
174
|
+
properties: {
|
|
175
|
+
query: { type: 'string', description: 'Search query across skill descriptions and documentation.' },
|
|
176
|
+
topic: { type: 'string', description: 'Optional filter: all | ecs | obs | vpc | iam | rds | cce | modelarts | dew. Defaults to all.' },
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: 'huaweicloud_retrieve_skill',
|
|
182
|
+
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.',
|
|
183
|
+
inputSchema: {
|
|
184
|
+
type: 'object',
|
|
185
|
+
required: ['name'],
|
|
186
|
+
properties: {
|
|
187
|
+
name: { type: 'string', description: 'Skill name, e.g., huaweicloud-core, huawei-ecs, huawei-obs.' },
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'huaweicloud_list_regions',
|
|
193
|
+
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.',
|
|
194
|
+
inputSchema: {
|
|
195
|
+
type: 'object',
|
|
196
|
+
properties: {},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: 'huaweicloud_get_regional_availability',
|
|
201
|
+
description: 'Check if a specific Huawei Cloud service is available in a target region. Use before creating resources to prevent failures from regional unavailability.',
|
|
202
|
+
inputSchema: {
|
|
203
|
+
type: 'object',
|
|
204
|
+
required: ['service', 'region'],
|
|
205
|
+
properties: {
|
|
206
|
+
service: { type: 'string', description: 'Service name: ecs, obs, rds, gaussdb, cce, modelarts, functiongraph, etc.' },
|
|
207
|
+
region: { type: 'string', description: 'Region ID: cn-south-1, cn-north-4, ap-southeast-3, etc.' },
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: 'huaweicloud_search_marketplace',
|
|
213
|
+
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.',
|
|
214
|
+
inputSchema: {
|
|
215
|
+
type: 'object',
|
|
216
|
+
properties: {
|
|
217
|
+
query: { type: 'string', description: 'Search query across skill name, description, triggers, and service.' },
|
|
218
|
+
category: { type: 'string', description: 'Optional category filter: computing, storage, network, security, devtools, monitoring, etc.' },
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
export async function callTool(name, args = {}) {
|
|
225
|
+
switch (name) {
|
|
226
|
+
case 'huaweicloud_check_cli':
|
|
227
|
+
return runVersionCheck();
|
|
228
|
+
case 'huaweicloud_plan_cli_command':
|
|
229
|
+
return planHcloudCommand(args.args || [], { allowWrites: args.allowWrites === true });
|
|
230
|
+
case 'huaweicloud_run_readonly_command':
|
|
231
|
+
return runHcloud(args.args || [], {
|
|
232
|
+
timeoutMs: args.timeoutMs,
|
|
233
|
+
maxRetries: args.maxRetries,
|
|
234
|
+
cwd: args.cwd,
|
|
235
|
+
stdin: args.stdin,
|
|
236
|
+
});
|
|
237
|
+
case 'huaweicloud_list_operations':
|
|
238
|
+
return listOperations(args.service, { timeoutMs: args.timeoutMs });
|
|
239
|
+
case 'huaweicloud_run_approved_command':
|
|
240
|
+
return runApprovedCommand(args);
|
|
241
|
+
case 'huaweicloud_show_profile_redacted':
|
|
242
|
+
return showProfileRedacted(args.profile);
|
|
243
|
+
case 'huaweicloud_service_catalog':
|
|
244
|
+
return serviceCatalog(args.intent);
|
|
245
|
+
case 'huaweicloud_search_docs':
|
|
246
|
+
return searchDocs(args.query || '', args.topic || 'all');
|
|
247
|
+
case 'huaweicloud_retrieve_skill':
|
|
248
|
+
return retrieveSkill(args.name || '');
|
|
249
|
+
case 'huaweicloud_list_regions':
|
|
250
|
+
return listRegions();
|
|
251
|
+
case 'huaweicloud_get_regional_availability':
|
|
252
|
+
return getRegionalAvailability(args.service || '', args.region || '');
|
|
253
|
+
case 'huaweicloud_explain_error':
|
|
254
|
+
return explainError(args);
|
|
255
|
+
case 'huaweicloud_search_marketplace':
|
|
256
|
+
return searchMarketplace(args.query || '', args.category || '');
|
|
257
|
+
default:
|
|
258
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export async function runVersionCheck(options = {}) {
|
|
263
|
+
const result = await runHcloud(['version'], {
|
|
264
|
+
...options,
|
|
265
|
+
maxRetries: options.maxRetries ?? 0,
|
|
266
|
+
});
|
|
267
|
+
const errorText = result.error || result.stderr || '';
|
|
268
|
+
const isSpawnError = /ENOENT|SPAWN_ERROR/i.test(errorText) || result.code === 'SPAWN_ERROR';
|
|
269
|
+
return {
|
|
270
|
+
installed: result.ok,
|
|
271
|
+
authenticated: result.ok && !/配置文件中不存在配置项|USE_ERROR.*配置/i.test(result.stdout || ''),
|
|
272
|
+
errorCode: isSpawnError ? 'HCLOUD_NOT_FOUND' : undefined,
|
|
273
|
+
output: result.ok ? result.stdout : errorText,
|
|
274
|
+
nextStep: result.ok
|
|
275
|
+
? 'Use huaweicloud_show_profile_redacted to inspect the active KooCLI profile safely.'
|
|
276
|
+
: isSpawnError
|
|
277
|
+
? 'hcloud executable not found. Set HCLOUD_BIN to the full hcloud path, or install KooCLI: npx huaweicloud-devkit install-hcloud. Then restart the agent.'
|
|
278
|
+
: 'Install Huawei Cloud KooCLI: npx huaweicloud-devkit install-hcloud. Configure credentials outside the agent conversation.',
|
|
279
|
+
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.',
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function showProfileRedacted(profile) {
|
|
284
|
+
const args = ['configure', 'show'];
|
|
285
|
+
if (profile) {
|
|
286
|
+
args.push('--cli-profile', String(profile));
|
|
287
|
+
}
|
|
288
|
+
const result = await runHcloud(args, { allowWrites: false, allowCredentialRead: true }).catch((error) => ({
|
|
289
|
+
ok: false,
|
|
290
|
+
blocked: true,
|
|
291
|
+
reason: error.message,
|
|
292
|
+
}));
|
|
293
|
+
if (result.blocked) {
|
|
294
|
+
return {
|
|
295
|
+
ok: false,
|
|
296
|
+
blockedByPolicy: true,
|
|
297
|
+
reason: result.reason,
|
|
298
|
+
safeAlternative: 'Use huaweicloud_show_profile_redacted so profile output is returned through the redaction pipeline.',
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
ok: result.ok,
|
|
303
|
+
note: result.ok
|
|
304
|
+
? 'Profile information was returned through the toolkit redaction pipeline.'
|
|
305
|
+
: 'Failed to retrieve profile — hcloud may not be installed or configured.',
|
|
306
|
+
result: redactSecrets(result),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const SERVICE_EXAMPLES = {
|
|
311
|
+
ECS: { list: 'ECS ListServersDetails', create: 'ECS CreateServers', show: 'IMS GlanceShowImage' },
|
|
312
|
+
VPC: { list: 'VPC ListVpcs', create: 'VPC CreateVpc', show: 'VPC ShowVpc' },
|
|
313
|
+
FunctionGraph: { list: 'FunctionGraph ListFunctions', create: 'FunctionGraph CreateFunction', show: 'FunctionGraph ShowFunctionConfig' },
|
|
314
|
+
APIG: { list: 'APIG ListInstancesV2', create: 'APIG CreateInstanceV2', show: 'APIG ShowDetailsOfInstanceV2' },
|
|
315
|
+
OBS: { list: 'OBS ls', create: 'OBS mb obs://<bucket>', show: 'OBS stat obs://<bucket>/<key>' },
|
|
316
|
+
RDS: { list: 'RDS ListInstances', create: 'RDS CreateInstance', show: 'RDS ShowInstance' },
|
|
317
|
+
CES: { list: 'CES ListAlarms', create: 'CES CreateAlarm', show: 'CES ListMetrics' },
|
|
318
|
+
GaussDB: { list: 'GaussDB ListInstances', create: 'GaussDB CreateInstance', show: 'GaussDB ShowInstance' },
|
|
319
|
+
DDS: { list: 'DDS ListInstances', create: 'DDS CreateInstance', show: 'DDS ShowInstance' },
|
|
320
|
+
DCS: { list: 'DCS ListInstances', create: 'DCS CreateInstance', show: 'DCS ShowInstance' },
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
async function listOperations(service, options = {}) {
|
|
324
|
+
const serviceName = String(service || '').trim();
|
|
325
|
+
if (!/^[A-Za-z][A-Za-z0-9-]{1,63}$/.test(serviceName)) {
|
|
326
|
+
throw new Error('service must be a KooCLI service name such as ECS, VPC, IMS, OBS, RDS, or CDN.');
|
|
327
|
+
}
|
|
328
|
+
const isObs = /^obs$/i.test(serviceName);
|
|
329
|
+
const svc = isObs ? 'obs' : serviceName;
|
|
330
|
+
const args = isObs ? ['obs', 'help'] : [svc, '--help'];
|
|
331
|
+
let result = await runHcloud(args, {
|
|
332
|
+
timeoutMs: options.timeoutMs,
|
|
333
|
+
maxRetries: 0,
|
|
334
|
+
});
|
|
335
|
+
if (!result.ok && !isObs) {
|
|
336
|
+
result = await runHcloud([svc, 'help'], {
|
|
337
|
+
timeoutMs: options.timeoutMs,
|
|
338
|
+
maxRetries: 0,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
service: serviceName,
|
|
343
|
+
command: isObs ? 'hcloud obs help' : `hcloud ${svc} --help`,
|
|
344
|
+
selectionRule: 'Use this help text to select the exact KooCLI operation name before planning any service command.',
|
|
345
|
+
examples: SERVICE_EXAMPLES[serviceName.toUpperCase()] || { note: `No cached examples for ${serviceName}. Use the help text above to discover available operations.` },
|
|
346
|
+
result,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function runApprovedCommand(args = {}) {
|
|
351
|
+
if (args.approvedByUser !== true) {
|
|
352
|
+
throw new Error('approvedByUser must be true after explicit user approval for this exact command.');
|
|
353
|
+
}
|
|
354
|
+
const strictPlan = planHcloudCommand(args.args || [], { allowWrites: false });
|
|
355
|
+
const plan = planHcloudCommand(args.args || [], { allowWrites: true });
|
|
356
|
+
if (String(args.approvedCommand || '') !== plan.command) {
|
|
357
|
+
throw new Error('approvedCommand must exactly match the planned hcloud command.');
|
|
358
|
+
}
|
|
359
|
+
const result = await runHcloud(args.args || [], {
|
|
360
|
+
allowWrites: true,
|
|
361
|
+
timeoutMs: args.timeoutMs,
|
|
362
|
+
maxRetries: args.maxRetries,
|
|
363
|
+
cwd: args.cwd,
|
|
364
|
+
stdin: args.stdin,
|
|
365
|
+
});
|
|
366
|
+
result.approved = true;
|
|
367
|
+
result.plan = strictPlan;
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function serviceCatalog(intent = '') {
|
|
372
|
+
const it = String(intent).toLowerCase();
|
|
373
|
+
const routeMap = [
|
|
374
|
+
{ keywords: ['ecs', 'server', 'vm', 'instance', 'compute', 'flavor', 'image'], skills: ['huawei-ecs'], services: ['ECS'] },
|
|
375
|
+
{ keywords: ['vpc', 'subnet', 'network', 'security group', 'eip', 'nat', 'vpn', 'bandwidth'], skills: ['huawei-vpc'], services: ['VPC', 'EIP'] },
|
|
376
|
+
{ keywords: ['obs', 'bucket', 'storage', 'object', 'static website', 'static site', 'hosting'], skills: ['huawei-obs'], services: ['OBS'] },
|
|
377
|
+
{ keywords: ['functiongraph', 'serverless', 'function', 'lambda', 'trigger', 'faas'], skills: ['huawei-functiongraph'], services: ['FunctionGraph'] },
|
|
378
|
+
{ keywords: ['cce', 'kubernetes', 'k8s', 'container', 'cluster', 'node pool', 'swr', 'docker', 'image registry'], skills: ['huawei-cce'], services: ['CCE', 'SWR'] },
|
|
379
|
+
{ keywords: ['apig', 'api gateway', 'publish', 'throttle'], skills: ['huawei-apig'], services: ['APIG'] },
|
|
380
|
+
{ keywords: ['rds', 'mysql', 'postgresql', 'database', 'db'], skills: ['huawei-rds'], services: ['RDS'] },
|
|
381
|
+
{ keywords: ['gaussdb', 'distributed', 'sharding', 'opengauss'], skills: ['huawei-gaussdb'], services: ['GaussDB'] },
|
|
382
|
+
{ keywords: ['iam', 'permission', 'policy', 'role', 'user', 'ak/sk', 'access key', 'agency'], skills: ['huawei-iam'], services: ['IAM'] },
|
|
383
|
+
{ keywords: ['dew', 'secret', 'kms', 'encrypt', 'decrypt', 'certificate', 'csms'], skills: ['huawei-dew'], services: ['CSMS', 'KMS'] },
|
|
384
|
+
{ keywords: ['modelarts', 'ai', 'model', 'training', 'inference', 'machine learning'], skills: ['huawei-modelarts'], services: ['ModelArts'] },
|
|
385
|
+
{ keywords: ['billing', 'cost', 'bill', 'budget', 'expense', 'bss'], skills: ['huawei-billing'], services: ['BSS'] },
|
|
386
|
+
{ keywords: ['waf', 'aad', 'ddos', 'firewall', 'web protection'], skills: ['huawei-waf-aad'], services: ['WAF', 'AAD'] },
|
|
387
|
+
{ keywords: ['smn', 'dms', 'notification', 'message', 'kafka', 'rabbitmq'], skills: ['huawei-smn-dms'], services: ['SMN', 'DMS'] },
|
|
388
|
+
{ keywords: ['ces', 'monitor', 'alarm', 'metric', 'dashboard', 'cloud eye'], skills: ['huawei-cloud-eye'], services: ['CES'] },
|
|
389
|
+
{ keywords: ['cts', 'audit', 'trace', 'tracker'], skills: ['huawei-cts'], services: ['CTS'] },
|
|
390
|
+
{ keywords: ['cbr', 'backup', 'restore', 'vault', 'snapshot'], skills: ['huawei-cbr'], services: ['CBR'] },
|
|
391
|
+
{ keywords: ['deployment', 'deploy', 'ci/cd', 'pipeline', 'release'], skills: ['huawei-deployment'], services: ['CloudDeploy'] },
|
|
392
|
+
{ keywords: ['dds', 'dcs', 'mongodb', 'redis', 'memcached', 'cache', 'document db'], skills: ['huawei-dds-dcs'], services: ['DDS', 'DCS'] },
|
|
393
|
+
];
|
|
394
|
+
const matched = [];
|
|
395
|
+
const tokens = it.split(/[\s,./-]+/).filter((t) => t.length > 0);
|
|
396
|
+
for (const route of routeMap) {
|
|
397
|
+
if (route.keywords.some((kw) => tokens.includes(kw))) {
|
|
398
|
+
matched.push(route);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const recommendedSkills = [...new Set(matched.flatMap((r) => r.skills))];
|
|
402
|
+
const recommendedServices = [...new Set(matched.flatMap((r) => r.services))].slice(0, 5);
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
intent,
|
|
406
|
+
recommendedSkills: recommendedSkills.length ? recommendedSkills : ['Use huaweicloud-core to route intent.'],
|
|
407
|
+
recommendedServices: recommendedServices.length ? recommendedServices : ['Run hcloud --help to list available services.'],
|
|
408
|
+
capabilityOrder: [
|
|
409
|
+
'Huawei Cloud Skills for task-specific workflows and examples',
|
|
410
|
+
'KooCLI hcloud for local authenticated operations and quick inspection',
|
|
411
|
+
'Huawei Cloud API documentation for exact request and response contracts',
|
|
412
|
+
'Huawei Cloud SDKs for application code integration',
|
|
413
|
+
'Huawei Cloud MCP when an official or approved server is available',
|
|
414
|
+
'Terraform Provider only when IaC reviewability and repeatability are important',
|
|
415
|
+
],
|
|
416
|
+
ruleOfThumb: {
|
|
417
|
+
skills: 'Start here when the user describes a scenario or wants a guided workflow.',
|
|
418
|
+
cli: 'Use for local diagnostics, read-only inspection, and commands the user can review.',
|
|
419
|
+
api: 'Use for exact service contract, region endpoint, project_id, pagination, and error codes.',
|
|
420
|
+
sdk: 'Use when writing application code that calls Huawei Cloud services.',
|
|
421
|
+
mcp: 'Prefer approved MCP tools when available because tools can carry structured schemas.',
|
|
422
|
+
terraform: 'Keep low priority in V1; suggest it for reviewed infrastructure changes, not quick diagnosis.',
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function explainError({ service = 'unknown', errorCode = '', message = '', requestId = '' } = {}) {
|
|
428
|
+
const combined = `${errorCode} ${message}`.toLowerCase();
|
|
429
|
+
const suggestions = [];
|
|
430
|
+
const svc = String(service).toLowerCase();
|
|
431
|
+
|
|
432
|
+
const SERVICE_ALIASES = {
|
|
433
|
+
'functiongraph': 'FSS',
|
|
434
|
+
'fgs': 'FSS',
|
|
435
|
+
};
|
|
436
|
+
const patternKey = SERVICE_ALIASES[svc] || service;
|
|
437
|
+
|
|
438
|
+
const hwErrorPatterns = {
|
|
439
|
+
OBS: {
|
|
440
|
+
'InvalidAccessKeyId': 'OBS uses AK/SK directly (not IAM tokens). Verify AK/SK validity, OBS endpoint, and OBS permissions.',
|
|
441
|
+
'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.',
|
|
442
|
+
},
|
|
443
|
+
APIG: {
|
|
444
|
+
'APIC.7241': 'The enterprise_project_id is required for enterprise accounts. Add --enterprise_project_id=0.',
|
|
445
|
+
'APIC.7242': 'The EIP binding method depends on loadbalancer_provider. Use AddIngressEipV2 for elb, AddEipV2 for lvs.',
|
|
446
|
+
'APIC.7256': 'Bandwidth minimum is 5 Mbps. Use --bandwidth_size=5 or higher.',
|
|
447
|
+
'APIC.7310': 'available_zone_ids must use AZ codes (e.g. ap-southeast-3a), NOT UUIDs from ListAvailableZonesV2.',
|
|
448
|
+
},
|
|
449
|
+
FSS: {
|
|
450
|
+
'FSS.0403': 'Missing FunctionGraph IAM permissions. Attach FunctionGraph FullAccess role or grant specific actions.',
|
|
451
|
+
'FSS.1078': '--code_filename is filename-only (no path). cd to the file directory before running the command.',
|
|
452
|
+
'FSS.1417': 'event_data field validation failed. Check parameter format: use dotted key=value, verify required hidden-optional fields.',
|
|
453
|
+
},
|
|
454
|
+
VPC: {
|
|
455
|
+
'VPC.0301': 'Bandwidth name is required for PER type EIPs, even though --help marks it optional.',
|
|
456
|
+
},
|
|
457
|
+
APIGW: {
|
|
458
|
+
'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.',
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
if (hwErrorPatterns[patternKey] && hwErrorPatterns[patternKey][errorCode]) {
|
|
462
|
+
suggestions.push(hwErrorPatterns[patternKey][errorCode]);
|
|
463
|
+
}
|
|
464
|
+
const svcPatterns = hwErrorPatterns[patternKey] || {};
|
|
465
|
+
for (const [code, tip] of Object.entries(svcPatterns)) {
|
|
466
|
+
if (errorCode && code.includes(errorCode)) {
|
|
467
|
+
if (!suggestions.includes(tip)) suggestions.push(tip);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (/auth|token|credential|ak|sk|401|403|unauthorized|forbidden|Incorrect IAM/i.test(combined)) {
|
|
472
|
+
if (svc === 'obs') {
|
|
473
|
+
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.');
|
|
474
|
+
} else {
|
|
475
|
+
suggestions.push('Check KooCLI profile, region, project_id, and IAM permissions without printing secrets.');
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (/APIGW\.(\d+)/i.test(errorCode)) {
|
|
479
|
+
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.'));
|
|
480
|
+
}
|
|
481
|
+
if (/region|endpoint|project/i.test(combined)) {
|
|
482
|
+
suggestions.push('Confirm the service endpoint, region, and project_id match the target resource.');
|
|
483
|
+
}
|
|
484
|
+
if (/quota|limit|insufficient|reach the limit/i.test(combined)) {
|
|
485
|
+
suggestions.push('Check quota and resource limits before retrying a create or scale operation. Consider switching accounts or requesting a quota increase.');
|
|
486
|
+
}
|
|
487
|
+
if (/not.?found|404/i.test(combined)) {
|
|
488
|
+
if (/list.?regions/i.test(svc)) {
|
|
489
|
+
suggestions.push('Use hcloud IAM KeystoneListRegions (not list-regions).');
|
|
490
|
+
} else {
|
|
491
|
+
suggestions.push('List resources in the same region/project and verify the resource identifier.');
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (!suggestions.length) {
|
|
495
|
+
suggestions.push('Collect service name, operation, region, project_id, request_id, and the full redacted error message.');
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (requestId) {
|
|
499
|
+
suggestions.push('Provide the Request ID (' + requestId + ') when contacting Huawei Cloud support.');
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const uniqueSuggestions = suggestions.filter((s, i, arr) => {
|
|
503
|
+
return !arr.slice(0, i).some((prev) =>
|
|
504
|
+
prev.substring(0, 50).toLowerCase() === s.substring(0, 50).toLowerCase());
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
return {
|
|
508
|
+
service,
|
|
509
|
+
errorCode,
|
|
510
|
+
requestId,
|
|
511
|
+
suggestions: uniqueSuggestions,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async function searchDocs(query, topic = 'all') {
|
|
516
|
+
const q = String(query || '').toLowerCase();
|
|
517
|
+
const tokens = q.split(/\s+/).filter((t) => t.length > 0);
|
|
518
|
+
const results = [];
|
|
519
|
+
try {
|
|
520
|
+
if (existsSync(SKILLS_ROOT)) {
|
|
521
|
+
const dirs = readdirSync(SKILLS_ROOT, { withFileTypes: true })
|
|
522
|
+
.filter((d) => d.isDirectory())
|
|
523
|
+
.map((d) => d.name);
|
|
524
|
+
for (const dir of dirs) {
|
|
525
|
+
const skillPath = join(SKILLS_ROOT, dir, 'SKILL.md');
|
|
526
|
+
if (!existsSync(skillPath)) continue;
|
|
527
|
+
const content = readFileSync(skillPath, 'utf8');
|
|
528
|
+
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
529
|
+
let description = '';
|
|
530
|
+
let name = dir;
|
|
531
|
+
if (frontmatter) {
|
|
532
|
+
const fm = frontmatter[1];
|
|
533
|
+
const nameMatch = fm.match(/^name:\s*(.+)$/m);
|
|
534
|
+
if (nameMatch) name = nameMatch[1].trim();
|
|
535
|
+
const descMatch = fm.match(/^description:\s*(.+)$/m);
|
|
536
|
+
if (descMatch) description = descMatch[1].trim();
|
|
537
|
+
}
|
|
538
|
+
if (topic !== 'all') {
|
|
539
|
+
const topicLower = topic.toLowerCase();
|
|
540
|
+
if (!name.toLowerCase().includes(topicLower) && !description.toLowerCase().includes(topicLower)) continue;
|
|
541
|
+
}
|
|
542
|
+
const descLower = description.toLowerCase();
|
|
543
|
+
const nameLower = name.toLowerCase();
|
|
544
|
+
const contentLower = content.toLowerCase();
|
|
545
|
+
const relevance = tokens.reduce((score, token) => {
|
|
546
|
+
return score + (descLower.includes(token) ? 3 : 0) + (nameLower.includes(token) ? 2 : 0) + (contentLower.includes(token) ? 1 : 0);
|
|
547
|
+
}, 0);
|
|
548
|
+
if (relevance > 0) {
|
|
549
|
+
results.push({
|
|
550
|
+
source: "skills/" + dir + "/SKILL.md",
|
|
551
|
+
name,
|
|
552
|
+
snippet: description.substring(0, 200),
|
|
553
|
+
relevance,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
} catch (err) {
|
|
559
|
+
return { ok: false, error: err.message, results: [] };
|
|
560
|
+
}
|
|
561
|
+
results.sort((a, b) => b.relevance - a.relevance);
|
|
562
|
+
return { ok: true, query: q, topic, count: results.length, results: results.slice(0, 10) };
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async function retrieveSkill(name) {
|
|
566
|
+
const skillName = String(name || '').trim();
|
|
567
|
+
if (!skillName) return { ok: false, error: 'Skill name is required.' };
|
|
568
|
+
const skillPath = join(SKILLS_ROOT, skillName, 'SKILL.md');
|
|
569
|
+
if (!existsSync(skillPath)) {
|
|
570
|
+
const dirs = existsSync(SKILLS_ROOT)
|
|
571
|
+
? readdirSync(SKILLS_ROOT, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
572
|
+
: [];
|
|
573
|
+
return { ok: false, error: 'Skill "' + skillName + '" not found. Available: ' + dirs.join(', ') };
|
|
574
|
+
}
|
|
575
|
+
const content = readFileSync(skillPath, 'utf8');
|
|
576
|
+
const references = [];
|
|
577
|
+
const refDir = join(SKILLS_ROOT, skillName, 'references');
|
|
578
|
+
if (existsSync(refDir)) {
|
|
579
|
+
readdirSync(refDir).forEach((f) => {
|
|
580
|
+
const refPath = join(refDir, f);
|
|
581
|
+
references.push({ filename: f, content: readFileSync(refPath, 'utf8').substring(0, 4000) });
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
585
|
+
let version = 1, description = '';
|
|
586
|
+
if (frontmatter) {
|
|
587
|
+
const fm = frontmatter[1];
|
|
588
|
+
const vm = fm.match(/^version:\s*(.+)$/m);
|
|
589
|
+
if (vm) version = vm[1].trim();
|
|
590
|
+
const dm = fm.match(/^description:\s*(.+)$/m);
|
|
591
|
+
if (dm) description = dm[1].trim();
|
|
592
|
+
}
|
|
593
|
+
return { ok: true, name: skillName, version, description, content, references };
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
async function listRegions() {
|
|
597
|
+
const result = await runHcloud(['IAM', 'KeystoneListRegions'], { timeoutMs: 30000, maxRetries: 0 })
|
|
598
|
+
.catch((err) => ({ ok: false, error: err.message }));
|
|
599
|
+
if (!result.ok) {
|
|
600
|
+
return {
|
|
601
|
+
ok: false,
|
|
602
|
+
error: result.error || 'Failed to list regions.',
|
|
603
|
+
fallback: 'Check https://developer.huaweicloud.com/endpoint for available regions.',
|
|
604
|
+
staticRegions: [
|
|
605
|
+
{ id: 'cn-north-1', description: '华北-北京一' },
|
|
606
|
+
{ id: 'cn-north-4', description: '华北-北京四' },
|
|
607
|
+
{ id: 'cn-east-3', description: '华东-上海一' },
|
|
608
|
+
{ id: 'cn-east-2', description: '华东-上海二' },
|
|
609
|
+
{ id: 'cn-south-1', description: '华南-广州' },
|
|
610
|
+
{ id: 'ap-southeast-1', description: '香港' },
|
|
611
|
+
{ id: 'ap-southeast-3', description: '新加坡' },
|
|
612
|
+
{ id: 'ap-southeast-2', description: '曼谷' },
|
|
613
|
+
{ id: 'af-south-1', description: '约翰内斯堡' },
|
|
614
|
+
],
|
|
615
|
+
note: 'hcloud unavailable. Showing static region list. For the complete list, visit the fallback URL.',
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
let regions = [];
|
|
619
|
+
try {
|
|
620
|
+
const parsed = typeof result.stdout === 'string' ? JSON.parse(result.stdout) : result.stdout;
|
|
621
|
+
const rawRegions = parsed.regions || [];
|
|
622
|
+
regions = rawRegions.map((r) => ({
|
|
623
|
+
id: r.id,
|
|
624
|
+
description: r.description || r.names,
|
|
625
|
+
type: r.type,
|
|
626
|
+
locales: r.locales,
|
|
627
|
+
}));
|
|
628
|
+
} catch {
|
|
629
|
+
regions = [{ raw: String(result.stdout).substring(0, 1000) }];
|
|
630
|
+
}
|
|
631
|
+
regions.sort((a, b) => String(a.id || '').localeCompare(String(b.id || '')));
|
|
632
|
+
return { ok: true, count: regions.length, regions };
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
async function getRegionalAvailability(service, region) {
|
|
636
|
+
const svc = String(service || '').toLowerCase().trim();
|
|
637
|
+
const reg = String(region || '').toLowerCase().trim();
|
|
638
|
+
if (!svc || !reg) return { ok: false, error: 'Both service and region are required.' };
|
|
639
|
+
const known = {
|
|
640
|
+
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'],
|
|
641
|
+
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'],
|
|
642
|
+
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'],
|
|
643
|
+
iam: ['global'],
|
|
644
|
+
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'],
|
|
645
|
+
gaussdb: ['cn-south-1','cn-north-4','cn-east-3'],
|
|
646
|
+
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'],
|
|
647
|
+
modelarts: ['cn-south-1','cn-north-4','cn-east-3'],
|
|
648
|
+
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'],
|
|
649
|
+
dew: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
|
|
650
|
+
smn: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
|
|
651
|
+
ces: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
|
|
652
|
+
cts: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
|
|
653
|
+
apig: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
|
|
654
|
+
cbr: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
|
|
655
|
+
dds: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
|
|
656
|
+
dcs: ['cn-south-1','cn-north-4','cn-east-3','ap-southeast-3'],
|
|
657
|
+
};
|
|
658
|
+
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.' };
|
|
659
|
+
const available = known[svc].includes(reg) || known[svc].includes('global');
|
|
660
|
+
return {
|
|
661
|
+
ok: true, service: svc, region: reg, available,
|
|
662
|
+
note: available
|
|
663
|
+
? svc + ' is available in ' + reg + '.'
|
|
664
|
+
: svc + ' availability in ' + reg + ' could not be confirmed. Verify at https://developer.huaweicloud.com/endpoint.',
|
|
665
|
+
sourcedFrom: 'static cache, update via npm package upgrade',
|
|
666
|
+
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.',
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
export function classifyRawCommand(command) {
|
|
671
|
+
return classifyTextCommand(command);
|
|
672
|
+
}
|