huaweicloud-devkit 1.0.3-dev.0 → 1.0.3-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 +99 -41
  3. package/README.zh-CN.md +99 -41
  4. package/bin/setup.cjs +5 -3
  5. package/integrations/opencode/opencode.json +1 -4
  6. package/package.json +61 -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 +123 -2
  70. package/plugins/huaweicloud-core/src/auth/credentials.mjs +77 -4
  71. package/plugins/huaweicloud-core/src/auth/service.mjs +1 -1
  72. package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
  73. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +25 -7
  74. package/plugins/huaweicloud-core/src/icon-library.mjs +171 -0
  75. package/plugins/huaweicloud-core/src/mcp-server.mjs +30 -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 +122 -68
  80. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +14 -10
  81. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +34 -27
  82. package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
  83. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +521 -8
  84. package/plugins/huaweicloud-core/src/search-market.mjs +53 -14
  85. package/plugins/huaweicloud-core/src/setup-cli.mjs +1741 -215
  86. package/plugins/huaweicloud-core/src/tools.mjs +667 -123
  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 +12 -15
  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 +267 -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 +9 -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,35 @@
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';
9
18
  import { getAuthStatus, syncAuth } from './auth/service.mjs';
10
- import { globalCredentialsPath, readGlobalCredentials, writeGlobalCredentials, writeObsConfig } from './auth/credentials.mjs';
19
+ import { SUPPORTED_AGENT_TARGETS } from './auth/agent-registration.mjs';
20
+ import {
21
+ globalCredentialsPath,
22
+ readGlobalCredentials,
23
+ writeGlobalCredentials,
24
+ writeObsConfig,
25
+ } from './auth/credentials.mjs';
26
+ import {
27
+ proxyConfigPath,
28
+ readProxyConfig,
29
+ writeProxyConfig,
30
+ clearProxyConfig,
31
+ getProxySettings,
32
+ } from './proxy/proxy-config.mjs';
11
33
 
12
34
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
35
  const PLUGIN_ROOT = resolve(__dirname, '..');
@@ -30,31 +52,320 @@ function configRoot(target = 'opencode') {
30
52
  return join(home, '.config', target);
31
53
  }
32
54
 
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'); }
55
+ function opencodeSkillsDir() {
56
+ return join(configRoot('opencode'), 'skills');
57
+ }
58
+ function opencodeCommandsDir() {
59
+ return join(configRoot('opencode'), 'commands');
60
+ }
61
+ function opencodePluginsDir() {
62
+ return join(configRoot('opencode'), 'huaweicloud-plugins');
63
+ }
36
64
  function opencodeConfigFile() {
37
65
  const jsonc = join(configRoot('opencode'), 'opencode.jsonc');
38
66
  if (existsSync(jsonc)) return jsonc;
39
67
  return join(configRoot('opencode'), 'opencode.json');
40
68
  }
41
69
 
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'); }
70
+ function codexDesktopSkillsDir() {
71
+ return join(homedir(), '.agents', 'skills');
72
+ }
73
+ function codexDesktopCommandsDir() {
74
+ return join(homedir(), '.agents', 'commands');
75
+ }
76
+ function codexDesktopPluginsDir() {
77
+ return join(homedir(), '.agents', 'huaweicloud-plugins');
78
+ }
79
+ function codexConfigToml() {
80
+ return join(homedir(), '.codex', 'config.toml');
81
+ }
82
+
83
+ function codeartsSkillsDir() {
84
+ return join(homedir(), '.codeartsdoer', 'skills');
85
+ }
86
+ function codeartsMcpSettingsDir() {
87
+ return join(homedir(), '.codeartsdoer', 'mcp');
88
+ }
89
+ function codeartsMcpSettingsFile() {
90
+ return join(codeartsMcpSettingsDir(), 'mcp_settings.json');
91
+ }
92
+ function codeartsProjectDir() {
93
+ return join(process.cwd(), '.codeartsdoer');
94
+ }
95
+ function codeartsProjectSkillsDir() {
96
+ return join(codeartsProjectDir(), 'skills');
97
+ }
98
+ function codeartsProjectMcpSettingsFile() {
99
+ return join(codeartsProjectDir(), 'mcp', 'mcp_settings.json');
100
+ }
101
+ function codeartsPluginsDir() {
102
+ return join(homedir(), '.codeartsdoer', 'huaweicloud-plugins');
103
+ }
104
+
105
+ function workbuddySkillsDir() {
106
+ return join(homedir(), '.workbuddy', 'skills');
107
+ }
108
+ function workbuddyMcpConfigFile() {
109
+ return join(homedir(), '.workbuddy', 'mcp.json');
110
+ }
111
+ function workbuddyPluginsDir() {
112
+ return join(homedir(), '.workbuddy', 'huaweicloud-plugins');
113
+ }
114
+
115
+ function dshRoot() {
116
+ return process.env.DSH_HOME || join(homedir(), '.dsh');
117
+ }
118
+ function dshSkillsDir() {
119
+ return join(dshRoot(), 'skills');
120
+ }
121
+ function dshProfileDir() {
122
+ return join(dshRoot(), 'profiles', 'web');
123
+ }
124
+ function dshPatchFile() {
125
+ return join(dshProfileDir(), 'cordis.patch.yml');
126
+ }
127
+ function dshPluginsDir() {
128
+ return join(dshRoot(), 'huaweicloud-plugins');
129
+ }
130
+
131
+ function readOfficeaceRegistryInstallDir() {
132
+ if (platform() !== 'win32') return null;
133
+ try {
134
+ const r = spawnSync('reg', ['query', 'HKCU\\SOFTWARE\\OfficeAce\\OfficeAce', '/v', 'InstallDir'], {
135
+ encoding: 'utf8',
136
+ windowsHide: true,
137
+ timeout: 5000,
138
+ });
139
+ if (r.status === 0) {
140
+ const m = r.stdout.match(/InstallDir\s+REG_SZ\s+(.+)/);
141
+ if (m) return m[1].trim();
142
+ }
143
+ } catch {}
144
+ return null;
145
+ }
146
+
147
+ function officeaceCapabilitiesDir() {
148
+ const configRoot = process.env.OFFICE_CLAW_CONFIG_ROOT;
149
+ if (configRoot && existsSync(join(configRoot, 'capabilities.json'))) return configRoot;
150
+ const regDir = readOfficeaceRegistryInstallDir();
151
+ if (regDir) {
152
+ const dir = join(regDir, '.office-claw');
153
+ if (existsSync(join(dir, 'capabilities.json'))) return dir;
154
+ }
155
+ if (platform() === 'win32') {
156
+ const bases = [process.env.ProgramFiles, 'C:\\Program Files', 'D:\\Program Files'];
157
+ if (process.env.LOCALAPPDATA) bases.push(join(process.env.LOCALAPPDATA, 'Programs'));
158
+ for (const base of bases) {
159
+ if (!base) continue;
160
+ const dir = join(base, 'OfficeAce', '.office-claw');
161
+ if (existsSync(join(dir, 'capabilities.json'))) return dir;
162
+ }
163
+ }
164
+ return null;
165
+ }
166
+
167
+ function officeaceCapabilitiesDirSafe() {
168
+ return officeaceCapabilitiesDir() || join(homedir(), '.office-claw');
169
+ }
170
+ function officeaceCapabilitiesFile() {
171
+ return join(officeaceCapabilitiesDirSafe(), 'capabilities.json');
172
+ }
173
+ function officeaceSkillsDir() {
174
+ return join(officeaceCapabilitiesDirSafe(), 'skills');
175
+ }
176
+ function officeacePluginsDir() {
177
+ return join(officeaceCapabilitiesDirSafe(), 'huaweicloud-plugins');
178
+ }
179
+
180
+ function officeaceSqlitePath() {
181
+ const capDir = officeaceCapabilitiesDirSafe();
182
+ return join(resolve(capDir, '..'), 'data', 'mcp-connectors.sqlite');
183
+ }
184
+
185
+ function openOfficeaceDb() {
186
+ return new Database(officeaceSqlitePath());
187
+ }
188
+
189
+ function officeaceGetOwnerUserId() {
190
+ if (!existsSync(officeaceSqlitePath())) return null;
191
+ try {
192
+ const db = openOfficeaceDb();
193
+ const row = db.prepare('SELECT owner_user_id FROM mcp_connectors WHERE owner_user_id IS NOT NULL LIMIT 1').get();
194
+ db.close();
195
+ return row?.owner_user_id || null;
196
+ } catch {
197
+ return null;
198
+ }
199
+ }
200
+
201
+ function ensureOfficeaceMcpInSqlite() {
202
+ const dbPath = officeaceSqlitePath();
203
+ if (!existsSync(dbPath)) {
204
+ console.log(` \x1b[31mOfficeAce database not found: ${dbPath}\x1b[0m`);
205
+ console.log(` \x1b[33mPlease ensure OfficeAce is installed and has been launched at least once.\x1b[0m`);
206
+ return false;
207
+ }
208
+
209
+ const mcpPath = join(officeacePluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
210
+ const env = [{ key: 'HUAWEICLOUD_AGENT_TOOLKIT_MODE', value: 'local', sensitive: false }];
211
+ const hcloudBin = findHcloudBin();
212
+ if (hcloudBin) env.push({ key: 'HCLOUD_BIN', value: hcloudBin.replace(/\\/g, '/'), sensitive: false });
213
+
214
+ const now = Date.now();
215
+ let db;
216
+ try {
217
+ db = openOfficeaceDb();
218
+
219
+ const existing = db
220
+ .prepare("SELECT id, command, args_json FROM mcp_connectors WHERE name = 'huaweicloud-devkit'")
221
+ .get();
222
+
223
+ const argsJson = JSON.stringify([mcpPath]);
224
+ const envJson = JSON.stringify(env);
225
+
226
+ if (existing) {
227
+ if (existing.command === 'node' && existing.args_json === argsJson) {
228
+ console.log(` MCP config unchanged: ${dbPath}`);
229
+ db.close();
230
+ return true;
231
+ }
232
+ db.prepare(
233
+ 'UPDATE mcp_connectors SET command = ?, args_json = ?, env_json = ?, updated_at = ?, status = ?, enabled = 1 WHERE id = ?',
234
+ ).run('node', argsJson, envJson, now, 'disconnected', existing.id);
235
+ console.log(` MCP config updated: ${dbPath}`);
236
+ } else {
237
+ const ownerUserId = officeaceGetOwnerUserId();
238
+ if (!ownerUserId) {
239
+ console.log(` \x1b[31mCannot determine owner_user_id from database\x1b[0m`);
240
+ db.close();
241
+ return false;
242
+ }
243
+ db.prepare(
244
+ `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)
245
+ VALUES (?, ?, 'custom', 'huaweicloud-devkit', 'huaweicloud-devkit', 'stdio', 60000, 'node', ?, ?, 1, 'disconnected', ?, ?, 1, 0)`,
246
+ ).run(randomUUID(), ownerUserId, argsJson, envJson, now, now);
247
+ console.log(` MCP config created: ${dbPath}`);
248
+ }
249
+ db.close();
250
+ return true;
251
+ } catch (err) {
252
+ if (db) {
253
+ try {
254
+ db.close();
255
+ } catch {}
256
+ }
257
+ console.log(` \x1b[31mFailed to write MCP config: ${err.message}\x1b[0m`);
258
+ return false;
259
+ }
260
+ }
261
+
262
+ function removeOfficeaceMcpFromSqlite() {
263
+ const dbPath = officeaceSqlitePath();
264
+ if (!existsSync(dbPath)) return;
265
+ let db;
266
+ try {
267
+ db = openOfficeaceDb();
268
+ db.prepare(
269
+ "DELETE FROM mcp_connector_tools WHERE connector_id IN (SELECT id FROM mcp_connectors WHERE name = 'huaweicloud-devkit')",
270
+ ).run();
271
+ const result2 = db.prepare("DELETE FROM mcp_connectors WHERE name = 'huaweicloud-devkit'").run();
272
+ if (result2.changes > 0) {
273
+ console.log(` MCP config removed: ${dbPath}`);
274
+ }
275
+ db.close();
276
+ } catch (err) {
277
+ if (db) {
278
+ try {
279
+ db.close();
280
+ } catch {}
281
+ }
282
+ console.log(` \x1b[31mFailed to remove MCP config: ${err.message}\x1b[0m`);
283
+ }
284
+ }
285
+
286
+ function readCapabilitiesJson() {
287
+ const capFile = officeaceCapabilitiesFile();
288
+ if (!existsSync(capFile)) return { capabilities: [] };
289
+ try {
290
+ return JSON.parse(readFileSync(capFile, 'utf8'));
291
+ } catch {
292
+ return { capabilities: [] };
293
+ }
294
+ }
295
+
296
+ function writeCapabilitiesJson(config) {
297
+ mkdirSync(officeaceCapabilitiesDirSafe(), { recursive: true });
298
+ writeFileSync(officeaceCapabilitiesFile(), JSON.stringify(config, null, 2));
299
+ }
46
300
 
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'); }
301
+ function removeOfficeaceSkillCapabilities() {
302
+ const capFile = officeaceCapabilitiesFile();
303
+ if (!existsSync(capFile)) return;
304
+ let config;
305
+ try {
306
+ config = JSON.parse(readFileSync(capFile, 'utf8'));
307
+ } catch {
308
+ return;
309
+ }
310
+ if (!Array.isArray(config.capabilities)) return;
311
+ const origLen = config.capabilities.length;
312
+ config.capabilities = config.capabilities.filter(
313
+ (c) =>
314
+ !(
315
+ (c.id === 'huaweicloud-core' && c.type === 'skill') ||
316
+ (c.source === 'custom' && c.id && c.id.startsWith('huawei'))
317
+ ),
318
+ );
319
+ if (config.capabilities.length !== origLen) {
320
+ writeCapabilitiesJson(config);
321
+ console.log(` Skill capabilities cleaned: ${capFile}`);
322
+ }
323
+ }
54
324
 
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'); }
325
+ function registerOfficeaceSkillEntries() {
326
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
327
+ if (!existsSync(skillsSrc)) return;
328
+ const skillNames = readdirSync(skillsSrc, { withFileTypes: true })
329
+ .filter((d) => d.isDirectory() && d.name.startsWith('huawei'))
330
+ .map((d) => d.name);
331
+
332
+ const config = readCapabilitiesJson();
333
+ let changed = false;
334
+
335
+ for (const name of skillNames) {
336
+ const existingIdx = config.capabilities.findIndex((c) => c.id === name && c.type === 'skill');
337
+ if (existingIdx >= 0) continue;
338
+ config.capabilities.push({
339
+ id: name,
340
+ type: 'skill',
341
+ enabled: true,
342
+ source: 'custom',
343
+ selfEvolution: 'suggest',
344
+ followGlobalSelfEvolution: true,
345
+ });
346
+ changed = true;
347
+ }
348
+
349
+ if (!config.capabilities.some((c) => c.id === 'huaweicloud-core' && c.type === 'skill')) {
350
+ config.capabilities.push({
351
+ id: 'huaweicloud-core',
352
+ type: 'skill',
353
+ enabled: true,
354
+ source: 'custom',
355
+ selfEvolution: 'suggest',
356
+ followGlobalSelfEvolution: true,
357
+ });
358
+ changed = true;
359
+ }
360
+
361
+ if (changed) {
362
+ writeCapabilitiesJson(config);
363
+ console.log(` Skill entries registered: ${officeaceCapabilitiesFile()}`);
364
+ }
365
+ }
366
+
367
+ const DSH_MCP_PATCH_START = '# HuaweiCloud DevKit DSH integration start';
368
+ const DSH_MCP_PATCH_END = '# HuaweiCloud DevKit DSH integration end';
58
369
 
59
370
  // Detect CodeArts sandbox mode (bash_mode in permission config).
60
371
  function detectCodeartsSandbox() {
@@ -74,7 +385,11 @@ function findHcloudBin() {
74
385
  const isWin = platform() === 'win32';
75
386
  const candidates = isWin
76
387
  ? [join(homedir(), 'hcloud', 'hcloud.exe')]
77
- : [join(homedir(), '.local', 'bin', 'hcloud'), join(homedir(), 'hcloud', 'hcloud'), join(homedir(), 'hcloud', 'hcloud.exe')];
388
+ : [
389
+ join(homedir(), '.local', 'bin', 'hcloud'),
390
+ join(homedir(), 'hcloud', 'hcloud'),
391
+ join(homedir(), 'hcloud', 'hcloud.exe'),
392
+ ];
78
393
  for (const c of candidates) {
79
394
  if (existsSync(c)) return c;
80
395
  }
@@ -87,14 +402,14 @@ function printSandboxWarning(reason) {
87
402
  console.log(`\x1b[31m 请任选其一继续:\x1b[0m`);
88
403
  console.log(`\x1b[31m A. 在码道外的终端安装并使用 KooCLI (推荐):`);
89
404
  console.log(`\x1b[31m https://support.huaweicloud.com/qs-hcli/hcli_02_003.html`);
90
- console.log(`\x1b[31m B. 在码道设置中关闭沙箱模式后重试 (设置 → 权限Bash 模式)`);
405
+ console.log(`\x1b[31m B. 在码道设置中关闭沙箱模式后重试 (设置 → 对话流智能体 终端命令运行模式 → 自动运行)`);
91
406
  console.log(`\x1b[31m 关闭沙箱后重新运行: npx huaweicloud-devkit install-hcloud\x1b[0m`);
92
407
  }
93
408
 
94
409
  function checkNode() {
95
410
  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`);
411
+ if (v[0] < 22) {
412
+ console.error(`\x1b[31mNode.js >= 22 required (current: ${process.version})\x1b[0m`);
98
413
  process.exit(1);
99
414
  }
100
415
  console.log(` Node.js ${process.version} \x1b[32mOK\x1b[0m`);
@@ -114,6 +429,48 @@ function copyDir(src, dest) {
114
429
  }
115
430
  }
116
431
 
432
+ function installRuntimeDeps(pluginsDir) {
433
+ const pkgJson = {
434
+ name: 'huaweicloud-devkit',
435
+ version: pkgVersion,
436
+ type: 'module',
437
+ dependencies: { undici: '^8.10.0' },
438
+ };
439
+ mkdirSync(pluginsDir, { recursive: true });
440
+ writeFileSync(join(pluginsDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
441
+ const spawnOpts = {
442
+ cwd: pluginsDir,
443
+ shell: true,
444
+ windowsHide: true,
445
+ stdio: 'pipe',
446
+ timeout: 120000,
447
+ };
448
+ let r = spawnSync('npm', ['install', '--omit=dev'], spawnOpts);
449
+ const isRetryable = (res) => {
450
+ const stderr = (res.stderr || '').toString();
451
+ return res.error?.code === 'EPERM' || res.error?.code === 'EBUSY' || /EPERM|EBUSY/.test(stderr);
452
+ };
453
+ if (r.status !== 0 && isRetryable(r)) {
454
+ console.log(` \x1b[33m[WARN]\x1b[0m npm install hit file-lock error, retrying in 2s...`);
455
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2000);
456
+ r = spawnSync('npm', ['install', '--omit=dev'], spawnOpts);
457
+ }
458
+ const undiciDir = join(pluginsDir, 'node_modules', 'undici');
459
+ if (r.status === 0 && existsSync(undiciDir)) {
460
+ console.log(` Runtime deps installed -> ${join(pluginsDir, 'node_modules')}`);
461
+ } else {
462
+ const errCode = r.error?.code;
463
+ const err = (r.stderr || '').toString().trim().split(/\r?\n/).slice(-2).join(' ');
464
+ const hint = errCode === 'ENOENT' ? ' (npm not found — ensure Node.js/npm is in PATH)' : '';
465
+ console.log(` \x1b[33m[WARN]\x1b[0m npm install failed in ${pluginsDir}${err ? `: ${err}` : ''}${hint}`);
466
+ console.log(' Manual fix: cd %s && npm install', pluginsDir);
467
+ if (!existsSync(undiciDir)) {
468
+ console.log(` \x1b[31m[ERROR]\x1b[0m undici is NOT installed. MCP server will fail to start.`);
469
+ console.log(` Run manually: cd "${pluginsDir}" && npm install undici@^8.10.0`);
470
+ }
471
+ }
472
+ }
473
+
117
474
  function removeIfExists(p) {
118
475
  if (existsSync(p)) {
119
476
  try {
@@ -132,14 +489,23 @@ function updateOpenCodeConfig(pluginDir) {
132
489
  const mcpPath = join(pluginDir, 'src', 'mcp-server.mjs').replace(/\\/g, '/');
133
490
  let config = {};
134
491
  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}.`);
492
+ try {
493
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
494
+ } catch {
495
+ console.log(
496
+ ` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath} (jsonc comments?). Skipping MCP config write; ensure "mcp.huaweicloud-devkit" points to ${mcpPath}.`,
497
+ );
137
498
  return;
138
499
  }
139
500
  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) {
501
+ if (
502
+ existing &&
503
+ existing.type === 'local' &&
504
+ Array.isArray(existing.command) &&
505
+ existing.command[0] === 'node' &&
506
+ existing.command[1] === mcpPath &&
507
+ existing.timeout === 300000
508
+ ) {
143
509
  console.log(` OpenCode MCP config unchanged: ${configPath}`);
144
510
  return;
145
511
  }
@@ -149,6 +515,7 @@ function updateOpenCodeConfig(pluginDir) {
149
515
  type: 'local',
150
516
  command: ['node', mcpPath],
151
517
  enabled: true,
518
+ timeout: 300000,
152
519
  };
153
520
  writeFileSync(configPath, JSON.stringify(config, null, 2));
154
521
  console.log(` OpenCode MCP config updated: ${configPath}`);
@@ -157,8 +524,12 @@ function updateOpenCodeConfig(pluginDir) {
157
524
  function removeOpenCodeConfig() {
158
525
  const configPath = opencodeConfigFile();
159
526
  if (!existsSync(configPath)) return;
160
- let config = {};
161
- try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch { return; }
527
+ let config;
528
+ try {
529
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
530
+ } catch {
531
+ return;
532
+ }
162
533
  if (!config.mcp?.['huaweicloud-devkit']) return;
163
534
  delete config.mcp['huaweicloud-devkit'];
164
535
  if (Object.keys(config.mcp).length === 0) delete config.mcp;
@@ -179,11 +550,13 @@ function hasCodexCLI() {
179
550
  }
180
551
 
181
552
  function checkHcloud() {
182
- const bin = findHcloudBin() || (process.env.HCLOUD_BIN || 'hcloud');
553
+ const bin = findHcloudBin() || process.env.HCLOUD_BIN || 'hcloud';
183
554
  if (!existsSync(bin)) return false;
184
555
  try {
185
556
  if (statSync(bin).size < 1024) return false;
186
- } catch { return false; }
557
+ } catch {
558
+ return false;
559
+ }
187
560
  try {
188
561
  const r = spawnSync(`"${bin}" version`, [], { shell: true, windowsHide: true, stdio: 'pipe', timeout: 5000 });
189
562
  const out = (r.stdout ? r.stdout.toString() : '') + (r.stderr ? r.stderr.toString() : '');
@@ -209,7 +582,9 @@ function installCodex() {
209
582
 
210
583
  console.log(` Registering Codex marketplace: ${marketplaceRoot}`);
211
584
  const r1 = spawnSync(`codex plugin marketplace add "${marketplaceRoot}"`, [], {
212
- shell: true, windowsHide: true, stdio: 'pipe',
585
+ shell: true,
586
+ windowsHide: true,
587
+ stdio: 'pipe',
213
588
  });
214
589
  console.log(` ${r1.stdout ? r1.stdout.toString().trim() : r1.stderr.toString().trim()}`);
215
590
 
@@ -221,7 +596,9 @@ function installCodex() {
221
596
 
222
597
  console.log(` Installing plugin: ${pluginName}@${marketplaceName}`);
223
598
  const r2 = spawnSync(`codex plugin add "${pluginName}@${marketplaceName}"`, [], {
224
- shell: true, windowsHide: true, stdio: 'pipe',
599
+ shell: true,
600
+ windowsHide: true,
601
+ stdio: 'pipe',
225
602
  });
226
603
  console.log(` ${r2.stdout ? r2.stdout.toString().trim() : r2.stderr.toString().trim()}`);
227
604
 
@@ -239,14 +616,18 @@ function uninstallCodex() {
239
616
  const marketplaceName = getMarketplaceName();
240
617
  console.log(` Removing Codex plugin: ${pluginName}@${marketplaceName}`);
241
618
  const r = spawnSync(`codex plugin remove "${pluginName}@${marketplaceName}"`, [], {
242
- shell: true, windowsHide: true, stdio: 'pipe',
619
+ shell: true,
620
+ windowsHide: true,
621
+ stdio: 'pipe',
243
622
  });
244
623
  console.log(` ${r.stdout ? r.stdout.toString().trim() : r.stderr.toString().trim()}`);
245
624
 
246
625
  for (const name of new Set([marketplaceName, 'HuaweiCloud-Devkit'])) {
247
626
  console.log(` Removing Codex marketplace: ${name}`);
248
627
  const r2 = spawnSync(`codex plugin marketplace remove "${name}"`, [], {
249
- shell: true, windowsHide: true, stdio: 'pipe',
628
+ shell: true,
629
+ windowsHide: true,
630
+ stdio: 'pipe',
250
631
  });
251
632
  console.log(` ${r2.stdout ? r2.stdout.toString().trim() : r2.stderr.toString().trim()}`);
252
633
  }
@@ -274,6 +655,7 @@ async function installOpenCode() {
274
655
  copyDir(safetyDir, join(pluginDest, 'safety'));
275
656
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
276
657
  updateOpenCodeConfig(pluginDest);
658
+ installRuntimeDeps(pluginDest);
277
659
  }
278
660
 
279
661
  function uninstallOpenCode() {
@@ -336,7 +718,9 @@ async function updateOpenCode() {
336
718
  console.log(` Skills updated -> ${opencodeSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`);
337
719
  copyDir(commandsSrc, opencodeCommandsDir());
338
720
  const staleCommands = pruneStale(opencodeCommandsDir(), commandsSrc);
339
- console.log(` Commands updated -> ${opencodeCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`);
721
+ console.log(
722
+ ` Commands updated -> ${opencodeCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`,
723
+ );
340
724
  copyDir(srcDir, join(pluginDest, 'src'));
341
725
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
342
726
  copyDir(safetyDir, join(pluginDest, 'safety'));
@@ -344,6 +728,7 @@ async function updateOpenCode() {
344
728
  updateOpenCodeConfig(pluginDest);
345
729
  mkdirSync(pluginDest, { recursive: true });
346
730
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
731
+ installRuntimeDeps(pluginDest);
347
732
  }
348
733
 
349
734
  function codexMcpServerPath() {
@@ -367,7 +752,9 @@ function ensureCodexConfigSection(mcpPath) {
367
752
  const configPath = codexConfigToml();
368
753
  let existing = '';
369
754
  if (existsSync(configPath)) {
370
- try { existing = readFileSync(configPath, 'utf8'); } catch {}
755
+ try {
756
+ existing = readFileSync(configPath, 'utf8');
757
+ } catch {}
371
758
  if (existing.includes('[mcp_servers.huaweicloud-devkit]')) {
372
759
  if (existing.includes(`args = ["${mcpPath}"]`)) {
373
760
  console.log(` Config unchanged: ${configPath}`);
@@ -376,7 +763,9 @@ function ensureCodexConfigSection(mcpPath) {
376
763
  removeCodexConfigSection();
377
764
  existing = '';
378
765
  if (existsSync(configPath)) {
379
- try { existing = readFileSync(configPath, 'utf8'); } catch {}
766
+ try {
767
+ existing = readFileSync(configPath, 'utf8');
768
+ } catch {}
380
769
  }
381
770
  }
382
771
  }
@@ -394,7 +783,10 @@ function removeCodexConfigSection() {
394
783
  const out = [];
395
784
  let skip = false;
396
785
  for (const line of lines) {
397
- if (/^\[mcp_servers\.huaweicloud-devkit(\]|\.)/.test(line)) { skip = true; continue; }
786
+ if (/^\[mcp_servers\.huaweicloud-devkit(\]|\.)/.test(line)) {
787
+ skip = true;
788
+ continue;
789
+ }
398
790
  if (skip && line.startsWith('[')) skip = false;
399
791
  if (!skip) out.push(line);
400
792
  }
@@ -441,6 +833,7 @@ async function installCodexDesktop() {
441
833
  }
442
834
 
443
835
  ensureCodexConfigSection(mcpServerAbsPath);
836
+ installRuntimeDeps(codexDesktopPluginsDir());
444
837
  }
445
838
 
446
839
  // Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
@@ -453,10 +846,14 @@ async function updateCodexDesktop() {
453
846
 
454
847
  copyDir(skillsSrc, codexDesktopSkillsDir());
455
848
  const staleSkills = pruneStale(codexDesktopSkillsDir(), skillsSrc);
456
- console.log(` Skills updated -> ${codexDesktopSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`);
849
+ console.log(
850
+ ` Skills updated -> ${codexDesktopSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`,
851
+ );
457
852
  copyDir(commandsSrc, codexDesktopCommandsDir());
458
853
  const staleCommands = pruneStale(codexDesktopCommandsDir(), commandsSrc);
459
- console.log(` Commands updated -> ${codexDesktopCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`);
854
+ console.log(
855
+ ` Commands updated -> ${codexDesktopCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`,
856
+ );
460
857
  mkdirSync(pluginDest, { recursive: true });
461
858
  copyDir(srcDir, join(pluginDest, 'src'));
462
859
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
@@ -484,6 +881,7 @@ async function updateCodexDesktop() {
484
881
 
485
882
  ensureCodexConfigSection(mcpServerAbsPath);
486
883
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
884
+ installRuntimeDeps(pluginDest);
487
885
  }
488
886
 
489
887
  function uninstallCodexDesktop() {
@@ -524,13 +922,22 @@ function registerCodeartsMcp(configPath) {
524
922
  if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
525
923
  let config = {};
526
924
  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}.`);
925
+ try {
926
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
927
+ } catch {
928
+ console.log(
929
+ ` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`,
930
+ );
529
931
  return;
530
932
  }
531
933
  const existing = config.mcpServers?.['huaweicloud-devkit'];
532
- if (existing && existing.command === 'node'
533
- && Array.isArray(existing.args) && existing.args[0] === mcpPath) {
934
+ if (
935
+ existing &&
936
+ existing.command === 'node' &&
937
+ Array.isArray(existing.args) &&
938
+ existing.args[0] === mcpPath &&
939
+ existing.timeout === 300000
940
+ ) {
534
941
  console.log(` MCP config unchanged: ${configPath}`);
535
942
  return;
536
943
  }
@@ -541,6 +948,7 @@ function registerCodeartsMcp(configPath) {
541
948
  args: [mcpPath],
542
949
  env,
543
950
  enabled: true,
951
+ timeout: 300000,
544
952
  };
545
953
  mkdirSync(dirname(configPath), { recursive: true });
546
954
  writeFileSync(configPath, JSON.stringify(config, null, 2));
@@ -565,6 +973,7 @@ async function installCodeArts() {
565
973
 
566
974
  registerCodeartsMcp(codeartsMcpSettingsFile());
567
975
  registerCodeartsMcp(codeartsProjectMcpSettingsFile());
976
+ installRuntimeDeps(pluginDest);
568
977
  }
569
978
 
570
979
  // Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
@@ -587,127 +996,756 @@ async function updateCodeArts() {
587
996
  registerCodeartsMcp(codeartsProjectMcpSettingsFile());
588
997
  mkdirSync(pluginDest, { recursive: true });
589
998
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
999
+ installRuntimeDeps(pluginDest);
1000
+ }
1001
+
1002
+ function uninstallCodeArts() {
1003
+ let removed = 0;
1004
+ for (const skillsDir of [codeartsSkillsDir(), codeartsProjectSkillsDir()]) {
1005
+ if (!existsSync(skillsDir)) continue;
1006
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
1007
+ if (entry.name.startsWith('huawei')) {
1008
+ removeIfExists(join(skillsDir, entry.name));
1009
+ removed++;
1010
+ }
1011
+ }
1012
+ }
1013
+ if (removed > 0) console.log(` Removed ${removed} skills`);
1014
+
1015
+ if (removeIfExists(codeartsPluginsDir())) {
1016
+ console.log(' Removed MCP server and safety policy');
1017
+ }
1018
+ for (const configPath of [codeartsMcpSettingsFile(), codeartsProjectMcpSettingsFile()]) {
1019
+ if (!existsSync(configPath)) continue;
1020
+ let config = {};
1021
+ try {
1022
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
1023
+ } catch {}
1024
+ if (config.mcpServers?.['huaweicloud-devkit']) {
1025
+ delete config.mcpServers['huaweicloud-devkit'];
1026
+ if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
1027
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
1028
+ console.log(` Config cleaned: ${configPath}`);
1029
+ }
1030
+ }
1031
+ }
1032
+
1033
+ function codeartsStatus() {
1034
+ const pluginDir = codeartsPluginsDir();
1035
+ console.log(
1036
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1037
+ );
1038
+ console.log(
1039
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1040
+ );
1041
+ let skillCount = 0;
1042
+ if (existsSync(codeartsSkillsDir())) {
1043
+ skillCount = readdirSync(codeartsSkillsDir(), { withFileTypes: true }).filter(
1044
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1045
+ ).length;
1046
+ }
1047
+ console.log(
1048
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1049
+ );
1050
+ if (existsSync(codeartsMcpSettingsFile())) {
1051
+ try {
1052
+ const config = JSON.parse(readFileSync(codeartsMcpSettingsFile(), 'utf8'));
1053
+ console.log(
1054
+ ` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
1055
+ );
1056
+ } catch {
1057
+ console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
1058
+ }
1059
+ }
1060
+ }
1061
+
1062
+ // Returns true when the config file was written, false when it was already correct.
1063
+ function ensureWorkbuddyMcpConfig() {
1064
+ const configPath = workbuddyMcpConfigFile();
1065
+ const mcpPath = join(workbuddyPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
1066
+ const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
1067
+ const hcloudBin = findHcloudBin();
1068
+ if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
1069
+ let config = {};
1070
+ if (existsSync(configPath)) {
1071
+ try {
1072
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
1073
+ } catch {
1074
+ console.log(
1075
+ ` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`,
1076
+ );
1077
+ return false;
1078
+ }
1079
+ const existing = config.mcpServers?.['huaweicloud-devkit'];
1080
+ if (
1081
+ existing &&
1082
+ existing.command === 'node' &&
1083
+ Array.isArray(existing.args) &&
1084
+ existing.args[0] === mcpPath &&
1085
+ existing.timeout === 300000
1086
+ ) {
1087
+ console.log(` MCP config unchanged: ${configPath}`);
1088
+ return false;
1089
+ }
1090
+ }
1091
+ config.mcpServers = config.mcpServers || {};
1092
+ config.mcpServers['huaweicloud-devkit'] = {
1093
+ command: 'node',
1094
+ args: [mcpPath],
1095
+ env,
1096
+ timeout: 300000,
1097
+ };
1098
+ mkdirSync(dirname(configPath), { recursive: true });
1099
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
1100
+ console.log(` MCP config updated: ${configPath}`);
1101
+ return true;
1102
+ }
1103
+
1104
+ async function installWorkBuddy() {
1105
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1106
+ const srcDir = join(PLUGIN_ROOT, 'src');
1107
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1108
+ const pluginDest = workbuddyPluginsDir();
1109
+
1110
+ copyDir(skillsSrc, workbuddySkillsDir());
1111
+ console.log(` Skills -> ${workbuddySkillsDir()}`);
1112
+
1113
+ copyDir(srcDir, join(pluginDest, 'src'));
1114
+ console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1115
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1116
+ console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1117
+
1118
+ ensureWorkbuddyMcpConfig();
1119
+ installRuntimeDeps(pluginDest);
1120
+ }
1121
+
1122
+ // Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
1123
+ async function updateWorkBuddy() {
1124
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1125
+ const srcDir = join(PLUGIN_ROOT, 'src');
1126
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1127
+ const pluginDest = workbuddyPluginsDir();
1128
+
1129
+ copyDir(skillsSrc, workbuddySkillsDir());
1130
+ const stale = pruneStale(workbuddySkillsDir(), skillsSrc);
1131
+ console.log(` Skills updated -> ${workbuddySkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
1132
+ copyDir(srcDir, join(pluginDest, 'src'));
1133
+ console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1134
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1135
+ console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1136
+ ensureWorkbuddyMcpConfig();
1137
+ mkdirSync(pluginDest, { recursive: true });
1138
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1139
+ installRuntimeDeps(pluginDest);
1140
+ }
1141
+
1142
+ function uninstallWorkBuddy() {
1143
+ const skillsDir = workbuddySkillsDir();
1144
+ let removed = 0;
1145
+ if (existsSync(skillsDir)) {
1146
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
1147
+ if (entry.name.startsWith('huawei')) {
1148
+ removeIfExists(join(skillsDir, entry.name));
1149
+ removed++;
1150
+ }
1151
+ }
1152
+ if (removed > 0) console.log(` Removed ${removed} skills`);
1153
+ }
1154
+
1155
+ if (removeIfExists(workbuddyPluginsDir())) {
1156
+ console.log(' Removed MCP server and safety policy');
1157
+ }
1158
+
1159
+ const configPath = workbuddyMcpConfigFile();
1160
+ if (existsSync(configPath)) {
1161
+ let config = {};
1162
+ try {
1163
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
1164
+ } catch {}
1165
+ if (config.mcpServers?.['huaweicloud-devkit']) {
1166
+ delete config.mcpServers['huaweicloud-devkit'];
1167
+ if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
1168
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
1169
+ console.log(` MCP config cleaned: ${configPath}`);
1170
+ }
1171
+ }
1172
+ }
1173
+
1174
+ function workbuddyStatus() {
1175
+ const pluginDir = workbuddyPluginsDir();
1176
+ const skillsDir = workbuddySkillsDir();
1177
+ console.log(
1178
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1179
+ );
1180
+ console.log(
1181
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1182
+ );
1183
+ let skillCount = 0;
1184
+ if (existsSync(skillsDir)) {
1185
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1186
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1187
+ ).length;
1188
+ }
1189
+ console.log(
1190
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1191
+ );
1192
+ const configPath = workbuddyMcpConfigFile();
1193
+ if (existsSync(configPath)) {
1194
+ try {
1195
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
1196
+ console.log(
1197
+ ` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
1198
+ );
1199
+ } catch {
1200
+ console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
1201
+ }
1202
+ }
1203
+ }
1204
+
1205
+ function dshMcpServerPath() {
1206
+ return join(dshPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
1207
+ }
1208
+
1209
+ function dshPatchBlock() {
1210
+ const hcloudBin = findHcloudBin();
1211
+ const envLines = [' HUAWEICLOUD_AGENT_TOOLKIT_MODE: local', " HDKITSERVICE_ENDPOINT: ''"];
1212
+ if (hcloudBin) {
1213
+ envLines.push(` HCLOUD_BIN: '${hcloudBin.replace(/\\/g, '/').replace(/'/g, "''")}'`);
1214
+ }
1215
+ return [
1216
+ DSH_MCP_PATCH_START,
1217
+ '- insert:',
1218
+ ' - id: mcp-huaweicloud',
1219
+ " name: '@deepseek-ai/dsh-mcp-client'",
1220
+ ' config:',
1221
+ ' serverName: huaweicloud',
1222
+ ' transport: stdio',
1223
+ ' command: node',
1224
+ ' args:',
1225
+ ` - '${dshMcpServerPath().replace(/'/g, "''")}'`,
1226
+ ' env:',
1227
+ ...envLines,
1228
+ ' failOnStartupError: false',
1229
+ DSH_MCP_PATCH_END,
1230
+ ].join('\n');
1231
+ }
1232
+
1233
+ function escapeRegExp(value) {
1234
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1235
+ }
1236
+
1237
+ function removeManagedDshPatchBlock(content) {
1238
+ const pattern = new RegExp(
1239
+ `\\n?${escapeRegExp(DSH_MCP_PATCH_START)}[\\s\\S]*?${escapeRegExp(DSH_MCP_PATCH_END)}\\s*`,
1240
+ 'g',
1241
+ );
1242
+ return String(content || '')
1243
+ .replace(pattern, '\n')
1244
+ .replace(/\n{3,}/g, '\n\n')
1245
+ .trimEnd();
1246
+ }
1247
+
1248
+ function dshPatchHasOnlyCommentsOrEmptyList(content) {
1249
+ const meaningful = String(content || '')
1250
+ .split(/\r?\n/)
1251
+ .map((line) => line.trim())
1252
+ .filter((line) => line && !line.startsWith('#'));
1253
+ return meaningful.length === 0 || (meaningful.length === 1 && meaningful[0] === '[]');
1254
+ }
1255
+
1256
+ function ensureDshMcpPatch() {
1257
+ const patchFile = dshPatchFile();
1258
+ const existing = existsSync(patchFile) ? readFileSync(patchFile, 'utf8') : '';
1259
+ const cleaned = removeManagedDshPatchBlock(existing);
1260
+ const block = dshPatchBlock();
1261
+ let next;
1262
+ if (dshPatchHasOnlyCommentsOrEmptyList(cleaned)) {
1263
+ const prefix = cleaned
1264
+ .split(/\r?\n/)
1265
+ .filter((line) => line.trim() !== '[]')
1266
+ .join('\n')
1267
+ .trimEnd();
1268
+ next = `${prefix ? `${prefix}\n` : ''}${block}\n`;
1269
+ } else {
1270
+ next = `${cleaned}\n\n${block}\n`;
1271
+ }
1272
+ if (existing.replace(/\r\n/g, '\n') === next) {
1273
+ console.log(` DSH patch unchanged: ${patchFile}`);
1274
+ return false;
1275
+ }
1276
+ mkdirSync(dirname(patchFile), { recursive: true });
1277
+ writeFileSync(patchFile, next);
1278
+ console.log(` DSH patch updated: ${patchFile}`);
1279
+ return true;
1280
+ }
1281
+
1282
+ function removeDshMcpPatch() {
1283
+ const patchFile = dshPatchFile();
1284
+ if (!existsSync(patchFile)) return false;
1285
+ const existing = readFileSync(patchFile, 'utf8');
1286
+ const cleaned = removeManagedDshPatchBlock(existing);
1287
+ if (cleaned === existing.trimEnd()) return false;
1288
+ const prefix = cleaned
1289
+ .split(/\r?\n/)
1290
+ .filter((line) => line.trim() !== '[]')
1291
+ .join('\n')
1292
+ .trimEnd();
1293
+ const next = dshPatchHasOnlyCommentsOrEmptyList(cleaned) ? `${prefix ? `${prefix}\n` : ''}[]\n` : `${cleaned}\n`;
1294
+ writeFileSync(patchFile, next);
1295
+ console.log(` DSH patch cleaned: ${patchFile}`);
1296
+ return true;
1297
+ }
1298
+
1299
+ function dshPatchConfigured() {
1300
+ const patchFile = dshPatchFile();
1301
+ if (!existsSync(patchFile)) return false;
1302
+ try {
1303
+ const patch = readFileSync(patchFile, 'utf8');
1304
+ return (
1305
+ patch.includes('id: mcp-huaweicloud') &&
1306
+ patch.includes('@deepseek-ai/dsh-mcp-client') &&
1307
+ patch.includes('serverName: huaweicloud')
1308
+ );
1309
+ } catch {
1310
+ return false;
1311
+ }
1312
+ }
1313
+
1314
+ function commandAvailable(command, args = ['--version']) {
1315
+ try {
1316
+ const r = spawnSync(command, args, { shell: false, windowsHide: true, stdio: 'pipe', timeout: 10000 });
1317
+ if (r.status === 0) return true;
1318
+ } catch {}
1319
+ if (process.platform === 'win32') {
1320
+ try {
1321
+ const w = spawnSync('where.exe', [command], { windowsHide: true, stdio: 'pipe', timeout: 10000 });
1322
+ return w.status === 0 && w.stdout.toString().trim().length > 0;
1323
+ } catch {}
1324
+ }
1325
+ return false;
1326
+ }
1327
+
1328
+ function dshMcpClientAvailable() {
1329
+ const modulePath = join('node_modules', '@deepseek-ai', 'dsh-mcp-client', 'package.json');
1330
+ const candidates = [
1331
+ join(dshProfileDir(), modulePath),
1332
+ join(dshRoot(), 'profiles', modulePath),
1333
+ join(dshRoot(), modulePath),
1334
+ ];
1335
+ if (candidates.some((p) => existsSync(p))) return true;
1336
+ const pkgPath = join(dshProfileDir(), 'package.json');
1337
+ if (!existsSync(pkgPath)) return false;
1338
+ try {
1339
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
1340
+ return Boolean(
1341
+ pkg.dependencies?.['@deepseek-ai/dsh-mcp-client'] || pkg.devDependencies?.['@deepseek-ai/dsh-mcp-client'],
1342
+ );
1343
+ } catch {
1344
+ return false;
1345
+ }
1346
+ }
1347
+
1348
+ function tryInstallDshMcpClient() {
1349
+ if (process.env.HUAWEICLOUD_DEVKIT_SKIP_DSH_PLUGIN_INSTALL === '1') {
1350
+ console.log(' DSH MCP client install skipped by environment');
1351
+ return false;
1352
+ }
1353
+ if (dshMcpClientAvailable()) {
1354
+ console.log(' DSH MCP client package detected');
1355
+ return true;
1356
+ }
1357
+ if (commandAvailable('dsh')) {
1358
+ const r = spawnSync('dsh', ['plugin', '--profile', 'web', 'add', '@deepseek-ai/dsh-mcp-client'], {
1359
+ env: { ...process.env, DSH_HOME: dshRoot() },
1360
+ windowsHide: true,
1361
+ stdio: 'pipe',
1362
+ timeout: 60000,
1363
+ });
1364
+ if (r.status === 0) {
1365
+ console.log(' DSH MCP client package installed via dsh');
1366
+ return true;
1367
+ }
1368
+ const err = `${r.stderr || ''}${r.stdout || ''}`.trim().split(/\r?\n/).slice(-2).join(' ');
1369
+ console.log(` \x1b[33m[WARN]\x1b[0m DSH MCP client auto-install failed${err ? `: ${err}` : ''}`);
1370
+ }
1371
+ if (commandAvailable('pnpm') && existsSync(join(dshProfileDir(), 'package.json'))) {
1372
+ const r = spawnSync('pnpm', ['--dir', dshProfileDir(), 'add', '@deepseek-ai/dsh-mcp-client'], {
1373
+ windowsHide: true,
1374
+ stdio: 'pipe',
1375
+ timeout: 60000,
1376
+ });
1377
+ if (r.status === 0) {
1378
+ console.log(' DSH MCP client package installed via pnpm');
1379
+ return true;
1380
+ }
1381
+ }
1382
+ console.log(' \x1b[33m[WARN]\x1b[0m DSH MCP client package not detected.');
1383
+ console.log(' Manual: npx @deepseek-ai/dsh plugin --profile web add @deepseek-ai/dsh-mcp-client');
1384
+ console.log(' If pnpm is missing, run: corepack enable pnpm');
1385
+ return false;
1386
+ }
1387
+
1388
+ async function installDsh() {
1389
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1390
+ const srcDir = join(PLUGIN_ROOT, 'src');
1391
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1392
+ const pluginDest = dshPluginsDir();
1393
+
1394
+ mkdirSync(pluginDest, { recursive: true });
1395
+ copyDir(skillsSrc, dshSkillsDir());
1396
+ console.log(` Skills -> ${dshSkillsDir()}`);
1397
+ copyDir(srcDir, join(pluginDest, 'src'));
1398
+ console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1399
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1400
+ console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1401
+ ensureDshMcpPatch();
1402
+ tryInstallDshMcpClient();
1403
+ installRuntimeDeps(pluginDest);
1404
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1405
+ }
1406
+
1407
+ async function updateDsh() {
1408
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1409
+ const srcDir = join(PLUGIN_ROOT, 'src');
1410
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1411
+ const pluginDest = dshPluginsDir();
1412
+
1413
+ mkdirSync(pluginDest, { recursive: true });
1414
+ copyDir(skillsSrc, dshSkillsDir());
1415
+ const stale = pruneStale(dshSkillsDir(), skillsSrc);
1416
+ console.log(` Skills updated -> ${dshSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
1417
+ copyDir(srcDir, join(pluginDest, 'src'));
1418
+ console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1419
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1420
+ console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1421
+ ensureDshMcpPatch();
1422
+ tryInstallDshMcpClient();
1423
+ installRuntimeDeps(pluginDest);
1424
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1425
+ }
1426
+
1427
+ function uninstallDsh() {
1428
+ const skillsDir = dshSkillsDir();
1429
+ let removed = 0;
1430
+ if (existsSync(skillsDir)) {
1431
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
1432
+ if (entry.name.startsWith('huawei')) {
1433
+ removeIfExists(join(skillsDir, entry.name));
1434
+ removed++;
1435
+ }
1436
+ }
1437
+ if (removed > 0) console.log(` Removed ${removed} skills`);
1438
+ try {
1439
+ if (readdirSync(skillsDir).length === 0) {
1440
+ rmSync(skillsDir, { recursive: true, force: true });
1441
+ console.log(` Removed empty skills directory: ${skillsDir}`);
1442
+ }
1443
+ } catch {}
1444
+ }
1445
+ if (removeIfExists(dshPluginsDir())) {
1446
+ console.log(' Removed MCP server and safety policy');
1447
+ }
1448
+ removeDshMcpPatch();
1449
+ }
1450
+
1451
+ function dshStatus() {
1452
+ const pluginDir = dshPluginsDir();
1453
+ const skillsDir = dshSkillsDir();
1454
+ console.log(
1455
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1456
+ );
1457
+ console.log(
1458
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1459
+ );
1460
+ let skillCount = 0;
1461
+ if (existsSync(skillsDir)) {
1462
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1463
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1464
+ ).length;
1465
+ }
1466
+ console.log(
1467
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1468
+ );
1469
+ console.log(` DSH patch: ${dshPatchConfigured() ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
1470
+ console.log(
1471
+ ` DSH MCP client package: ${dshMcpClientAvailable() ? '\x1b[32mDetected\x1b[0m' : '\x1b[33mCheck DSH profile\x1b[0m'}`,
1472
+ );
1473
+ }
1474
+
1475
+ async function promptOfficeaceInstallDir() {
1476
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1477
+ const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
1478
+ while (true) {
1479
+ const path = (await ask(' Install directory: ')).trim();
1480
+ if (!path) {
1481
+ console.log(' \x1b[33mPath cannot be empty.\x1b[0m');
1482
+ continue;
1483
+ }
1484
+ const capFile = join(path, '.office-claw', 'capabilities.json');
1485
+ if (existsSync(capFile)) {
1486
+ rl.close();
1487
+ return path;
1488
+ }
1489
+ console.log(` \x1b[33mcapabilities.json not found at: ${capFile}\x1b[0m`);
1490
+ console.log(' Please verify the path and try again.');
1491
+ }
1492
+ }
1493
+
1494
+ async function installOfficeAce() {
1495
+ if (!officeaceCapabilitiesDir()) {
1496
+ if (process.stdin.isTTY) {
1497
+ console.log(' \x1b[33mOfficeAce install directory not found automatically.\x1b[0m');
1498
+ console.log(' Please enter the OfficeAce install directory.');
1499
+ const entered = await promptOfficeaceInstallDir();
1500
+ process.env.OFFICE_CLAW_CONFIG_ROOT = join(entered, '.office-claw');
1501
+ } else {
1502
+ console.log(
1503
+ ' \x1b[31mOfficeAce install directory not found. Please set OFFICE_CLAW_CONFIG_ROOT env var and retry.\x1b[0m',
1504
+ );
1505
+ return;
1506
+ }
1507
+ }
1508
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1509
+ const srcDir = join(PLUGIN_ROOT, 'src');
1510
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1511
+ const pluginDest = officeacePluginsDir();
1512
+
1513
+ copyDir(skillsSrc, officeaceSkillsDir());
1514
+ console.log(` Skills -> ${officeaceSkillsDir()}`);
1515
+
1516
+ copyDir(srcDir, join(pluginDest, 'src'));
1517
+ console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1518
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1519
+ console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1520
+
1521
+ installRuntimeDeps(pluginDest);
1522
+ ensureOfficeaceMcpInSqlite();
1523
+ registerOfficeaceSkillEntries();
1524
+ }
1525
+
1526
+ async function updateOfficeAce() {
1527
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1528
+ const srcDir = join(PLUGIN_ROOT, 'src');
1529
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1530
+ const pluginDest = officeacePluginsDir();
1531
+
1532
+ copyDir(skillsSrc, officeaceSkillsDir());
1533
+ const stale = pruneStale(officeaceSkillsDir(), skillsSrc);
1534
+ console.log(` Skills updated -> ${officeaceSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
1535
+ copyDir(srcDir, join(pluginDest, 'src'));
1536
+ console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1537
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1538
+ console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1539
+ installRuntimeDeps(pluginDest);
1540
+ ensureOfficeaceMcpInSqlite();
1541
+ registerOfficeaceSkillEntries();
1542
+ mkdirSync(pluginDest, { recursive: true });
1543
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
590
1544
  }
591
1545
 
592
- function uninstallCodeArts() {
1546
+ function uninstallOfficeAce() {
1547
+ const skillsDir = officeaceSkillsDir();
593
1548
  let removed = 0;
594
- for (const skillsDir of [codeartsSkillsDir(), codeartsProjectSkillsDir()]) {
595
- if (!existsSync(skillsDir)) continue;
1549
+ if (existsSync(skillsDir)) {
596
1550
  for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
597
1551
  if (entry.name.startsWith('huawei')) {
598
1552
  removeIfExists(join(skillsDir, entry.name));
599
1553
  removed++;
600
1554
  }
601
1555
  }
1556
+ if (removed > 0) console.log(` Removed ${removed} skills`);
1557
+ try {
1558
+ if (readdirSync(skillsDir).length === 0) {
1559
+ rmSync(skillsDir, { recursive: true, force: true });
1560
+ console.log(` Removed empty skills directory: ${skillsDir}`);
1561
+ }
1562
+ } catch {}
602
1563
  }
603
- if (removed > 0) console.log(` Removed ${removed} skills`);
604
1564
 
605
- if (removeIfExists(codeartsPluginsDir())) {
1565
+ if (removeIfExists(officeacePluginsDir())) {
606
1566
  console.log(' Removed MCP server and safety policy');
607
1567
  }
608
- for (const configPath of [codeartsMcpSettingsFile(), codeartsProjectMcpSettingsFile()]) {
609
- if (!existsSync(configPath)) continue;
610
- let config = {};
611
- try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
612
- if (config.mcpServers?.['huaweicloud-devkit']) {
613
- delete config.mcpServers['huaweicloud-devkit'];
614
- if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
615
- writeFileSync(configPath, JSON.stringify(config, null, 2));
616
- console.log(` Config cleaned: ${configPath}`);
617
- }
618
- }
1568
+ removeOfficeaceSkillCapabilities();
1569
+ removeOfficeaceMcpFromSqlite();
619
1570
  }
620
1571
 
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'}`);
1572
+ function officeaceStatus() {
1573
+ const pluginDir = officeacePluginsDir();
1574
+ const skillsDir = officeaceSkillsDir();
1575
+ console.log(
1576
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1577
+ );
1578
+ console.log(
1579
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1580
+ );
625
1581
  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())) {
1582
+ if (existsSync(skillsDir)) {
1583
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1584
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1585
+ ).length;
1586
+ }
1587
+ console.log(
1588
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1589
+ );
1590
+ const dbPath = officeaceSqlitePath();
1591
+ if (existsSync(dbPath)) {
632
1592
  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'}`);
1593
+ const db = openOfficeaceDb();
1594
+ const row = db.prepare("SELECT enabled, status FROM mcp_connectors WHERE name = 'huaweicloud-devkit'").get();
1595
+ db.close();
1596
+ if (row) {
1597
+ const statusIcon = row.status === 'connected' ? '\x1b[32m' : '\x1b[33m';
1598
+ console.log(` MCP config: ${statusIcon}${row.status}\x1b[0m (enabled: ${row.enabled ? 'yes' : 'no'})`);
1599
+ } else {
1600
+ console.log(` MCP config: \x1b[31mNot configured\x1b[0m`);
1601
+ }
635
1602
  } catch {
636
1603
  console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
637
1604
  }
1605
+ } else {
1606
+ console.log(` MCP config: \x1b[31mDatabase not found\x1b[0m`);
1607
+ }
1608
+ }
1609
+
1610
+ // ── Hermes Agent ──
1611
+
1612
+ function hermesHomeDir() {
1613
+ if (process.env.HERMES_HOME) return process.env.HERMES_HOME;
1614
+ // Hermes on Windows stores under LOCALAPPDATA, not ~/.hermes
1615
+ if (platform() === 'win32' && process.env.LOCALAPPDATA) {
1616
+ return join(process.env.LOCALAPPDATA, 'hermes');
638
1617
  }
1618
+ return join(homedir(), '.hermes');
1619
+ }
1620
+
1621
+ function hermesSkillsDir() {
1622
+ return join(hermesHomeDir(), 'skills');
1623
+ }
1624
+
1625
+ function hermesPluginsDir() {
1626
+ return join(hermesHomeDir(), 'huaweicloud-plugins');
1627
+ }
1628
+
1629
+ function hermesConfigFile() {
1630
+ return join(hermesHomeDir(), 'config.yaml');
639
1631
  }
640
1632
 
641
1633
  // 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' };
1634
+ function ensureHermesMcpConfig() {
1635
+ const configPath = hermesConfigFile();
1636
+ const mcpPath = join(hermesPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
646
1637
  const hcloudBin = findHcloudBin();
647
- if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
648
- let config = {};
1638
+
1639
+ const blockLines = [
1640
+ 'mcp_servers:',
1641
+ ' huaweicloud-devkit:',
1642
+ ' command: "node"',
1643
+ ` args: ["${mcpPath}"]`,
1644
+ ' env:',
1645
+ ' HUAWEICLOUD_AGENT_TOOLKIT_MODE: "local"',
1646
+ ];
1647
+ if (hcloudBin) {
1648
+ blockLines.push(` HCLOUD_BIN: "${hcloudBin.replace(/\\/g, '/')}"`);
1649
+ }
1650
+ const block = blockLines.join('\n');
1651
+
1652
+ let existing = '';
649
1653
  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;
1654
+ try {
1655
+ existing = readFileSync(configPath, 'utf8');
1656
+ } catch {}
1657
+ if (existing.includes('mcp_servers:') && existing.includes('huaweicloud-devkit')) {
1658
+ if (existing.includes(`args: ["${mcpPath}"]`)) {
1659
+ console.log(` MCP config unchanged: ${configPath}`);
1660
+ return false;
1661
+ }
1662
+ removeHermesMcpConfigBlock();
1663
+ existing = '';
1664
+ if (existsSync(configPath)) {
1665
+ try {
1666
+ existing = readFileSync(configPath, 'utf8');
1667
+ } catch {}
1668
+ }
659
1669
  }
660
1670
  }
661
- config.mcpServers = config.mcpServers || {};
662
- config.mcpServers['huaweicloud-devkit'] = {
663
- command: 'node',
664
- args: [mcpPath],
665
- env,
666
- };
667
1671
  mkdirSync(dirname(configPath), { recursive: true });
668
- writeFileSync(configPath, JSON.stringify(config, null, 2));
1672
+ const newContent = existing ? `${existing.trimEnd()}\n\n${block}\n` : `${block}\n`;
1673
+ writeFileSync(configPath, newContent);
669
1674
  console.log(` MCP config updated: ${configPath}`);
670
1675
  return true;
671
1676
  }
672
1677
 
673
- async function installWorkBuddy() {
1678
+ function removeHermesMcpConfigBlock() {
1679
+ const configPath = hermesConfigFile();
1680
+ if (!existsSync(configPath)) return;
1681
+ const lines = readFileSync(configPath, 'utf8').split(/\r?\n/);
1682
+ const out = [];
1683
+ let skip = false;
1684
+ for (const line of lines) {
1685
+ if (/^\s*huaweicloud-devkit\s*:/.test(line)) {
1686
+ skip = true;
1687
+ continue;
1688
+ }
1689
+ if (skip) {
1690
+ // Continue skipping indented lines (the server config block)
1691
+ if (/^\s{2,}/.test(line) && line.trim() !== '') continue;
1692
+ // Stop skipping at a top-level key or empty line
1693
+ skip = false;
1694
+ }
1695
+ // If we just skipped the only server under mcp_servers, remove the key too
1696
+ if (line.trim() === 'mcp_servers:') {
1697
+ // Peek ahead to check if this mcp_servers block only has huaweicloud-devkit
1698
+ const remaining = out.join('\n');
1699
+ // Remove trailing mcp_servers: if it's the last meaningful line
1700
+ continue; // skip the mcp_servers key for now, re-add if other servers exist
1701
+ }
1702
+ out.push(line);
1703
+ }
1704
+ // Reconstruct: add mcp_servers back if there are other servers
1705
+ const cleaned = out.join('\n').trimEnd();
1706
+ writeFileSync(configPath, cleaned ? `${cleaned}\n` : '');
1707
+ console.log(' MCP config cleaned');
1708
+ }
1709
+
1710
+ async function installHermes() {
674
1711
  const skillsSrc = join(PLUGIN_ROOT, 'skills');
675
1712
  const srcDir = join(PLUGIN_ROOT, 'src');
676
1713
  const safetyDir = join(PLUGIN_ROOT, 'safety');
677
- const pluginDest = workbuddyPluginsDir();
1714
+ const pluginDest = hermesPluginsDir();
678
1715
 
679
- copyDir(skillsSrc, workbuddySkillsDir());
680
- console.log(` Skills -> ${workbuddySkillsDir()}`);
1716
+ copyDir(skillsSrc, hermesSkillsDir());
1717
+ console.log(` Skills -> ${hermesSkillsDir()}`);
681
1718
 
682
1719
  copyDir(srcDir, join(pluginDest, 'src'));
683
1720
  console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
684
1721
  copyDir(safetyDir, join(pluginDest, 'safety'));
685
1722
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
686
1723
 
687
- ensureWorkbuddyMcpConfig();
1724
+ ensureHermesMcpConfig();
1725
+ installRuntimeDeps(pluginDest);
688
1726
  }
689
1727
 
690
- // Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
691
- async function updateWorkBuddy() {
1728
+ async function updateHermes() {
692
1729
  const skillsSrc = join(PLUGIN_ROOT, 'skills');
693
1730
  const srcDir = join(PLUGIN_ROOT, 'src');
694
1731
  const safetyDir = join(PLUGIN_ROOT, 'safety');
695
- const pluginDest = workbuddyPluginsDir();
1732
+ const pluginDest = hermesPluginsDir();
696
1733
 
697
- copyDir(skillsSrc, workbuddySkillsDir());
698
- const stale = pruneStale(workbuddySkillsDir(), skillsSrc);
699
- console.log(` Skills updated -> ${workbuddySkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
1734
+ copyDir(skillsSrc, hermesSkillsDir());
1735
+ const stale = pruneStale(hermesSkillsDir(), skillsSrc);
1736
+ console.log(` Skills updated -> ${hermesSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
700
1737
  copyDir(srcDir, join(pluginDest, 'src'));
701
1738
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
702
1739
  copyDir(safetyDir, join(pluginDest, 'safety'));
703
1740
  console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
704
- ensureWorkbuddyMcpConfig();
1741
+ ensureHermesMcpConfig();
705
1742
  mkdirSync(pluginDest, { recursive: true });
706
1743
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1744
+ installRuntimeDeps(pluginDest);
707
1745
  }
708
1746
 
709
- function uninstallWorkBuddy() {
710
- const skillsDir = workbuddySkillsDir();
1747
+ function uninstallHermes() {
1748
+ const skillsDir = hermesSkillsDir();
711
1749
  let removed = 0;
712
1750
  if (existsSync(skillsDir)) {
713
1751
  for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
@@ -717,79 +1755,119 @@ function uninstallWorkBuddy() {
717
1755
  }
718
1756
  }
719
1757
  if (removed > 0) console.log(` Removed ${removed} skills`);
1758
+ try {
1759
+ if (readdirSync(skillsDir).length === 0) {
1760
+ rmSync(skillsDir, { recursive: true, force: true });
1761
+ console.log(` Removed empty skills directory: ${skillsDir}`);
1762
+ }
1763
+ } catch {}
720
1764
  }
721
1765
 
722
- if (removeIfExists(workbuddyPluginsDir())) {
1766
+ if (removeIfExists(hermesPluginsDir())) {
723
1767
  console.log(' Removed MCP server and safety policy');
724
1768
  }
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
- }
1769
+ removeHermesMcpConfigBlock();
737
1770
  }
738
1771
 
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'}`);
1772
+ function hermesStatus() {
1773
+ const pluginDir = hermesPluginsDir();
1774
+ const skillsDir = hermesSkillsDir();
1775
+ console.log(
1776
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1777
+ );
1778
+ console.log(
1779
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1780
+ );
744
1781
  let skillCount = 0;
745
1782
  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();
1783
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1784
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1785
+ ).length;
1786
+ }
1787
+ console.log(
1788
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1789
+ );
1790
+ const configPath = hermesConfigFile();
751
1791
  if (existsSync(configPath)) {
752
1792
  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'}`);
1793
+ const config = readFileSync(configPath, 'utf8');
1794
+ const configured = config.includes('mcp_servers:') && config.includes('huaweicloud-devkit');
1795
+ console.log(` MCP config: ${configured ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
755
1796
  } catch {
756
1797
  console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
757
1798
  }
1799
+ } else {
1800
+ console.log(` MCP config: \x1b[31mNot found\x1b[0m`);
758
1801
  }
759
1802
  }
760
1803
 
761
1804
  function opencodeStatus() {
762
1805
  const pluginDir = opencodePluginsDir();
763
1806
  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'}`);
1807
+ console.log(
1808
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1809
+ );
1810
+ console.log(
1811
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1812
+ );
766
1813
  let skillCount = 0;
767
1814
  if (existsSync(skillsDir)) {
768
- skillCount = readdirSync(skillsDir, { withFileTypes: true })
769
- .filter((d) => d.isDirectory() && d.name.startsWith('huawei')).length;
1815
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1816
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1817
+ ).length;
770
1818
  }
771
- console.log(` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`);
1819
+ console.log(
1820
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1821
+ );
772
1822
  const configPath = opencodeConfigFile();
773
1823
  if (existsSync(configPath)) {
774
1824
  try {
775
1825
  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'}`);
1826
+ console.log(
1827
+ ` MCP config: ${config.mcp?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
1828
+ );
777
1829
  } catch {
778
1830
  console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
779
1831
  }
780
1832
  }
781
1833
  }
782
1834
 
1835
+ function autoDetectTarget() {
1836
+ const checks = [
1837
+ ['opencode', () => existsSync(join(homedir(), '.config', 'opencode'))],
1838
+ ['codex-desktop', () => existsSync(join(homedir(), '.codex'))],
1839
+ ['codearts', () => existsSync(join(homedir(), '.codeartsdoer'))],
1840
+ ['workbuddy', () => existsSync(join(homedir(), '.workbuddy'))],
1841
+ [
1842
+ 'dsh',
1843
+ () => {
1844
+ const dsh = process.env.DSH_HOME || join(homedir(), '.dsh');
1845
+ return existsSync(dsh);
1846
+ },
1847
+ ],
1848
+ ['officeace', () => existsSync(officeaceCapabilitiesDir())],
1849
+ ['hermes', () => existsSync(hermesHomeDir())],
1850
+ ['openclaw', () => existsSync(join(homedir(), '.openclaw'))],
1851
+ ];
1852
+ const detected = checks.filter(([, check]) => check()).map(([name]) => name);
1853
+ if (detected.length === 0) {
1854
+ console.error('No supported agent detected.');
1855
+ console.error(`Supported: ${SUPPORTED_AGENT_TARGETS.join(', ')} (or "all")`);
1856
+ console.error('Use --target <agent> to specify.');
1857
+ process.exit(1);
1858
+ }
1859
+ if (detected.length === 1) return detected[0];
1860
+ return 'all';
1861
+ }
1862
+
783
1863
  function parseTarget() {
784
1864
  const idx = process.argv.indexOf('--target');
785
- if (idx < 0) return 'opencode';
1865
+ if (idx < 0) return autoDetectTarget();
786
1866
  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';
1867
+ if (val === 'all' || SUPPORTED_AGENT_TARGETS.includes(val)) return val;
1868
+ console.error(`Unknown target: ${val}`);
1869
+ console.error(`Supported: ${SUPPORTED_AGENT_TARGETS.join(', ')} (or "all")`);
1870
+ process.exit(1);
793
1871
  }
794
1872
 
795
1873
  async function cmdInstall() {
@@ -814,6 +1892,22 @@ async function cmdInstall() {
814
1892
  console.log('\n[WorkBuddy]');
815
1893
  await installWorkBuddy();
816
1894
  }
1895
+ if (target === 'dsh' || target === 'all') {
1896
+ console.log('\n[DSH]');
1897
+ await installDsh();
1898
+ }
1899
+ if (target === 'officeace' || target === 'all') {
1900
+ console.log('\n[OfficeAce]');
1901
+ await installOfficeAce();
1902
+ }
1903
+ if (target === 'hermes' || target === 'all') {
1904
+ console.log('\n[Hermes Agent]');
1905
+ await installHermes();
1906
+ }
1907
+ if (target === 'openclaw' || target === 'all') {
1908
+ console.log('\n[OpenClaw]');
1909
+ await installCodexDesktop();
1910
+ }
817
1911
  if (target === 'codex' || target === 'all') {
818
1912
  console.log('\n[Codex]');
819
1913
  if (!hasCodexCLI()) {
@@ -836,18 +1930,45 @@ async function cmdInstall() {
836
1930
  } else {
837
1931
  installCodex();
838
1932
  }
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';
1933
+ }
1934
+ console.log(`\n\x1b[32mInstallation complete!\x1b[0m`);
1935
+ const appName =
1936
+ target === 'codearts'
1937
+ ? 'CodeArts'
1938
+ : target === 'codex-desktop'
1939
+ ? 'Codex Desktop'
1940
+ : target === 'codex'
1941
+ ? 'Codex'
1942
+ : target === 'workbuddy'
1943
+ ? 'WorkBuddy'
1944
+ : target === 'dsh'
1945
+ ? 'DSH'
1946
+ : target === 'officeace'
1947
+ ? 'OfficeAce'
1948
+ : target === 'hermes'
1949
+ ? 'Hermes Agent'
1950
+ : target === 'openclaw'
1951
+ ? 'OpenClaw'
1952
+ : '当前 agent';
845
1953
  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`);
1954
+ if (target === 'officeace') {
1955
+ console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════════╗`);
1956
+ console.log(`\x1b[1m\x1b[33m║ 打开连接器我的连接器 → huaweicloud-devkit �`);
1957
+ console.log(`\x1b[1m\x1b[33m║ 连接 回到对话 → 输入框开启连接器 �`);
1958
+ console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
1959
+ } else if (target === 'workbuddy') {
1960
+ console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════════╗`);
1961
+ console.log(`\x1b[1m\x1b[33m║ MCP 工具即时生效,无需重启会话 �`);
1962
+ console.log(`\x1b[1m\x1b[33m║ 前往连接器 → 自定义连接器,确认 huaweicloud-devkit �`);
1963
+ console.log(`\x1b[1m\x1b[33m║ 已添加信任并启用 �`);
1964
+ console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
1965
+ } else {
1966
+ console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════╗`);
1967
+ console.log(`\x1b[1m\x1b[33m║ MCP 工具在重启 ${appName} 会话后才生效${pad}║`);
1968
+ console.log(`\x1b[1m\x1b[33m║ 关闭当前会话 → 重新打开,直接描述华为云任务即可 ║`);
1969
+ console.log(`\x1b[1m\x1b[33m║ 重启前请勿执行 hcloud 命令,避免 AK/SK 泄露 ║`);
1970
+ console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
1971
+ }
851
1972
 
852
1973
  const hcloudOk = checkHcloud();
853
1974
  if (!hcloudOk) {
@@ -859,14 +1980,31 @@ async function cmdInstall() {
859
1980
 
860
1981
  console.log(`\n\x1b[1m下一步:\x1b[0m`);
861
1982
  console.log(` 1. 配置统一凭据:npx huaweicloud-devkit auth init`);
862
- console.log(` 2. 重启 ${appName} 会话(MCP 工具重启后生效)`);
863
- console.log(` 3. 运行自检:npx huaweicloud-devkit doctor`);
1983
+ console.log(` 2. 配置代理(企业内网):npx huaweicloud-devkit proxy init`);
1984
+ if (target === 'officeace') {
1985
+ console.log(` 3. 打开连接器 → 我的连接器 → huaweicloud-devkit → 连接 → 回到对话 → 输入框开启连接器`);
1986
+ } else if (target === 'workbuddy') {
1987
+ console.log(` 3. 前往连接器 → 自定义连接器,确认 huaweicloud-devkit 已添加信任并启用`);
1988
+ } else {
1989
+ console.log(` 3. 重启 ${appName} 会话(MCP 工具重启后生效)`);
1990
+ }
1991
+ console.log(` 4. 运行自检:npx huaweicloud-devkit doctor`);
864
1992
 
865
1993
  // Write install marker for doctor to detect
866
- const markerDir = target === 'codearts' ? codeartsPluginsDir()
867
- : target === 'workbuddy' ? workbuddyPluginsDir()
868
- : target === 'codex-desktop' ? codexDesktopPluginsDir()
869
- : opencodePluginsDir();
1994
+ const markerDir =
1995
+ target === 'dsh'
1996
+ ? dshPluginsDir()
1997
+ : target === 'codearts'
1998
+ ? codeartsPluginsDir()
1999
+ : target === 'workbuddy'
2000
+ ? workbuddyPluginsDir()
2001
+ : target === 'officeace'
2002
+ ? officeacePluginsDir()
2003
+ : target === 'openclaw'
2004
+ ? codexDesktopPluginsDir()
2005
+ : target === 'codex-desktop'
2006
+ ? codexDesktopPluginsDir()
2007
+ : opencodePluginsDir();
870
2008
  mkdirSync(markerDir, { recursive: true });
871
2009
  writeFileSync(join(markerDir, '.installed'), new Date().toISOString());
872
2010
  if (target === 'opencode' || target === 'all') {
@@ -881,6 +2019,15 @@ async function cmdInstall() {
881
2019
  if (target === 'workbuddy' || target === 'all') {
882
2020
  console.log('Or describe your Huawei Cloud task in WorkBuddy');
883
2021
  }
2022
+ if (target === 'dsh' || target === 'all') {
2023
+ console.log('Or describe your Huawei Cloud task in DSH');
2024
+ }
2025
+ if (target === 'officeace' || target === 'all') {
2026
+ console.log('Or describe your Huawei Cloud task in OfficeAce');
2027
+ }
2028
+ if (target === 'openclaw' || target === 'all') {
2029
+ console.log('Or describe your Huawei Cloud task in OpenClaw');
2030
+ }
884
2031
  }
885
2032
 
886
2033
  async function cmdUninstall() {
@@ -900,6 +2047,22 @@ async function cmdUninstall() {
900
2047
  console.log('\n[WorkBuddy]');
901
2048
  uninstallWorkBuddy();
902
2049
  }
2050
+ if (target === 'dsh' || target === 'all') {
2051
+ console.log('\n[DSH]');
2052
+ uninstallDsh();
2053
+ }
2054
+ if (target === 'officeace' || target === 'all') {
2055
+ console.log('\n[OfficeAce]');
2056
+ uninstallOfficeAce();
2057
+ }
2058
+ if (target === 'hermes' || target === 'all') {
2059
+ console.log('\n[Hermes Agent]');
2060
+ uninstallHermes();
2061
+ }
2062
+ if (target === 'openclaw' || target === 'all') {
2063
+ console.log('\n[OpenClaw]');
2064
+ uninstallCodexDesktop();
2065
+ }
903
2066
  if (target === 'codex-desktop' || target === 'codex' || target === 'all') {
904
2067
  console.log('\n[Codex]');
905
2068
  uninstallCodexDesktop();
@@ -944,6 +2107,38 @@ async function cmdStatus() {
944
2107
  console.log('\n[WorkBuddy]');
945
2108
  workbuddyStatus();
946
2109
  }
2110
+ if (target === 'dsh' || target === 'all') {
2111
+ console.log('\n[DSH]');
2112
+ dshStatus();
2113
+ }
2114
+ if (target === 'officeace' || target === 'all') {
2115
+ console.log('\n[OfficeAce]');
2116
+ officeaceStatus();
2117
+ }
2118
+ if (target === 'hermes' || target === 'all') {
2119
+ console.log('\n[Hermes Agent]');
2120
+ hermesStatus();
2121
+ }
2122
+ if (target === 'openclaw' || target === 'all') {
2123
+ console.log('\n[OpenClaw]');
2124
+ const cdPluginDir = codexDesktopPluginsDir();
2125
+ const cdSkillsDir = codexDesktopSkillsDir();
2126
+ console.log(
2127
+ ` MCP Server: ${existsSync(join(cdPluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
2128
+ );
2129
+ console.log(
2130
+ ` Safety Policy: ${existsSync(join(cdPluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
2131
+ );
2132
+ let cdSkillCount = 0;
2133
+ if (existsSync(cdSkillsDir)) {
2134
+ cdSkillCount = readdirSync(cdSkillsDir, { withFileTypes: true }).filter(
2135
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
2136
+ ).length;
2137
+ }
2138
+ console.log(
2139
+ ` Skills: ${cdSkillCount > 0 ? `\x1b[32m${cdSkillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
2140
+ );
2141
+ }
947
2142
  if (target === 'codex' || target === 'all') {
948
2143
  console.log('\n[Codex]');
949
2144
  if (!hasCodexCLI()) {
@@ -961,66 +2156,170 @@ async function cmdDoctor() {
961
2156
  console.log(BANNER);
962
2157
  console.log('HuaweiCloud DevKit Doctor\n');
963
2158
 
964
- let pass = 0, warn = 0, fail = 0;
2159
+ let pass = 0,
2160
+ warn = 0,
2161
+ fail = 0;
965
2162
 
966
2163
  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++; }
2164
+ if (ok) {
2165
+ console.log(` \x1b[32m[PASS]\x1b[0m ${label}`);
2166
+ pass++;
2167
+ } else {
2168
+ console.log(` \x1b[31m[FAIL]\x1b[0m ${label} — ${msg}`);
2169
+ fail++;
2170
+ }
969
2171
  }
970
2172
 
971
2173
  // Node.js
972
- check('Node.js >= 20', process.versions.node.split('.')[0] >= 20, 'Run: nvm install 20 && nvm use 20');
2174
+ check('Node.js >= 22', process.versions.node.split('.')[0] >= 22, 'Run: nvm install 22 && nvm use 22');
973
2175
 
974
- // MCP server — check OpenCode, Codex Desktop, CodeArts, and WorkBuddy paths
2176
+ // MCP server — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH paths
975
2177
  const opencodePluginDir = opencodePluginsDir();
976
2178
  const codexPluginDir = codexDesktopPluginsDir();
2179
+ const codeartsPluginDir = codeartsPluginsDir();
977
2180
  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' : '';
2181
+ const dshPluginDir = dshPluginsDir();
2182
+ const officeacePluginDir = officeacePluginsDir();
2183
+ const hermesPluginDir = hermesPluginsDir();
2184
+ const mcpOk =
2185
+ existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs')) ||
2186
+ existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs')) ||
2187
+ existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs')) ||
2188
+ existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs')) ||
2189
+ existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs')) ||
2190
+ existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs')) ||
2191
+ existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'));
2192
+ const mcpTarget = existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs'))
2193
+ ? 'OpenCode'
2194
+ : existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs'))
2195
+ ? 'Codex Desktop'
2196
+ : existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs'))
2197
+ ? 'CodeArts'
2198
+ : existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs'))
2199
+ ? 'WorkBuddy'
2200
+ : existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs'))
2201
+ ? 'DSH'
2202
+ : existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs'))
2203
+ ? 'OfficeAce'
2204
+ : existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'))
2205
+ ? 'Hermes Agent'
2206
+ : '';
984
2207
  check('MCP server installed', mcpOk, 'Run: npx huaweicloud-devkit install');
985
2208
 
986
2209
  if (mcpOk) {
987
2210
  check(`MCP server can start (${mcpTarget})`, true, '');
988
2211
  }
989
2212
 
990
- const safetyOk = existsSync(join(opencodePluginDir, 'safety', 'policy.json'))
991
- || existsSync(join(codexPluginDir, 'safety', 'policy.json'))
992
- || existsSync(join(workbuddyPluginDir, 'safety', 'policy.json'));
2213
+ const undiciPluginDirs = [
2214
+ opencodePluginDir,
2215
+ codexPluginDir,
2216
+ codeartsPluginDir,
2217
+ workbuddyPluginDir,
2218
+ dshPluginDir,
2219
+ officeacePluginDir,
2220
+ hermesPluginDir,
2221
+ ];
2222
+ const undiciOk = undiciPluginDirs.some((d) => existsSync(join(d, 'node_modules', 'undici')));
2223
+ check('Runtime deps (undici) installed', undiciOk, 'Run: npx huaweicloud-devkit install');
2224
+
2225
+ const safetyOk =
2226
+ existsSync(join(opencodePluginDir, 'safety', 'policy.json')) ||
2227
+ existsSync(join(codexPluginDir, 'safety', 'policy.json')) ||
2228
+ existsSync(join(codeartsPluginDir, 'safety', 'policy.json')) ||
2229
+ existsSync(join(workbuddyPluginDir, 'safety', 'policy.json')) ||
2230
+ existsSync(join(dshPluginDir, 'safety', 'policy.json')) ||
2231
+ existsSync(join(officeacePluginDir, 'safety', 'policy.json')) ||
2232
+ existsSync(join(hermesPluginDir, 'safety', 'policy.json'));
993
2233
  check('Safety policy installed', safetyOk, 'Run: npx huaweicloud-devkit install');
994
2234
 
995
- // MCP config — check OpenCode, Codex Desktop, and WorkBuddy
2235
+ // MCP config — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH
996
2236
  let mcpConfigured = false;
997
2237
  let mcpCfgTarget = '';
998
2238
  const opencodeCfg = opencodeConfigFile();
999
2239
  if (existsSync(opencodeCfg)) {
1000
2240
  try {
1001
2241
  const cfg = JSON.parse(readFileSync(opencodeCfg, 'utf8'));
1002
- if (cfg.mcp && cfg.mcp['huaweicloud-devkit']) { mcpConfigured = true; mcpCfgTarget = 'OpenCode'; }
2242
+ if (cfg.mcp && cfg.mcp['huaweicloud-devkit']) {
2243
+ mcpConfigured = true;
2244
+ mcpCfgTarget = 'OpenCode';
2245
+ }
1003
2246
  } catch {}
1004
2247
  }
1005
2248
  const codexCfg = codexConfigToml();
1006
2249
  if (!mcpConfigured && existsSync(codexCfg)) {
1007
2250
  try {
1008
2251
  const cfg = readFileSync(codexCfg, 'utf8');
1009
- if (cfg.includes('[mcp_servers.huaweicloud-devkit]')) { mcpConfigured = true; mcpCfgTarget = 'Codex Desktop'; }
2252
+ if (cfg.includes('[mcp_servers.huaweicloud-devkit]')) {
2253
+ mcpConfigured = true;
2254
+ mcpCfgTarget = 'Codex Desktop';
2255
+ }
2256
+ } catch {}
2257
+ }
2258
+ const codeartsCfg = codeartsMcpSettingsFile();
2259
+ if (!mcpConfigured && existsSync(codeartsCfg)) {
2260
+ try {
2261
+ const cfg = JSON.parse(readFileSync(codeartsCfg, 'utf8'));
2262
+ if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
2263
+ mcpConfigured = true;
2264
+ mcpCfgTarget = 'CodeArts';
2265
+ }
1010
2266
  } catch {}
1011
2267
  }
1012
2268
  const workbuddyCfg = workbuddyMcpConfigFile();
1013
2269
  if (!mcpConfigured && existsSync(workbuddyCfg)) {
1014
2270
  try {
1015
2271
  const cfg = JSON.parse(readFileSync(workbuddyCfg, 'utf8'));
1016
- if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) { mcpConfigured = true; mcpCfgTarget = 'WorkBuddy'; }
2272
+ if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
2273
+ mcpConfigured = true;
2274
+ mcpCfgTarget = 'WorkBuddy';
2275
+ }
1017
2276
  } catch {}
1018
2277
  }
1019
- check('MCP configured', mcpConfigured, mcpCfgTarget ? `Found in ${mcpCfgTarget} config` : 'Run: npx huaweicloud-devkit install');
2278
+ if (!mcpConfigured && dshPatchConfigured()) {
2279
+ mcpConfigured = true;
2280
+ mcpCfgTarget = 'DSH';
2281
+ }
2282
+ if (!mcpConfigured) {
2283
+ const officeaceCfg = officeaceCapabilitiesFile();
2284
+ if (existsSync(officeaceCfg)) {
2285
+ try {
2286
+ const cfg = JSON.parse(readFileSync(officeaceCfg, 'utf8'));
2287
+ if (
2288
+ Array.isArray(cfg.capabilities) &&
2289
+ cfg.capabilities.some((c) => c.id === 'huaweicloud-devkit' && c.type === 'mcp')
2290
+ ) {
2291
+ mcpConfigured = true;
2292
+ mcpCfgTarget = 'OfficeAce';
2293
+ }
2294
+ } catch {}
2295
+ }
2296
+ }
2297
+ if (!mcpConfigured) {
2298
+ const hermesCfg = hermesConfigFile();
2299
+ if (existsSync(hermesCfg)) {
2300
+ try {
2301
+ const cfg = readFileSync(hermesCfg, 'utf8');
2302
+ if (cfg.includes('mcp_servers:') && cfg.includes('huaweicloud-devkit')) {
2303
+ mcpConfigured = true;
2304
+ mcpCfgTarget = 'Hermes Agent';
2305
+ }
2306
+ } catch {}
2307
+ }
2308
+ }
2309
+ check(
2310
+ 'MCP configured',
2311
+ mcpConfigured,
2312
+ mcpCfgTarget ? `Found in ${mcpCfgTarget} config` : 'Run: npx huaweicloud-devkit install',
2313
+ );
1020
2314
 
1021
2315
  // 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 });
2316
+ const hcloudBin = findHcloudBin() || process.env.HCLOUD_BIN || 'hcloud';
2317
+ const hcloudCheck = spawnSync(`"${hcloudBin}" version`, [], {
2318
+ shell: true,
2319
+ windowsHide: true,
2320
+ stdio: 'pipe',
2321
+ timeout: 5000,
2322
+ });
1024
2323
  const hcloudOut = (hcloudCheck.stdout || '').toString() + (hcloudCheck.stderr || '').toString();
1025
2324
  const hcloudOk = hcloudCheck.status === 0 && /KooCLI|Current.*version|当前KooCLI/i.test(hcloudOut);
1026
2325
  check('hcloud CLI installed', hcloudOk, 'Run: npx huaweicloud-devkit install-hcloud');
@@ -1039,20 +2338,37 @@ async function cmdDoctor() {
1039
2338
  console.log(` Version: ${ver}`);
1040
2339
 
1041
2340
  // Check auth
1042
- const authCheck = spawnSync(`"${hcloudBin}" configure list`, [], { shell: true, windowsHide: true, stdio: 'pipe', timeout: 5000 });
2341
+ const authCheck = spawnSync(`"${hcloudBin}" configure list`, [], {
2342
+ shell: true,
2343
+ windowsHide: true,
2344
+ stdio: 'pipe',
2345
+ timeout: 5000,
2346
+ });
1043
2347
  const hasAuth = authCheck.status === 0 && /access.?key/i.test(authCheck.stdout.toString());
1044
2348
  check('hcloud credentials configured', hasAuth, 'Run: npx huaweicloud-devkit auth init');
1045
2349
  }
1046
2350
 
1047
2351
  // Skills
1048
- const skillsOptions = [opencodeSkillsDir(), codexDesktopSkillsDir(), codeartsSkillsDir(), workbuddySkillsDir()];
1049
- let skillCount = 0, skillsDir = '', missingSkills = [];
2352
+ const skillsOptions = [
2353
+ opencodeSkillsDir(),
2354
+ codexDesktopSkillsDir(),
2355
+ codeartsSkillsDir(),
2356
+ workbuddySkillsDir(),
2357
+ dshSkillsDir(),
2358
+ officeaceSkillsDir(),
2359
+ hermesSkillsDir(),
2360
+ ];
2361
+ let skillCount = 0;
2362
+ const missingSkills = [];
1050
2363
  for (const dir of skillsOptions) {
1051
2364
  if (!existsSync(dir)) continue;
1052
- const entries = readdirSync(dir, { withFileTypes: true })
1053
- .filter((d) => d.isDirectory() && d.name.startsWith('huawei'));
2365
+ const entries = readdirSync(dir, { withFileTypes: true }).filter(
2366
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
2367
+ );
1054
2368
  const count = entries.length;
1055
- if (count > skillCount) { skillCount = count; skillsDir = dir; }
2369
+ if (count > skillCount) {
2370
+ skillCount = count;
2371
+ }
1056
2372
  for (const d of entries) {
1057
2373
  if (!existsSync(join(dir, d.name, 'SKILL.md'))) missingSkills.push(d.name);
1058
2374
  }
@@ -1060,14 +2376,27 @@ async function cmdDoctor() {
1060
2376
  const skillsOk = skillCount >= 6;
1061
2377
  check(`Skills installed (${skillCount})`, skillsOk, 'Run: npx huaweicloud-devkit install');
1062
2378
  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`);
2379
+ console.log(
2380
+ ` \x1b[33m[WARN]\x1b[0m ${missingSkills.length} skill(s) missing SKILL.md: ${missingSkills.join(', ')} — Run: npx huaweicloud-devkit install`,
2381
+ );
1064
2382
  warn++;
1065
2383
  }
1066
2384
 
2385
+ const proxyConfig = readProxyConfig();
2386
+ const proxyEnv =
2387
+ process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
2388
+ if (proxyConfig || proxyEnv) {
2389
+ const source = proxyEnv ? 'env' : 'file';
2390
+ const proxyUrl = proxyEnv || proxyConfig.https_proxy || proxyConfig.http_proxy;
2391
+ console.log(` \x1b[36m[INFO]\x1b[0m Proxy configured (${source}): ${proxyUrl}`);
2392
+ }
2393
+
1067
2394
  console.log(`\nResults: ${pass} pass, ${warn} warn, ${fail} fail`);
1068
2395
 
1069
2396
  if (mcpConfigured && !hcloudOk) {
1070
- console.log('\n\x1b[33mMCP is configured but hcloud is not installed. Install hcloud then restart your session.\x1b[0m');
2397
+ console.log(
2398
+ '\n\x1b[33mMCP is configured but hcloud is not installed. Install hcloud then restart your session.\x1b[0m',
2399
+ );
1071
2400
  }
1072
2401
  if (fail > 0) {
1073
2402
  console.log('\x1b[33mFix failures above, then restart your session.\x1b[0m');
@@ -1082,6 +2411,8 @@ async function cmdDoctor() {
1082
2411
  { path: join(codexDesktopPluginsDir(), '.installed'), name: 'Codex Desktop' },
1083
2412
  { path: join(workbuddyPluginsDir(), '.installed'), name: 'WorkBuddy' },
1084
2413
  { path: join(codeartsPluginsDir(), '.installed'), name: 'CodeArts' },
2414
+ { path: join(dshPluginsDir(), '.installed'), name: 'DSH' },
2415
+ { path: join(officeacePluginsDir(), '.installed'), name: 'OfficeAce' },
1085
2416
  ];
1086
2417
  for (const marker of installedMarkers) {
1087
2418
  if (existsSync(marker.path)) {
@@ -1163,6 +2494,54 @@ async function cmdUpdate() {
1163
2494
  return;
1164
2495
  }
1165
2496
 
2497
+ if (target === 'dsh') {
2498
+ if (!existsSync(join(dshPluginsDir(), 'src', 'mcp-server.mjs'))) {
2499
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
2500
+ return;
2501
+ }
2502
+ console.log('[DSH]');
2503
+ await updateDsh();
2504
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
2505
+ console.log(`\x1b[33mRestart the DSH session for changes to take effect.\x1b[0m`);
2506
+ return;
2507
+ }
2508
+
2509
+ if (target === 'officeace') {
2510
+ if (!existsSync(join(officeacePluginsDir(), 'src', 'mcp-server.mjs'))) {
2511
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
2512
+ return;
2513
+ }
2514
+ console.log('[OfficeAce]');
2515
+ await updateOfficeAce();
2516
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
2517
+ console.log(`\x1b[33m打开连接器 → 我的连接器 → huaweicloud-devkit → 连接 → 回到对话 → 输入框开启连接器\x1b[0m`);
2518
+ return;
2519
+ }
2520
+
2521
+ if (target === 'hermes') {
2522
+ if (!existsSync(join(hermesPluginsDir(), 'src', 'mcp-server.mjs'))) {
2523
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
2524
+ return;
2525
+ }
2526
+ console.log('[Hermes Agent]');
2527
+ await updateHermes();
2528
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
2529
+ console.log(`\x1b[33mRestart Hermes Agent for changes to take effect.\x1b[0m`);
2530
+ return;
2531
+ }
2532
+
2533
+ if (target === 'openclaw') {
2534
+ if (!existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
2535
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
2536
+ return;
2537
+ }
2538
+ console.log('[OpenClaw]');
2539
+ await updateCodexDesktop();
2540
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
2541
+ console.log(`\x1b[33mRestart OpenClaw for changes to take effect.\x1b[0m`);
2542
+ return;
2543
+ }
2544
+
1166
2545
  if (target === 'all') {
1167
2546
  let updatedAny = false;
1168
2547
  if (existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))) {
@@ -1185,6 +2564,26 @@ async function cmdUpdate() {
1185
2564
  await updateWorkBuddy();
1186
2565
  updatedAny = true;
1187
2566
  }
2567
+ if (existsSync(join(dshPluginsDir(), 'src', 'mcp-server.mjs'))) {
2568
+ console.log('\n[DSH]');
2569
+ await updateDsh();
2570
+ updatedAny = true;
2571
+ }
2572
+ if (existsSync(join(officeacePluginsDir(), 'src', 'mcp-server.mjs'))) {
2573
+ console.log('\n[OfficeAce]');
2574
+ await updateOfficeAce();
2575
+ updatedAny = true;
2576
+ }
2577
+ if (existsSync(join(hermesPluginsDir(), 'src', 'mcp-server.mjs'))) {
2578
+ console.log('\n[Hermes Agent]');
2579
+ await updateHermes();
2580
+ updatedAny = true;
2581
+ }
2582
+ if (existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
2583
+ console.log('\n[OpenClaw]');
2584
+ await updateCodexDesktop();
2585
+ updatedAny = true;
2586
+ }
1188
2587
  if (codexStatus()) {
1189
2588
  console.log('\n[Codex]');
1190
2589
  installCodex();
@@ -1235,9 +2634,7 @@ async function cmdInstallHcloud() {
1235
2634
  const os = platform();
1236
2635
  const arch = process.arch;
1237
2636
  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');
2637
+ const installDir = os === 'win32' ? join(homedir(), 'hcloud') : join(homedir(), '.local', 'bin');
1241
2638
 
1242
2639
  if (os === 'win32') {
1243
2640
  const url = `${baseUrl}/huaweicloud-cli-windows-amd64.zip`;
@@ -1251,12 +2648,20 @@ async function cmdInstallHcloud() {
1251
2648
  // Download
1252
2649
  console.log(` Downloading ${url}...`);
1253
2650
  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 });
2651
+ const dl = spawnSync('powershell', ['-NoProfile', '-Command', psCmd], {
2652
+ stdio: 'inherit',
2653
+ windowsHide: true,
2654
+ timeout: 180000,
2655
+ });
1255
2656
  if (dl.status !== 0) throw new Error(`下载失败 (PowerShell exit ${dl.status})`);
1256
2657
 
1257
2658
  // Extract
1258
2659
  console.log(' Extracting...');
1259
- const ex = spawnSync('powershell', ['-NoProfile', '-Command', `Expand-Archive -Path '${zipPath}' -DestinationPath '${installDir}' -Force`], { stdio: 'inherit', windowsHide: true, timeout: 60000 });
2660
+ const ex = spawnSync(
2661
+ 'powershell',
2662
+ ['-NoProfile', '-Command', `Expand-Archive -Path '${zipPath}' -DestinationPath '${installDir}' -Force`],
2663
+ { stdio: 'inherit', windowsHide: true, timeout: 60000 },
2664
+ );
1260
2665
  if (ex.status !== 0) throw new Error(`解压失败 (PowerShell exit ${ex.status})`);
1261
2666
  if (!existsSync(join(installDir, 'hcloud.exe'))) throw new Error('hcloud.exe 未生成,可能已被安全软件拦截');
1262
2667
 
@@ -1280,7 +2685,11 @@ async function cmdInstallHcloud() {
1280
2685
  ' Write-Output " Already in user PATH: $target"',
1281
2686
  '}',
1282
2687
  ].join('; ');
1283
- spawnSync('powershell', ['-NoProfile', '-Command', pathPs], { stdio: 'inherit', windowsHide: true, timeout: 30000 });
2688
+ spawnSync('powershell', ['-NoProfile', '-Command', pathPs], {
2689
+ stdio: 'inherit',
2690
+ windowsHide: true,
2691
+ timeout: 30000,
2692
+ });
1284
2693
 
1285
2694
  console.log(`\n\x1b[32mInstall complete.\x1b[0m`);
1286
2695
  console.log(` Verify: ${join(installDir, 'hcloud.exe')} version`);
@@ -1296,7 +2705,12 @@ async function cmdInstallHcloud() {
1296
2705
  });
1297
2706
  });
1298
2707
  if (agree) {
1299
- const r = spawnSync(hcloudBin, ['version'], { input: 'y\n', encoding: 'utf8', timeout: 10000, windowsHide: true });
2708
+ const r = spawnSync(hcloudBin, ['version'], {
2709
+ input: 'y\n',
2710
+ encoding: 'utf8',
2711
+ timeout: 10000,
2712
+ windowsHide: true,
2713
+ });
1300
2714
  if (r.status === 0) {
1301
2715
  console.log(' \x1b[32mPrivacy agreement accepted. KooCLI ready.\x1b[0m');
1302
2716
  } else {
@@ -1317,7 +2731,9 @@ async function cmdInstallHcloud() {
1317
2731
  }
1318
2732
  } else if (os === 'linux') {
1319
2733
  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');
2734
+ console.log(
2735
+ ' 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',
2736
+ );
1321
2737
  console.log(`\nOr manual: ${arch === 'arm64' ? 'ARM64' : 'AMD64'}`);
1322
2738
  const pkg = arch === 'arm64' ? 'linux-arm64' : 'linux-amd64';
1323
2739
  console.log(` curl -LO "${baseUrl}/huaweicloud-cli-${pkg}.tar.gz"`);
@@ -1327,7 +2743,9 @@ async function cmdInstallHcloud() {
1327
2743
  console.log(`\nFull guide: https://support.huaweicloud.com/qs-hcli/hcli_02_003_02.html`);
1328
2744
  } else if (os === 'darwin') {
1329
2745
  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');
2746
+ console.log(
2747
+ ' 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',
2748
+ );
1331
2749
  console.log(`\nOr manual: ${arch === 'arm64' ? 'ARM64 (Apple Silicon)' : 'AMD64 (Intel)'}`);
1332
2750
  const pkg = arch === 'arm64' ? 'mac-arm64' : 'mac-amd64';
1333
2751
  console.log(` curl -LO "${baseUrl}/huaweicloud-cli-${pkg}.tar.gz"`);
@@ -1358,7 +2776,7 @@ function readLineQuestion(prompt) {
1358
2776
  async function readSecret(prompt) {
1359
2777
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
1360
2778
  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.`
2779
+ `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
2780
  );
1363
2781
  }
1364
2782
 
@@ -1398,7 +2816,7 @@ async function readSecret(prompt) {
1398
2816
  }
1399
2817
 
1400
2818
  function configureHcloud(credentials) {
1401
- const hcloudBin = findHcloudBin() || (process.env.HCLOUD_BIN || 'hcloud');
2819
+ const hcloudBin = findHcloudBin() || process.env.HCLOUD_BIN || 'hcloud';
1402
2820
  const args = [
1403
2821
  'configure',
1404
2822
  'set',
@@ -1415,7 +2833,9 @@ function configureHcloud(credentials) {
1415
2833
  return {
1416
2834
  ok: r.status === 0,
1417
2835
  code: r.status,
1418
- error: String(r.stderr || '').trim().slice(0, 240),
2836
+ error: String(r.stderr || '')
2837
+ .trim()
2838
+ .slice(0, 240),
1419
2839
  };
1420
2840
  }
1421
2841
 
@@ -1426,7 +2846,9 @@ function printAuthAgents(agents = {}) {
1426
2846
  }
1427
2847
 
1428
2848
  function printAuthStatus(status) {
1429
- console.log(`Credentials vault: ${status.credentialsConfigured ? 'configured' : 'missing'} (${status.credentialsPath})`);
2849
+ console.log(
2850
+ `Credentials vault: ${status.credentialsConfigured ? 'configured' : 'missing'} (${status.credentialsPath})`,
2851
+ );
1430
2852
  console.log(`OBS config: ${status.obsConfigured ? 'configured' : 'missing'} (${status.obsConfigPath})`);
1431
2853
  console.log(`KooCLI: ${status.kooCliInstalled ? 'installed' : 'missing'}`);
1432
2854
  console.log('Agent MCP registration:');
@@ -1450,7 +2872,9 @@ async function cmdAuthInit() {
1450
2872
 
1451
2873
  const interactive = process.stdin.isTTY && process.stdout.isTTY;
1452
2874
  if (!interactive && (!ak || !sk)) {
1453
- console.error('\x1b[31mNon-interactive session detected. Provide credentials via environment variables instead:\x1b[0m');
2875
+ console.error(
2876
+ '\x1b[31mNon-interactive session detected. Provide credentials via environment variables instead:\x1b[0m',
2877
+ );
1454
2878
  console.error(' HW_ACCESS_KEY, HW_SECRET_KEY');
1455
2879
  console.error(' (Or run "npx huaweicloud-devkit auth init" in a real terminal.)');
1456
2880
  process.exitCode = 1;
@@ -1459,7 +2883,8 @@ async function cmdAuthInit() {
1459
2883
 
1460
2884
  if (!ak) ak = await readLineQuestion('Access Key ID (AK): ');
1461
2885
  if (!sk) sk = await readSecret('Secret Access Key (SK): ');
1462
- if (interactive && !securityToken) securityToken = await readLineQuestion('Security Token (optional, press Enter to skip): ');
2886
+ if (interactive && !securityToken)
2887
+ securityToken = await readLineQuestion('Security Token (optional, press Enter to skip): ');
1463
2888
  if (!region) region = 'cn-north-4';
1464
2889
 
1465
2890
  if (!ak || !sk) {
@@ -1468,8 +2893,7 @@ async function cmdAuthInit() {
1468
2893
  return;
1469
2894
  }
1470
2895
 
1471
-
1472
- const vaultPath = writeGlobalCredentials({ ak, sk, securityToken, region });
2896
+ writeGlobalCredentials({ ak, sk, securityToken, region });
1473
2897
 
1474
2898
  try {
1475
2899
  writeObsConfig({ ak, sk, securityToken, region });
@@ -1524,6 +2948,95 @@ async function cmdAuth() {
1524
2948
  return cmdAuthStatus();
1525
2949
  }
1526
2950
 
2951
+ async function cmdProxyInit() {
2952
+ console.log(BANNER);
2953
+ console.log('HuaweiCloud DevKit Proxy Configuration\n');
2954
+ console.log('Configure HTTP/HTTPS proxy for connections to Huawei Cloud services.');
2955
+ console.log('Proxy settings are saved to ~/.config/huaweicloud/proxy.json\n');
2956
+
2957
+ const existing = readProxyConfig() || {};
2958
+ const interactive = process.stdin.isTTY && process.stdout.isTTY;
2959
+
2960
+ if (!interactive) {
2961
+ console.error('\x1b[31mNon-interactive session. Set proxy via environment variables:\x1b[0m');
2962
+ console.error(' HTTPS_PROXY=http://proxy:port');
2963
+ console.error(' HTTP_PROXY=http://proxy:port');
2964
+ console.error(' NO_PROXY=localhost,127.0.0.1');
2965
+ console.error('\nOr run "npx huaweicloud-devkit proxy init" in a real terminal.');
2966
+ process.exitCode = 1;
2967
+ return;
2968
+ }
2969
+
2970
+ const httpsProxy = await readLineQuestion(`HTTPS proxy [${existing.https_proxy || 'none'}]: `);
2971
+ const httpProxy = await readLineQuestion(`HTTP proxy [${existing.http_proxy || 'none'}]: `);
2972
+ const noProxy = await readLineQuestion(
2973
+ `NO_PROXY hosts [${existing.no_proxy || '127.0.0.1,localhost,.huawei.com'}]: `,
2974
+ );
2975
+
2976
+ const config = {
2977
+ https_proxy: httpsProxy || existing.https_proxy || '',
2978
+ http_proxy: httpProxy || existing.http_proxy || '',
2979
+ no_proxy: noProxy || existing.no_proxy || '127.0.0.1,localhost,.huawei.com',
2980
+ };
2981
+
2982
+ const path = writeProxyConfig(config);
2983
+ console.log(`\nProxy configuration saved to ${path}`);
2984
+ console.log('\nEffective settings:');
2985
+ console.log(` HTTPS_PROXY: ${config.https_proxy || '(none)'}`);
2986
+ console.log(` HTTP_PROXY: ${config.http_proxy || '(none)'}`);
2987
+ console.log(` NO_PROXY: ${config.no_proxy || '(none)'}`);
2988
+ console.log('\nEnvironment variables (HTTPS_PROXY, HTTP_PROXY, NO_PROXY) override file settings.');
2989
+ }
2990
+
2991
+ async function cmdProxyShow() {
2992
+ console.log(BANNER);
2993
+ console.log('HuaweiCloud DevKit Proxy Configuration\n');
2994
+
2995
+ const config = readProxyConfig();
2996
+ const configPath = proxyConfigPath();
2997
+
2998
+ console.log(`Config file: ${configPath}`);
2999
+ console.log(`File exists: ${config ? 'yes' : 'no'}\n`);
3000
+
3001
+ if (config) {
3002
+ console.log('File settings:');
3003
+ console.log(` https_proxy: ${config.https_proxy || '(empty)'}`);
3004
+ console.log(` http_proxy: ${config.http_proxy || '(empty)'}`);
3005
+ console.log(` no_proxy: ${config.no_proxy || '(empty)'}`);
3006
+ }
3007
+
3008
+ console.log('\nEnvironment variables:');
3009
+ console.log(` HTTPS_PROXY: ${process.env.HTTPS_PROXY || process.env.https_proxy || '(not set)'}`);
3010
+ console.log(` HTTP_PROXY: ${process.env.HTTP_PROXY || process.env.http_proxy || '(not set)'}`);
3011
+ console.log(` NO_PROXY: ${process.env.NO_PROXY || process.env.no_proxy || '(not set)'}`);
3012
+
3013
+ const effective = getProxySettings();
3014
+ console.log('\nEffective (env > file):');
3015
+ if (effective) {
3016
+ console.log(` https_proxy: ${effective.https_proxy || '(none)'}`);
3017
+ console.log(` http_proxy: ${effective.http_proxy || '(none)'}`);
3018
+ console.log(` no_proxy: ${effective.no_proxy || '(none)'}`);
3019
+ } else {
3020
+ console.log(' (no proxy configured)');
3021
+ }
3022
+ }
3023
+
3024
+ async function cmdProxyClear() {
3025
+ const removed = clearProxyConfig();
3026
+ if (removed) {
3027
+ console.log('Proxy configuration removed.');
3028
+ } else {
3029
+ console.log('No proxy configuration file found.');
3030
+ }
3031
+ }
3032
+
3033
+ async function cmdProxy() {
3034
+ const sub = (process.argv[3] || 'show').toLowerCase();
3035
+ if (sub === 'init' || sub === 'setup') return cmdProxyInit();
3036
+ if (sub === 'clear' || sub === 'remove' || sub === 'reset') return cmdProxyClear();
3037
+ return cmdProxyShow();
3038
+ }
3039
+
1527
3040
  async function main() {
1528
3041
  const cmd = process.argv[2] || 'help';
1529
3042
 
@@ -1557,12 +3070,17 @@ async function main() {
1557
3070
  case 'auth':
1558
3071
  await cmdAuth();
1559
3072
  break;
3073
+ case 'proxy':
3074
+ await cmdProxy();
3075
+ break;
1560
3076
  case 'help':
1561
3077
  case '--help':
1562
3078
  case '-h':
1563
3079
  default:
1564
3080
  console.log(BANNER);
1565
- console.log('Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|all>]\n');
3081
+ console.log(
3082
+ 'Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|dsh|officeace|hermes|openclaw|all>]\n',
3083
+ );
1566
3084
  console.log('Commands:');
1567
3085
  console.log(' install Install skills, MCP server, safety policy');
1568
3086
  console.log(' uninstall Remove installed files');
@@ -1572,18 +3090,26 @@ async function main() {
1572
3090
  console.log(' doctor Self-check: hcloud, MCP, skills, auth');
1573
3091
  console.log(' install-hcloud Show KooCLI install commands for your OS');
1574
3092
  console.log(' auth Manage unified auth: init | sync | status');
3093
+ console.log(' proxy Manage proxy config: init | show | clear');
1575
3094
  console.log(' help Show this help');
1576
3095
  console.log('\nOptions:');
1577
- console.log(' --target Target agent: opencode (default), codex, codearts, workbuddy, all');
3096
+ console.log(
3097
+ ' --target Target agent: opencode (default), codex, codearts, workbuddy, dsh, officeace, hermes, openclaw, all',
3098
+ );
1578
3099
  console.log('\nExamples:');
1579
3100
  console.log(' npx huaweicloud-devkit install');
1580
3101
  console.log(' npx huaweicloud-devkit install --target codex');
1581
3102
  console.log(' npx huaweicloud-devkit install --target codearts');
1582
3103
  console.log(' npx huaweicloud-devkit install --target workbuddy');
3104
+ console.log(' npx huaweicloud-devkit install --target dsh');
3105
+ console.log(' npx huaweicloud-devkit install --target officeace');
3106
+ console.log(' npx huaweicloud-devkit install --target hermes');
1583
3107
  console.log(' npx huaweicloud-devkit install --target all');
1584
3108
  console.log(' npx huaweicloud-devkit auth init');
1585
3109
  console.log(' npx huaweicloud-devkit auth sync --target all');
1586
3110
  console.log(' npx huaweicloud-devkit auth status --target all');
3111
+ console.log(' npx huaweicloud-devkit proxy init');
3112
+ console.log(' npx huaweicloud-devkit proxy show');
1587
3113
  break;
1588
3114
  }
1589
3115
  }