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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.agents/plugins/marketplace.json +20 -20
  2. package/LICENSE +201 -201
  3. package/README.md +163 -138
  4. package/README.zh-CN.md +161 -138
  5. package/bin/setup.cjs +8 -8
  6. package/integrations/opencode/commands/huaweicloud-doctor.md +14 -14
  7. package/integrations/opencode/opencode.json +14 -14
  8. package/integrations/opencode/skills/huaweicloud-core/SKILL.md +32 -32
  9. package/package.json +46 -44
  10. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +44 -44
  11. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +43 -43
  12. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +44 -44
  13. package/plugins/huaweicloud-core/.mcp.json +15 -15
  14. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +44 -44
  15. package/plugins/huaweicloud-core/hooks/hooks.json +26 -26
  16. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +136 -136
  17. package/plugins/huaweicloud-core/safety/policy.json +99 -99
  18. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +199 -199
  19. package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +164 -164
  20. package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +48 -48
  21. package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +50 -50
  22. package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +101 -101
  23. package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +90 -90
  24. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +190 -190
  25. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +124 -124
  26. package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +55 -55
  27. package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +70 -70
  28. package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +56 -56
  29. package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +97 -97
  30. package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +29 -29
  31. package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +23 -23
  32. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +185 -185
  33. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +133 -133
  34. package/plugins/huaweicloud-core/skills/huawei-ecs/references/evs.md +31 -31
  35. package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +55 -55
  36. package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +52 -52
  37. package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +131 -131
  38. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +58 -58
  39. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +71 -71
  40. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/deploy-workflow.md +119 -119
  41. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +142 -142
  42. package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +56 -56
  43. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +65 -65
  44. package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +111 -111
  45. package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +122 -122
  46. package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +56 -56
  47. package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +65 -65
  48. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +133 -127
  49. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +49 -49
  50. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +26 -26
  51. package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +100 -100
  52. package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +40 -40
  53. package/plugins/huaweicloud-core/skills/huawei-obs/references/static-website.md +56 -56
  54. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +224 -224
  55. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +135 -134
  56. package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +69 -69
  57. package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +84 -84
  58. package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +76 -76
  59. package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +59 -59
  60. package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +50 -50
  61. package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +63 -63
  62. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +56 -56
  63. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +4 -4
  64. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +51 -47
  65. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +4 -4
  66. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +128 -128
  67. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +4 -4
  68. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +110 -95
  69. package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +4 -4
  70. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +19 -19
  71. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +27 -27
  72. package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +60 -60
  73. package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +4 -4
  74. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +83 -83
  75. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +4 -4
  76. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +105 -87
  77. package/plugins/huaweicloud-core/src/auth/credentials.mjs +95 -95
  78. package/plugins/huaweicloud-core/src/auth/service.mjs +63 -63
  79. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +265 -265
  80. package/plugins/huaweicloud-core/src/mcp-server.mjs +129 -129
  81. package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +155 -0
  82. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +87 -0
  83. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +137 -137
  84. package/plugins/huaweicloud-core/src/safety-policy.mjs +302 -302
  85. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +91 -87
  86. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +162 -153
  87. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +112 -105
  88. package/plugins/huaweicloud-core/src/search-market.mjs +174 -169
  89. package/plugins/huaweicloud-core/src/setup-cli.mjs +2017 -1594
  90. package/plugins/huaweicloud-core/src/tools.mjs +1008 -993
  91. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +427 -427
  92. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +132 -132
  93. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +227 -227
  94. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +202 -202
  95. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +158 -158
  96. package/plugins/huaweicloud-core/src/ws-exec/index.js +19 -19
  97. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +338 -338
@@ -1,265 +1,265 @@
1
- import { spawn } from 'node:child_process';
2
- import { classifyHcloudArgs, redactSecrets, assertAllowed } from './safety-policy.mjs';
3
-
4
- const DEFAULT_TIMEOUT_MS = 60_000;
5
- const DEFAULT_FORCE_KILL_AFTER_MS = 2_000;
6
- const DEFAULT_MAX_RETRIES = 1;
7
-
8
- export function planHcloudCommand(args, options = {}) {
9
- const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
10
- const classification = classifyHcloudArgs(normalizedArgs, options);
11
- const command = ['hcloud', ...normalizedArgs].map(quoteShellArg).join(' ');
12
- const warnings = planningWarnings(normalizedArgs);
13
- const paramValidation = validateRequiredParams(normalizedArgs);
14
- if (paramValidation.missing.length > 0) {
15
- warnings.push('Missing required parameters: ' + paramValidation.missing.join(', '));
16
- if (paramValidation.hints && paramValidation.hints.length > 0) {
17
- warnings.push('Find valid values: ' + paramValidation.hints.join('; '));
18
- }
19
- }
20
- return {
21
- executable: 'hcloud',
22
- args: redactSecrets(normalizedArgs),
23
- command: redactOutput(command),
24
- executableBlock: redactOutput(command),
25
- warnings,
26
- classification,
27
- safeToRun: classification.decision === 'allow',
28
- };
29
- }
30
-
31
- export async function runHcloud(args, options = {}) {
32
- const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
33
- const plan = {
34
- ...planHcloudCommand(normalizedArgs, options),
35
- rawArgs: normalizedArgs,
36
- };
37
- assertAllowed(plan.classification);
38
-
39
- const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
40
- for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
41
- const result = await runHcloudOnce(plan, options);
42
- if (result.ok || attempt >= maxRetries || !isRetryableNetworkError(result)) {
43
- return {
44
- ...result,
45
- retries: attempt,
46
- attempts: attempt + 1,
47
- };
48
- }
49
- await wait((options.retryBaseDelayMs ?? 500) * (2 ** attempt));
50
- }
51
- throw new Error('Unreachable retry state.');
52
- }
53
-
54
- function runHcloudOnce(plan, options) {
55
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
56
- const forceKillAfterMs = options.forceKillAfterMs ?? DEFAULT_FORCE_KILL_AFTER_MS;
57
- const executable = options.executable || options.env?.HCLOUD_BIN || process.env.HCLOUD_BIN || 'hcloud';
58
- const executableArgs = Array.isArray(options.executableArgs) ? options.executableArgs.map(String) : [];
59
- const cwd = options.cwd || undefined;
60
-
61
- return new Promise((resolve) => {
62
- const child = spawn(executable, [...executableArgs, ...plan.rawArgs], {
63
- shell: false,
64
- windowsHide: true,
65
- cwd,
66
- env: {
67
- ...process.env,
68
- ...options.env,
69
- },
70
- });
71
- if (options.stdin) {
72
- if (typeof options.stdin === 'function') {
73
- options.stdin(child.stdin);
74
- } else {
75
- child.stdin.write(String(options.stdin));
76
- child.stdin.end();
77
- }
78
- }
79
- let stdout = '';
80
- let stderr = '';
81
- let timedOut = false;
82
- let settled = false;
83
- let forceTimer;
84
- let settleTimer;
85
-
86
- function finish(result) {
87
- if (settled) return;
88
- settled = true;
89
- clearTimeout(timer);
90
- clearTimeout(forceTimer);
91
- clearTimeout(settleTimer);
92
- resolve(result);
93
- }
94
-
95
- const timer = setTimeout(() => {
96
- timedOut = true;
97
- child.kill('SIGTERM');
98
- forceTimer = setTimeout(() => child.kill('SIGKILL'), forceKillAfterMs);
99
- settleTimer = setTimeout(() => {
100
- finish({
101
- ok: false,
102
- code: 'TIMEOUT',
103
- error: `hcloud command timed out after ${timeoutMs} ms.`,
104
- stdout: redactOutput(stdout),
105
- stderr: redactOutput(stderr),
106
- plan,
107
- });
108
- }, forceKillAfterMs + 500);
109
- }, timeoutMs);
110
-
111
- child.stdout.on('data', (chunk) => {
112
- stdout += chunk.toString('utf8');
113
- });
114
- child.stderr.on('data', (chunk) => {
115
- stderr += chunk.toString('utf8');
116
- });
117
- child.on('error', (error) => {
118
- finish({
119
- ok: false,
120
- code: 'SPAWN_ERROR',
121
- error: error.message,
122
- plan,
123
- });
124
- });
125
- child.on('close', (code, signal) => {
126
- if (timedOut) {
127
- finish({
128
- ok: false,
129
- code: 'TIMEOUT',
130
- error: `hcloud command timed out after ${timeoutMs} ms.`,
131
- exitCode: code,
132
- signal,
133
- stdout: redactOutput(stdout),
134
- stderr: redactOutput(stderr),
135
- plan,
136
- });
137
- return;
138
- }
139
- const apiError = extractApiError(stdout);
140
- if (apiError) {
141
- finish({
142
- ok: false,
143
- exitCode: code,
144
- signal,
145
- errorCode: apiError.errorCode,
146
- errorMessage: apiError.errorMessage,
147
- stdout: redactOutput(stdout),
148
- stderr: redactOutput(stderr),
149
- plan,
150
- });
151
- return;
152
- }
153
- finish({
154
- ok: code === 0 || (code !== 0 && /successfully|succ?ess.*\[200\]|create bucket successfully|upload successfully/i.test(stdout + stderr)),
155
- exitCode: code,
156
- signal,
157
- stdout: redactOutput(stdout),
158
- stderr: redactOutput(stderr),
159
- plan,
160
- });
161
- });
162
- });
163
- }
164
-
165
- function isRetryableNetworkError(result) {
166
- if (result.code === 'TIMEOUT') return false;
167
- const text = `${result.error || ''}\n${result.stdout || ''}\n${result.stderr || ''}`;
168
- return /\[NETWORK_ERROR\]|connection timed out|ECONNRESET|ETIMEDOUT|temporary failure|TLS handshake timeout/i.test(text);
169
- }
170
-
171
- function wait(ms) {
172
- return new Promise((resolve) => setTimeout(resolve, ms));
173
- }
174
-
175
- function quoteShellArg(value) {
176
- const text = String(value);
177
- if (!text) return '""';
178
- if (/^[A-Za-z0-9_./:=@-]+$/.test(text)) return text;
179
- return `"${text.replace(/(["\\])/g, '\\$1')}"`;
180
- }
181
-
182
- function planningWarnings(args) {
183
- const joined = args.join(' ');
184
- const warnings = [];
185
- if (/adminPass|password|passwd|secret|token/i.test(joined)) {
186
- warnings.push(
187
- 'This command appears to contain a password or secret field. Do not leave plaintext secrets in shell history; prefer local-only input or a runtime injection pattern.',
188
- );
189
- }
190
- return warnings;
191
- }
192
-
193
- const REQUIRED_PARAMS = {
194
- 'ECS CreateServers': ['server.flavorRef', 'server.imageRef', 'server.nics.1.subnet_id'],
195
- 'VPC CreateVpc': ['vpc.cidr'],
196
- 'VPC CreateSubnet': ['subnet.vpc_id', 'subnet.cidr'],
197
- 'VPC CreateSecurityGroupRule': ['security_group_rule.security_group_id', 'security_group_rule.direction', 'security_group_rule.protocol'],
198
- 'EIP CreatePublicip': ['bandwidth.share_type', 'publicip.type'],
199
- 'FunctionGraph CreateFunction': ['func_name', 'runtime', 'handler', 'memory_size', 'package', 'timeout'],
200
- 'FunctionGraph CreateFunctionTrigger': ['function_urn', 'trigger_type_code'],
201
- 'APIG CreateInstanceV2': ['spec_id'],
202
- 'OBS mb': ['obs://'],
203
- 'OBS rm': ['obs://'],
204
- };
205
-
206
- const PARAM_VALUE_HINTS = {
207
- 'server.flavorRef': 'Run `hcloud ECS ListFlavors --cli-region=<r>` to find valid flavors',
208
- 'server.imageRef': 'Run `hcloud IMS ListImages --cli-region=<r> --__imagetype=gold` to find valid image IDs',
209
- 'server.nics.1.subnet_id': 'Run `hcloud VPC ListSubnets --cli-region=<r>` to find subnet IDs',
210
- 'subnet.vpc_id': 'Run `hcloud VPC ListVpcs --cli-region=<r>` to find VPC IDs',
211
- 'func_name': 'Function name must be unique within project',
212
- 'runtime': 'Run `hcloud FunctionGraph ListRuntimes` to see available runtimes',
213
- 'spec_id': 'APIG spec: BASIC (no public IP) or PROFESSIONAL (requires --loadbalancer_provider)',
214
- 'obs://': 'Bucket name must be globally unique and DNS-compliant (lowercase, numbers, hyphens only)',
215
- };
216
-
217
- function validateRequiredParams(args) {
218
- if (!args || args.length < 2) return { valid: true, missing: [], hints: [] };
219
- const key = `${args[0]} ${args[1]}`;
220
- const required = REQUIRED_PARAMS[key];
221
- if (!required) return { valid: true, missing: [], hints: [] };
222
- const argsStr = args.join(' ');
223
- const missing = required.filter((param) => !argsStr.includes(param));
224
- const hints = missing.map((param) => PARAM_VALUE_HINTS[param]).filter(Boolean);
225
- return { valid: missing.length === 0, missing, hints };
226
- }
227
-
228
- function extractApiError(stdout) {
229
- let text = String(stdout || '');
230
- // Strip KooCLI multi-version prefix lines (e.g. "ListVpcs有多个版本,默认使用该API版本v3…")
231
- const bracketIdx = text.indexOf('{');
232
- if (bracketIdx > 0) text = text.substring(bracketIdx);
233
- try {
234
- const parsed = JSON.parse(text);
235
- if (parsed.error_code || parsed.errorCode) {
236
- return {
237
- errorCode: parsed.error_code || parsed.errorCode || 'UNKNOWN',
238
- errorMessage: parsed.error_msg || parsed.errorMsg || parsed.message || '',
239
- };
240
- }
241
- if (parsed.error && typeof parsed.error === 'object') {
242
- return {
243
- errorCode: parsed.error.code || parsed.error.error_code || 'UNKNOWN',
244
- errorMessage: parsed.error.message || parsed.error.error_msg || '',
245
- };
246
- }
247
- } catch {}
248
- const ecMatch = text.match(/"error_code"\s*:\s*"([^"]+)"/);
249
- const emMatch = text.match(/"error_msg"\s*:\s*"([^"]+)"/);
250
- if (ecMatch) {
251
- return { errorCode: ecMatch[1], errorMessage: emMatch ? emMatch[1] : '' };
252
- }
253
- return null;
254
- }
255
-
256
- export function redactOutput(output) {
257
- let text = String(output || '');
258
- const bracketIdx = text.indexOf('{');
259
- if (bracketIdx > 0) text = text.substring(bracketIdx);
260
- try {
261
- return JSON.stringify(redactSecrets(JSON.parse(text)), null, 2);
262
- } catch {
263
- return redactSecrets(text);
264
- }
265
- }
1
+ import { spawn } from 'node:child_process';
2
+ import { classifyHcloudArgs, redactSecrets, assertAllowed } from './safety-policy.mjs';
3
+
4
+ const DEFAULT_TIMEOUT_MS = 60_000;
5
+ const DEFAULT_FORCE_KILL_AFTER_MS = 2_000;
6
+ const DEFAULT_MAX_RETRIES = 1;
7
+
8
+ export function planHcloudCommand(args, options = {}) {
9
+ const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
10
+ const classification = classifyHcloudArgs(normalizedArgs, options);
11
+ const command = ['hcloud', ...normalizedArgs].map(quoteShellArg).join(' ');
12
+ const warnings = planningWarnings(normalizedArgs);
13
+ const paramValidation = validateRequiredParams(normalizedArgs);
14
+ if (paramValidation.missing.length > 0) {
15
+ warnings.push('Missing required parameters: ' + paramValidation.missing.join(', '));
16
+ if (paramValidation.hints && paramValidation.hints.length > 0) {
17
+ warnings.push('Find valid values: ' + paramValidation.hints.join('; '));
18
+ }
19
+ }
20
+ return {
21
+ executable: 'hcloud',
22
+ args: redactSecrets(normalizedArgs),
23
+ command: redactOutput(command),
24
+ executableBlock: redactOutput(command),
25
+ warnings,
26
+ classification,
27
+ safeToRun: classification.decision === 'allow',
28
+ };
29
+ }
30
+
31
+ export async function runHcloud(args, options = {}) {
32
+ const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
33
+ const plan = {
34
+ ...planHcloudCommand(normalizedArgs, options),
35
+ rawArgs: normalizedArgs,
36
+ };
37
+ assertAllowed(plan.classification);
38
+
39
+ const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
40
+ for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
41
+ const result = await runHcloudOnce(plan, options);
42
+ if (result.ok || attempt >= maxRetries || !isRetryableNetworkError(result)) {
43
+ return {
44
+ ...result,
45
+ retries: attempt,
46
+ attempts: attempt + 1,
47
+ };
48
+ }
49
+ await wait((options.retryBaseDelayMs ?? 500) * (2 ** attempt));
50
+ }
51
+ throw new Error('Unreachable retry state.');
52
+ }
53
+
54
+ function runHcloudOnce(plan, options) {
55
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
56
+ const forceKillAfterMs = options.forceKillAfterMs ?? DEFAULT_FORCE_KILL_AFTER_MS;
57
+ const executable = options.executable || options.env?.HCLOUD_BIN || process.env.HCLOUD_BIN || 'hcloud';
58
+ const executableArgs = Array.isArray(options.executableArgs) ? options.executableArgs.map(String) : [];
59
+ const cwd = options.cwd || undefined;
60
+
61
+ return new Promise((resolve) => {
62
+ const child = spawn(executable, [...executableArgs, ...plan.rawArgs], {
63
+ shell: false,
64
+ windowsHide: true,
65
+ cwd,
66
+ env: {
67
+ ...process.env,
68
+ ...options.env,
69
+ },
70
+ });
71
+ if (options.stdin) {
72
+ if (typeof options.stdin === 'function') {
73
+ options.stdin(child.stdin);
74
+ } else {
75
+ child.stdin.write(String(options.stdin));
76
+ child.stdin.end();
77
+ }
78
+ }
79
+ let stdout = '';
80
+ let stderr = '';
81
+ let timedOut = false;
82
+ let settled = false;
83
+ let forceTimer;
84
+ let settleTimer;
85
+
86
+ function finish(result) {
87
+ if (settled) return;
88
+ settled = true;
89
+ clearTimeout(timer);
90
+ clearTimeout(forceTimer);
91
+ clearTimeout(settleTimer);
92
+ resolve(result);
93
+ }
94
+
95
+ const timer = setTimeout(() => {
96
+ timedOut = true;
97
+ child.kill('SIGTERM');
98
+ forceTimer = setTimeout(() => child.kill('SIGKILL'), forceKillAfterMs);
99
+ settleTimer = setTimeout(() => {
100
+ finish({
101
+ ok: false,
102
+ code: 'TIMEOUT',
103
+ error: `hcloud command timed out after ${timeoutMs} ms.`,
104
+ stdout: redactOutput(stdout),
105
+ stderr: redactOutput(stderr),
106
+ plan,
107
+ });
108
+ }, forceKillAfterMs + 500);
109
+ }, timeoutMs);
110
+
111
+ child.stdout.on('data', (chunk) => {
112
+ stdout += chunk.toString('utf8');
113
+ });
114
+ child.stderr.on('data', (chunk) => {
115
+ stderr += chunk.toString('utf8');
116
+ });
117
+ child.on('error', (error) => {
118
+ finish({
119
+ ok: false,
120
+ code: 'SPAWN_ERROR',
121
+ error: error.message,
122
+ plan,
123
+ });
124
+ });
125
+ child.on('close', (code, signal) => {
126
+ if (timedOut) {
127
+ finish({
128
+ ok: false,
129
+ code: 'TIMEOUT',
130
+ error: `hcloud command timed out after ${timeoutMs} ms.`,
131
+ exitCode: code,
132
+ signal,
133
+ stdout: redactOutput(stdout),
134
+ stderr: redactOutput(stderr),
135
+ plan,
136
+ });
137
+ return;
138
+ }
139
+ const apiError = extractApiError(stdout);
140
+ if (apiError) {
141
+ finish({
142
+ ok: false,
143
+ exitCode: code,
144
+ signal,
145
+ errorCode: apiError.errorCode,
146
+ errorMessage: apiError.errorMessage,
147
+ stdout: redactOutput(stdout),
148
+ stderr: redactOutput(stderr),
149
+ plan,
150
+ });
151
+ return;
152
+ }
153
+ finish({
154
+ ok: code === 0 || (code !== 0 && /successfully|succ?ess.*\[200\]|create bucket successfully|upload successfully/i.test(stdout + stderr)),
155
+ exitCode: code,
156
+ signal,
157
+ stdout: redactOutput(stdout),
158
+ stderr: redactOutput(stderr),
159
+ plan,
160
+ });
161
+ });
162
+ });
163
+ }
164
+
165
+ function isRetryableNetworkError(result) {
166
+ if (result.code === 'TIMEOUT') return false;
167
+ const text = `${result.error || ''}\n${result.stdout || ''}\n${result.stderr || ''}`;
168
+ return /\[NETWORK_ERROR\]|connection timed out|ECONNRESET|ETIMEDOUT|temporary failure|TLS handshake timeout/i.test(text);
169
+ }
170
+
171
+ function wait(ms) {
172
+ return new Promise((resolve) => setTimeout(resolve, ms));
173
+ }
174
+
175
+ function quoteShellArg(value) {
176
+ const text = String(value);
177
+ if (!text) return '""';
178
+ if (/^[A-Za-z0-9_./:=@-]+$/.test(text)) return text;
179
+ return `"${text.replace(/(["\\])/g, '\\$1')}"`;
180
+ }
181
+
182
+ function planningWarnings(args) {
183
+ const joined = args.join(' ');
184
+ const warnings = [];
185
+ if (/adminPass|password|passwd|secret|token/i.test(joined)) {
186
+ warnings.push(
187
+ 'This command appears to contain a password or secret field. Do not leave plaintext secrets in shell history; prefer local-only input or a runtime injection pattern.',
188
+ );
189
+ }
190
+ return warnings;
191
+ }
192
+
193
+ const REQUIRED_PARAMS = {
194
+ 'ECS CreateServers': ['server.flavorRef', 'server.imageRef', 'server.nics.1.subnet_id'],
195
+ 'VPC CreateVpc': ['vpc.cidr'],
196
+ 'VPC CreateSubnet': ['subnet.vpc_id', 'subnet.cidr'],
197
+ 'VPC CreateSecurityGroupRule': ['security_group_rule.security_group_id', 'security_group_rule.direction', 'security_group_rule.protocol'],
198
+ 'EIP CreatePublicip': ['bandwidth.share_type', 'publicip.type'],
199
+ 'FunctionGraph CreateFunction': ['func_name', 'runtime', 'handler', 'memory_size', 'package', 'timeout'],
200
+ 'FunctionGraph CreateFunctionTrigger': ['function_urn', 'trigger_type_code'],
201
+ 'APIG CreateInstanceV2': ['spec_id'],
202
+ 'OBS mb': ['obs://'],
203
+ 'OBS rm': ['obs://'],
204
+ };
205
+
206
+ const PARAM_VALUE_HINTS = {
207
+ 'server.flavorRef': 'Run `hcloud ECS ListFlavors --cli-region=<r>` to find valid flavors',
208
+ 'server.imageRef': 'Run `hcloud IMS ListImages --cli-region=<r> --__imagetype=gold` to find valid image IDs',
209
+ 'server.nics.1.subnet_id': 'Run `hcloud VPC ListSubnets --cli-region=<r>` to find subnet IDs',
210
+ 'subnet.vpc_id': 'Run `hcloud VPC ListVpcs --cli-region=<r>` to find VPC IDs',
211
+ 'func_name': 'Function name must be unique within project',
212
+ 'runtime': 'Run `hcloud FunctionGraph ListRuntimes` to see available runtimes',
213
+ 'spec_id': 'APIG spec: BASIC (no public IP) or PROFESSIONAL (requires --loadbalancer_provider)',
214
+ 'obs://': 'Bucket name must be globally unique and DNS-compliant (lowercase, numbers, hyphens only)',
215
+ };
216
+
217
+ function validateRequiredParams(args) {
218
+ if (!args || args.length < 2) return { valid: true, missing: [], hints: [] };
219
+ const key = `${args[0]} ${args[1]}`;
220
+ const required = REQUIRED_PARAMS[key];
221
+ if (!required) return { valid: true, missing: [], hints: [] };
222
+ const argsStr = args.join(' ');
223
+ const missing = required.filter((param) => !argsStr.includes(param));
224
+ const hints = missing.map((param) => PARAM_VALUE_HINTS[param]).filter(Boolean);
225
+ return { valid: missing.length === 0, missing, hints };
226
+ }
227
+
228
+ function extractApiError(stdout) {
229
+ let text = String(stdout || '');
230
+ // Strip KooCLI multi-version prefix lines (e.g. "ListVpcs有多个版本,默认使用该API版本v3…")
231
+ const bracketIdx = text.indexOf('{');
232
+ if (bracketIdx > 0) text = text.substring(bracketIdx);
233
+ try {
234
+ const parsed = JSON.parse(text);
235
+ if (parsed.error_code || parsed.errorCode) {
236
+ return {
237
+ errorCode: parsed.error_code || parsed.errorCode || 'UNKNOWN',
238
+ errorMessage: parsed.error_msg || parsed.errorMsg || parsed.message || '',
239
+ };
240
+ }
241
+ if (parsed.error && typeof parsed.error === 'object') {
242
+ return {
243
+ errorCode: parsed.error.code || parsed.error.error_code || 'UNKNOWN',
244
+ errorMessage: parsed.error.message || parsed.error.error_msg || '',
245
+ };
246
+ }
247
+ } catch {}
248
+ const ecMatch = text.match(/"error_code"\s*:\s*"([^"]+)"/);
249
+ const emMatch = text.match(/"error_msg"\s*:\s*"([^"]+)"/);
250
+ if (ecMatch) {
251
+ return { errorCode: ecMatch[1], errorMessage: emMatch ? emMatch[1] : '' };
252
+ }
253
+ return null;
254
+ }
255
+
256
+ export function redactOutput(output) {
257
+ let text = String(output || '');
258
+ const bracketIdx = text.indexOf('{');
259
+ if (bracketIdx > 0) text = text.substring(bracketIdx);
260
+ try {
261
+ return JSON.stringify(redactSecrets(JSON.parse(text)), null, 2);
262
+ } catch {
263
+ return redactSecrets(text);
264
+ }
265
+ }