huaweicloud-devkit 1.1.2-next.0 → 1.1.2-next.2

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 (125) hide show
  1. package/.agents/plugins/marketplace.json +18 -18
  2. package/README.md +182 -38
  3. package/README.zh-CN.md +180 -36
  4. package/bin/dsh-postinstall.cjs +77 -0
  5. package/bin/setup.cjs +7 -4
  6. package/cordis.patch.yml +13 -0
  7. package/integrations/atomcode/hooks/huaweicloud-telemetry.js +181 -0
  8. package/integrations/atomcode/hooks.json +9 -0
  9. package/integrations/dsh/hook-plugin.mjs +148 -0
  10. package/integrations/hermes/hooks/huaweicloud-telemetry.py +238 -0
  11. package/integrations/hermes/manifest.yaml +43 -0
  12. package/integrations/opencode/hooks/skill-tracker.js +115 -0
  13. package/integrations/opencode/opencode.json +1 -4
  14. package/integrations/workbuddy/hooks/telemetry-tracker.py +226 -0
  15. package/package.json +74 -43
  16. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +4 -17
  17. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +23 -33
  18. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +4 -17
  19. package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +30 -0
  20. package/plugins/huaweicloud-core/.mcp.json +2 -5
  21. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +4 -18
  22. package/plugins/huaweicloud-core/assets/icon.png +0 -0
  23. package/plugins/huaweicloud-core/assets/logo-dark.png +0 -0
  24. package/plugins/huaweicloud-core/assets/logo.png +0 -0
  25. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +104 -28
  26. package/plugins/huaweicloud-core/openclaw.plugin.json +20 -0
  27. package/plugins/huaweicloud-core/safety/policy.json +75 -99
  28. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +196 -35
  29. package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +38 -34
  30. package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +21 -20
  31. package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +19 -19
  32. package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +38 -32
  33. package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +30 -30
  34. package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +20 -20
  35. package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +31 -31
  36. package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +21 -20
  37. package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +40 -31
  38. package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +8 -1
  39. package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +6 -0
  40. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +45 -37
  41. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +57 -6
  42. package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +11 -9
  43. package/plugins/huaweicloud-core/skills/huawei-ecs/references/hc-activity.md +27 -0
  44. package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +13 -13
  45. package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +44 -39
  46. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +12 -10
  47. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +21 -20
  48. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +19 -19
  49. package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +20 -20
  50. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +27 -23
  51. package/plugins/huaweicloud-core/skills/huawei-iac/SKILL.md +93 -0
  52. package/plugins/huaweicloud-core/skills/huawei-iac/references/architectures.md +83 -0
  53. package/plugins/huaweicloud-core/skills/huawei-iac/references/resource-catalog.md +107 -0
  54. package/plugins/huaweicloud-core/skills/huawei-iac/references/state-file.md +104 -0
  55. package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +30 -30
  56. package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +65 -39
  57. package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +9 -9
  58. package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +33 -25
  59. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +46 -45
  60. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +19 -19
  61. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +12 -7
  62. package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +13 -12
  63. package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +4 -4
  64. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +71 -61
  65. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +776 -44
  66. package/plugins/huaweicloud-core/skills/huawei-sandbox/references/framework-commands.md +84 -0
  67. package/plugins/huaweicloud-core/skills/huawei-sandbox/references/nginx-templates.md +100 -0
  68. package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +36 -29
  69. package/plugins/huaweicloud-core/skills/huawei-voucher/SKILL.md +57 -0
  70. package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +39 -39
  71. package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +22 -15
  72. package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +20 -19
  73. package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +15 -15
  74. package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +23 -20
  75. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +13 -13
  76. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +3 -3
  77. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +21 -2
  78. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +3 -3
  79. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +30 -4
  80. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +3 -3
  81. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +68 -54
  82. package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +3 -3
  83. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +6 -6
  84. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +15 -1
  85. package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +0 -1
  86. package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +3 -3
  87. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +36 -34
  88. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +3 -3
  89. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +142 -5
  90. package/plugins/huaweicloud-core/src/auth/credentials.mjs +217 -5
  91. package/plugins/huaweicloud-core/src/auth/reconcile.mjs +174 -0
  92. package/plugins/huaweicloud-core/src/auth/service.mjs +57 -2
  93. package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
  94. package/plugins/huaweicloud-core/src/detect-framework.mjs +366 -0
  95. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +203 -16
  96. package/plugins/huaweicloud-core/src/icon-library.mjs +176 -0
  97. package/plugins/huaweicloud-core/src/mcp-protocol.mjs +88 -0
  98. package/plugins/huaweicloud-core/src/mcp-server-remote.mjs +107 -0
  99. package/plugins/huaweicloud-core/src/mcp-server.mjs +154 -99
  100. package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +20 -102
  101. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +11 -4
  102. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +4 -1
  103. package/plugins/huaweicloud-core/src/safety-policy.mjs +123 -68
  104. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +48 -12
  105. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +25 -26
  106. package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
  107. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +943 -10
  108. package/plugins/huaweicloud-core/src/sandbox/uninstall-cleanup.mjs +50 -0
  109. package/plugins/huaweicloud-core/src/search-market.mjs +49 -14
  110. package/plugins/huaweicloud-core/src/setup-cli.mjs +3091 -498
  111. package/plugins/huaweicloud-core/src/telemetry/agent-detect.mjs +40 -0
  112. package/plugins/huaweicloud-core/src/telemetry/agent-registry.mjs +328 -0
  113. package/plugins/huaweicloud-core/src/telemetry/telemetry.mjs +378 -0
  114. package/plugins/huaweicloud-core/src/tools.mjs +1272 -146
  115. package/plugins/huaweicloud-core/src/update-check.mjs +383 -0
  116. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +72 -38
  117. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +10 -5
  118. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +13 -16
  119. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +12 -16
  120. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +46 -31
  121. package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +268 -0
  122. package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
  123. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +40 -13
  124. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +0 -190
  125. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +0 -124
@@ -0,0 +1,366 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ const FRAMEWORKS = {
5
+ nextjs: {
6
+ type: 'ssr',
7
+ framework: 'Next.js',
8
+ installCmd: 'npm install',
9
+ buildCmd: 'npm run build',
10
+ outputDir: '.next',
11
+ port: 3000,
12
+ serveCmd: 'nohup npm start > /tmp/app.log 2>&1 &',
13
+ checkUrl: 'http://localhost:3000',
14
+ nginxType: 'proxy',
15
+ },
16
+ nuxt: {
17
+ type: 'ssr',
18
+ framework: 'Nuxt',
19
+ installCmd: 'npm install',
20
+ buildCmd: 'npm run build',
21
+ outputDir: '.output',
22
+ port: 3000,
23
+ serveCmd: 'nohup node .output/server/index.mjs > /tmp/app.log 2>&1 &',
24
+ checkUrl: 'http://localhost:3000',
25
+ nginxType: 'proxy',
26
+ },
27
+ vitepress: {
28
+ type: 'ssg',
29
+ framework: 'VitePress',
30
+ installCmd: 'npm install',
31
+ buildCmd: 'npm run docs:build',
32
+ outputDir: '.vitepress/dist',
33
+ port: 8080,
34
+ serveCmd: null,
35
+ checkUrl: 'http://localhost:8080',
36
+ nginxType: 'spa',
37
+ },
38
+ docusaurus: {
39
+ type: 'ssg',
40
+ framework: 'Docusaurus',
41
+ installCmd: 'npm install',
42
+ buildCmd: 'npm run build',
43
+ outputDir: 'build',
44
+ port: 8080,
45
+ serveCmd: null,
46
+ checkUrl: 'http://localhost:8080',
47
+ nginxType: 'spa',
48
+ },
49
+ hugo: {
50
+ type: 'ssg',
51
+ framework: 'Hugo',
52
+ installCmd: null,
53
+ buildCmd: 'hugo',
54
+ outputDir: 'public',
55
+ port: 8080,
56
+ serveCmd: null,
57
+ checkUrl: 'http://localhost:8080',
58
+ nginxType: 'static',
59
+ },
60
+ hexo: {
61
+ type: 'ssg',
62
+ framework: 'Hexo',
63
+ installCmd: 'npm install',
64
+ buildCmd: 'npm run build',
65
+ outputDir: 'public',
66
+ port: 8080,
67
+ serveCmd: null,
68
+ checkUrl: 'http://localhost:8080',
69
+ nginxType: 'static',
70
+ },
71
+ taro: {
72
+ type: 'cross-platform',
73
+ framework: 'Taro',
74
+ installCmd: 'npm install',
75
+ buildCmd: 'npm run build:h5',
76
+ outputDir: 'dist',
77
+ port: 8080,
78
+ serveCmd: null,
79
+ checkUrl: 'http://localhost:8080',
80
+ nginxType: 'spa',
81
+ },
82
+ uniapp: {
83
+ type: 'cross-platform',
84
+ framework: 'uni-app',
85
+ installCmd: 'npm install',
86
+ buildCmd: 'npm run build:h5',
87
+ outputDir: 'dist/build/h5',
88
+ port: 8080,
89
+ serveCmd: null,
90
+ checkUrl: 'http://localhost:8080',
91
+ nginxType: 'spa',
92
+ },
93
+ angular: {
94
+ type: 'spa',
95
+ framework: 'Angular',
96
+ installCmd: 'npm install',
97
+ buildCmd: 'npm run build',
98
+ outputDir: null,
99
+ port: 8080,
100
+ serveCmd: null,
101
+ checkUrl: 'http://localhost:8080',
102
+ nginxType: 'spa',
103
+ },
104
+ vite: {
105
+ type: 'spa',
106
+ framework: 'Vite (React/Vue/Svelte)',
107
+ installCmd: 'npm install',
108
+ buildCmd: 'npm run build',
109
+ outputDir: 'dist',
110
+ port: 8080,
111
+ serveCmd: null,
112
+ checkUrl: 'http://localhost:8080',
113
+ nginxType: 'spa',
114
+ },
115
+ cra: {
116
+ type: 'spa',
117
+ framework: 'Create React App',
118
+ installCmd: 'npm install',
119
+ buildCmd: 'npm run build',
120
+ outputDir: 'build',
121
+ port: 8080,
122
+ serveCmd: null,
123
+ checkUrl: 'http://localhost:8080',
124
+ nginxType: 'spa',
125
+ },
126
+ vueCli: {
127
+ type: 'spa',
128
+ framework: 'Vue CLI',
129
+ installCmd: 'npm install',
130
+ buildCmd: 'npm run build',
131
+ outputDir: 'dist',
132
+ port: 8080,
133
+ serveCmd: null,
134
+ checkUrl: 'http://localhost:8080',
135
+ nginxType: 'spa',
136
+ },
137
+ static: {
138
+ type: 'static',
139
+ framework: 'Static Site',
140
+ installCmd: null,
141
+ buildCmd: null,
142
+ outputDir: '.',
143
+ port: 8080,
144
+ serveCmd: null,
145
+ checkUrl: 'http://localhost:8080',
146
+ nginxType: 'static',
147
+ },
148
+ };
149
+
150
+ function readPkg(projectPath) {
151
+ const pkgPath = join(projectPath, 'package.json');
152
+ if (!existsSync(pkgPath)) return null;
153
+ try {
154
+ return JSON.parse(readFileSync(pkgPath, 'utf8'));
155
+ } catch {
156
+ return null;
157
+ }
158
+ }
159
+
160
+ function detectPackageManager(projectPath, pkg) {
161
+ if (existsSync(join(projectPath, 'pnpm-lock.yaml'))) return 'pnpm';
162
+ if (existsSync(join(projectPath, 'yarn.lock'))) return 'yarn';
163
+ if (existsSync(join(projectPath, 'package-lock.json'))) return 'npm';
164
+ if (existsSync(join(projectPath, 'bun.lockb'))) return 'bun';
165
+ const declared = (pkg?.packageManager || '').split('@')[0];
166
+ if (declared && ['pnpm', 'yarn', 'npm', 'bun'].includes(declared)) return declared;
167
+ return 'npm';
168
+ }
169
+
170
+ function collectDeps(pkg) {
171
+ if (!pkg) return {};
172
+ return { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
173
+ }
174
+
175
+ function scanSubApps(projectPath) {
176
+ const apps = [];
177
+ for (const dir of ['apps', 'packages']) {
178
+ const dirPath = join(projectPath, dir);
179
+ if (!existsSync(dirPath)) continue;
180
+ try {
181
+ const entries = readdirSync(dirPath, { withFileTypes: true });
182
+ for (const entry of entries) {
183
+ if (!entry.isDirectory()) continue;
184
+ const subPath = join(dirPath, entry.name);
185
+ const subPkg = readPkg(subPath);
186
+ if (!subPkg) continue;
187
+ const result = detectFramework(subPath);
188
+ if (!result) continue;
189
+ apps.push({
190
+ name: subPkg.name || entry.name,
191
+ path: `${dir}/${entry.name}`,
192
+ framework: result.framework,
193
+ type: result.type,
194
+ });
195
+ }
196
+ } catch {
197
+ // skip unreadable directories
198
+ }
199
+ }
200
+ return apps;
201
+ }
202
+
203
+ function resolveAngularOutput(projectPath, pkg) {
204
+ try {
205
+ const angularJson = join(projectPath, 'angular.json');
206
+ if (existsSync(angularJson)) {
207
+ const config = JSON.parse(readFileSync(angularJson, 'utf8'));
208
+ const defaultProject = config.defaultProject || Object.keys(config.projects || {})[0];
209
+ if (defaultProject && config.projects?.[defaultProject]?.architect?.build?.options?.outputPath) {
210
+ return config.projects[defaultProject].architect.build.options.outputPath;
211
+ }
212
+ }
213
+ } catch {
214
+ // ignore parse errors
215
+ }
216
+ const name = pkg?.name || 'app';
217
+ return `dist/${name}`;
218
+ }
219
+
220
+ function patchCommands(result, pm) {
221
+ if (result.installCmd && result.installCmd.startsWith('npm install')) {
222
+ if (pm === 'pnpm') result.installCmd = 'pnpm install';
223
+ else if (pm === 'yarn') result.installCmd = 'yarn install';
224
+ }
225
+ if (result.buildCmd && result.buildCmd.startsWith('npm run')) {
226
+ if (pm === 'pnpm') result.buildCmd = result.buildCmd.replace('npm run', 'pnpm run');
227
+ else if (pm === 'yarn') result.buildCmd = result.buildCmd.replace('npm run', 'yarn run');
228
+ }
229
+ if (result.serveCmd && result.serveCmd.includes('npm start')) {
230
+ if (pm === 'pnpm') result.serveCmd = result.serveCmd.replace('npm start', 'pnpm start');
231
+ else if (pm === 'yarn') result.serveCmd = result.serveCmd.replace('npm start', 'yarn start');
232
+ }
233
+ return result;
234
+ }
235
+
236
+ function frameworkResult(fw, pm, projectPath) {
237
+ return patchCommands({ ...fw, packageManager: pm, rootDir: projectPath }, pm);
238
+ }
239
+
240
+ function readVitepressOutDir(projectPath) {
241
+ const configFiles = ['config.mts', 'config.ts', 'config.mjs', 'config.js'];
242
+ for (const cf of configFiles) {
243
+ const configPath = join(projectPath, '.vitepress', cf);
244
+ if (!existsSync(configPath)) continue;
245
+ try {
246
+ const content = readFileSync(configPath, 'utf8');
247
+ const match = content.match(/outDir\s*:\s*['"`]?([^'"`\s,}]+)['"`]?/);
248
+ if (match) return match[1];
249
+ } catch {}
250
+ }
251
+ return null;
252
+ }
253
+
254
+ export function detectFramework(projectPath) {
255
+ const pkg = readPkg(projectPath);
256
+ const deps = collectDeps(pkg);
257
+ const pm = detectPackageManager(projectPath, pkg);
258
+
259
+ if (
260
+ existsSync(join(projectPath, 'pnpm-workspace.yaml')) ||
261
+ existsSync(join(projectPath, 'turbo.json')) ||
262
+ existsSync(join(projectPath, 'nx.json')) ||
263
+ existsSync(join(projectPath, 'lerna.json'))
264
+ ) {
265
+ const monorepoSubApps = scanSubApps(projectPath);
266
+ return {
267
+ type: 'monorepo',
268
+ framework: 'Monorepo',
269
+ monorepoTool: existsSync(join(projectPath, 'turbo.json'))
270
+ ? 'Turborepo'
271
+ : existsSync(join(projectPath, 'nx.json'))
272
+ ? 'Nx'
273
+ : existsSync(join(projectPath, 'lerna.json'))
274
+ ? 'Lerna'
275
+ : 'pnpm Workspace',
276
+ subApps: monorepoSubApps,
277
+ packageManager: pm,
278
+ rootDir: projectPath,
279
+ };
280
+ }
281
+
282
+ if (
283
+ existsSync(join(projectPath, 'next.config.js')) ||
284
+ existsSync(join(projectPath, 'next.config.mjs')) ||
285
+ existsSync(join(projectPath, 'next.config.ts'))
286
+ ) {
287
+ return frameworkResult(FRAMEWORKS.nextjs, pm, projectPath);
288
+ }
289
+
290
+ if (
291
+ existsSync(join(projectPath, 'nuxt.config.js')) ||
292
+ existsSync(join(projectPath, 'nuxt.config.mjs')) ||
293
+ existsSync(join(projectPath, 'nuxt.config.ts'))
294
+ ) {
295
+ return frameworkResult(FRAMEWORKS.nuxt, pm, projectPath);
296
+ }
297
+
298
+ if ('@tarojs/taro' in deps) {
299
+ return frameworkResult(FRAMEWORKS.taro, pm, projectPath);
300
+ }
301
+
302
+ if ('@dcloudio/uni-app' in deps || 'uni-app' in deps) {
303
+ return frameworkResult(FRAMEWORKS.uniapp, pm, projectPath);
304
+ }
305
+
306
+ if (existsSync(join(projectPath, '.vitepress'))) {
307
+ const outDir = readVitepressOutDir(projectPath);
308
+ return frameworkResult(
309
+ outDir ? { ...FRAMEWORKS.vitepress, outputDir: outDir } : FRAMEWORKS.vitepress,
310
+ pm,
311
+ projectPath,
312
+ );
313
+ }
314
+
315
+ if (
316
+ existsSync(join(projectPath, 'docusaurus.config.js')) ||
317
+ existsSync(join(projectPath, 'docusaurus.config.ts')) ||
318
+ existsSync(join(projectPath, 'docusaurus.config.mjs'))
319
+ ) {
320
+ return frameworkResult(FRAMEWORKS.docusaurus, pm, projectPath);
321
+ }
322
+
323
+ if (existsSync(join(projectPath, 'hugo.toml')) || existsSync(join(projectPath, 'config.toml'))) {
324
+ return frameworkResult(FRAMEWORKS.hugo, pm, projectPath);
325
+ }
326
+
327
+ if (existsSync(join(projectPath, '_config.yml'))) {
328
+ return frameworkResult(FRAMEWORKS.hexo, pm, projectPath);
329
+ }
330
+
331
+ if (existsSync(join(projectPath, 'angular.json'))) {
332
+ const outputDir = resolveAngularOutput(projectPath, pkg);
333
+ return frameworkResult({ ...FRAMEWORKS.angular, outputDir }, pm, projectPath);
334
+ }
335
+
336
+ if (
337
+ existsSync(join(projectPath, 'vite.config.js')) ||
338
+ existsSync(join(projectPath, 'vite.config.mjs')) ||
339
+ existsSync(join(projectPath, 'vite.config.ts'))
340
+ ) {
341
+ return frameworkResult(FRAMEWORKS.vite, pm, projectPath);
342
+ }
343
+
344
+ if ('@vue/cli-service' in deps) {
345
+ return frameworkResult(FRAMEWORKS.vueCli, pm, projectPath);
346
+ }
347
+
348
+ if (
349
+ 'react-scripts' in deps ||
350
+ 'react' in deps ||
351
+ '@angular/core' in deps ||
352
+ 'vue' in deps ||
353
+ 'svelte' in deps ||
354
+ 'solid-js' in deps
355
+ ) {
356
+ if (existsSync(join(projectPath, 'public', 'index.html')) || existsSync(join(projectPath, 'index.html'))) {
357
+ return frameworkResult(FRAMEWORKS.cra, pm, projectPath);
358
+ }
359
+ }
360
+
361
+ if (existsSync(join(projectPath, 'index.html'))) {
362
+ return frameworkResult(FRAMEWORKS.static, pm, projectPath);
363
+ }
364
+
365
+ return null;
366
+ }
@@ -1,15 +1,145 @@
1
- import { spawn } from 'node:child_process';
1
+ import { spawn, spawnSync } from 'node:child_process';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
4
+ import { homedir } from 'node:os';
5
+ import { join } from 'node:path';
6
+
2
7
  import { classifyHcloudArgs, redactSecrets, assertAllowed } from './safety-policy.mjs';
8
+ import { getProxySettings } from './proxy/proxy-config.mjs';
3
9
 
4
10
  const DEFAULT_TIMEOUT_MS = 60_000;
5
11
  const DEFAULT_FORCE_KILL_AFTER_MS = 2_000;
6
12
  const DEFAULT_MAX_RETRIES = 1;
13
+ const APPROVAL_TTL_MS = 5 * 60_000;
14
+ const LARGE_OUTPUT_THRESHOLD = 50_000;
15
+ const OUTPUT_DIR = join('/tmp', 'huaweicloud-devkit');
16
+
17
+ const approvalStore = new Map();
18
+
19
+ export function createApprovalToken(rawArgs) {
20
+ const token = randomUUID();
21
+ approvalStore.set(token, { rawArgs, createdAt: Date.now() });
22
+ if (approvalStore.size % 20 === 0) {
23
+ const now = Date.now();
24
+ for (const [k, v] of approvalStore) {
25
+ if (now - v.createdAt > APPROVAL_TTL_MS) approvalStore.delete(k);
26
+ }
27
+ }
28
+ return token;
29
+ }
30
+
31
+ export function consumeApprovalToken(token) {
32
+ const entry = approvalStore.get(token);
33
+ if (!entry) return null;
34
+ if (Date.now() - entry.createdAt > APPROVAL_TTL_MS) {
35
+ approvalStore.delete(token);
36
+ return null;
37
+ }
38
+ approvalStore.delete(token);
39
+ return entry.rawArgs;
40
+ }
41
+
42
+ function saveLargeOutput(rawStdout) {
43
+ if (rawStdout.length <= LARGE_OUTPUT_THRESHOLD) return null;
44
+ try {
45
+ mkdirSync(OUTPUT_DIR, { recursive: true });
46
+ const filePath = join(OUTPUT_DIR, `output-${Date.now()}.json`);
47
+ writeFileSync(filePath, rawStdout, { encoding: 'utf8' });
48
+ return filePath;
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ function preflightSecurityGroupCheck(normalizedArgs) {
55
+ const service = normalizedArgs[0];
56
+ const operation = normalizedArgs[1];
57
+ if (service !== 'ECS' || !/CreateServers|RunInstances/i.test(operation)) return [];
58
+
59
+ const sgIds = [];
60
+ for (const arg of normalizedArgs) {
61
+ const m = arg.match(/^(?:--security_group_id(?:\.\d+)?|--server\.security_groups(?:\.\d+)?\.id)=(.+)/);
62
+ if (m) sgIds.push(m[1]);
63
+ }
64
+ if (sgIds.length === 0) return [];
65
+
66
+ const regionArg = normalizedArgs.find((a) => a.startsWith('--cli-region='));
67
+ const findings = [];
68
+ for (const sgId of sgIds) {
69
+ try {
70
+ const hcloudBin = process.env.HCLOUD_BIN || 'hcloud';
71
+ const spawnArgs = ['VPC', 'ListSecurityGroupRules', `--security_group_id.1=${sgId}`];
72
+ if (regionArg) spawnArgs.push(regionArg);
73
+ const r = spawnSync(hcloudBin, spawnArgs, {
74
+ shell: false,
75
+ windowsHide: true,
76
+ stdio: 'pipe',
77
+ timeout: 20000,
78
+ });
79
+ const stdout = (r.stdout || '').toString();
80
+ if (r.status !== 0 || !stdout.includes('security_group_rules')) continue;
81
+
82
+ const bracketIdx = stdout.indexOf('{');
83
+ const jsonText = bracketIdx >= 0 ? stdout.slice(bracketIdx) : stdout;
84
+ const data = JSON.parse(jsonText);
85
+ const rules = data?.security_group_rules || [];
86
+
87
+ for (const rule of rules) {
88
+ const direction = rule.direction || '';
89
+ const remoteIp = rule.remote_ip_prefix || rule.remote_address_group_id || '';
90
+ const portMin = String(rule.port_range_min || rule.multiport || '');
91
+ const portMax = String(rule.port_range_max || rule.multiport || '');
92
+ const protocol = (rule.protocol || '').toLowerCase();
93
+ if (direction !== 'ingress') continue;
94
+ if (remoteIp !== '0.0.0.0/0' && remoteIp !== '::/0') continue;
95
+ if (protocol === 'icmp' || protocol === 'icmpv6') {
96
+ findings.push({
97
+ severity: 'warn',
98
+ title: `Security group ${sgId}: ICMP open to public`,
99
+ message: `安全组 ${sgId} 对公网开放了 ICMP (ping),可能被用于探测。是否继续?`,
100
+ });
101
+ continue;
102
+ }
103
+ const sensitivePorts = ['22', '3389', '3306', '5432', '6379', '9200', '27017', '8080', '8443'];
104
+ const port = portMin || portMax;
105
+ if (port && sensitivePorts.includes(port)) {
106
+ findings.push({
107
+ severity: 'deny',
108
+ title: `Security group ${sgId}: port ${port} open to public`,
109
+ message: `安全组 ${sgId} 已对公网开放 ${port} 端口(${protocol || 'TCP'})。确认继续创建 ECS?`,
110
+ });
111
+ }
112
+ }
113
+ } catch (error) {
114
+ // Preflight failed silently; don't block the plan
115
+ }
116
+ }
117
+ return findings;
118
+ }
119
+
120
+ function applyPreflightFindings(classification, sgFindings) {
121
+ if (!sgFindings || sgFindings.length === 0) return classification;
122
+ const hasDeny = sgFindings.some((f) => f.severity === 'deny');
123
+ if (hasDeny) {
124
+ return {
125
+ ...classification,
126
+ decision: 'deny',
127
+ risk: 'public_exposure',
128
+ reason: sgFindings[0].message,
129
+ };
130
+ }
131
+ return classification;
132
+ }
7
133
 
8
134
  export function planHcloudCommand(args, options = {}) {
9
135
  const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
10
136
  const classification = classifyHcloudArgs(normalizedArgs, options);
11
- const command = ['hcloud', ...normalizedArgs].map(quoteShellArg).join(' ');
137
+ const command = ['hcloud', ...normalizedArgs].map((arg) => quoteShellArg(arg)).join(' ');
12
138
  const warnings = planningWarnings(normalizedArgs);
139
+ const sgFindings = preflightSecurityGroupCheck(normalizedArgs);
140
+ if (sgFindings.length > 0) {
141
+ for (const f of sgFindings) warnings.push(f);
142
+ }
13
143
  const paramValidation = validateRequiredParams(normalizedArgs);
14
144
  if (paramValidation.missing.length > 0) {
15
145
  warnings.push('Missing required parameters: ' + paramValidation.missing.join(', '));
@@ -23,7 +153,9 @@ export function planHcloudCommand(args, options = {}) {
23
153
  command: redactOutput(command),
24
154
  executableBlock: redactOutput(command),
25
155
  warnings,
26
- classification,
156
+ classification: applyPreflightFindings(classification, sgFindings),
157
+ sgFindings,
158
+ approvalToken: createApprovalToken(normalizedArgs),
27
159
  safeToRun: classification.decision === 'allow',
28
160
  };
29
161
  }
@@ -40,39 +172,78 @@ export async function runHcloud(args, options = {}) {
40
172
  for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
41
173
  const result = await runHcloudOnce(plan, options);
42
174
  if (result.ok || attempt >= maxRetries || !isRetryableNetworkError(result)) {
43
- return {
175
+ const merged = {
44
176
  ...result,
45
177
  retries: attempt,
46
178
  attempts: attempt + 1,
47
179
  };
180
+ if (merged.ok) {
181
+ const warning = await runtimeCurrentMismatchWarning();
182
+ return warning ? { ...merged, authWarning: warning } : merged;
183
+ }
184
+ return merged;
48
185
  }
49
- await wait((options.retryBaseDelayMs ?? 500) * (2 ** attempt));
186
+ await wait((options.retryBaseDelayMs ?? 500) * 2 ** attempt);
50
187
  }
51
188
  throw new Error('Unreachable retry state.');
52
189
  }
53
190
 
191
+ async function runtimeCurrentMismatchWarning() {
192
+ try {
193
+ const { hasRuntimeCredentials, scanState } = await import('./auth/reconcile.mjs');
194
+ if (!hasRuntimeCredentials()) return null;
195
+ const scan = scanState();
196
+ const { runtimeFingerprint, currentFingerprint } = scan.stores;
197
+ if (runtimeFingerprint && currentFingerprint && runtimeFingerprint !== currentFingerprint) {
198
+ return '会话内临时账号与 KooCLI current 档不一致:hcloud 命令仍使用 current 档账号。如需对齐请用 huaweicloud_auth_switch action=persist。';
199
+ }
200
+ return null;
201
+ } catch {
202
+ // reconcile unavailable → no warning, never block the command
203
+ return null;
204
+ }
205
+ }
206
+
207
+ function discoverHcloudPath() {
208
+ if (process.env.HCLOUD_BIN && existsSync(process.env.HCLOUD_BIN)) return process.env.HCLOUD_BIN;
209
+ const candidates =
210
+ process.platform === 'win32'
211
+ ? [join(homedir(), 'hcloud', 'hcloud.exe')]
212
+ : [join(homedir(), '.local', 'bin', 'hcloud'), join(homedir(), 'hcloud', 'hcloud')];
213
+ return candidates.find((c) => existsSync(c)) || null;
214
+ }
215
+
54
216
  function runHcloudOnce(plan, options) {
55
217
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
56
218
  const forceKillAfterMs = options.forceKillAfterMs ?? DEFAULT_FORCE_KILL_AFTER_MS;
57
- const executable = options.executable || options.env?.HCLOUD_BIN || process.env.HCLOUD_BIN || 'hcloud';
219
+ const executable = options.executable || options.env?.HCLOUD_BIN || discoverHcloudPath() || 'hcloud';
58
220
  const executableArgs = Array.isArray(options.executableArgs) ? options.executableArgs.map(String) : [];
59
221
  const cwd = options.cwd || undefined;
222
+ const stdin = options.stdin ?? 'y\n';
60
223
 
61
224
  return new Promise((resolve) => {
225
+ const proxySettings = getProxySettings();
226
+ const proxyEnv = {};
227
+ if (proxySettings) {
228
+ if (proxySettings.https_proxy) proxyEnv.HTTPS_PROXY = proxySettings.https_proxy;
229
+ if (proxySettings.http_proxy) proxyEnv.HTTP_PROXY = proxySettings.http_proxy;
230
+ if (proxySettings.no_proxy) proxyEnv.NO_PROXY = proxySettings.no_proxy;
231
+ }
62
232
  const child = spawn(executable, [...executableArgs, ...plan.rawArgs], {
63
233
  shell: false,
64
234
  windowsHide: true,
65
235
  cwd,
66
236
  env: {
67
237
  ...process.env,
238
+ ...proxyEnv,
68
239
  ...options.env,
69
240
  },
70
241
  });
71
- if (options.stdin) {
72
- if (typeof options.stdin === 'function') {
73
- options.stdin(child.stdin);
242
+ if (stdin) {
243
+ if (typeof stdin === 'function') {
244
+ stdin(child.stdin);
74
245
  } else {
75
- child.stdin.write(String(options.stdin));
246
+ child.stdin.write(String(stdin));
76
247
  child.stdin.end();
77
248
  }
78
249
  }
@@ -89,6 +260,13 @@ function runHcloudOnce(plan, options) {
89
260
  clearTimeout(timer);
90
261
  clearTimeout(forceTimer);
91
262
  clearTimeout(settleTimer);
263
+ if (result.stdout && String(result.stdout).length > LARGE_OUTPUT_THRESHOLD) {
264
+ const outputFile = saveLargeOutput(stdout);
265
+ if (outputFile) {
266
+ result.outputFile = outputFile;
267
+ result.stdout = String(result.stdout).slice(0, 2000) + `\n...(truncated, full output saved to ${outputFile})`;
268
+ }
269
+ }
92
270
  resolve(result);
93
271
  }
94
272
 
@@ -151,7 +329,10 @@ function runHcloudOnce(plan, options) {
151
329
  return;
152
330
  }
153
331
  finish({
154
- ok: code === 0 || (code !== 0 && /successfully|succ?ess.*\[200\]|create bucket successfully|upload successfully/i.test(stdout + stderr)),
332
+ ok:
333
+ code === 0 ||
334
+ (code !== 0 &&
335
+ /successfully|succ?ess.*\[200\]|create bucket successfully|upload successfully/i.test(stdout + stderr)),
155
336
  exitCode: code,
156
337
  signal,
157
338
  stdout: redactOutput(stdout),
@@ -165,7 +346,9 @@ function runHcloudOnce(plan, options) {
165
346
  function isRetryableNetworkError(result) {
166
347
  if (result.code === 'TIMEOUT') return false;
167
348
  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);
349
+ return /\[NETWORK_ERROR\]|connection timed out|ECONNRESET|ETIMEDOUT|temporary failure|TLS handshake timeout/i.test(
350
+ text,
351
+ );
169
352
  }
170
353
 
171
354
  function wait(ms) {
@@ -194,7 +377,11 @@ const REQUIRED_PARAMS = {
194
377
  'ECS CreateServers': ['server.flavorRef', 'server.imageRef', 'server.nics.1.subnet_id'],
195
378
  'VPC CreateVpc': ['vpc.cidr'],
196
379
  'VPC CreateSubnet': ['subnet.vpc_id', 'subnet.cidr'],
197
- 'VPC CreateSecurityGroupRule': ['security_group_rule.security_group_id', 'security_group_rule.direction', 'security_group_rule.protocol'],
380
+ 'VPC CreateSecurityGroupRule': [
381
+ 'security_group_rule.security_group_id',
382
+ 'security_group_rule.direction',
383
+ 'security_group_rule.protocol',
384
+ ],
198
385
  'EIP CreatePublicip': ['bandwidth.share_type', 'publicip.type'],
199
386
  'FunctionGraph CreateFunction': ['func_name', 'runtime', 'handler', 'memory_size', 'package', 'timeout'],
200
387
  'FunctionGraph CreateFunctionTrigger': ['function_urn', 'trigger_type_code'],
@@ -208,9 +395,9 @@ const PARAM_VALUE_HINTS = {
208
395
  'server.imageRef': 'Run `hcloud IMS ListImages --cli-region=<r> --__imagetype=gold` to find valid image IDs',
209
396
  'server.nics.1.subnet_id': 'Run `hcloud VPC ListSubnets --cli-region=<r>` to find subnet IDs',
210
397
  '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)',
398
+ func_name: 'Function name must be unique within project',
399
+ runtime: 'Run `hcloud FunctionGraph ListRuntimes` to see available runtimes',
400
+ spec_id: 'APIG spec: BASIC (no public IP) or PROFESSIONAL (requires --loadbalancer_provider)',
214
401
  'obs://': 'Bucket name must be globally unique and DNS-compliant (lowercase, numbers, hyphens only)',
215
402
  };
216
403