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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/.agents/plugins/marketplace.json +18 -18
  2. package/README.md +114 -41
  3. package/README.zh-CN.md +114 -41
  4. package/bin/setup.cjs +7 -4
  5. package/integrations/opencode/opencode.json +1 -4
  6. package/package.json +63 -42
  7. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +4 -17
  8. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +4 -17
  9. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +4 -17
  10. package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +30 -0
  11. package/plugins/huaweicloud-core/.mcp.json +2 -5
  12. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +4 -18
  13. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +32 -9
  14. package/plugins/huaweicloud-core/openclaw.plugin.json +20 -0
  15. package/plugins/huaweicloud-core/safety/policy.json +75 -99
  16. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +41 -13
  17. package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +34 -34
  18. package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +18 -18
  19. package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +19 -19
  20. package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +34 -32
  21. package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +30 -30
  22. package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +20 -20
  23. package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +31 -31
  24. package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +21 -20
  25. package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +39 -30
  26. package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +8 -1
  27. package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +6 -0
  28. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +39 -36
  29. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +15 -4
  30. package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +11 -9
  31. package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +13 -13
  32. package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +40 -39
  33. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +12 -10
  34. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +21 -20
  35. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +19 -19
  36. package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +20 -20
  37. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +27 -23
  38. package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +27 -27
  39. package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +65 -39
  40. package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +9 -9
  41. package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +33 -25
  42. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +51 -44
  43. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +19 -19
  44. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +12 -7
  45. package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +13 -12
  46. package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +4 -4
  47. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +63 -58
  48. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +128 -42
  49. package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +26 -26
  50. package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +39 -39
  51. package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +13 -12
  52. package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +20 -19
  53. package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +15 -15
  54. package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +18 -18
  55. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +13 -13
  56. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +3 -3
  57. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +25 -2
  58. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +3 -3
  59. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +27 -1
  60. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +3 -3
  61. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +65 -47
  62. package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +3 -3
  63. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +6 -6
  64. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +15 -1
  65. package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +0 -1
  66. package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +3 -3
  67. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +33 -33
  68. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +3 -3
  69. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +134 -2
  70. package/plugins/huaweicloud-core/src/auth/credentials.mjs +77 -4
  71. package/plugins/huaweicloud-core/src/auth/service.mjs +2 -1
  72. package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
  73. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +27 -8
  74. package/plugins/huaweicloud-core/src/icon-library.mjs +176 -0
  75. package/plugins/huaweicloud-core/src/mcp-server.mjs +31 -3
  76. package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +73 -0
  77. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +94 -0
  78. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +4 -1
  79. package/plugins/huaweicloud-core/src/safety-policy.mjs +123 -68
  80. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +14 -10
  81. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +35 -27
  82. package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
  83. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +526 -8
  84. package/plugins/huaweicloud-core/src/search-market.mjs +55 -16
  85. package/plugins/huaweicloud-core/src/setup-cli.mjs +1942 -206
  86. package/plugins/huaweicloud-core/src/tools.mjs +678 -131
  87. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +72 -38
  88. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +10 -5
  89. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +13 -16
  90. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +12 -16
  91. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +46 -31
  92. package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +268 -0
  93. package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
  94. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +11 -5
  95. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +0 -190
  96. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +0 -124
@@ -1,13 +1,36 @@
1
- #!/usr/bin/env node
2
-
3
- import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
1
+ import {
2
+ copyFileSync,
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ readdirSync,
7
+ rmSync,
8
+ statSync,
9
+ writeFileSync,
10
+ } from 'node:fs';
4
11
  import { join, dirname, resolve } from 'node:path';
5
12
  import { fileURLToPath } from 'node:url';
6
13
  import { homedir, platform } from 'node:os';
7
14
  import { createInterface } from 'node:readline';
8
15
  import { spawnSync } from 'node:child_process';
16
+ import { randomUUID } from 'node:crypto';
17
+ import Database from 'better-sqlite3';
18
+
9
19
  import { getAuthStatus, syncAuth } from './auth/service.mjs';
10
- import { globalCredentialsPath, readGlobalCredentials, writeGlobalCredentials, writeObsConfig } from './auth/credentials.mjs';
20
+ import { SUPPORTED_AGENT_TARGETS } from './auth/agent-registration.mjs';
21
+ import {
22
+ globalCredentialsPath,
23
+ readGlobalCredentials,
24
+ writeGlobalCredentials,
25
+ writeObsConfig,
26
+ } from './auth/credentials.mjs';
27
+ import {
28
+ proxyConfigPath,
29
+ readProxyConfig,
30
+ writeProxyConfig,
31
+ clearProxyConfig,
32
+ getProxySettings,
33
+ } from './proxy/proxy-config.mjs';
11
34
 
12
35
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
36
  const PLUGIN_ROOT = resolve(__dirname, '..');
@@ -30,31 +53,333 @@ function configRoot(target = 'opencode') {
30
53
  return join(home, '.config', target);
31
54
  }
32
55
 
33
- function opencodeSkillsDir() { return join(configRoot('opencode'), 'skills'); }
34
- function opencodeCommandsDir() { return join(configRoot('opencode'), 'commands'); }
35
- function opencodePluginsDir() { return join(configRoot('opencode'), 'huaweicloud-plugins'); }
56
+ function opencodeSkillsDir() {
57
+ return join(configRoot('opencode'), 'skills');
58
+ }
59
+ function opencodeCommandsDir() {
60
+ return join(configRoot('opencode'), 'commands');
61
+ }
62
+ function opencodePluginsDir() {
63
+ return join(configRoot('opencode'), 'huaweicloud-plugins');
64
+ }
36
65
  function opencodeConfigFile() {
37
66
  const jsonc = join(configRoot('opencode'), 'opencode.jsonc');
38
67
  if (existsSync(jsonc)) return jsonc;
39
68
  return join(configRoot('opencode'), 'opencode.json');
40
69
  }
41
70
 
42
- function codexDesktopSkillsDir() { return join(homedir(), '.agents', 'skills'); }
43
- function codexDesktopCommandsDir() { return join(homedir(), '.agents', 'commands'); }
44
- function codexDesktopPluginsDir() { return join(homedir(), '.agents', 'huaweicloud-plugins'); }
45
- function codexConfigToml() { return join(homedir(), '.codex', 'config.toml'); }
71
+ function codexDesktopSkillsDir() {
72
+ return join(homedir(), '.agents', 'skills');
73
+ }
74
+ function codexDesktopCommandsDir() {
75
+ return join(homedir(), '.agents', 'commands');
76
+ }
77
+ function codexDesktopPluginsDir() {
78
+ return join(homedir(), '.agents', 'huaweicloud-plugins');
79
+ }
80
+ function codexConfigToml() {
81
+ return join(homedir(), '.codex', 'config.toml');
82
+ }
83
+
84
+ function codeartsSkillsDir() {
85
+ return join(homedir(), '.codeartsdoer', 'skills');
86
+ }
87
+ function codeartsMcpSettingsDir() {
88
+ return join(homedir(), '.codeartsdoer', 'mcp');
89
+ }
90
+ function codeartsMcpSettingsFile() {
91
+ return join(codeartsMcpSettingsDir(), 'mcp_settings.json');
92
+ }
93
+ function codeartsProjectDir() {
94
+ return join(process.cwd(), '.codeartsdoer');
95
+ }
96
+ function codeartsProjectSkillsDir() {
97
+ return join(codeartsProjectDir(), 'skills');
98
+ }
99
+ function codeartsProjectMcpSettingsFile() {
100
+ return join(codeartsProjectDir(), 'mcp', 'mcp_settings.json');
101
+ }
102
+ function codeartsPluginsDir() {
103
+ return join(homedir(), '.codeartsdoer', 'huaweicloud-plugins');
104
+ }
105
+
106
+ function workbuddySkillsDir() {
107
+ return join(homedir(), '.workbuddy', 'skills');
108
+ }
109
+ function workbuddyMcpConfigFile() {
110
+ return join(homedir(), '.workbuddy', 'mcp.json');
111
+ }
112
+ function workbuddyPluginsDir() {
113
+ return join(homedir(), '.workbuddy', 'huaweicloud-plugins');
114
+ }
115
+
116
+ function atomcodeHome() {
117
+ return process.env.ATOMCODE_HOME || join(homedir(), '.atomcode');
118
+ }
119
+ function atomcodeSkillsDir() {
120
+ return join(atomcodeHome(), 'skills');
121
+ }
122
+ function atomcodeMcpConfigFile() {
123
+ return join(atomcodeHome(), 'mcp.json');
124
+ }
125
+ function atomcodePluginsDir() {
126
+ return join(atomcodeHome(), 'huaweicloud-plugins');
127
+ }
128
+
129
+ function dshRoot() {
130
+ return process.env.DSH_HOME || join(homedir(), '.dsh');
131
+ }
132
+ function dshSkillsDir() {
133
+ return join(dshRoot(), 'skills');
134
+ }
135
+ function dshProfileDir() {
136
+ return join(dshRoot(), 'profiles', 'web');
137
+ }
138
+ function dshPatchFile() {
139
+ return join(dshProfileDir(), 'cordis.patch.yml');
140
+ }
141
+ function dshPluginsDir() {
142
+ return join(dshRoot(), 'huaweicloud-plugins');
143
+ }
144
+
145
+ function readOfficeaceRegistryInstallDir() {
146
+ if (platform() !== 'win32') return null;
147
+ try {
148
+ const r = spawnSync('reg', ['query', 'HKCU\\SOFTWARE\\OfficeAce\\OfficeAce', '/v', 'InstallDir'], {
149
+ encoding: 'utf8',
150
+ windowsHide: true,
151
+ timeout: 5000,
152
+ });
153
+ if (r.status === 0) {
154
+ const m = r.stdout.match(/InstallDir\s+REG_SZ\s+(.+)/);
155
+ if (m) return m[1].trim();
156
+ }
157
+ } catch {}
158
+ return null;
159
+ }
160
+
161
+ function officeaceCapabilitiesDir() {
162
+ const configRoot = process.env.OFFICE_CLAW_CONFIG_ROOT;
163
+ if (configRoot && existsSync(join(configRoot, 'capabilities.json'))) return configRoot;
164
+ const regDir = readOfficeaceRegistryInstallDir();
165
+ if (regDir) {
166
+ const dir = join(regDir, '.office-claw');
167
+ if (existsSync(join(dir, 'capabilities.json'))) return dir;
168
+ }
169
+ if (platform() === 'win32') {
170
+ const bases = [process.env.ProgramFiles, 'C:\\Program Files', 'D:\\Program Files'];
171
+ if (process.env.LOCALAPPDATA) bases.push(join(process.env.LOCALAPPDATA, 'Programs'));
172
+ for (const base of bases) {
173
+ if (!base) continue;
174
+ const dir = join(base, 'OfficeAce', '.office-claw');
175
+ if (existsSync(join(dir, 'capabilities.json'))) return dir;
176
+ }
177
+ }
178
+ return null;
179
+ }
180
+
181
+ function officeaceCapabilitiesDirSafe() {
182
+ return officeaceCapabilitiesDir() || join(homedir(), '.office-claw');
183
+ }
184
+ function officeaceCapabilitiesFile() {
185
+ return join(officeaceCapabilitiesDirSafe(), 'capabilities.json');
186
+ }
187
+ function officeaceSkillsDir() {
188
+ return join(officeaceCapabilitiesDirSafe(), 'skills');
189
+ }
190
+ function officeacePluginsDir() {
191
+ return join(officeaceCapabilitiesDirSafe(), 'huaweicloud-plugins');
192
+ }
193
+
194
+ function officeaceSqlitePath() {
195
+ const capDir = officeaceCapabilitiesDirSafe();
196
+ return join(resolve(capDir, '..'), 'data', 'mcp-connectors.sqlite');
197
+ }
198
+
199
+ function openOfficeaceDb() {
200
+ return new Database(officeaceSqlitePath());
201
+ }
202
+
203
+ function officeaceGetOwnerUserId() {
204
+ if (!existsSync(officeaceSqlitePath())) return null;
205
+ try {
206
+ const db = openOfficeaceDb();
207
+ const row = db.prepare('SELECT owner_user_id FROM mcp_connectors WHERE owner_user_id IS NOT NULL LIMIT 1').get();
208
+ db.close();
209
+ return row?.owner_user_id || null;
210
+ } catch {
211
+ return null;
212
+ }
213
+ }
214
+
215
+ function ensureOfficeaceMcpInSqlite() {
216
+ const dbPath = officeaceSqlitePath();
217
+ if (!existsSync(dbPath)) {
218
+ console.log(` \x1b[31mOfficeAce database not found: ${dbPath}\x1b[0m`);
219
+ console.log(` \x1b[33mPlease ensure OfficeAce is installed and has been launched at least once.\x1b[0m`);
220
+ return false;
221
+ }
222
+
223
+ const mcpPath = join(officeacePluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
224
+ const env = [{ key: 'HUAWEICLOUD_AGENT_TOOLKIT_MODE', value: 'local', sensitive: false }];
225
+ const hcloudBin = findHcloudBin();
226
+ if (hcloudBin) env.push({ key: 'HCLOUD_BIN', value: hcloudBin.replace(/\\/g, '/'), sensitive: false });
227
+
228
+ const now = Date.now();
229
+ let db;
230
+ try {
231
+ db = openOfficeaceDb();
232
+
233
+ const existing = db
234
+ .prepare("SELECT id, command, args_json FROM mcp_connectors WHERE name = 'huaweicloud-devkit'")
235
+ .get();
236
+
237
+ const argsJson = JSON.stringify([mcpPath]);
238
+ const envJson = JSON.stringify(env);
239
+
240
+ if (existing) {
241
+ if (existing.command === 'node' && existing.args_json === argsJson) {
242
+ console.log(` MCP config unchanged: ${dbPath}`);
243
+ db.close();
244
+ return true;
245
+ }
246
+ db.prepare(
247
+ 'UPDATE mcp_connectors SET command = ?, args_json = ?, env_json = ?, updated_at = ?, status = ?, enabled = 1 WHERE id = ?',
248
+ ).run('node', argsJson, envJson, now, 'disconnected', existing.id);
249
+ console.log(` MCP config updated: ${dbPath}`);
250
+ } else {
251
+ const ownerUserId = officeaceGetOwnerUserId();
252
+ if (!ownerUserId) {
253
+ console.log(` \x1b[31mCannot determine owner_user_id from database\x1b[0m`);
254
+ db.close();
255
+ return false;
256
+ }
257
+ db.prepare(
258
+ `INSERT INTO mcp_connectors (id, owner_user_id, type, name, normalized_name, transport, timeout_ms, command, args_json, env_json, enabled, status, created_at, updated_at, version, seeded)
259
+ VALUES (?, ?, 'custom', 'huaweicloud-devkit', 'huaweicloud-devkit', 'stdio', 60000, 'node', ?, ?, 1, 'disconnected', ?, ?, 1, 0)`,
260
+ ).run(randomUUID(), ownerUserId, argsJson, envJson, now, now);
261
+ console.log(` MCP config created: ${dbPath}`);
262
+ }
263
+ db.close();
264
+ return true;
265
+ } catch (error) {
266
+ if (db) {
267
+ try {
268
+ db.close();
269
+ } catch {}
270
+ }
271
+ console.log(` \x1b[31mFailed to write MCP config: ${error.message}\x1b[0m`);
272
+ return false;
273
+ }
274
+ }
275
+
276
+ function removeOfficeaceMcpFromSqlite() {
277
+ const dbPath = officeaceSqlitePath();
278
+ if (!existsSync(dbPath)) return;
279
+ let db;
280
+ try {
281
+ db = openOfficeaceDb();
282
+ db.prepare(
283
+ "DELETE FROM mcp_connector_tools WHERE connector_id IN (SELECT id FROM mcp_connectors WHERE name = 'huaweicloud-devkit')",
284
+ ).run();
285
+ const result2 = db.prepare("DELETE FROM mcp_connectors WHERE name = 'huaweicloud-devkit'").run();
286
+ if (result2.changes > 0) {
287
+ console.log(` MCP config removed: ${dbPath}`);
288
+ }
289
+ db.close();
290
+ } catch (error) {
291
+ if (db) {
292
+ try {
293
+ db.close();
294
+ } catch {}
295
+ }
296
+ console.log(` \x1b[31mFailed to remove MCP config: ${error.message}\x1b[0m`);
297
+ }
298
+ }
299
+
300
+ function readCapabilitiesJson() {
301
+ const capFile = officeaceCapabilitiesFile();
302
+ if (!existsSync(capFile)) return { capabilities: [] };
303
+ try {
304
+ return JSON.parse(readFileSync(capFile, 'utf8'));
305
+ } catch {
306
+ return { capabilities: [] };
307
+ }
308
+ }
309
+
310
+ function writeCapabilitiesJson(config) {
311
+ mkdirSync(officeaceCapabilitiesDirSafe(), { recursive: true });
312
+ writeFileSync(officeaceCapabilitiesFile(), JSON.stringify(config, null, 2));
313
+ }
314
+
315
+ function removeOfficeaceSkillCapabilities() {
316
+ const capFile = officeaceCapabilitiesFile();
317
+ if (!existsSync(capFile)) return;
318
+ let config;
319
+ try {
320
+ config = JSON.parse(readFileSync(capFile, 'utf8'));
321
+ } catch {
322
+ return;
323
+ }
324
+ if (!Array.isArray(config.capabilities)) return;
325
+ const origLen = config.capabilities.length;
326
+ config.capabilities = config.capabilities.filter(
327
+ (c) =>
328
+ !(
329
+ (c.id === 'huaweicloud-core' && c.type === 'skill') ||
330
+ (c.source === 'custom' && c.id && c.id.startsWith('huawei'))
331
+ ),
332
+ );
333
+ if (config.capabilities.length !== origLen) {
334
+ writeCapabilitiesJson(config);
335
+ console.log(` Skill capabilities cleaned: ${capFile}`);
336
+ }
337
+ }
338
+
339
+ function registerOfficeaceSkillEntries() {
340
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
341
+ if (!existsSync(skillsSrc)) return;
342
+ const skillNames = readdirSync(skillsSrc, { withFileTypes: true })
343
+ .filter((d) => d.isDirectory() && d.name.startsWith('huawei'))
344
+ .map((d) => d.name);
345
+
346
+ const config = readCapabilitiesJson();
347
+ let changed = false;
348
+
349
+ for (const name of skillNames) {
350
+ const existingIdx = config.capabilities.findIndex((c) => c.id === name && c.type === 'skill');
351
+ if (existingIdx >= 0) continue;
352
+ config.capabilities.push({
353
+ id: name,
354
+ type: 'skill',
355
+ enabled: true,
356
+ source: 'custom',
357
+ selfEvolution: 'suggest',
358
+ followGlobalSelfEvolution: true,
359
+ });
360
+ changed = true;
361
+ }
362
+
363
+ if (!config.capabilities.some((c) => c.id === 'huaweicloud-core' && c.type === 'skill')) {
364
+ config.capabilities.push({
365
+ id: 'huaweicloud-core',
366
+ type: 'skill',
367
+ enabled: true,
368
+ source: 'custom',
369
+ selfEvolution: 'suggest',
370
+ followGlobalSelfEvolution: true,
371
+ });
372
+ changed = true;
373
+ }
46
374
 
47
- function codeartsSkillsDir() { return join(homedir(), '.codeartsdoer', 'skills'); }
48
- function codeartsMcpSettingsDir() { return join(homedir(), '.codeartsdoer', 'mcp'); }
49
- function codeartsMcpSettingsFile() { return join(codeartsMcpSettingsDir(), 'mcp_settings.json'); }
50
- function codeartsProjectDir() { return join(process.cwd(), '.codeartsdoer'); }
51
- function codeartsProjectSkillsDir() { return join(codeartsProjectDir(), 'skills'); }
52
- function codeartsProjectMcpSettingsFile() { return join(codeartsProjectDir(), 'mcp', 'mcp_settings.json'); }
53
- function codeartsPluginsDir() { return join(homedir(), '.codeartsdoer', 'huaweicloud-plugins'); }
375
+ if (changed) {
376
+ writeCapabilitiesJson(config);
377
+ console.log(` Skill entries registered: ${officeaceCapabilitiesFile()}`);
378
+ }
379
+ }
54
380
 
55
- function workbuddySkillsDir() { return join(homedir(), '.workbuddy', 'skills'); }
56
- function workbuddyMcpConfigFile() { return join(homedir(), '.workbuddy', 'mcp.json'); }
57
- function workbuddyPluginsDir() { return join(homedir(), '.workbuddy', 'huaweicloud-plugins'); }
381
+ const DSH_MCP_PATCH_START = '# HuaweiCloud DevKit DSH integration start';
382
+ const DSH_MCP_PATCH_END = '# HuaweiCloud DevKit DSH integration end';
58
383
 
59
384
  // Detect CodeArts sandbox mode (bash_mode in permission config).
60
385
  function detectCodeartsSandbox() {
@@ -74,7 +399,11 @@ function findHcloudBin() {
74
399
  const isWin = platform() === 'win32';
75
400
  const candidates = isWin
76
401
  ? [join(homedir(), 'hcloud', 'hcloud.exe')]
77
- : [join(homedir(), '.local', 'bin', 'hcloud'), join(homedir(), 'hcloud', 'hcloud'), join(homedir(), 'hcloud', 'hcloud.exe')];
402
+ : [
403
+ join(homedir(), '.local', 'bin', 'hcloud'),
404
+ join(homedir(), 'hcloud', 'hcloud'),
405
+ join(homedir(), 'hcloud', 'hcloud.exe'),
406
+ ];
78
407
  for (const c of candidates) {
79
408
  if (existsSync(c)) return c;
80
409
  }
@@ -87,14 +416,14 @@ function printSandboxWarning(reason) {
87
416
  console.log(`\x1b[31m 请任选其一继续:\x1b[0m`);
88
417
  console.log(`\x1b[31m A. 在码道外的终端安装并使用 KooCLI (推荐):`);
89
418
  console.log(`\x1b[31m https://support.huaweicloud.com/qs-hcli/hcli_02_003.html`);
90
- console.log(`\x1b[31m B. 在码道设置中关闭沙箱模式后重试 (设置 → 权限 → Bash 模式)`);
419
+ console.log(`\x1b[31m B. 在码道设置中关闭沙箱模式后重试 (设置 → 对话流 → 智能体 终端命令运行模式 → 自动运行)`);
91
420
  console.log(`\x1b[31m 关闭沙箱后重新运行: npx huaweicloud-devkit install-hcloud\x1b[0m`);
92
421
  }
93
422
 
94
423
  function checkNode() {
95
424
  const v = process.versions.node.split('.').map(Number);
96
- if (v[0] < 20) {
97
- console.error(`\x1b[31mNode.js >= 20 required (current: ${process.version})\x1b[0m`);
425
+ if (v[0] < 22) {
426
+ console.error(`\x1b[31mNode.js >= 22 required (current: ${process.version})\x1b[0m`);
98
427
  process.exit(1);
99
428
  }
100
429
  console.log(` Node.js ${process.version} \x1b[32mOK\x1b[0m`);
@@ -114,13 +443,55 @@ function copyDir(src, dest) {
114
443
  }
115
444
  }
116
445
 
446
+ function installRuntimeDeps(pluginsDir) {
447
+ const pkgJson = {
448
+ name: 'huaweicloud-devkit',
449
+ version: pkgVersion,
450
+ type: 'module',
451
+ dependencies: { undici: '^8.10.0' },
452
+ };
453
+ mkdirSync(pluginsDir, { recursive: true });
454
+ writeFileSync(join(pluginsDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
455
+ const spawnOpts = {
456
+ cwd: pluginsDir,
457
+ shell: true,
458
+ windowsHide: true,
459
+ stdio: 'pipe',
460
+ timeout: 120000,
461
+ };
462
+ let r = spawnSync('npm', ['install', '--omit=dev'], spawnOpts);
463
+ const isRetryable = (res) => {
464
+ const stderr = (res.stderr || '').toString();
465
+ return res.error?.code === 'EPERM' || res.error?.code === 'EBUSY' || /EPERM|EBUSY/.test(stderr);
466
+ };
467
+ if (r.status !== 0 && isRetryable(r)) {
468
+ console.log(` \x1b[33m[WARN]\x1b[0m npm install hit file-lock error, retrying in 2s...`);
469
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2000);
470
+ r = spawnSync('npm', ['install', '--omit=dev'], spawnOpts);
471
+ }
472
+ const undiciDir = join(pluginsDir, 'node_modules', 'undici');
473
+ if (r.status === 0 && existsSync(undiciDir)) {
474
+ console.log(` Runtime deps installed -> ${join(pluginsDir, 'node_modules')}`);
475
+ } else {
476
+ const errCode = r.error?.code;
477
+ const err = (r.stderr || '').toString().trim().split(/\r?\n/).slice(-2).join(' ');
478
+ const hint = errCode === 'ENOENT' ? ' (npm not found — ensure Node.js/npm is in PATH)' : '';
479
+ console.log(` \x1b[33m[WARN]\x1b[0m npm install failed in ${pluginsDir}${err ? `: ${err}` : ''}${hint}`);
480
+ console.log(' Manual fix: cd %s && npm install', pluginsDir);
481
+ if (!existsSync(undiciDir)) {
482
+ console.log(` \x1b[31m[ERROR]\x1b[0m undici is NOT installed. MCP server will fail to start.`);
483
+ console.log(` Run manually: cd "${pluginsDir}" && npm install undici@^8.10.0`);
484
+ }
485
+ }
486
+ }
487
+
117
488
  function removeIfExists(p) {
118
489
  if (existsSync(p)) {
119
490
  try {
120
491
  rmSync(p, { recursive: true, force: true });
121
492
  return true;
122
- } catch (e) {
123
- console.log(` \x1b[33m[WARN]\x1b[0m Could not remove ${p}: ${e.message}`);
493
+ } catch (error) {
494
+ console.log(` \x1b[33m[WARN]\x1b[0m Could not remove ${p}: ${error.message}`);
124
495
  return false;
125
496
  }
126
497
  }
@@ -132,14 +503,23 @@ function updateOpenCodeConfig(pluginDir) {
132
503
  const mcpPath = join(pluginDir, 'src', 'mcp-server.mjs').replace(/\\/g, '/');
133
504
  let config = {};
134
505
  if (existsSync(configPath)) {
135
- try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {
136
- console.log(` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath} (jsonc comments?). Skipping MCP config write; ensure "mcp.huaweicloud-devkit" points to ${mcpPath}.`);
506
+ try {
507
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
508
+ } catch {
509
+ console.log(
510
+ ` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath} (jsonc comments?). Skipping MCP config write; ensure "mcp.huaweicloud-devkit" points to ${mcpPath}.`,
511
+ );
137
512
  return;
138
513
  }
139
514
  const existing = config.mcp?.['huaweicloud-devkit'];
140
- if (existing && existing.type === 'local'
141
- && Array.isArray(existing.command) && existing.command[0] === 'node'
142
- && existing.command[1] === mcpPath) {
515
+ if (
516
+ existing &&
517
+ existing.type === 'local' &&
518
+ Array.isArray(existing.command) &&
519
+ existing.command[0] === 'node' &&
520
+ existing.command[1] === mcpPath &&
521
+ existing.timeout === 300000
522
+ ) {
143
523
  console.log(` OpenCode MCP config unchanged: ${configPath}`);
144
524
  return;
145
525
  }
@@ -149,6 +529,7 @@ function updateOpenCodeConfig(pluginDir) {
149
529
  type: 'local',
150
530
  command: ['node', mcpPath],
151
531
  enabled: true,
532
+ timeout: 300000,
152
533
  };
153
534
  writeFileSync(configPath, JSON.stringify(config, null, 2));
154
535
  console.log(` OpenCode MCP config updated: ${configPath}`);
@@ -157,8 +538,12 @@ function updateOpenCodeConfig(pluginDir) {
157
538
  function removeOpenCodeConfig() {
158
539
  const configPath = opencodeConfigFile();
159
540
  if (!existsSync(configPath)) return;
160
- let config = {};
161
- try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch { return; }
541
+ let config;
542
+ try {
543
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
544
+ } catch {
545
+ return;
546
+ }
162
547
  if (!config.mcp?.['huaweicloud-devkit']) return;
163
548
  delete config.mcp['huaweicloud-devkit'];
164
549
  if (Object.keys(config.mcp).length === 0) delete config.mcp;
@@ -179,11 +564,13 @@ function hasCodexCLI() {
179
564
  }
180
565
 
181
566
  function checkHcloud() {
182
- const bin = findHcloudBin() || (process.env.HCLOUD_BIN || 'hcloud');
567
+ const bin = findHcloudBin() || process.env.HCLOUD_BIN || 'hcloud';
183
568
  if (!existsSync(bin)) return false;
184
569
  try {
185
570
  if (statSync(bin).size < 1024) return false;
186
- } catch { return false; }
571
+ } catch {
572
+ return false;
573
+ }
187
574
  try {
188
575
  const r = spawnSync(`"${bin}" version`, [], { shell: true, windowsHide: true, stdio: 'pipe', timeout: 5000 });
189
576
  const out = (r.stdout ? r.stdout.toString() : '') + (r.stderr ? r.stderr.toString() : '');
@@ -209,7 +596,9 @@ function installCodex() {
209
596
 
210
597
  console.log(` Registering Codex marketplace: ${marketplaceRoot}`);
211
598
  const r1 = spawnSync(`codex plugin marketplace add "${marketplaceRoot}"`, [], {
212
- shell: true, windowsHide: true, stdio: 'pipe',
599
+ shell: true,
600
+ windowsHide: true,
601
+ stdio: 'pipe',
213
602
  });
214
603
  console.log(` ${r1.stdout ? r1.stdout.toString().trim() : r1.stderr.toString().trim()}`);
215
604
 
@@ -221,7 +610,9 @@ function installCodex() {
221
610
 
222
611
  console.log(` Installing plugin: ${pluginName}@${marketplaceName}`);
223
612
  const r2 = spawnSync(`codex plugin add "${pluginName}@${marketplaceName}"`, [], {
224
- shell: true, windowsHide: true, stdio: 'pipe',
613
+ shell: true,
614
+ windowsHide: true,
615
+ stdio: 'pipe',
225
616
  });
226
617
  console.log(` ${r2.stdout ? r2.stdout.toString().trim() : r2.stderr.toString().trim()}`);
227
618
 
@@ -239,14 +630,18 @@ function uninstallCodex() {
239
630
  const marketplaceName = getMarketplaceName();
240
631
  console.log(` Removing Codex plugin: ${pluginName}@${marketplaceName}`);
241
632
  const r = spawnSync(`codex plugin remove "${pluginName}@${marketplaceName}"`, [], {
242
- shell: true, windowsHide: true, stdio: 'pipe',
633
+ shell: true,
634
+ windowsHide: true,
635
+ stdio: 'pipe',
243
636
  });
244
637
  console.log(` ${r.stdout ? r.stdout.toString().trim() : r.stderr.toString().trim()}`);
245
638
 
246
639
  for (const name of new Set([marketplaceName, 'HuaweiCloud-Devkit'])) {
247
640
  console.log(` Removing Codex marketplace: ${name}`);
248
641
  const r2 = spawnSync(`codex plugin marketplace remove "${name}"`, [], {
249
- shell: true, windowsHide: true, stdio: 'pipe',
642
+ shell: true,
643
+ windowsHide: true,
644
+ stdio: 'pipe',
250
645
  });
251
646
  console.log(` ${r2.stdout ? r2.stdout.toString().trim() : r2.stderr.toString().trim()}`);
252
647
  }
@@ -274,6 +669,7 @@ async function installOpenCode() {
274
669
  copyDir(safetyDir, join(pluginDest, 'safety'));
275
670
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
276
671
  updateOpenCodeConfig(pluginDest);
672
+ installRuntimeDeps(pluginDest);
277
673
  }
278
674
 
279
675
  function uninstallOpenCode() {
@@ -336,7 +732,9 @@ async function updateOpenCode() {
336
732
  console.log(` Skills updated -> ${opencodeSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`);
337
733
  copyDir(commandsSrc, opencodeCommandsDir());
338
734
  const staleCommands = pruneStale(opencodeCommandsDir(), commandsSrc);
339
- console.log(` Commands updated -> ${opencodeCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`);
735
+ console.log(
736
+ ` Commands updated -> ${opencodeCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`,
737
+ );
340
738
  copyDir(srcDir, join(pluginDest, 'src'));
341
739
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
342
740
  copyDir(safetyDir, join(pluginDest, 'safety'));
@@ -344,6 +742,7 @@ async function updateOpenCode() {
344
742
  updateOpenCodeConfig(pluginDest);
345
743
  mkdirSync(pluginDest, { recursive: true });
346
744
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
745
+ installRuntimeDeps(pluginDest);
347
746
  }
348
747
 
349
748
  function codexMcpServerPath() {
@@ -367,7 +766,9 @@ function ensureCodexConfigSection(mcpPath) {
367
766
  const configPath = codexConfigToml();
368
767
  let existing = '';
369
768
  if (existsSync(configPath)) {
370
- try { existing = readFileSync(configPath, 'utf8'); } catch {}
769
+ try {
770
+ existing = readFileSync(configPath, 'utf8');
771
+ } catch {}
371
772
  if (existing.includes('[mcp_servers.huaweicloud-devkit]')) {
372
773
  if (existing.includes(`args = ["${mcpPath}"]`)) {
373
774
  console.log(` Config unchanged: ${configPath}`);
@@ -376,7 +777,9 @@ function ensureCodexConfigSection(mcpPath) {
376
777
  removeCodexConfigSection();
377
778
  existing = '';
378
779
  if (existsSync(configPath)) {
379
- try { existing = readFileSync(configPath, 'utf8'); } catch {}
780
+ try {
781
+ existing = readFileSync(configPath, 'utf8');
782
+ } catch {}
380
783
  }
381
784
  }
382
785
  }
@@ -394,7 +797,10 @@ function removeCodexConfigSection() {
394
797
  const out = [];
395
798
  let skip = false;
396
799
  for (const line of lines) {
397
- if (/^\[mcp_servers\.huaweicloud-devkit(\]|\.)/.test(line)) { skip = true; continue; }
800
+ if (/^\[mcp_servers\.huaweicloud-devkit(\]|\.)/.test(line)) {
801
+ skip = true;
802
+ continue;
803
+ }
398
804
  if (skip && line.startsWith('[')) skip = false;
399
805
  if (!skip) out.push(line);
400
806
  }
@@ -441,6 +847,7 @@ async function installCodexDesktop() {
441
847
  }
442
848
 
443
849
  ensureCodexConfigSection(mcpServerAbsPath);
850
+ installRuntimeDeps(codexDesktopPluginsDir());
444
851
  }
445
852
 
446
853
  // Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
@@ -453,10 +860,14 @@ async function updateCodexDesktop() {
453
860
 
454
861
  copyDir(skillsSrc, codexDesktopSkillsDir());
455
862
  const staleSkills = pruneStale(codexDesktopSkillsDir(), skillsSrc);
456
- console.log(` Skills updated -> ${codexDesktopSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`);
863
+ console.log(
864
+ ` Skills updated -> ${codexDesktopSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`,
865
+ );
457
866
  copyDir(commandsSrc, codexDesktopCommandsDir());
458
867
  const staleCommands = pruneStale(codexDesktopCommandsDir(), commandsSrc);
459
- console.log(` Commands updated -> ${codexDesktopCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`);
868
+ console.log(
869
+ ` Commands updated -> ${codexDesktopCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`,
870
+ );
460
871
  mkdirSync(pluginDest, { recursive: true });
461
872
  copyDir(srcDir, join(pluginDest, 'src'));
462
873
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
@@ -484,6 +895,7 @@ async function updateCodexDesktop() {
484
895
 
485
896
  ensureCodexConfigSection(mcpServerAbsPath);
486
897
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
898
+ installRuntimeDeps(pluginDest);
487
899
  }
488
900
 
489
901
  function uninstallCodexDesktop() {
@@ -524,13 +936,22 @@ function registerCodeartsMcp(configPath) {
524
936
  if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
525
937
  let config = {};
526
938
  if (existsSync(configPath)) {
527
- try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {
528
- console.log(` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`);
939
+ try {
940
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
941
+ } catch {
942
+ console.log(
943
+ ` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`,
944
+ );
529
945
  return;
530
946
  }
531
947
  const existing = config.mcpServers?.['huaweicloud-devkit'];
532
- if (existing && existing.command === 'node'
533
- && Array.isArray(existing.args) && existing.args[0] === mcpPath) {
948
+ if (
949
+ existing &&
950
+ existing.command === 'node' &&
951
+ Array.isArray(existing.args) &&
952
+ existing.args[0] === mcpPath &&
953
+ existing.timeout === 300000
954
+ ) {
534
955
  console.log(` MCP config unchanged: ${configPath}`);
535
956
  return;
536
957
  }
@@ -541,6 +962,7 @@ function registerCodeartsMcp(configPath) {
541
962
  args: [mcpPath],
542
963
  env,
543
964
  enabled: true,
965
+ timeout: 300000,
544
966
  };
545
967
  mkdirSync(dirname(configPath), { recursive: true });
546
968
  writeFileSync(configPath, JSON.stringify(config, null, 2));
@@ -565,6 +987,7 @@ async function installCodeArts() {
565
987
 
566
988
  registerCodeartsMcp(codeartsMcpSettingsFile());
567
989
  registerCodeartsMcp(codeartsProjectMcpSettingsFile());
990
+ installRuntimeDeps(pluginDest);
568
991
  }
569
992
 
570
993
  // Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
@@ -587,6 +1010,7 @@ async function updateCodeArts() {
587
1010
  registerCodeartsMcp(codeartsProjectMcpSettingsFile());
588
1011
  mkdirSync(pluginDest, { recursive: true });
589
1012
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1013
+ installRuntimeDeps(pluginDest);
590
1014
  }
591
1015
 
592
1016
  function uninstallCodeArts() {
@@ -608,7 +1032,9 @@ function uninstallCodeArts() {
608
1032
  for (const configPath of [codeartsMcpSettingsFile(), codeartsProjectMcpSettingsFile()]) {
609
1033
  if (!existsSync(configPath)) continue;
610
1034
  let config = {};
611
- try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
1035
+ try {
1036
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
1037
+ } catch {}
612
1038
  if (config.mcpServers?.['huaweicloud-devkit']) {
613
1039
  delete config.mcpServers['huaweicloud-devkit'];
614
1040
  if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
@@ -618,96 +1044,861 @@ function uninstallCodeArts() {
618
1044
  }
619
1045
  }
620
1046
 
621
- function codeartsStatus() {
622
- const pluginDir = codeartsPluginsDir();
623
- console.log(` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
624
- console.log(` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
1047
+ function codeartsStatus() {
1048
+ const pluginDir = codeartsPluginsDir();
1049
+ console.log(
1050
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1051
+ );
1052
+ console.log(
1053
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1054
+ );
1055
+ let skillCount = 0;
1056
+ if (existsSync(codeartsSkillsDir())) {
1057
+ skillCount = readdirSync(codeartsSkillsDir(), { withFileTypes: true }).filter(
1058
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1059
+ ).length;
1060
+ }
1061
+ console.log(
1062
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1063
+ );
1064
+ if (existsSync(codeartsMcpSettingsFile())) {
1065
+ try {
1066
+ const config = JSON.parse(readFileSync(codeartsMcpSettingsFile(), 'utf8'));
1067
+ console.log(
1068
+ ` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
1069
+ );
1070
+ } catch {
1071
+ console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
1072
+ }
1073
+ }
1074
+ }
1075
+
1076
+ // Returns true when the config file was written, false when it was already correct.
1077
+ function ensureWorkbuddyMcpConfig() {
1078
+ const configPath = workbuddyMcpConfigFile();
1079
+ const mcpPath = join(workbuddyPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
1080
+ const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
1081
+ const hcloudBin = findHcloudBin();
1082
+ if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
1083
+ let config = {};
1084
+ if (existsSync(configPath)) {
1085
+ try {
1086
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
1087
+ } catch {
1088
+ console.log(
1089
+ ` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`,
1090
+ );
1091
+ return false;
1092
+ }
1093
+ const existing = config.mcpServers?.['huaweicloud-devkit'];
1094
+ if (
1095
+ existing &&
1096
+ existing.command === 'node' &&
1097
+ Array.isArray(existing.args) &&
1098
+ existing.args[0] === mcpPath &&
1099
+ existing.timeout === 300000
1100
+ ) {
1101
+ console.log(` MCP config unchanged: ${configPath}`);
1102
+ return false;
1103
+ }
1104
+ }
1105
+ config.mcpServers = config.mcpServers || {};
1106
+ config.mcpServers['huaweicloud-devkit'] = {
1107
+ command: 'node',
1108
+ args: [mcpPath],
1109
+ env,
1110
+ timeout: 300000,
1111
+ };
1112
+ mkdirSync(dirname(configPath), { recursive: true });
1113
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
1114
+ console.log(` MCP config updated: ${configPath}`);
1115
+ return true;
1116
+ }
1117
+
1118
+ async function installWorkBuddy() {
1119
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1120
+ const srcDir = join(PLUGIN_ROOT, 'src');
1121
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1122
+ const pluginDest = workbuddyPluginsDir();
1123
+
1124
+ copyDir(skillsSrc, workbuddySkillsDir());
1125
+ console.log(` Skills -> ${workbuddySkillsDir()}`);
1126
+
1127
+ copyDir(srcDir, join(pluginDest, 'src'));
1128
+ console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1129
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1130
+ console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1131
+
1132
+ ensureWorkbuddyMcpConfig();
1133
+ installRuntimeDeps(pluginDest);
1134
+ }
1135
+
1136
+ // Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
1137
+ async function updateWorkBuddy() {
1138
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1139
+ const srcDir = join(PLUGIN_ROOT, 'src');
1140
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1141
+ const pluginDest = workbuddyPluginsDir();
1142
+
1143
+ copyDir(skillsSrc, workbuddySkillsDir());
1144
+ const stale = pruneStale(workbuddySkillsDir(), skillsSrc);
1145
+ console.log(` Skills updated -> ${workbuddySkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
1146
+ copyDir(srcDir, join(pluginDest, 'src'));
1147
+ console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1148
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1149
+ console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1150
+ ensureWorkbuddyMcpConfig();
1151
+ mkdirSync(pluginDest, { recursive: true });
1152
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1153
+ installRuntimeDeps(pluginDest);
1154
+ }
1155
+
1156
+ function uninstallWorkBuddy() {
1157
+ const skillsDir = workbuddySkillsDir();
1158
+ let removed = 0;
1159
+ if (existsSync(skillsDir)) {
1160
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
1161
+ if (entry.name.startsWith('huawei')) {
1162
+ removeIfExists(join(skillsDir, entry.name));
1163
+ removed++;
1164
+ }
1165
+ }
1166
+ if (removed > 0) console.log(` Removed ${removed} skills`);
1167
+ }
1168
+
1169
+ if (removeIfExists(workbuddyPluginsDir())) {
1170
+ console.log(' Removed MCP server and safety policy');
1171
+ }
1172
+
1173
+ const configPath = workbuddyMcpConfigFile();
1174
+ if (existsSync(configPath)) {
1175
+ let config = {};
1176
+ try {
1177
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
1178
+ } catch {}
1179
+ if (config.mcpServers?.['huaweicloud-devkit']) {
1180
+ delete config.mcpServers['huaweicloud-devkit'];
1181
+ if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
1182
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
1183
+ console.log(` MCP config cleaned: ${configPath}`);
1184
+ }
1185
+ }
1186
+ }
1187
+
1188
+ function workbuddyStatus() {
1189
+ const pluginDir = workbuddyPluginsDir();
1190
+ const skillsDir = workbuddySkillsDir();
1191
+ console.log(
1192
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1193
+ );
1194
+ console.log(
1195
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1196
+ );
1197
+ let skillCount = 0;
1198
+ if (existsSync(skillsDir)) {
1199
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1200
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1201
+ ).length;
1202
+ }
1203
+ console.log(
1204
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1205
+ );
1206
+ const configPath = workbuddyMcpConfigFile();
1207
+ if (existsSync(configPath)) {
1208
+ try {
1209
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
1210
+ console.log(
1211
+ ` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
1212
+ );
1213
+ } catch {
1214
+ console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
1215
+ }
1216
+ }
1217
+ }
1218
+
1219
+ function ensureAtomcodeMcpConfig() {
1220
+ const configPath = atomcodeMcpConfigFile();
1221
+ const mcpPath = join(atomcodePluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
1222
+ const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
1223
+ const hcloudBin = findHcloudBin();
1224
+ if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
1225
+ let config = {};
1226
+ if (existsSync(configPath)) {
1227
+ try {
1228
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
1229
+ } catch {
1230
+ console.log(
1231
+ ` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`,
1232
+ );
1233
+ return false;
1234
+ }
1235
+ const existing = config.mcpServers?.['huaweicloud-devkit'];
1236
+ if (
1237
+ existing &&
1238
+ existing.command === 'node' &&
1239
+ Array.isArray(existing.args) &&
1240
+ existing.args[0] === mcpPath &&
1241
+ existing.timeout === 300000
1242
+ ) {
1243
+ console.log(` MCP config unchanged: ${configPath}`);
1244
+ return false;
1245
+ }
1246
+ }
1247
+ config.mcpServers = config.mcpServers || {};
1248
+ config.mcpServers['huaweicloud-devkit'] = {
1249
+ command: 'node',
1250
+ args: [mcpPath],
1251
+ env,
1252
+ timeout: 300000,
1253
+ };
1254
+ mkdirSync(dirname(configPath), { recursive: true });
1255
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
1256
+ console.log(` MCP config updated: ${configPath}`);
1257
+ return true;
1258
+ }
1259
+
1260
+ async function installAtomCode() {
1261
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1262
+ const srcDir = join(PLUGIN_ROOT, 'src');
1263
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1264
+ const pluginDest = atomcodePluginsDir();
1265
+
1266
+ copyDir(skillsSrc, atomcodeSkillsDir());
1267
+ console.log(` Skills -> ${atomcodeSkillsDir()}`);
1268
+
1269
+ copyDir(srcDir, join(pluginDest, 'src'));
1270
+ console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1271
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1272
+ console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1273
+
1274
+ ensureAtomcodeMcpConfig();
1275
+ installRuntimeDeps(pluginDest);
1276
+ }
1277
+
1278
+ // Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
1279
+ async function updateAtomCode() {
1280
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1281
+ const srcDir = join(PLUGIN_ROOT, 'src');
1282
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1283
+ const pluginDest = atomcodePluginsDir();
1284
+
1285
+ copyDir(skillsSrc, atomcodeSkillsDir());
1286
+ const stale = pruneStale(atomcodeSkillsDir(), skillsSrc);
1287
+ console.log(` Skills updated -> ${atomcodeSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
1288
+ copyDir(srcDir, join(pluginDest, 'src'));
1289
+ console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1290
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1291
+ console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1292
+ ensureAtomcodeMcpConfig();
1293
+ mkdirSync(pluginDest, { recursive: true });
1294
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1295
+ installRuntimeDeps(pluginDest);
1296
+ }
1297
+
1298
+ function uninstallAtomCode() {
1299
+ const skillsDir = atomcodeSkillsDir();
1300
+ let removed = 0;
1301
+ if (existsSync(skillsDir)) {
1302
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
1303
+ if (entry.name.startsWith('huawei')) {
1304
+ removeIfExists(join(skillsDir, entry.name));
1305
+ removed++;
1306
+ }
1307
+ }
1308
+ if (removed > 0) console.log(` Removed ${removed} skills`);
1309
+ }
1310
+
1311
+ if (removeIfExists(atomcodePluginsDir())) {
1312
+ console.log(' Removed MCP server and safety policy');
1313
+ }
1314
+
1315
+ const configPath = atomcodeMcpConfigFile();
1316
+ if (existsSync(configPath)) {
1317
+ let config = {};
1318
+ try {
1319
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
1320
+ } catch {}
1321
+ if (config.mcpServers?.['huaweicloud-devkit']) {
1322
+ delete config.mcpServers['huaweicloud-devkit'];
1323
+ if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
1324
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
1325
+ console.log(` MCP config cleaned: ${configPath}`);
1326
+ }
1327
+ }
1328
+ }
1329
+
1330
+ function atomcodeStatus() {
1331
+ const pluginDir = atomcodePluginsDir();
1332
+ const skillsDir = atomcodeSkillsDir();
1333
+ console.log(
1334
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1335
+ );
1336
+ console.log(
1337
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1338
+ );
1339
+ let skillCount = 0;
1340
+ if (existsSync(skillsDir)) {
1341
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1342
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1343
+ ).length;
1344
+ }
1345
+ console.log(
1346
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1347
+ );
1348
+ const configPath = atomcodeMcpConfigFile();
1349
+ if (existsSync(configPath)) {
1350
+ try {
1351
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
1352
+ console.log(
1353
+ ` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
1354
+ );
1355
+ } catch {
1356
+ console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
1357
+ }
1358
+ }
1359
+ }
1360
+
1361
+ function dshMcpServerPath() {
1362
+ return join(dshPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
1363
+ }
1364
+
1365
+ function dshPatchBlock() {
1366
+ const hcloudBin = findHcloudBin();
1367
+ const envLines = [' HUAWEICLOUD_AGENT_TOOLKIT_MODE: local', " HDKITSERVICE_ENDPOINT: ''"];
1368
+ if (hcloudBin) {
1369
+ envLines.push(` HCLOUD_BIN: '${hcloudBin.replace(/\\/g, '/').replace(/'/g, "''")}'`);
1370
+ }
1371
+ return [
1372
+ DSH_MCP_PATCH_START,
1373
+ '- insert:',
1374
+ ' - id: mcp-huaweicloud',
1375
+ " name: '@deepseek-ai/dsh-mcp-client'",
1376
+ ' config:',
1377
+ ' serverName: huaweicloud',
1378
+ ' transport: stdio',
1379
+ ' command: node',
1380
+ ' args:',
1381
+ ` - '${dshMcpServerPath().replace(/'/g, "''")}'`,
1382
+ ' env:',
1383
+ ...envLines,
1384
+ ' failOnStartupError: false',
1385
+ DSH_MCP_PATCH_END,
1386
+ ].join('\n');
1387
+ }
1388
+
1389
+ function escapeRegExp(value) {
1390
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1391
+ }
1392
+
1393
+ function removeManagedDshPatchBlock(content) {
1394
+ const pattern = new RegExp(
1395
+ `\\n?${escapeRegExp(DSH_MCP_PATCH_START)}[\\s\\S]*?${escapeRegExp(DSH_MCP_PATCH_END)}\\s*`,
1396
+ 'g',
1397
+ );
1398
+ return String(content || '')
1399
+ .replace(pattern, '\n')
1400
+ .replace(/\n{3,}/g, '\n\n')
1401
+ .trimEnd();
1402
+ }
1403
+
1404
+ function dshPatchHasOnlyCommentsOrEmptyList(content) {
1405
+ const meaningful = String(content || '')
1406
+ .split(/\r?\n/)
1407
+ .map((line) => line.trim())
1408
+ .filter((line) => line && !line.startsWith('#'));
1409
+ return meaningful.length === 0 || (meaningful.length === 1 && meaningful[0] === '[]');
1410
+ }
1411
+
1412
+ function ensureDshMcpPatch() {
1413
+ const patchFile = dshPatchFile();
1414
+ const existing = existsSync(patchFile) ? readFileSync(patchFile, 'utf8') : '';
1415
+ const cleaned = removeManagedDshPatchBlock(existing);
1416
+ const block = dshPatchBlock();
1417
+ let next;
1418
+ if (dshPatchHasOnlyCommentsOrEmptyList(cleaned)) {
1419
+ const prefix = cleaned
1420
+ .split(/\r?\n/)
1421
+ .filter((line) => line.trim() !== '[]')
1422
+ .join('\n')
1423
+ .trimEnd();
1424
+ next = `${prefix ? `${prefix}\n` : ''}${block}\n`;
1425
+ } else {
1426
+ next = `${cleaned}\n\n${block}\n`;
1427
+ }
1428
+ if (existing.replace(/\r\n/g, '\n') === next) {
1429
+ console.log(` DSH patch unchanged: ${patchFile}`);
1430
+ return false;
1431
+ }
1432
+ mkdirSync(dirname(patchFile), { recursive: true });
1433
+ writeFileSync(patchFile, next);
1434
+ console.log(` DSH patch updated: ${patchFile}`);
1435
+ return true;
1436
+ }
1437
+
1438
+ function removeDshMcpPatch() {
1439
+ const patchFile = dshPatchFile();
1440
+ if (!existsSync(patchFile)) return false;
1441
+ const existing = readFileSync(patchFile, 'utf8');
1442
+ const cleaned = removeManagedDshPatchBlock(existing);
1443
+ if (cleaned === existing.trimEnd()) return false;
1444
+ const prefix = cleaned
1445
+ .split(/\r?\n/)
1446
+ .filter((line) => line.trim() !== '[]')
1447
+ .join('\n')
1448
+ .trimEnd();
1449
+ const next = dshPatchHasOnlyCommentsOrEmptyList(cleaned) ? `${prefix ? `${prefix}\n` : ''}[]\n` : `${cleaned}\n`;
1450
+ writeFileSync(patchFile, next);
1451
+ console.log(` DSH patch cleaned: ${patchFile}`);
1452
+ return true;
1453
+ }
1454
+
1455
+ function dshPatchConfigured() {
1456
+ const patchFile = dshPatchFile();
1457
+ if (!existsSync(patchFile)) return false;
1458
+ try {
1459
+ const patch = readFileSync(patchFile, 'utf8');
1460
+ return (
1461
+ patch.includes('id: mcp-huaweicloud') &&
1462
+ patch.includes('@deepseek-ai/dsh-mcp-client') &&
1463
+ patch.includes('serverName: huaweicloud')
1464
+ );
1465
+ } catch {
1466
+ return false;
1467
+ }
1468
+ }
1469
+
1470
+ function commandAvailable(command, args = ['--version']) {
1471
+ try {
1472
+ const r = spawnSync(command, args, { shell: false, windowsHide: true, stdio: 'pipe', timeout: 10000 });
1473
+ if (r.status === 0) return true;
1474
+ } catch {}
1475
+ if (process.platform === 'win32') {
1476
+ try {
1477
+ const w = spawnSync('where.exe', [command], { windowsHide: true, stdio: 'pipe', timeout: 10000 });
1478
+ return w.status === 0 && w.stdout.toString().trim().length > 0;
1479
+ } catch {}
1480
+ }
1481
+ return false;
1482
+ }
1483
+
1484
+ function dshMcpClientAvailable() {
1485
+ const modulePath = join('node_modules', '@deepseek-ai', 'dsh-mcp-client', 'package.json');
1486
+ const candidates = [
1487
+ join(dshProfileDir(), modulePath),
1488
+ join(dshRoot(), 'profiles', modulePath),
1489
+ join(dshRoot(), modulePath),
1490
+ ];
1491
+ if (candidates.some((p) => existsSync(p))) return true;
1492
+ const pkgPath = join(dshProfileDir(), 'package.json');
1493
+ if (!existsSync(pkgPath)) return false;
1494
+ try {
1495
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
1496
+ return Boolean(
1497
+ pkg.dependencies?.['@deepseek-ai/dsh-mcp-client'] || pkg.devDependencies?.['@deepseek-ai/dsh-mcp-client'],
1498
+ );
1499
+ } catch {
1500
+ return false;
1501
+ }
1502
+ }
1503
+
1504
+ function tryInstallDshMcpClient() {
1505
+ if (process.env.HUAWEICLOUD_DEVKIT_SKIP_DSH_PLUGIN_INSTALL === '1') {
1506
+ console.log(' DSH MCP client install skipped by environment');
1507
+ return false;
1508
+ }
1509
+ if (dshMcpClientAvailable()) {
1510
+ console.log(' DSH MCP client package detected');
1511
+ return true;
1512
+ }
1513
+ if (commandAvailable('dsh')) {
1514
+ const r = spawnSync('dsh', ['plugin', '--profile', 'web', 'add', '@deepseek-ai/dsh-mcp-client'], {
1515
+ env: { ...process.env, DSH_HOME: dshRoot() },
1516
+ windowsHide: true,
1517
+ stdio: 'pipe',
1518
+ timeout: 60000,
1519
+ });
1520
+ if (r.status === 0) {
1521
+ console.log(' DSH MCP client package installed via dsh');
1522
+ return true;
1523
+ }
1524
+ const err = `${r.stderr || ''}${r.stdout || ''}`.trim().split(/\r?\n/).slice(-2).join(' ');
1525
+ console.log(` \x1b[33m[WARN]\x1b[0m DSH MCP client auto-install failed${err ? `: ${err}` : ''}`);
1526
+ }
1527
+ if (commandAvailable('pnpm') && existsSync(join(dshProfileDir(), 'package.json'))) {
1528
+ const r = spawnSync('pnpm', ['--dir', dshProfileDir(), 'add', '@deepseek-ai/dsh-mcp-client'], {
1529
+ windowsHide: true,
1530
+ stdio: 'pipe',
1531
+ timeout: 60000,
1532
+ });
1533
+ if (r.status === 0) {
1534
+ console.log(' DSH MCP client package installed via pnpm');
1535
+ return true;
1536
+ }
1537
+ }
1538
+ console.log(' \x1b[33m[WARN]\x1b[0m DSH MCP client package not detected.');
1539
+ console.log(' Manual: npx @deepseek-ai/dsh plugin --profile web add @deepseek-ai/dsh-mcp-client');
1540
+ console.log(' If pnpm is missing, run: corepack enable pnpm');
1541
+ return false;
1542
+ }
1543
+
1544
+ async function installDsh() {
1545
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1546
+ const srcDir = join(PLUGIN_ROOT, 'src');
1547
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1548
+ const pluginDest = dshPluginsDir();
1549
+
1550
+ mkdirSync(pluginDest, { recursive: true });
1551
+ copyDir(skillsSrc, dshSkillsDir());
1552
+ console.log(` Skills -> ${dshSkillsDir()}`);
1553
+ copyDir(srcDir, join(pluginDest, 'src'));
1554
+ console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1555
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1556
+ console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1557
+ ensureDshMcpPatch();
1558
+ tryInstallDshMcpClient();
1559
+ installRuntimeDeps(pluginDest);
1560
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1561
+ }
1562
+
1563
+ async function updateDsh() {
1564
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1565
+ const srcDir = join(PLUGIN_ROOT, 'src');
1566
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1567
+ const pluginDest = dshPluginsDir();
1568
+
1569
+ mkdirSync(pluginDest, { recursive: true });
1570
+ copyDir(skillsSrc, dshSkillsDir());
1571
+ const stale = pruneStale(dshSkillsDir(), skillsSrc);
1572
+ console.log(` Skills updated -> ${dshSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
1573
+ copyDir(srcDir, join(pluginDest, 'src'));
1574
+ console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1575
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1576
+ console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1577
+ ensureDshMcpPatch();
1578
+ tryInstallDshMcpClient();
1579
+ installRuntimeDeps(pluginDest);
1580
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1581
+ }
1582
+
1583
+ function uninstallDsh() {
1584
+ const skillsDir = dshSkillsDir();
1585
+ let removed = 0;
1586
+ if (existsSync(skillsDir)) {
1587
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
1588
+ if (entry.name.startsWith('huawei')) {
1589
+ removeIfExists(join(skillsDir, entry.name));
1590
+ removed++;
1591
+ }
1592
+ }
1593
+ if (removed > 0) console.log(` Removed ${removed} skills`);
1594
+ try {
1595
+ if (readdirSync(skillsDir).length === 0) {
1596
+ rmSync(skillsDir, { recursive: true, force: true });
1597
+ console.log(` Removed empty skills directory: ${skillsDir}`);
1598
+ }
1599
+ } catch {}
1600
+ }
1601
+ if (removeIfExists(dshPluginsDir())) {
1602
+ console.log(' Removed MCP server and safety policy');
1603
+ }
1604
+ removeDshMcpPatch();
1605
+ }
1606
+
1607
+ function dshStatus() {
1608
+ const pluginDir = dshPluginsDir();
1609
+ const skillsDir = dshSkillsDir();
1610
+ console.log(
1611
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1612
+ );
1613
+ console.log(
1614
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1615
+ );
1616
+ let skillCount = 0;
1617
+ if (existsSync(skillsDir)) {
1618
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1619
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1620
+ ).length;
1621
+ }
1622
+ console.log(
1623
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1624
+ );
1625
+ console.log(` DSH patch: ${dshPatchConfigured() ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
1626
+ console.log(
1627
+ ` DSH MCP client package: ${dshMcpClientAvailable() ? '\x1b[32mDetected\x1b[0m' : '\x1b[33mCheck DSH profile\x1b[0m'}`,
1628
+ );
1629
+ }
1630
+
1631
+ async function promptOfficeaceInstallDir() {
1632
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1633
+ const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
1634
+ while (true) {
1635
+ const path = (await ask(' Install directory: ')).trim();
1636
+ if (!path) {
1637
+ console.log(' \x1b[33mPath cannot be empty.\x1b[0m');
1638
+ continue;
1639
+ }
1640
+ const capFile = join(path, '.office-claw', 'capabilities.json');
1641
+ if (existsSync(capFile)) {
1642
+ rl.close();
1643
+ return path;
1644
+ }
1645
+ console.log(` \x1b[33mcapabilities.json not found at: ${capFile}\x1b[0m`);
1646
+ console.log(' Please verify the path and try again.');
1647
+ }
1648
+ }
1649
+
1650
+ async function installOfficeAce() {
1651
+ if (!officeaceCapabilitiesDir()) {
1652
+ if (process.stdin.isTTY) {
1653
+ console.log(' \x1b[33mOfficeAce install directory not found automatically.\x1b[0m');
1654
+ console.log(' Please enter the OfficeAce install directory.');
1655
+ const entered = await promptOfficeaceInstallDir();
1656
+ process.env.OFFICE_CLAW_CONFIG_ROOT = join(entered, '.office-claw');
1657
+ } else {
1658
+ console.log(
1659
+ ' \x1b[31mOfficeAce install directory not found. Please set OFFICE_CLAW_CONFIG_ROOT env var and retry.\x1b[0m',
1660
+ );
1661
+ return;
1662
+ }
1663
+ }
1664
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1665
+ const srcDir = join(PLUGIN_ROOT, 'src');
1666
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1667
+ const pluginDest = officeacePluginsDir();
1668
+
1669
+ copyDir(skillsSrc, officeaceSkillsDir());
1670
+ console.log(` Skills -> ${officeaceSkillsDir()}`);
1671
+
1672
+ copyDir(srcDir, join(pluginDest, 'src'));
1673
+ console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1674
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1675
+ console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1676
+
1677
+ installRuntimeDeps(pluginDest);
1678
+ ensureOfficeaceMcpInSqlite();
1679
+ registerOfficeaceSkillEntries();
1680
+ }
1681
+
1682
+ async function updateOfficeAce() {
1683
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1684
+ const srcDir = join(PLUGIN_ROOT, 'src');
1685
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1686
+ const pluginDest = officeacePluginsDir();
1687
+
1688
+ copyDir(skillsSrc, officeaceSkillsDir());
1689
+ const stale = pruneStale(officeaceSkillsDir(), skillsSrc);
1690
+ console.log(` Skills updated -> ${officeaceSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
1691
+ copyDir(srcDir, join(pluginDest, 'src'));
1692
+ console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1693
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1694
+ console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1695
+ installRuntimeDeps(pluginDest);
1696
+ ensureOfficeaceMcpInSqlite();
1697
+ registerOfficeaceSkillEntries();
1698
+ mkdirSync(pluginDest, { recursive: true });
1699
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1700
+ }
1701
+
1702
+ function uninstallOfficeAce() {
1703
+ const skillsDir = officeaceSkillsDir();
1704
+ let removed = 0;
1705
+ if (existsSync(skillsDir)) {
1706
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
1707
+ if (entry.name.startsWith('huawei')) {
1708
+ removeIfExists(join(skillsDir, entry.name));
1709
+ removed++;
1710
+ }
1711
+ }
1712
+ if (removed > 0) console.log(` Removed ${removed} skills`);
1713
+ try {
1714
+ if (readdirSync(skillsDir).length === 0) {
1715
+ rmSync(skillsDir, { recursive: true, force: true });
1716
+ console.log(` Removed empty skills directory: ${skillsDir}`);
1717
+ }
1718
+ } catch {}
1719
+ }
1720
+
1721
+ if (removeIfExists(officeacePluginsDir())) {
1722
+ console.log(' Removed MCP server and safety policy');
1723
+ }
1724
+ removeOfficeaceSkillCapabilities();
1725
+ removeOfficeaceMcpFromSqlite();
1726
+ }
1727
+
1728
+ function officeaceStatus() {
1729
+ const pluginDir = officeacePluginsDir();
1730
+ const skillsDir = officeaceSkillsDir();
1731
+ console.log(
1732
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1733
+ );
1734
+ console.log(
1735
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1736
+ );
625
1737
  let skillCount = 0;
626
- if (existsSync(codeartsSkillsDir())) {
627
- skillCount = readdirSync(codeartsSkillsDir(), { withFileTypes: true })
628
- .filter((d) => d.isDirectory() && d.name.startsWith('huawei')).length;
629
- }
630
- console.log(` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`);
631
- if (existsSync(codeartsMcpSettingsFile())) {
1738
+ if (existsSync(skillsDir)) {
1739
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1740
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1741
+ ).length;
1742
+ }
1743
+ console.log(
1744
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1745
+ );
1746
+ const dbPath = officeaceSqlitePath();
1747
+ if (existsSync(dbPath)) {
632
1748
  try {
633
- const config = JSON.parse(readFileSync(codeartsMcpSettingsFile(), 'utf8'));
634
- console.log(` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
1749
+ const db = openOfficeaceDb();
1750
+ const row = db.prepare("SELECT enabled, status FROM mcp_connectors WHERE name = 'huaweicloud-devkit'").get();
1751
+ db.close();
1752
+ if (row) {
1753
+ const statusIcon = row.status === 'connected' ? '\x1b[32m' : '\x1b[33m';
1754
+ console.log(` MCP config: ${statusIcon}${row.status}\x1b[0m (enabled: ${row.enabled ? 'yes' : 'no'})`);
1755
+ } else {
1756
+ console.log(` MCP config: \x1b[31mNot configured\x1b[0m`);
1757
+ }
635
1758
  } catch {
636
1759
  console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
637
1760
  }
1761
+ } else {
1762
+ console.log(` MCP config: \x1b[31mDatabase not found\x1b[0m`);
1763
+ }
1764
+ }
1765
+
1766
+ // ── Hermes Agent ──
1767
+
1768
+ function hermesHomeDir() {
1769
+ if (process.env.HERMES_HOME) return process.env.HERMES_HOME;
1770
+ // Hermes on Windows stores under LOCALAPPDATA, not ~/.hermes
1771
+ if (platform() === 'win32' && process.env.LOCALAPPDATA) {
1772
+ return join(process.env.LOCALAPPDATA, 'hermes');
638
1773
  }
1774
+ return join(homedir(), '.hermes');
1775
+ }
1776
+
1777
+ function hermesSkillsDir() {
1778
+ return join(hermesHomeDir(), 'skills');
1779
+ }
1780
+
1781
+ function hermesPluginsDir() {
1782
+ return join(hermesHomeDir(), 'huaweicloud-plugins');
1783
+ }
1784
+
1785
+ function hermesConfigFile() {
1786
+ return join(hermesHomeDir(), 'config.yaml');
639
1787
  }
640
1788
 
641
1789
  // Returns true when the config file was written, false when it was already correct.
642
- function ensureWorkbuddyMcpConfig() {
643
- const configPath = workbuddyMcpConfigFile();
644
- const mcpPath = join(workbuddyPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
645
- const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
1790
+ function ensureHermesMcpConfig() {
1791
+ const configPath = hermesConfigFile();
1792
+ const mcpPath = join(hermesPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
646
1793
  const hcloudBin = findHcloudBin();
647
- if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
648
- let config = {};
1794
+
1795
+ const blockLines = [
1796
+ 'mcp_servers:',
1797
+ ' huaweicloud-devkit:',
1798
+ ' command: "node"',
1799
+ ` args: ["${mcpPath}"]`,
1800
+ ' env:',
1801
+ ' HUAWEICLOUD_AGENT_TOOLKIT_MODE: "local"',
1802
+ ];
1803
+ if (hcloudBin) {
1804
+ blockLines.push(` HCLOUD_BIN: "${hcloudBin.replace(/\\/g, '/')}"`);
1805
+ }
1806
+ const block = blockLines.join('\n');
1807
+
1808
+ let existing = '';
649
1809
  if (existsSync(configPath)) {
650
- try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {
651
- console.log(` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`);
652
- return false;
653
- }
654
- const existing = config.mcpServers?.['huaweicloud-devkit'];
655
- if (existing && existing.command === 'node'
656
- && Array.isArray(existing.args) && existing.args[0] === mcpPath) {
657
- console.log(` MCP config unchanged: ${configPath}`);
658
- return false;
1810
+ try {
1811
+ existing = readFileSync(configPath, 'utf8');
1812
+ } catch {}
1813
+ if (existing.includes('mcp_servers:') && existing.includes('huaweicloud-devkit')) {
1814
+ if (existing.includes(`args: ["${mcpPath}"]`)) {
1815
+ console.log(` MCP config unchanged: ${configPath}`);
1816
+ return false;
1817
+ }
1818
+ removeHermesMcpConfigBlock();
1819
+ existing = '';
1820
+ if (existsSync(configPath)) {
1821
+ try {
1822
+ existing = readFileSync(configPath, 'utf8');
1823
+ } catch {}
1824
+ }
659
1825
  }
660
1826
  }
661
- config.mcpServers = config.mcpServers || {};
662
- config.mcpServers['huaweicloud-devkit'] = {
663
- command: 'node',
664
- args: [mcpPath],
665
- env,
666
- };
667
1827
  mkdirSync(dirname(configPath), { recursive: true });
668
- writeFileSync(configPath, JSON.stringify(config, null, 2));
1828
+ const newContent = existing ? `${existing.trimEnd()}\n\n${block}\n` : `${block}\n`;
1829
+ writeFileSync(configPath, newContent);
669
1830
  console.log(` MCP config updated: ${configPath}`);
670
1831
  return true;
671
1832
  }
672
1833
 
673
- async function installWorkBuddy() {
1834
+ function removeHermesMcpConfigBlock() {
1835
+ const configPath = hermesConfigFile();
1836
+ if (!existsSync(configPath)) return;
1837
+ const lines = readFileSync(configPath, 'utf8').split(/\r?\n/);
1838
+ const out = [];
1839
+ let skip = false;
1840
+ for (const line of lines) {
1841
+ if (/^\s*huaweicloud-devkit\s*:/.test(line)) {
1842
+ skip = true;
1843
+ continue;
1844
+ }
1845
+ if (skip) {
1846
+ // Continue skipping indented lines (the server config block)
1847
+ if (/^\s{2,}/.test(line) && line.trim() !== '') continue;
1848
+ // Stop skipping at a top-level key or empty line
1849
+ skip = false;
1850
+ }
1851
+ // If we just skipped the only server under mcp_servers, remove the key too
1852
+ if (line.trim() === 'mcp_servers:') {
1853
+ continue; // skip the mcp_servers key for now, re-add if other servers exist
1854
+ }
1855
+ out.push(line);
1856
+ }
1857
+ // Reconstruct: add mcp_servers back if there are other servers
1858
+ const cleaned = out.join('\n').trimEnd();
1859
+ writeFileSync(configPath, cleaned ? `${cleaned}\n` : '');
1860
+ console.log(' MCP config cleaned');
1861
+ }
1862
+
1863
+ async function installHermes() {
674
1864
  const skillsSrc = join(PLUGIN_ROOT, 'skills');
675
1865
  const srcDir = join(PLUGIN_ROOT, 'src');
676
1866
  const safetyDir = join(PLUGIN_ROOT, 'safety');
677
- const pluginDest = workbuddyPluginsDir();
1867
+ const pluginDest = hermesPluginsDir();
678
1868
 
679
- copyDir(skillsSrc, workbuddySkillsDir());
680
- console.log(` Skills -> ${workbuddySkillsDir()}`);
1869
+ copyDir(skillsSrc, hermesSkillsDir());
1870
+ console.log(` Skills -> ${hermesSkillsDir()}`);
681
1871
 
682
1872
  copyDir(srcDir, join(pluginDest, 'src'));
683
1873
  console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
684
1874
  copyDir(safetyDir, join(pluginDest, 'safety'));
685
1875
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
686
1876
 
687
- ensureWorkbuddyMcpConfig();
1877
+ ensureHermesMcpConfig();
1878
+ installRuntimeDeps(pluginDest);
688
1879
  }
689
1880
 
690
- // Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
691
- async function updateWorkBuddy() {
1881
+ async function updateHermes() {
692
1882
  const skillsSrc = join(PLUGIN_ROOT, 'skills');
693
1883
  const srcDir = join(PLUGIN_ROOT, 'src');
694
1884
  const safetyDir = join(PLUGIN_ROOT, 'safety');
695
- const pluginDest = workbuddyPluginsDir();
1885
+ const pluginDest = hermesPluginsDir();
696
1886
 
697
- copyDir(skillsSrc, workbuddySkillsDir());
698
- const stale = pruneStale(workbuddySkillsDir(), skillsSrc);
699
- console.log(` Skills updated -> ${workbuddySkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
1887
+ copyDir(skillsSrc, hermesSkillsDir());
1888
+ const stale = pruneStale(hermesSkillsDir(), skillsSrc);
1889
+ console.log(` Skills updated -> ${hermesSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
700
1890
  copyDir(srcDir, join(pluginDest, 'src'));
701
1891
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
702
1892
  copyDir(safetyDir, join(pluginDest, 'safety'));
703
1893
  console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
704
- ensureWorkbuddyMcpConfig();
1894
+ ensureHermesMcpConfig();
705
1895
  mkdirSync(pluginDest, { recursive: true });
706
1896
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1897
+ installRuntimeDeps(pluginDest);
707
1898
  }
708
1899
 
709
- function uninstallWorkBuddy() {
710
- const skillsDir = workbuddySkillsDir();
1900
+ function uninstallHermes() {
1901
+ const skillsDir = hermesSkillsDir();
711
1902
  let removed = 0;
712
1903
  if (existsSync(skillsDir)) {
713
1904
  for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
@@ -717,79 +1908,120 @@ function uninstallWorkBuddy() {
717
1908
  }
718
1909
  }
719
1910
  if (removed > 0) console.log(` Removed ${removed} skills`);
1911
+ try {
1912
+ if (readdirSync(skillsDir).length === 0) {
1913
+ rmSync(skillsDir, { recursive: true, force: true });
1914
+ console.log(` Removed empty skills directory: ${skillsDir}`);
1915
+ }
1916
+ } catch {}
720
1917
  }
721
1918
 
722
- if (removeIfExists(workbuddyPluginsDir())) {
1919
+ if (removeIfExists(hermesPluginsDir())) {
723
1920
  console.log(' Removed MCP server and safety policy');
724
1921
  }
725
-
726
- const configPath = workbuddyMcpConfigFile();
727
- if (existsSync(configPath)) {
728
- let config = {};
729
- try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
730
- if (config.mcpServers?.['huaweicloud-devkit']) {
731
- delete config.mcpServers['huaweicloud-devkit'];
732
- if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
733
- writeFileSync(configPath, JSON.stringify(config, null, 2));
734
- console.log(` MCP config cleaned: ${configPath}`);
735
- }
736
- }
1922
+ removeHermesMcpConfigBlock();
737
1923
  }
738
1924
 
739
- function workbuddyStatus() {
740
- const pluginDir = workbuddyPluginsDir();
741
- const skillsDir = workbuddySkillsDir();
742
- console.log(` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
743
- console.log(` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
1925
+ function hermesStatus() {
1926
+ const pluginDir = hermesPluginsDir();
1927
+ const skillsDir = hermesSkillsDir();
1928
+ console.log(
1929
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1930
+ );
1931
+ console.log(
1932
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1933
+ );
744
1934
  let skillCount = 0;
745
1935
  if (existsSync(skillsDir)) {
746
- skillCount = readdirSync(skillsDir, { withFileTypes: true })
747
- .filter((d) => d.isDirectory() && d.name.startsWith('huawei')).length;
748
- }
749
- console.log(` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`);
750
- const configPath = workbuddyMcpConfigFile();
1936
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1937
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1938
+ ).length;
1939
+ }
1940
+ console.log(
1941
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1942
+ );
1943
+ const configPath = hermesConfigFile();
751
1944
  if (existsSync(configPath)) {
752
1945
  try {
753
- const config = JSON.parse(readFileSync(configPath, 'utf8'));
754
- console.log(` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
1946
+ const config = readFileSync(configPath, 'utf8');
1947
+ const configured = config.includes('mcp_servers:') && config.includes('huaweicloud-devkit');
1948
+ console.log(` MCP config: ${configured ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
755
1949
  } catch {
756
1950
  console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
757
1951
  }
1952
+ } else {
1953
+ console.log(` MCP config: \x1b[31mNot found\x1b[0m`);
758
1954
  }
759
1955
  }
760
1956
 
761
1957
  function opencodeStatus() {
762
1958
  const pluginDir = opencodePluginsDir();
763
1959
  const skillsDir = opencodeSkillsDir();
764
- console.log(` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
765
- console.log(` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
1960
+ console.log(
1961
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1962
+ );
1963
+ console.log(
1964
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1965
+ );
766
1966
  let skillCount = 0;
767
1967
  if (existsSync(skillsDir)) {
768
- skillCount = readdirSync(skillsDir, { withFileTypes: true })
769
- .filter((d) => d.isDirectory() && d.name.startsWith('huawei')).length;
1968
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1969
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1970
+ ).length;
770
1971
  }
771
- console.log(` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`);
1972
+ console.log(
1973
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1974
+ );
772
1975
  const configPath = opencodeConfigFile();
773
1976
  if (existsSync(configPath)) {
774
1977
  try {
775
1978
  const config = JSON.parse(readFileSync(configPath, 'utf8'));
776
- console.log(` MCP config: ${config.mcp?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
1979
+ console.log(
1980
+ ` MCP config: ${config.mcp?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
1981
+ );
777
1982
  } catch {
778
1983
  console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
779
1984
  }
780
1985
  }
781
1986
  }
782
1987
 
1988
+ function autoDetectTarget() {
1989
+ const checks = [
1990
+ ['opencode', () => existsSync(join(homedir(), '.config', 'opencode'))],
1991
+ ['codex-desktop', () => existsSync(join(homedir(), '.codex'))],
1992
+ ['codearts', () => existsSync(join(homedir(), '.codeartsdoer'))],
1993
+ ['workbuddy', () => existsSync(join(homedir(), '.workbuddy'))],
1994
+ [
1995
+ 'dsh',
1996
+ () => {
1997
+ const dsh = process.env.DSH_HOME || join(homedir(), '.dsh');
1998
+ return existsSync(dsh);
1999
+ },
2000
+ ],
2001
+ ['officeace', () => existsSync(officeaceCapabilitiesDir())],
2002
+ ['hermes', () => existsSync(hermesHomeDir())],
2003
+ ['openclaw', () => existsSync(join(homedir(), '.openclaw'))],
2004
+ ['atomcode', () => existsSync(atomcodeHome())],
2005
+ ];
2006
+ const detected = checks.filter(([, check]) => check()).map(([name]) => name);
2007
+ if (detected.length === 0) {
2008
+ console.error('No supported agent detected.');
2009
+ console.error(`Supported: ${SUPPORTED_AGENT_TARGETS.join(', ')} (or "all")`);
2010
+ console.error('Use --target <agent> to specify.');
2011
+ process.exit(1);
2012
+ }
2013
+ if (detected.length === 1) return detected[0];
2014
+ return 'all';
2015
+ }
2016
+
783
2017
  function parseTarget() {
784
2018
  const idx = process.argv.indexOf('--target');
785
- if (idx < 0) return 'opencode';
2019
+ if (idx < 0) return autoDetectTarget();
786
2020
  const val = (process.argv[idx + 1] || '').toLowerCase();
787
- if (val === 'codex') return 'codex';
788
- if (val === 'codex-desktop') return 'codex-desktop';
789
- if (val === 'codearts') return 'codearts';
790
- if (val === 'workbuddy') return 'workbuddy';
791
- if (val === 'all') return 'all';
792
- return 'opencode';
2021
+ if (val === 'all' || SUPPORTED_AGENT_TARGETS.includes(val)) return val;
2022
+ console.error(`Unknown target: ${val}`);
2023
+ console.error(`Supported: ${SUPPORTED_AGENT_TARGETS.join(', ')} (or "all")`);
2024
+ process.exit(1);
793
2025
  }
794
2026
 
795
2027
  async function cmdInstall() {
@@ -814,6 +2046,26 @@ async function cmdInstall() {
814
2046
  console.log('\n[WorkBuddy]');
815
2047
  await installWorkBuddy();
816
2048
  }
2049
+ if (target === 'dsh' || target === 'all') {
2050
+ console.log('\n[DSH]');
2051
+ await installDsh();
2052
+ }
2053
+ if (target === 'officeace' || target === 'all') {
2054
+ console.log('\n[OfficeAce]');
2055
+ await installOfficeAce();
2056
+ }
2057
+ if (target === 'hermes' || target === 'all') {
2058
+ console.log('\n[Hermes Agent]');
2059
+ await installHermes();
2060
+ }
2061
+ if (target === 'openclaw' || target === 'all') {
2062
+ console.log('\n[OpenClaw]');
2063
+ await installCodexDesktop();
2064
+ }
2065
+ if (target === 'atomcode' || target === 'all') {
2066
+ console.log('\n[AtomCode]');
2067
+ await installAtomCode();
2068
+ }
817
2069
  if (target === 'codex' || target === 'all') {
818
2070
  console.log('\n[Codex]');
819
2071
  if (!hasCodexCLI()) {
@@ -836,18 +2088,47 @@ async function cmdInstall() {
836
2088
  } else {
837
2089
  installCodex();
838
2090
  }
839
- } console.log(`\n\x1b[32mInstallation complete!\x1b[0m`);
840
- const appName = target === 'codearts' ? 'CodeArts'
841
- : target === 'codex-desktop' ? 'Codex Desktop'
842
- : target === 'codex' ? 'Codex'
843
- : target === 'workbuddy' ? 'WorkBuddy'
844
- : 'OpenCode';
2091
+ }
2092
+ console.log(`\n\x1b[32mInstallation complete!\x1b[0m`);
2093
+ const appName =
2094
+ target === 'codearts'
2095
+ ? 'CodeArts'
2096
+ : target === 'codex-desktop'
2097
+ ? 'Codex Desktop'
2098
+ : target === 'codex'
2099
+ ? 'Codex'
2100
+ : target === 'workbuddy'
2101
+ ? 'WorkBuddy'
2102
+ : target === 'dsh'
2103
+ ? 'DSH'
2104
+ : target === 'officeace'
2105
+ ? 'OfficeAce'
2106
+ : target === 'hermes'
2107
+ ? 'Hermes Agent'
2108
+ : target === 'openclaw'
2109
+ ? 'OpenClaw'
2110
+ : target === 'atomcode'
2111
+ ? 'AtomCode'
2112
+ : '当前 agent';
845
2113
  const pad = ' '.repeat(24 - appName.length);
846
- console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════╗`);
847
- console.log(`\x1b[1m\x1b[33m║ MCP 工具在重启 ${appName} 会话后才生效${pad}║`);
848
- console.log(`\x1b[1m\x1b[33m║ 关闭当前会话 → 重新打开,直接描述华为云任务即可 ║`);
849
- console.log(`\x1b[1m\x1b[33m║ 重启前请勿执行 hcloud 命令,避免 AK/SK 泄露 ║`);
850
- console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
2114
+ if (target === 'officeace') {
2115
+ console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════════╗`);
2116
+ console.log(`\x1b[1m\x1b[33m║ 打开连接器 → 我的连接器 → huaweicloud-devkit �`);
2117
+ console.log(`\x1b[1m\x1b[33m║ → 连接 → 回到对话 → 输入框开启连接器 �`);
2118
+ console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
2119
+ } else if (target === 'workbuddy') {
2120
+ console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════════╗`);
2121
+ console.log(`\x1b[1m\x1b[33m║ MCP 工具即时生效,无需重启会话 �`);
2122
+ console.log(`\x1b[1m\x1b[33m║ 前往连接器 → 自定义连接器,确认 huaweicloud-devkit �`);
2123
+ console.log(`\x1b[1m\x1b[33m║ 已添加信任并启用 �`);
2124
+ console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
2125
+ } else {
2126
+ console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════╗`);
2127
+ console.log(`\x1b[1m\x1b[33m║ MCP 工具在重启 ${appName} 会话后才生效${pad}║`);
2128
+ console.log(`\x1b[1m\x1b[33m║ 关闭当前会话 → 重新打开,直接描述华为云任务即可 ║`);
2129
+ console.log(`\x1b[1m\x1b[33m║ 重启前请勿执行 hcloud 命令,避免 AK/SK 泄露 ║`);
2130
+ console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
2131
+ }
851
2132
 
852
2133
  const hcloudOk = checkHcloud();
853
2134
  if (!hcloudOk) {
@@ -859,14 +2140,33 @@ async function cmdInstall() {
859
2140
 
860
2141
  console.log(`\n\x1b[1m下一步:\x1b[0m`);
861
2142
  console.log(` 1. 配置统一凭据:npx huaweicloud-devkit auth init`);
862
- console.log(` 2. 重启 ${appName} 会话(MCP 工具重启后生效)`);
863
- console.log(` 3. 运行自检:npx huaweicloud-devkit doctor`);
2143
+ console.log(` 2. 配置代理(企业内网):npx huaweicloud-devkit proxy init`);
2144
+ if (target === 'officeace') {
2145
+ console.log(` 3. 打开连接器 → 我的连接器 → huaweicloud-devkit → 连接 → 回到对话 → 输入框开启连接器`);
2146
+ } else if (target === 'workbuddy') {
2147
+ console.log(` 3. 前往连接器 → 自定义连接器,确认 huaweicloud-devkit 已添加信任并启用`);
2148
+ } else {
2149
+ console.log(` 3. 重启 ${appName} 会话(MCP 工具重启后生效)`);
2150
+ }
2151
+ console.log(` 4. 运行自检:npx huaweicloud-devkit doctor`);
864
2152
 
865
2153
  // Write install marker for doctor to detect
866
- const markerDir = target === 'codearts' ? codeartsPluginsDir()
867
- : target === 'workbuddy' ? workbuddyPluginsDir()
868
- : target === 'codex-desktop' ? codexDesktopPluginsDir()
869
- : opencodePluginsDir();
2154
+ const markerDir =
2155
+ target === 'dsh'
2156
+ ? dshPluginsDir()
2157
+ : target === 'codearts'
2158
+ ? codeartsPluginsDir()
2159
+ : target === 'workbuddy'
2160
+ ? workbuddyPluginsDir()
2161
+ : target === 'officeace'
2162
+ ? officeacePluginsDir()
2163
+ : target === 'openclaw'
2164
+ ? codexDesktopPluginsDir()
2165
+ : target === 'atomcode'
2166
+ ? atomcodePluginsDir()
2167
+ : target === 'codex-desktop'
2168
+ ? codexDesktopPluginsDir()
2169
+ : opencodePluginsDir();
870
2170
  mkdirSync(markerDir, { recursive: true });
871
2171
  writeFileSync(join(markerDir, '.installed'), new Date().toISOString());
872
2172
  if (target === 'opencode' || target === 'all') {
@@ -881,6 +2181,18 @@ async function cmdInstall() {
881
2181
  if (target === 'workbuddy' || target === 'all') {
882
2182
  console.log('Or describe your Huawei Cloud task in WorkBuddy');
883
2183
  }
2184
+ if (target === 'dsh' || target === 'all') {
2185
+ console.log('Or describe your Huawei Cloud task in DSH');
2186
+ }
2187
+ if (target === 'officeace' || target === 'all') {
2188
+ console.log('Or describe your Huawei Cloud task in OfficeAce');
2189
+ }
2190
+ if (target === 'openclaw' || target === 'all') {
2191
+ console.log('Or describe your Huawei Cloud task in OpenClaw');
2192
+ }
2193
+ if (target === 'atomcode' || target === 'all') {
2194
+ console.log('Or describe your Huawei Cloud task in AtomCode');
2195
+ }
884
2196
  }
885
2197
 
886
2198
  async function cmdUninstall() {
@@ -900,6 +2212,26 @@ async function cmdUninstall() {
900
2212
  console.log('\n[WorkBuddy]');
901
2213
  uninstallWorkBuddy();
902
2214
  }
2215
+ if (target === 'dsh' || target === 'all') {
2216
+ console.log('\n[DSH]');
2217
+ uninstallDsh();
2218
+ }
2219
+ if (target === 'officeace' || target === 'all') {
2220
+ console.log('\n[OfficeAce]');
2221
+ uninstallOfficeAce();
2222
+ }
2223
+ if (target === 'hermes' || target === 'all') {
2224
+ console.log('\n[Hermes Agent]');
2225
+ uninstallHermes();
2226
+ }
2227
+ if (target === 'openclaw' || target === 'all') {
2228
+ console.log('\n[OpenClaw]');
2229
+ uninstallCodexDesktop();
2230
+ }
2231
+ if (target === 'atomcode' || target === 'all') {
2232
+ console.log('\n[AtomCode]');
2233
+ uninstallAtomCode();
2234
+ }
903
2235
  if (target === 'codex-desktop' || target === 'codex' || target === 'all') {
904
2236
  console.log('\n[Codex]');
905
2237
  uninstallCodexDesktop();
@@ -944,6 +2276,42 @@ async function cmdStatus() {
944
2276
  console.log('\n[WorkBuddy]');
945
2277
  workbuddyStatus();
946
2278
  }
2279
+ if (target === 'dsh' || target === 'all') {
2280
+ console.log('\n[DSH]');
2281
+ dshStatus();
2282
+ }
2283
+ if (target === 'officeace' || target === 'all') {
2284
+ console.log('\n[OfficeAce]');
2285
+ officeaceStatus();
2286
+ }
2287
+ if (target === 'hermes' || target === 'all') {
2288
+ console.log('\n[Hermes Agent]');
2289
+ hermesStatus();
2290
+ }
2291
+ if (target === 'openclaw' || target === 'all') {
2292
+ console.log('\n[OpenClaw]');
2293
+ const cdPluginDir = codexDesktopPluginsDir();
2294
+ const cdSkillsDir = codexDesktopSkillsDir();
2295
+ console.log(
2296
+ ` MCP Server: ${existsSync(join(cdPluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
2297
+ );
2298
+ console.log(
2299
+ ` Safety Policy: ${existsSync(join(cdPluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
2300
+ );
2301
+ let cdSkillCount = 0;
2302
+ if (existsSync(cdSkillsDir)) {
2303
+ cdSkillCount = readdirSync(cdSkillsDir, { withFileTypes: true }).filter(
2304
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
2305
+ ).length;
2306
+ }
2307
+ console.log(
2308
+ ` Skills: ${cdSkillCount > 0 ? `\x1b[32m${cdSkillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
2309
+ );
2310
+ }
2311
+ if (target === 'atomcode' || target === 'all') {
2312
+ console.log('\n[AtomCode]');
2313
+ atomcodeStatus();
2314
+ }
947
2315
  if (target === 'codex' || target === 'all') {
948
2316
  console.log('\n[Codex]');
949
2317
  if (!hasCodexCLI()) {
@@ -961,66 +2329,187 @@ async function cmdDoctor() {
961
2329
  console.log(BANNER);
962
2330
  console.log('HuaweiCloud DevKit Doctor\n');
963
2331
 
964
- let pass = 0, warn = 0, fail = 0;
2332
+ let pass = 0,
2333
+ warn = 0,
2334
+ fail = 0;
965
2335
 
966
2336
  function check(label, ok, msg) {
967
- if (ok) { console.log(` \x1b[32m[PASS]\x1b[0m ${label}`); pass++; }
968
- else { console.log(` \x1b[31m[FAIL]\x1b[0m ${label} — ${msg}`); fail++; }
2337
+ if (ok) {
2338
+ console.log(` \x1b[32m[PASS]\x1b[0m ${label}`);
2339
+ pass++;
2340
+ } else {
2341
+ console.log(` \x1b[31m[FAIL]\x1b[0m ${label} — ${msg}`);
2342
+ fail++;
2343
+ }
969
2344
  }
970
2345
 
971
2346
  // Node.js
972
- check('Node.js >= 20', process.versions.node.split('.')[0] >= 20, 'Run: nvm install 20 && nvm use 20');
2347
+ check('Node.js >= 22', process.versions.node.split('.')[0] >= 22, 'Run: nvm install 22 && nvm use 22');
973
2348
 
974
- // MCP server — check OpenCode, Codex Desktop, CodeArts, and WorkBuddy paths
2349
+ // MCP server — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH paths
975
2350
  const opencodePluginDir = opencodePluginsDir();
976
2351
  const codexPluginDir = codexDesktopPluginsDir();
2352
+ const codeartsPluginDir = codeartsPluginsDir();
977
2353
  const workbuddyPluginDir = workbuddyPluginsDir();
978
- const mcpOk = existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs'))
979
- || existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs'))
980
- || existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs'));
981
- const mcpTarget = existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs')) ? 'OpenCode'
982
- : existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs')) ? 'Codex Desktop'
983
- : existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs')) ? 'WorkBuddy' : '';
2354
+ const dshPluginDir = dshPluginsDir();
2355
+ const officeacePluginDir = officeacePluginsDir();
2356
+ const hermesPluginDir = hermesPluginsDir();
2357
+ const atomcodePluginDir = atomcodePluginsDir();
2358
+ const mcpOk =
2359
+ existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs')) ||
2360
+ existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs')) ||
2361
+ existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs')) ||
2362
+ existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs')) ||
2363
+ existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs')) ||
2364
+ existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs')) ||
2365
+ existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs')) ||
2366
+ existsSync(join(atomcodePluginDir, 'src', 'mcp-server.mjs'));
2367
+ const mcpTarget = existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs'))
2368
+ ? 'OpenCode'
2369
+ : existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs'))
2370
+ ? 'Codex Desktop'
2371
+ : existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs'))
2372
+ ? 'CodeArts'
2373
+ : existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs'))
2374
+ ? 'WorkBuddy'
2375
+ : existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs'))
2376
+ ? 'DSH'
2377
+ : existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs'))
2378
+ ? 'OfficeAce'
2379
+ : existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'))
2380
+ ? 'Hermes Agent'
2381
+ : existsSync(join(atomcodePluginDir, 'src', 'mcp-server.mjs'))
2382
+ ? 'AtomCode'
2383
+ : '';
984
2384
  check('MCP server installed', mcpOk, 'Run: npx huaweicloud-devkit install');
985
2385
 
986
2386
  if (mcpOk) {
987
2387
  check(`MCP server can start (${mcpTarget})`, true, '');
988
2388
  }
989
2389
 
990
- const safetyOk = existsSync(join(opencodePluginDir, 'safety', 'policy.json'))
991
- || existsSync(join(codexPluginDir, 'safety', 'policy.json'))
992
- || existsSync(join(workbuddyPluginDir, 'safety', 'policy.json'));
2390
+ const undiciPluginDirs = [
2391
+ opencodePluginDir,
2392
+ codexPluginDir,
2393
+ codeartsPluginDir,
2394
+ workbuddyPluginDir,
2395
+ dshPluginDir,
2396
+ officeacePluginDir,
2397
+ hermesPluginDir,
2398
+ ];
2399
+ const undiciOk = undiciPluginDirs.some((d) => existsSync(join(d, 'node_modules', 'undici')));
2400
+ check('Runtime deps (undici) installed', undiciOk, 'Run: npx huaweicloud-devkit install');
2401
+
2402
+ const safetyOk =
2403
+ existsSync(join(opencodePluginDir, 'safety', 'policy.json')) ||
2404
+ existsSync(join(codexPluginDir, 'safety', 'policy.json')) ||
2405
+ existsSync(join(codeartsPluginDir, 'safety', 'policy.json')) ||
2406
+ existsSync(join(workbuddyPluginDir, 'safety', 'policy.json')) ||
2407
+ existsSync(join(dshPluginDir, 'safety', 'policy.json')) ||
2408
+ existsSync(join(officeacePluginDir, 'safety', 'policy.json')) ||
2409
+ existsSync(join(hermesPluginDir, 'safety', 'policy.json')) ||
2410
+ existsSync(join(atomcodePluginDir, 'safety', 'policy.json'));
993
2411
  check('Safety policy installed', safetyOk, 'Run: npx huaweicloud-devkit install');
994
2412
 
995
- // MCP config — check OpenCode, Codex Desktop, and WorkBuddy
2413
+ // MCP config — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH
996
2414
  let mcpConfigured = false;
997
2415
  let mcpCfgTarget = '';
998
2416
  const opencodeCfg = opencodeConfigFile();
999
2417
  if (existsSync(opencodeCfg)) {
1000
2418
  try {
1001
2419
  const cfg = JSON.parse(readFileSync(opencodeCfg, 'utf8'));
1002
- if (cfg.mcp && cfg.mcp['huaweicloud-devkit']) { mcpConfigured = true; mcpCfgTarget = 'OpenCode'; }
2420
+ if (cfg.mcp && cfg.mcp['huaweicloud-devkit']) {
2421
+ mcpConfigured = true;
2422
+ mcpCfgTarget = 'OpenCode';
2423
+ }
1003
2424
  } catch {}
1004
2425
  }
1005
2426
  const codexCfg = codexConfigToml();
1006
2427
  if (!mcpConfigured && existsSync(codexCfg)) {
1007
2428
  try {
1008
2429
  const cfg = readFileSync(codexCfg, 'utf8');
1009
- if (cfg.includes('[mcp_servers.huaweicloud-devkit]')) { mcpConfigured = true; mcpCfgTarget = 'Codex Desktop'; }
2430
+ if (cfg.includes('[mcp_servers.huaweicloud-devkit]')) {
2431
+ mcpConfigured = true;
2432
+ mcpCfgTarget = 'Codex Desktop';
2433
+ }
2434
+ } catch {}
2435
+ }
2436
+ const codeartsCfg = codeartsMcpSettingsFile();
2437
+ if (!mcpConfigured && existsSync(codeartsCfg)) {
2438
+ try {
2439
+ const cfg = JSON.parse(readFileSync(codeartsCfg, 'utf8'));
2440
+ if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
2441
+ mcpConfigured = true;
2442
+ mcpCfgTarget = 'CodeArts';
2443
+ }
1010
2444
  } catch {}
1011
2445
  }
1012
2446
  const workbuddyCfg = workbuddyMcpConfigFile();
1013
2447
  if (!mcpConfigured && existsSync(workbuddyCfg)) {
1014
2448
  try {
1015
2449
  const cfg = JSON.parse(readFileSync(workbuddyCfg, 'utf8'));
1016
- if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) { mcpConfigured = true; mcpCfgTarget = 'WorkBuddy'; }
2450
+ if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
2451
+ mcpConfigured = true;
2452
+ mcpCfgTarget = 'WorkBuddy';
2453
+ }
1017
2454
  } catch {}
1018
2455
  }
1019
- check('MCP configured', mcpConfigured, mcpCfgTarget ? `Found in ${mcpCfgTarget} config` : 'Run: npx huaweicloud-devkit install');
2456
+ if (!mcpConfigured && dshPatchConfigured()) {
2457
+ mcpConfigured = true;
2458
+ mcpCfgTarget = 'DSH';
2459
+ }
2460
+ if (!mcpConfigured) {
2461
+ const officeaceCfg = officeaceCapabilitiesFile();
2462
+ if (existsSync(officeaceCfg)) {
2463
+ try {
2464
+ const cfg = JSON.parse(readFileSync(officeaceCfg, 'utf8'));
2465
+ if (
2466
+ Array.isArray(cfg.capabilities) &&
2467
+ cfg.capabilities.some((c) => c.id === 'huaweicloud-devkit' && c.type === 'mcp')
2468
+ ) {
2469
+ mcpConfigured = true;
2470
+ mcpCfgTarget = 'OfficeAce';
2471
+ }
2472
+ } catch {}
2473
+ }
2474
+ }
2475
+ if (!mcpConfigured) {
2476
+ const hermesCfg = hermesConfigFile();
2477
+ if (existsSync(hermesCfg)) {
2478
+ try {
2479
+ const cfg = readFileSync(hermesCfg, 'utf8');
2480
+ if (cfg.includes('mcp_servers:') && cfg.includes('huaweicloud-devkit')) {
2481
+ mcpConfigured = true;
2482
+ mcpCfgTarget = 'Hermes Agent';
2483
+ }
2484
+ } catch {}
2485
+ }
2486
+ }
2487
+ if (!mcpConfigured) {
2488
+ const atomcodeCfg = atomcodeMcpConfigFile();
2489
+ if (existsSync(atomcodeCfg)) {
2490
+ try {
2491
+ const cfg = JSON.parse(readFileSync(atomcodeCfg, 'utf8'));
2492
+ if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
2493
+ mcpConfigured = true;
2494
+ mcpCfgTarget = 'AtomCode';
2495
+ }
2496
+ } catch {}
2497
+ }
2498
+ }
2499
+ check(
2500
+ 'MCP configured',
2501
+ mcpConfigured,
2502
+ mcpCfgTarget ? `Found in ${mcpCfgTarget} config` : 'Run: npx huaweicloud-devkit install',
2503
+ );
1020
2504
 
1021
2505
  // hcloud CLI
1022
- const hcloudBin = findHcloudBin() || (process.env.HCLOUD_BIN || 'hcloud');
1023
- const hcloudCheck = spawnSync(`"${hcloudBin}" version`, [], { shell: true, windowsHide: true, stdio: 'pipe', timeout: 5000 });
2506
+ const hcloudBin = findHcloudBin() || process.env.HCLOUD_BIN || 'hcloud';
2507
+ const hcloudCheck = spawnSync(`"${hcloudBin}" version`, [], {
2508
+ shell: true,
2509
+ windowsHide: true,
2510
+ stdio: 'pipe',
2511
+ timeout: 5000,
2512
+ });
1024
2513
  const hcloudOut = (hcloudCheck.stdout || '').toString() + (hcloudCheck.stderr || '').toString();
1025
2514
  const hcloudOk = hcloudCheck.status === 0 && /KooCLI|Current.*version|当前KooCLI/i.test(hcloudOut);
1026
2515
  check('hcloud CLI installed', hcloudOk, 'Run: npx huaweicloud-devkit install-hcloud');
@@ -1039,20 +2528,38 @@ async function cmdDoctor() {
1039
2528
  console.log(` Version: ${ver}`);
1040
2529
 
1041
2530
  // Check auth
1042
- const authCheck = spawnSync(`"${hcloudBin}" configure list`, [], { shell: true, windowsHide: true, stdio: 'pipe', timeout: 5000 });
2531
+ const authCheck = spawnSync(`"${hcloudBin}" configure list`, [], {
2532
+ shell: true,
2533
+ windowsHide: true,
2534
+ stdio: 'pipe',
2535
+ timeout: 5000,
2536
+ });
1043
2537
  const hasAuth = authCheck.status === 0 && /access.?key/i.test(authCheck.stdout.toString());
1044
2538
  check('hcloud credentials configured', hasAuth, 'Run: npx huaweicloud-devkit auth init');
1045
2539
  }
1046
2540
 
1047
2541
  // Skills
1048
- const skillsOptions = [opencodeSkillsDir(), codexDesktopSkillsDir(), codeartsSkillsDir(), workbuddySkillsDir()];
1049
- let skillCount = 0, skillsDir = '', missingSkills = [];
2542
+ const skillsOptions = [
2543
+ opencodeSkillsDir(),
2544
+ codexDesktopSkillsDir(),
2545
+ codeartsSkillsDir(),
2546
+ workbuddySkillsDir(),
2547
+ dshSkillsDir(),
2548
+ officeaceSkillsDir(),
2549
+ hermesSkillsDir(),
2550
+ atomcodeSkillsDir(),
2551
+ ];
2552
+ let skillCount = 0;
2553
+ const missingSkills = [];
1050
2554
  for (const dir of skillsOptions) {
1051
2555
  if (!existsSync(dir)) continue;
1052
- const entries = readdirSync(dir, { withFileTypes: true })
1053
- .filter((d) => d.isDirectory() && d.name.startsWith('huawei'));
2556
+ const entries = readdirSync(dir, { withFileTypes: true }).filter(
2557
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
2558
+ );
1054
2559
  const count = entries.length;
1055
- if (count > skillCount) { skillCount = count; skillsDir = dir; }
2560
+ if (count > skillCount) {
2561
+ skillCount = count;
2562
+ }
1056
2563
  for (const d of entries) {
1057
2564
  if (!existsSync(join(dir, d.name, 'SKILL.md'))) missingSkills.push(d.name);
1058
2565
  }
@@ -1060,14 +2567,27 @@ async function cmdDoctor() {
1060
2567
  const skillsOk = skillCount >= 6;
1061
2568
  check(`Skills installed (${skillCount})`, skillsOk, 'Run: npx huaweicloud-devkit install');
1062
2569
  if (missingSkills.length > 0) {
1063
- console.log(` \x1b[33m[WARN]\x1b[0m ${missingSkills.length} skill(s) missing SKILL.md: ${missingSkills.join(', ')} — Run: npx huaweicloud-devkit install`);
2570
+ console.log(
2571
+ ` \x1b[33m[WARN]\x1b[0m ${missingSkills.length} skill(s) missing SKILL.md: ${missingSkills.join(', ')} — Run: npx huaweicloud-devkit install`,
2572
+ );
1064
2573
  warn++;
1065
2574
  }
1066
2575
 
2576
+ const proxyConfig = readProxyConfig();
2577
+ const proxyEnv =
2578
+ process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
2579
+ if (proxyConfig || proxyEnv) {
2580
+ const source = proxyEnv ? 'env' : 'file';
2581
+ const proxyUrl = proxyEnv || proxyConfig.https_proxy || proxyConfig.http_proxy;
2582
+ console.log(` \x1b[36m[INFO]\x1b[0m Proxy configured (${source}): ${proxyUrl}`);
2583
+ }
2584
+
1067
2585
  console.log(`\nResults: ${pass} pass, ${warn} warn, ${fail} fail`);
1068
2586
 
1069
2587
  if (mcpConfigured && !hcloudOk) {
1070
- console.log('\n\x1b[33mMCP is configured but hcloud is not installed. Install hcloud then restart your session.\x1b[0m');
2588
+ console.log(
2589
+ '\n\x1b[33mMCP is configured but hcloud is not installed. Install hcloud then restart your session.\x1b[0m',
2590
+ );
1071
2591
  }
1072
2592
  if (fail > 0) {
1073
2593
  console.log('\x1b[33mFix failures above, then restart your session.\x1b[0m');
@@ -1082,6 +2602,9 @@ async function cmdDoctor() {
1082
2602
  { path: join(codexDesktopPluginsDir(), '.installed'), name: 'Codex Desktop' },
1083
2603
  { path: join(workbuddyPluginsDir(), '.installed'), name: 'WorkBuddy' },
1084
2604
  { path: join(codeartsPluginsDir(), '.installed'), name: 'CodeArts' },
2605
+ { path: join(dshPluginsDir(), '.installed'), name: 'DSH' },
2606
+ { path: join(officeacePluginsDir(), '.installed'), name: 'OfficeAce' },
2607
+ { path: join(atomcodePluginsDir(), '.installed'), name: 'AtomCode' },
1085
2608
  ];
1086
2609
  for (const marker of installedMarkers) {
1087
2610
  if (existsSync(marker.path)) {
@@ -1163,6 +2686,66 @@ async function cmdUpdate() {
1163
2686
  return;
1164
2687
  }
1165
2688
 
2689
+ if (target === 'dsh') {
2690
+ if (!existsSync(join(dshPluginsDir(), 'src', 'mcp-server.mjs'))) {
2691
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
2692
+ return;
2693
+ }
2694
+ console.log('[DSH]');
2695
+ await updateDsh();
2696
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
2697
+ console.log(`\x1b[33mRestart the DSH session for changes to take effect.\x1b[0m`);
2698
+ return;
2699
+ }
2700
+
2701
+ if (target === 'officeace') {
2702
+ if (!existsSync(join(officeacePluginsDir(), 'src', 'mcp-server.mjs'))) {
2703
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
2704
+ return;
2705
+ }
2706
+ console.log('[OfficeAce]');
2707
+ await updateOfficeAce();
2708
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
2709
+ console.log(`\x1b[33m打开连接器 → 我的连接器 → huaweicloud-devkit → 连接 → 回到对话 → 输入框开启连接器\x1b[0m`);
2710
+ return;
2711
+ }
2712
+
2713
+ if (target === 'hermes') {
2714
+ if (!existsSync(join(hermesPluginsDir(), 'src', 'mcp-server.mjs'))) {
2715
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
2716
+ return;
2717
+ }
2718
+ console.log('[Hermes Agent]');
2719
+ await updateHermes();
2720
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
2721
+ console.log(`\x1b[33mRestart Hermes Agent for changes to take effect.\x1b[0m`);
2722
+ return;
2723
+ }
2724
+
2725
+ if (target === 'openclaw') {
2726
+ if (!existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
2727
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
2728
+ return;
2729
+ }
2730
+ console.log('[OpenClaw]');
2731
+ await updateCodexDesktop();
2732
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
2733
+ console.log(`\x1b[33mRestart OpenClaw for changes to take effect.\x1b[0m`);
2734
+ return;
2735
+ }
2736
+
2737
+ if (target === 'atomcode') {
2738
+ if (!existsSync(join(atomcodePluginsDir(), 'src', 'mcp-server.mjs'))) {
2739
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
2740
+ return;
2741
+ }
2742
+ console.log('[AtomCode]');
2743
+ await updateAtomCode();
2744
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
2745
+ console.log(`\x1b[33mRestart the AtomCode session for changes to take effect.\x1b[0m`);
2746
+ return;
2747
+ }
2748
+
1166
2749
  if (target === 'all') {
1167
2750
  let updatedAny = false;
1168
2751
  if (existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))) {
@@ -1185,6 +2768,31 @@ async function cmdUpdate() {
1185
2768
  await updateWorkBuddy();
1186
2769
  updatedAny = true;
1187
2770
  }
2771
+ if (existsSync(join(dshPluginsDir(), 'src', 'mcp-server.mjs'))) {
2772
+ console.log('\n[DSH]');
2773
+ await updateDsh();
2774
+ updatedAny = true;
2775
+ }
2776
+ if (existsSync(join(officeacePluginsDir(), 'src', 'mcp-server.mjs'))) {
2777
+ console.log('\n[OfficeAce]');
2778
+ await updateOfficeAce();
2779
+ updatedAny = true;
2780
+ }
2781
+ if (existsSync(join(hermesPluginsDir(), 'src', 'mcp-server.mjs'))) {
2782
+ console.log('\n[Hermes Agent]');
2783
+ await updateHermes();
2784
+ updatedAny = true;
2785
+ }
2786
+ if (existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
2787
+ console.log('\n[OpenClaw]');
2788
+ await updateCodexDesktop();
2789
+ updatedAny = true;
2790
+ }
2791
+ if (existsSync(join(atomcodePluginsDir(), 'src', 'mcp-server.mjs'))) {
2792
+ console.log('\n[AtomCode]');
2793
+ await updateAtomCode();
2794
+ updatedAny = true;
2795
+ }
1188
2796
  if (codexStatus()) {
1189
2797
  console.log('\n[Codex]');
1190
2798
  installCodex();
@@ -1235,9 +2843,7 @@ async function cmdInstallHcloud() {
1235
2843
  const os = platform();
1236
2844
  const arch = process.arch;
1237
2845
  const baseUrl = 'https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/latest';
1238
- const installDir = os === 'win32'
1239
- ? join(homedir(), 'hcloud')
1240
- : join(homedir(), '.local', 'bin');
2846
+ const installDir = os === 'win32' ? join(homedir(), 'hcloud') : join(homedir(), '.local', 'bin');
1241
2847
 
1242
2848
  if (os === 'win32') {
1243
2849
  const url = `${baseUrl}/huaweicloud-cli-windows-amd64.zip`;
@@ -1251,12 +2857,20 @@ async function cmdInstallHcloud() {
1251
2857
  // Download
1252
2858
  console.log(` Downloading ${url}...`);
1253
2859
  const psCmd = `[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '${url}' -OutFile '${zipPath}' -UseBasicParsing`;
1254
- const dl = spawnSync('powershell', ['-NoProfile', '-Command', psCmd], { stdio: 'inherit', windowsHide: true, timeout: 180000 });
2860
+ const dl = spawnSync('powershell', ['-NoProfile', '-Command', psCmd], {
2861
+ stdio: 'inherit',
2862
+ windowsHide: true,
2863
+ timeout: 180000,
2864
+ });
1255
2865
  if (dl.status !== 0) throw new Error(`下载失败 (PowerShell exit ${dl.status})`);
1256
2866
 
1257
2867
  // Extract
1258
2868
  console.log(' Extracting...');
1259
- const ex = spawnSync('powershell', ['-NoProfile', '-Command', `Expand-Archive -Path '${zipPath}' -DestinationPath '${installDir}' -Force`], { stdio: 'inherit', windowsHide: true, timeout: 60000 });
2869
+ const ex = spawnSync(
2870
+ 'powershell',
2871
+ ['-NoProfile', '-Command', `Expand-Archive -Path '${zipPath}' -DestinationPath '${installDir}' -Force`],
2872
+ { stdio: 'inherit', windowsHide: true, timeout: 60000 },
2873
+ );
1260
2874
  if (ex.status !== 0) throw new Error(`解压失败 (PowerShell exit ${ex.status})`);
1261
2875
  if (!existsSync(join(installDir, 'hcloud.exe'))) throw new Error('hcloud.exe 未生成,可能已被安全软件拦截');
1262
2876
 
@@ -1280,7 +2894,11 @@ async function cmdInstallHcloud() {
1280
2894
  ' Write-Output " Already in user PATH: $target"',
1281
2895
  '}',
1282
2896
  ].join('; ');
1283
- spawnSync('powershell', ['-NoProfile', '-Command', pathPs], { stdio: 'inherit', windowsHide: true, timeout: 30000 });
2897
+ spawnSync('powershell', ['-NoProfile', '-Command', pathPs], {
2898
+ stdio: 'inherit',
2899
+ windowsHide: true,
2900
+ timeout: 30000,
2901
+ });
1284
2902
 
1285
2903
  console.log(`\n\x1b[32mInstall complete.\x1b[0m`);
1286
2904
  console.log(` Verify: ${join(installDir, 'hcloud.exe')} version`);
@@ -1296,7 +2914,12 @@ async function cmdInstallHcloud() {
1296
2914
  });
1297
2915
  });
1298
2916
  if (agree) {
1299
- const r = spawnSync(hcloudBin, ['version'], { input: 'y\n', encoding: 'utf8', timeout: 10000, windowsHide: true });
2917
+ const r = spawnSync(hcloudBin, ['version'], {
2918
+ input: 'y\n',
2919
+ encoding: 'utf8',
2920
+ timeout: 10000,
2921
+ windowsHide: true,
2922
+ });
1300
2923
  if (r.status === 0) {
1301
2924
  console.log(' \x1b[32mPrivacy agreement accepted. KooCLI ready.\x1b[0m');
1302
2925
  } else {
@@ -1307,8 +2930,8 @@ async function cmdInstallHcloud() {
1307
2930
  }
1308
2931
 
1309
2932
  console.log(' Or restart terminal and: hcloud version');
1310
- } catch (e) {
1311
- console.log(`\n\x1b[33mAuto-install failed: ${e.message}\x1b[0m`);
2933
+ } catch (error) {
2934
+ console.log(`\n\x1b[33mAuto-install failed: ${error.message}\x1b[0m`);
1312
2935
  console.log(` Manual: download ${url}, unzip to ${installDir}, add to PATH`);
1313
2936
  console.log(` Guide: https://support.huaweicloud.com/qs-hcli/hcli_02_003_01.html`);
1314
2937
  if (detectCodeartsSandbox() === 'sandbox') {
@@ -1317,7 +2940,9 @@ async function cmdInstallHcloud() {
1317
2940
  }
1318
2941
  } else if (os === 'linux') {
1319
2942
  console.log('[Linux] One-liner install:');
1320
- console.log(' curl -sSL https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/latest/hcloud_install.sh -o ./hcloud_install.sh && bash ./hcloud_install.sh -y');
2943
+ console.log(
2944
+ ' curl -sSL https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/latest/hcloud_install.sh -o ./hcloud_install.sh && bash ./hcloud_install.sh -y',
2945
+ );
1321
2946
  console.log(`\nOr manual: ${arch === 'arm64' ? 'ARM64' : 'AMD64'}`);
1322
2947
  const pkg = arch === 'arm64' ? 'linux-arm64' : 'linux-amd64';
1323
2948
  console.log(` curl -LO "${baseUrl}/huaweicloud-cli-${pkg}.tar.gz"`);
@@ -1327,7 +2952,9 @@ async function cmdInstallHcloud() {
1327
2952
  console.log(`\nFull guide: https://support.huaweicloud.com/qs-hcli/hcli_02_003_02.html`);
1328
2953
  } else if (os === 'darwin') {
1329
2954
  console.log('[macOS] One-liner install:');
1330
- console.log(' curl -sSL https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/latest/hcloud_install.sh -o ./hcloud_install.sh && bash ./hcloud_install.sh -y');
2955
+ console.log(
2956
+ ' curl -sSL https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/latest/hcloud_install.sh -o ./hcloud_install.sh && bash ./hcloud_install.sh -y',
2957
+ );
1331
2958
  console.log(`\nOr manual: ${arch === 'arm64' ? 'ARM64 (Apple Silicon)' : 'AMD64 (Intel)'}`);
1332
2959
  const pkg = arch === 'arm64' ? 'mac-arm64' : 'mac-amd64';
1333
2960
  console.log(` curl -LO "${baseUrl}/huaweicloud-cli-${pkg}.tar.gz"`);
@@ -1358,7 +2985,7 @@ function readLineQuestion(prompt) {
1358
2985
  async function readSecret(prompt) {
1359
2986
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
1360
2987
  throw new Error(
1361
- `Cannot read "${prompt.trim()}" securely in a non-interactive session. Set HW_ACCESS_KEY/HW_SECRET_KEY environment variables instead, or run "npx huaweicloud-devkit auth init" in a real terminal.`
2988
+ `Cannot read "${prompt.trim()}" securely in a non-interactive session. Set HW_ACCESS_KEY/HW_SECRET_KEY environment variables instead, or run "npx huaweicloud-devkit auth init" in a real terminal.`,
1362
2989
  );
1363
2990
  }
1364
2991
 
@@ -1398,7 +3025,7 @@ async function readSecret(prompt) {
1398
3025
  }
1399
3026
 
1400
3027
  function configureHcloud(credentials) {
1401
- const hcloudBin = findHcloudBin() || (process.env.HCLOUD_BIN || 'hcloud');
3028
+ const hcloudBin = findHcloudBin() || process.env.HCLOUD_BIN || 'hcloud';
1402
3029
  const args = [
1403
3030
  'configure',
1404
3031
  'set',
@@ -1415,7 +3042,9 @@ function configureHcloud(credentials) {
1415
3042
  return {
1416
3043
  ok: r.status === 0,
1417
3044
  code: r.status,
1418
- error: String(r.stderr || '').trim().slice(0, 240),
3045
+ error: String(r.stderr || '')
3046
+ .trim()
3047
+ .slice(0, 240),
1419
3048
  };
1420
3049
  }
1421
3050
 
@@ -1426,7 +3055,9 @@ function printAuthAgents(agents = {}) {
1426
3055
  }
1427
3056
 
1428
3057
  function printAuthStatus(status) {
1429
- console.log(`Credentials vault: ${status.credentialsConfigured ? 'configured' : 'missing'} (${status.credentialsPath})`);
3058
+ console.log(
3059
+ `Credentials vault: ${status.credentialsConfigured ? 'configured' : 'missing'} (${status.credentialsPath})`,
3060
+ );
1430
3061
  console.log(`OBS config: ${status.obsConfigured ? 'configured' : 'missing'} (${status.obsConfigPath})`);
1431
3062
  console.log(`KooCLI: ${status.kooCliInstalled ? 'installed' : 'missing'}`);
1432
3063
  console.log('Agent MCP registration:');
@@ -1450,7 +3081,9 @@ async function cmdAuthInit() {
1450
3081
 
1451
3082
  const interactive = process.stdin.isTTY && process.stdout.isTTY;
1452
3083
  if (!interactive && (!ak || !sk)) {
1453
- console.error('\x1b[31mNon-interactive session detected. Provide credentials via environment variables instead:\x1b[0m');
3084
+ console.error(
3085
+ '\x1b[31mNon-interactive session detected. Provide credentials via environment variables instead:\x1b[0m',
3086
+ );
1454
3087
  console.error(' HW_ACCESS_KEY, HW_SECRET_KEY');
1455
3088
  console.error(' (Or run "npx huaweicloud-devkit auth init" in a real terminal.)');
1456
3089
  process.exitCode = 1;
@@ -1459,7 +3092,8 @@ async function cmdAuthInit() {
1459
3092
 
1460
3093
  if (!ak) ak = await readLineQuestion('Access Key ID (AK): ');
1461
3094
  if (!sk) sk = await readSecret('Secret Access Key (SK): ');
1462
- if (interactive && !securityToken) securityToken = await readLineQuestion('Security Token (optional, press Enter to skip): ');
3095
+ if (interactive && !securityToken)
3096
+ securityToken = await readLineQuestion('Security Token (optional, press Enter to skip): ');
1463
3097
  if (!region) region = 'cn-north-4';
1464
3098
 
1465
3099
  if (!ak || !sk) {
@@ -1468,8 +3102,7 @@ async function cmdAuthInit() {
1468
3102
  return;
1469
3103
  }
1470
3104
 
1471
-
1472
- const vaultPath = writeGlobalCredentials({ ak, sk, securityToken, region });
3105
+ writeGlobalCredentials({ ak, sk, securityToken, region });
1473
3106
 
1474
3107
  try {
1475
3108
  writeObsConfig({ ak, sk, securityToken, region });
@@ -1524,6 +3157,95 @@ async function cmdAuth() {
1524
3157
  return cmdAuthStatus();
1525
3158
  }
1526
3159
 
3160
+ async function cmdProxyInit() {
3161
+ console.log(BANNER);
3162
+ console.log('HuaweiCloud DevKit Proxy Configuration\n');
3163
+ console.log('Configure HTTP/HTTPS proxy for connections to Huawei Cloud services.');
3164
+ console.log('Proxy settings are saved to ~/.config/huaweicloud/proxy.json\n');
3165
+
3166
+ const existing = readProxyConfig() || {};
3167
+ const interactive = process.stdin.isTTY && process.stdout.isTTY;
3168
+
3169
+ if (!interactive) {
3170
+ console.error('\x1b[31mNon-interactive session. Set proxy via environment variables:\x1b[0m');
3171
+ console.error(' HTTPS_PROXY=http://proxy:port');
3172
+ console.error(' HTTP_PROXY=http://proxy:port');
3173
+ console.error(' NO_PROXY=localhost,127.0.0.1');
3174
+ console.error('\nOr run "npx huaweicloud-devkit proxy init" in a real terminal.');
3175
+ process.exitCode = 1;
3176
+ return;
3177
+ }
3178
+
3179
+ const httpsProxy = await readLineQuestion(`HTTPS proxy [${existing.https_proxy || 'none'}]: `);
3180
+ const httpProxy = await readLineQuestion(`HTTP proxy [${existing.http_proxy || 'none'}]: `);
3181
+ const noProxy = await readLineQuestion(
3182
+ `NO_PROXY hosts [${existing.no_proxy || '127.0.0.1,localhost,.huawei.com'}]: `,
3183
+ );
3184
+
3185
+ const config = {
3186
+ https_proxy: httpsProxy || existing.https_proxy || '',
3187
+ http_proxy: httpProxy || existing.http_proxy || '',
3188
+ no_proxy: noProxy || existing.no_proxy || '127.0.0.1,localhost,.huawei.com',
3189
+ };
3190
+
3191
+ const path = writeProxyConfig(config);
3192
+ console.log(`\nProxy configuration saved to ${path}`);
3193
+ console.log('\nEffective settings:');
3194
+ console.log(` HTTPS_PROXY: ${config.https_proxy || '(none)'}`);
3195
+ console.log(` HTTP_PROXY: ${config.http_proxy || '(none)'}`);
3196
+ console.log(` NO_PROXY: ${config.no_proxy || '(none)'}`);
3197
+ console.log('\nEnvironment variables (HTTPS_PROXY, HTTP_PROXY, NO_PROXY) override file settings.');
3198
+ }
3199
+
3200
+ async function cmdProxyShow() {
3201
+ console.log(BANNER);
3202
+ console.log('HuaweiCloud DevKit Proxy Configuration\n');
3203
+
3204
+ const config = readProxyConfig();
3205
+ const configPath = proxyConfigPath();
3206
+
3207
+ console.log(`Config file: ${configPath}`);
3208
+ console.log(`File exists: ${config ? 'yes' : 'no'}\n`);
3209
+
3210
+ if (config) {
3211
+ console.log('File settings:');
3212
+ console.log(` https_proxy: ${config.https_proxy || '(empty)'}`);
3213
+ console.log(` http_proxy: ${config.http_proxy || '(empty)'}`);
3214
+ console.log(` no_proxy: ${config.no_proxy || '(empty)'}`);
3215
+ }
3216
+
3217
+ console.log('\nEnvironment variables:');
3218
+ console.log(` HTTPS_PROXY: ${process.env.HTTPS_PROXY || process.env.https_proxy || '(not set)'}`);
3219
+ console.log(` HTTP_PROXY: ${process.env.HTTP_PROXY || process.env.http_proxy || '(not set)'}`);
3220
+ console.log(` NO_PROXY: ${process.env.NO_PROXY || process.env.no_proxy || '(not set)'}`);
3221
+
3222
+ const effective = getProxySettings();
3223
+ console.log('\nEffective (env > file):');
3224
+ if (effective) {
3225
+ console.log(` https_proxy: ${effective.https_proxy || '(none)'}`);
3226
+ console.log(` http_proxy: ${effective.http_proxy || '(none)'}`);
3227
+ console.log(` no_proxy: ${effective.no_proxy || '(none)'}`);
3228
+ } else {
3229
+ console.log(' (no proxy configured)');
3230
+ }
3231
+ }
3232
+
3233
+ async function cmdProxyClear() {
3234
+ const removed = clearProxyConfig();
3235
+ if (removed) {
3236
+ console.log('Proxy configuration removed.');
3237
+ } else {
3238
+ console.log('No proxy configuration file found.');
3239
+ }
3240
+ }
3241
+
3242
+ async function cmdProxy() {
3243
+ const sub = (process.argv[3] || 'show').toLowerCase();
3244
+ if (sub === 'init' || sub === 'setup') return cmdProxyInit();
3245
+ if (sub === 'clear' || sub === 'remove' || sub === 'reset') return cmdProxyClear();
3246
+ return cmdProxyShow();
3247
+ }
3248
+
1527
3249
  async function main() {
1528
3250
  const cmd = process.argv[2] || 'help';
1529
3251
 
@@ -1557,12 +3279,17 @@ async function main() {
1557
3279
  case 'auth':
1558
3280
  await cmdAuth();
1559
3281
  break;
3282
+ case 'proxy':
3283
+ await cmdProxy();
3284
+ break;
1560
3285
  case 'help':
1561
3286
  case '--help':
1562
3287
  case '-h':
1563
3288
  default:
1564
3289
  console.log(BANNER);
1565
- console.log('Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|all>]\n');
3290
+ console.log(
3291
+ 'Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|dsh|officeace|hermes|openclaw|atomcode|all>]\n',
3292
+ );
1566
3293
  console.log('Commands:');
1567
3294
  console.log(' install Install skills, MCP server, safety policy');
1568
3295
  console.log(' uninstall Remove installed files');
@@ -1572,23 +3299,32 @@ async function main() {
1572
3299
  console.log(' doctor Self-check: hcloud, MCP, skills, auth');
1573
3300
  console.log(' install-hcloud Show KooCLI install commands for your OS');
1574
3301
  console.log(' auth Manage unified auth: init | sync | status');
3302
+ console.log(' proxy Manage proxy config: init | show | clear');
1575
3303
  console.log(' help Show this help');
1576
3304
  console.log('\nOptions:');
1577
- console.log(' --target Target agent: opencode (default), codex, codearts, workbuddy, all');
3305
+ console.log(
3306
+ ' --target Target agent: opencode (default), codex, codearts, workbuddy, dsh, officeace, hermes, openclaw, atomcode, all',
3307
+ );
1578
3308
  console.log('\nExamples:');
1579
3309
  console.log(' npx huaweicloud-devkit install');
1580
3310
  console.log(' npx huaweicloud-devkit install --target codex');
1581
3311
  console.log(' npx huaweicloud-devkit install --target codearts');
1582
3312
  console.log(' npx huaweicloud-devkit install --target workbuddy');
3313
+ console.log(' npx huaweicloud-devkit install --target dsh');
3314
+ console.log(' npx huaweicloud-devkit install --target officeace');
3315
+ console.log(' npx huaweicloud-devkit install --target hermes');
3316
+ console.log(' npx huaweicloud-devkit install --target atomcode');
1583
3317
  console.log(' npx huaweicloud-devkit install --target all');
1584
3318
  console.log(' npx huaweicloud-devkit auth init');
1585
3319
  console.log(' npx huaweicloud-devkit auth sync --target all');
1586
3320
  console.log(' npx huaweicloud-devkit auth status --target all');
3321
+ console.log(' npx huaweicloud-devkit proxy init');
3322
+ console.log(' npx huaweicloud-devkit proxy show');
1587
3323
  break;
1588
3324
  }
1589
3325
  }
1590
3326
 
1591
- main().catch((e) => {
1592
- console.error(`\x1b[31mError: ${e.message}\x1b[0m`);
3327
+ main().catch((error) => {
3328
+ console.error(`\x1b[31mError: ${error.message}\x1b[0m`);
1593
3329
  process.exit(1);
1594
3330
  });