huaweicloud-devkit 1.0.2-next.2 → 1.0.2-next.21

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 +81 -51
  3. package/README.zh-CN.md +81 -49
  4. package/bin/setup.cjs +5 -3
  5. package/integrations/opencode/opencode.json +1 -4
  6. package/package.json +61 -44
  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-cloud-find-skills/SKILL.md +13 -12
  23. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +64 -17
  24. package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +20 -20
  25. package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +31 -31
  26. package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +21 -20
  27. package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +39 -30
  28. package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +8 -1
  29. package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +6 -0
  30. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +39 -36
  31. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +15 -4
  32. package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +11 -9
  33. package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +13 -13
  34. package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +40 -39
  35. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +12 -10
  36. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +21 -20
  37. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +19 -19
  38. package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +20 -20
  39. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +27 -23
  40. package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +27 -27
  41. package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +65 -39
  42. package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +9 -9
  43. package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +25 -25
  44. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +45 -44
  45. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +19 -19
  46. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +12 -7
  47. package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +13 -12
  48. package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +4 -4
  49. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +63 -58
  50. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +127 -42
  51. package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +26 -26
  52. package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +39 -39
  53. package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +13 -12
  54. package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +20 -19
  55. package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +15 -15
  56. package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +18 -18
  57. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +13 -13
  58. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +3 -3
  59. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +17 -1
  60. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +3 -3
  61. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +27 -1
  62. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +3 -3
  63. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +56 -53
  64. package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +3 -3
  65. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +6 -6
  66. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +15 -1
  67. package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +0 -1
  68. package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +3 -3
  69. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +33 -33
  70. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +3 -3
  71. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +108 -5
  72. package/plugins/huaweicloud-core/src/auth/credentials.mjs +77 -4
  73. package/plugins/huaweicloud-core/src/auth/service.mjs +1 -1
  74. package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
  75. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +25 -7
  76. package/plugins/huaweicloud-core/src/icon-library.mjs +171 -0
  77. package/plugins/huaweicloud-core/src/mcp-server.mjs +30 -3
  78. package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +20 -102
  79. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +11 -4
  80. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +4 -1
  81. package/plugins/huaweicloud-core/src/safety-policy.mjs +122 -68
  82. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +9 -9
  83. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +24 -26
  84. package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
  85. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +510 -10
  86. package/plugins/huaweicloud-core/src/search-market.mjs +46 -12
  87. package/plugins/huaweicloud-core/src/setup-cli.mjs +1251 -181
  88. package/plugins/huaweicloud-core/src/tools.mjs +600 -122
  89. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +72 -38
  90. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +10 -5
  91. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +12 -15
  92. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +12 -16
  93. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +46 -31
  94. package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +267 -0
  95. package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
  96. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +9 -5
@@ -1,14 +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';
11
- import { proxyConfigPath, readProxyConfig, writeProxyConfig, clearProxyConfig, getProxySettings } from './proxy/proxy-config.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';
12
33
 
13
34
  const __dirname = dirname(fileURLToPath(import.meta.url));
14
35
  const PLUGIN_ROOT = resolve(__dirname, '..');
@@ -31,37 +52,317 @@ function configRoot(target = 'opencode') {
31
52
  return join(home, '.config', target);
32
53
  }
33
54
 
34
- function opencodeSkillsDir() { return join(configRoot('opencode'), 'skills'); }
35
- function opencodeCommandsDir() { return join(configRoot('opencode'), 'commands'); }
36
- 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
+ }
37
64
  function opencodeConfigFile() {
38
65
  const jsonc = join(configRoot('opencode'), 'opencode.jsonc');
39
66
  if (existsSync(jsonc)) return jsonc;
40
67
  return join(configRoot('opencode'), 'opencode.json');
41
68
  }
42
69
 
43
- function codexDesktopSkillsDir() { return join(homedir(), '.agents', 'skills'); }
44
- function codexDesktopCommandsDir() { return join(homedir(), '.agents', 'commands'); }
45
- function codexDesktopPluginsDir() { return join(homedir(), '.agents', 'huaweicloud-plugins'); }
46
- 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
+ }
47
82
 
48
- function codeartsSkillsDir() { return join(homedir(), '.codeartsdoer', 'skills'); }
49
- function codeartsMcpSettingsDir() { return join(homedir(), '.codeartsdoer', 'mcp'); }
50
- function codeartsMcpSettingsFile() { return join(codeartsMcpSettingsDir(), 'mcp_settings.json'); }
51
- function codeartsProjectDir() { return join(process.cwd(), '.codeartsdoer'); }
52
- function codeartsProjectSkillsDir() { return join(codeartsProjectDir(), 'skills'); }
53
- function codeartsProjectMcpSettingsFile() { return join(codeartsProjectDir(), 'mcp', 'mcp_settings.json'); }
54
- function codeartsPluginsDir() { return join(homedir(), '.codeartsdoer', 'huaweicloud-plugins'); }
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
+ }
55
104
 
56
- function workbuddySkillsDir() { return join(homedir(), '.workbuddy', 'skills'); }
57
- function workbuddyMcpConfigFile() { return join(homedir(), '.workbuddy', 'mcp.json'); }
58
- function workbuddyPluginsDir() { return join(homedir(), '.workbuddy', 'huaweicloud-plugins'); }
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
+ }
59
114
 
60
- function dshRoot() { return process.env.DSH_HOME || join(homedir(), '.dsh'); }
61
- function dshSkillsDir() { return join(dshRoot(), 'skills'); }
62
- function dshProfileDir() { return join(dshRoot(), 'profiles', 'web'); }
63
- function dshPatchFile() { return join(dshProfileDir(), 'cordis.patch.yml'); }
64
- function dshPluginsDir() { return join(dshRoot(), 'huaweicloud-plugins'); }
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
+ }
300
+
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
+ }
324
+
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
+ }
65
366
 
66
367
  const DSH_MCP_PATCH_START = '# HuaweiCloud DevKit DSH integration start';
67
368
  const DSH_MCP_PATCH_END = '# HuaweiCloud DevKit DSH integration end';
@@ -84,7 +385,11 @@ function findHcloudBin() {
84
385
  const isWin = platform() === 'win32';
85
386
  const candidates = isWin
86
387
  ? [join(homedir(), 'hcloud', 'hcloud.exe')]
87
- : [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
+ ];
88
393
  for (const c of candidates) {
89
394
  if (existsSync(c)) return c;
90
395
  }
@@ -97,14 +402,14 @@ function printSandboxWarning(reason) {
97
402
  console.log(`\x1b[31m 请任选其一继续:\x1b[0m`);
98
403
  console.log(`\x1b[31m A. 在码道外的终端安装并使用 KooCLI (推荐):`);
99
404
  console.log(`\x1b[31m https://support.huaweicloud.com/qs-hcli/hcli_02_003.html`);
100
- console.log(`\x1b[31m B. 在码道设置中关闭沙箱模式后重试 (设置 → 权限Bash 模式)`);
405
+ console.log(`\x1b[31m B. 在码道设置中关闭沙箱模式后重试 (设置 → 对话流智能体 终端命令运行模式 → 自动运行)`);
101
406
  console.log(`\x1b[31m 关闭沙箱后重新运行: npx huaweicloud-devkit install-hcloud\x1b[0m`);
102
407
  }
103
408
 
104
409
  function checkNode() {
105
410
  const v = process.versions.node.split('.').map(Number);
106
- if (v[0] < 20) {
107
- 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`);
108
413
  process.exit(1);
109
414
  }
110
415
  console.log(` Node.js ${process.version} \x1b[32mOK\x1b[0m`);
@@ -124,6 +429,35 @@ function copyDir(src, dest) {
124
429
  }
125
430
  }
126
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 r = spawnSync('npm', ['install', '--omit=dev'], {
442
+ cwd: pluginsDir,
443
+ windowsHide: true,
444
+ stdio: 'pipe',
445
+ timeout: 120000,
446
+ });
447
+ const undiciDir = join(pluginsDir, 'node_modules', 'undici');
448
+ if (r.status === 0 && existsSync(undiciDir)) {
449
+ console.log(` Runtime deps installed -> ${join(pluginsDir, 'node_modules')}`);
450
+ } else {
451
+ const err = (r.stderr || '').toString().trim().split(/\r?\n/).slice(-2).join(' ');
452
+ console.log(` \x1b[33m[WARN]\x1b[0m npm install failed in ${pluginsDir}${err ? `: ${err}` : ''}`);
453
+ console.log(' Manual fix: cd %s && npm install', pluginsDir);
454
+ if (!existsSync(undiciDir)) {
455
+ console.log(` \x1b[31m[ERROR]\x1b[0m undici is NOT installed. MCP server will fail to start.`);
456
+ console.log(` Run manually: cd "${pluginsDir}" && npm install undici@^8.10.0`);
457
+ }
458
+ }
459
+ }
460
+
127
461
  function removeIfExists(p) {
128
462
  if (existsSync(p)) {
129
463
  try {
@@ -142,14 +476,23 @@ function updateOpenCodeConfig(pluginDir) {
142
476
  const mcpPath = join(pluginDir, 'src', 'mcp-server.mjs').replace(/\\/g, '/');
143
477
  let config = {};
144
478
  if (existsSync(configPath)) {
145
- try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {
146
- console.log(` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath} (jsonc comments?). Skipping MCP config write; ensure "mcp.huaweicloud-devkit" points to ${mcpPath}.`);
479
+ try {
480
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
481
+ } catch {
482
+ console.log(
483
+ ` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath} (jsonc comments?). Skipping MCP config write; ensure "mcp.huaweicloud-devkit" points to ${mcpPath}.`,
484
+ );
147
485
  return;
148
486
  }
149
487
  const existing = config.mcp?.['huaweicloud-devkit'];
150
- if (existing && existing.type === 'local'
151
- && Array.isArray(existing.command) && existing.command[0] === 'node'
152
- && existing.command[1] === mcpPath) {
488
+ if (
489
+ existing &&
490
+ existing.type === 'local' &&
491
+ Array.isArray(existing.command) &&
492
+ existing.command[0] === 'node' &&
493
+ existing.command[1] === mcpPath &&
494
+ existing.timeout === 300000
495
+ ) {
153
496
  console.log(` OpenCode MCP config unchanged: ${configPath}`);
154
497
  return;
155
498
  }
@@ -159,6 +502,7 @@ function updateOpenCodeConfig(pluginDir) {
159
502
  type: 'local',
160
503
  command: ['node', mcpPath],
161
504
  enabled: true,
505
+ timeout: 300000,
162
506
  };
163
507
  writeFileSync(configPath, JSON.stringify(config, null, 2));
164
508
  console.log(` OpenCode MCP config updated: ${configPath}`);
@@ -167,8 +511,12 @@ function updateOpenCodeConfig(pluginDir) {
167
511
  function removeOpenCodeConfig() {
168
512
  const configPath = opencodeConfigFile();
169
513
  if (!existsSync(configPath)) return;
170
- let config = {};
171
- try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch { return; }
514
+ let config;
515
+ try {
516
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
517
+ } catch {
518
+ return;
519
+ }
172
520
  if (!config.mcp?.['huaweicloud-devkit']) return;
173
521
  delete config.mcp['huaweicloud-devkit'];
174
522
  if (Object.keys(config.mcp).length === 0) delete config.mcp;
@@ -189,11 +537,13 @@ function hasCodexCLI() {
189
537
  }
190
538
 
191
539
  function checkHcloud() {
192
- const bin = findHcloudBin() || (process.env.HCLOUD_BIN || 'hcloud');
540
+ const bin = findHcloudBin() || process.env.HCLOUD_BIN || 'hcloud';
193
541
  if (!existsSync(bin)) return false;
194
542
  try {
195
543
  if (statSync(bin).size < 1024) return false;
196
- } catch { return false; }
544
+ } catch {
545
+ return false;
546
+ }
197
547
  try {
198
548
  const r = spawnSync(`"${bin}" version`, [], { shell: true, windowsHide: true, stdio: 'pipe', timeout: 5000 });
199
549
  const out = (r.stdout ? r.stdout.toString() : '') + (r.stderr ? r.stderr.toString() : '');
@@ -219,7 +569,9 @@ function installCodex() {
219
569
 
220
570
  console.log(` Registering Codex marketplace: ${marketplaceRoot}`);
221
571
  const r1 = spawnSync(`codex plugin marketplace add "${marketplaceRoot}"`, [], {
222
- shell: true, windowsHide: true, stdio: 'pipe',
572
+ shell: true,
573
+ windowsHide: true,
574
+ stdio: 'pipe',
223
575
  });
224
576
  console.log(` ${r1.stdout ? r1.stdout.toString().trim() : r1.stderr.toString().trim()}`);
225
577
 
@@ -231,7 +583,9 @@ function installCodex() {
231
583
 
232
584
  console.log(` Installing plugin: ${pluginName}@${marketplaceName}`);
233
585
  const r2 = spawnSync(`codex plugin add "${pluginName}@${marketplaceName}"`, [], {
234
- shell: true, windowsHide: true, stdio: 'pipe',
586
+ shell: true,
587
+ windowsHide: true,
588
+ stdio: 'pipe',
235
589
  });
236
590
  console.log(` ${r2.stdout ? r2.stdout.toString().trim() : r2.stderr.toString().trim()}`);
237
591
 
@@ -249,14 +603,18 @@ function uninstallCodex() {
249
603
  const marketplaceName = getMarketplaceName();
250
604
  console.log(` Removing Codex plugin: ${pluginName}@${marketplaceName}`);
251
605
  const r = spawnSync(`codex plugin remove "${pluginName}@${marketplaceName}"`, [], {
252
- shell: true, windowsHide: true, stdio: 'pipe',
606
+ shell: true,
607
+ windowsHide: true,
608
+ stdio: 'pipe',
253
609
  });
254
610
  console.log(` ${r.stdout ? r.stdout.toString().trim() : r.stderr.toString().trim()}`);
255
611
 
256
612
  for (const name of new Set([marketplaceName, 'HuaweiCloud-Devkit'])) {
257
613
  console.log(` Removing Codex marketplace: ${name}`);
258
614
  const r2 = spawnSync(`codex plugin marketplace remove "${name}"`, [], {
259
- shell: true, windowsHide: true, stdio: 'pipe',
615
+ shell: true,
616
+ windowsHide: true,
617
+ stdio: 'pipe',
260
618
  });
261
619
  console.log(` ${r2.stdout ? r2.stdout.toString().trim() : r2.stderr.toString().trim()}`);
262
620
  }
@@ -284,6 +642,7 @@ async function installOpenCode() {
284
642
  copyDir(safetyDir, join(pluginDest, 'safety'));
285
643
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
286
644
  updateOpenCodeConfig(pluginDest);
645
+ installRuntimeDeps(pluginDest);
287
646
  }
288
647
 
289
648
  function uninstallOpenCode() {
@@ -346,7 +705,9 @@ async function updateOpenCode() {
346
705
  console.log(` Skills updated -> ${opencodeSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`);
347
706
  copyDir(commandsSrc, opencodeCommandsDir());
348
707
  const staleCommands = pruneStale(opencodeCommandsDir(), commandsSrc);
349
- console.log(` Commands updated -> ${opencodeCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`);
708
+ console.log(
709
+ ` Commands updated -> ${opencodeCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`,
710
+ );
350
711
  copyDir(srcDir, join(pluginDest, 'src'));
351
712
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
352
713
  copyDir(safetyDir, join(pluginDest, 'safety'));
@@ -354,6 +715,7 @@ async function updateOpenCode() {
354
715
  updateOpenCodeConfig(pluginDest);
355
716
  mkdirSync(pluginDest, { recursive: true });
356
717
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
718
+ installRuntimeDeps(pluginDest);
357
719
  }
358
720
 
359
721
  function codexMcpServerPath() {
@@ -377,7 +739,9 @@ function ensureCodexConfigSection(mcpPath) {
377
739
  const configPath = codexConfigToml();
378
740
  let existing = '';
379
741
  if (existsSync(configPath)) {
380
- try { existing = readFileSync(configPath, 'utf8'); } catch {}
742
+ try {
743
+ existing = readFileSync(configPath, 'utf8');
744
+ } catch {}
381
745
  if (existing.includes('[mcp_servers.huaweicloud-devkit]')) {
382
746
  if (existing.includes(`args = ["${mcpPath}"]`)) {
383
747
  console.log(` Config unchanged: ${configPath}`);
@@ -386,7 +750,9 @@ function ensureCodexConfigSection(mcpPath) {
386
750
  removeCodexConfigSection();
387
751
  existing = '';
388
752
  if (existsSync(configPath)) {
389
- try { existing = readFileSync(configPath, 'utf8'); } catch {}
753
+ try {
754
+ existing = readFileSync(configPath, 'utf8');
755
+ } catch {}
390
756
  }
391
757
  }
392
758
  }
@@ -404,7 +770,10 @@ function removeCodexConfigSection() {
404
770
  const out = [];
405
771
  let skip = false;
406
772
  for (const line of lines) {
407
- if (/^\[mcp_servers\.huaweicloud-devkit(\]|\.)/.test(line)) { skip = true; continue; }
773
+ if (/^\[mcp_servers\.huaweicloud-devkit(\]|\.)/.test(line)) {
774
+ skip = true;
775
+ continue;
776
+ }
408
777
  if (skip && line.startsWith('[')) skip = false;
409
778
  if (!skip) out.push(line);
410
779
  }
@@ -451,6 +820,7 @@ async function installCodexDesktop() {
451
820
  }
452
821
 
453
822
  ensureCodexConfigSection(mcpServerAbsPath);
823
+ installRuntimeDeps(codexDesktopPluginsDir());
454
824
  }
455
825
 
456
826
  // Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
@@ -463,10 +833,14 @@ async function updateCodexDesktop() {
463
833
 
464
834
  copyDir(skillsSrc, codexDesktopSkillsDir());
465
835
  const staleSkills = pruneStale(codexDesktopSkillsDir(), skillsSrc);
466
- console.log(` Skills updated -> ${codexDesktopSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`);
836
+ console.log(
837
+ ` Skills updated -> ${codexDesktopSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`,
838
+ );
467
839
  copyDir(commandsSrc, codexDesktopCommandsDir());
468
840
  const staleCommands = pruneStale(codexDesktopCommandsDir(), commandsSrc);
469
- console.log(` Commands updated -> ${codexDesktopCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`);
841
+ console.log(
842
+ ` Commands updated -> ${codexDesktopCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`,
843
+ );
470
844
  mkdirSync(pluginDest, { recursive: true });
471
845
  copyDir(srcDir, join(pluginDest, 'src'));
472
846
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
@@ -494,6 +868,7 @@ async function updateCodexDesktop() {
494
868
 
495
869
  ensureCodexConfigSection(mcpServerAbsPath);
496
870
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
871
+ installRuntimeDeps(pluginDest);
497
872
  }
498
873
 
499
874
  function uninstallCodexDesktop() {
@@ -534,13 +909,22 @@ function registerCodeartsMcp(configPath) {
534
909
  if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
535
910
  let config = {};
536
911
  if (existsSync(configPath)) {
537
- try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {
538
- console.log(` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`);
912
+ try {
913
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
914
+ } catch {
915
+ console.log(
916
+ ` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`,
917
+ );
539
918
  return;
540
919
  }
541
920
  const existing = config.mcpServers?.['huaweicloud-devkit'];
542
- if (existing && existing.command === 'node'
543
- && Array.isArray(existing.args) && existing.args[0] === mcpPath) {
921
+ if (
922
+ existing &&
923
+ existing.command === 'node' &&
924
+ Array.isArray(existing.args) &&
925
+ existing.args[0] === mcpPath &&
926
+ existing.timeout === 300000
927
+ ) {
544
928
  console.log(` MCP config unchanged: ${configPath}`);
545
929
  return;
546
930
  }
@@ -551,6 +935,7 @@ function registerCodeartsMcp(configPath) {
551
935
  args: [mcpPath],
552
936
  env,
553
937
  enabled: true,
938
+ timeout: 300000,
554
939
  };
555
940
  mkdirSync(dirname(configPath), { recursive: true });
556
941
  writeFileSync(configPath, JSON.stringify(config, null, 2));
@@ -575,6 +960,7 @@ async function installCodeArts() {
575
960
 
576
961
  registerCodeartsMcp(codeartsMcpSettingsFile());
577
962
  registerCodeartsMcp(codeartsProjectMcpSettingsFile());
963
+ installRuntimeDeps(pluginDest);
578
964
  }
579
965
 
580
966
  // Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
@@ -597,6 +983,7 @@ async function updateCodeArts() {
597
983
  registerCodeartsMcp(codeartsProjectMcpSettingsFile());
598
984
  mkdirSync(pluginDest, { recursive: true });
599
985
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
986
+ installRuntimeDeps(pluginDest);
600
987
  }
601
988
 
602
989
  function uninstallCodeArts() {
@@ -618,7 +1005,9 @@ function uninstallCodeArts() {
618
1005
  for (const configPath of [codeartsMcpSettingsFile(), codeartsProjectMcpSettingsFile()]) {
619
1006
  if (!existsSync(configPath)) continue;
620
1007
  let config = {};
621
- try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
1008
+ try {
1009
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
1010
+ } catch {}
622
1011
  if (config.mcpServers?.['huaweicloud-devkit']) {
623
1012
  delete config.mcpServers['huaweicloud-devkit'];
624
1013
  if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
@@ -630,18 +1019,27 @@ function uninstallCodeArts() {
630
1019
 
631
1020
  function codeartsStatus() {
632
1021
  const pluginDir = codeartsPluginsDir();
633
- console.log(` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
634
- console.log(` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
1022
+ console.log(
1023
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1024
+ );
1025
+ console.log(
1026
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1027
+ );
635
1028
  let skillCount = 0;
636
1029
  if (existsSync(codeartsSkillsDir())) {
637
- skillCount = readdirSync(codeartsSkillsDir(), { withFileTypes: true })
638
- .filter((d) => d.isDirectory() && d.name.startsWith('huawei')).length;
1030
+ skillCount = readdirSync(codeartsSkillsDir(), { withFileTypes: true }).filter(
1031
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1032
+ ).length;
639
1033
  }
640
- console.log(` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`);
1034
+ console.log(
1035
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1036
+ );
641
1037
  if (existsSync(codeartsMcpSettingsFile())) {
642
1038
  try {
643
1039
  const config = JSON.parse(readFileSync(codeartsMcpSettingsFile(), 'utf8'));
644
- console.log(` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
1040
+ console.log(
1041
+ ` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
1042
+ );
645
1043
  } catch {
646
1044
  console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
647
1045
  }
@@ -657,13 +1055,22 @@ function ensureWorkbuddyMcpConfig() {
657
1055
  if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
658
1056
  let config = {};
659
1057
  if (existsSync(configPath)) {
660
- try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {
661
- console.log(` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`);
1058
+ try {
1059
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
1060
+ } catch {
1061
+ console.log(
1062
+ ` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`,
1063
+ );
662
1064
  return false;
663
1065
  }
664
1066
  const existing = config.mcpServers?.['huaweicloud-devkit'];
665
- if (existing && existing.command === 'node'
666
- && Array.isArray(existing.args) && existing.args[0] === mcpPath) {
1067
+ if (
1068
+ existing &&
1069
+ existing.command === 'node' &&
1070
+ Array.isArray(existing.args) &&
1071
+ existing.args[0] === mcpPath &&
1072
+ existing.timeout === 300000
1073
+ ) {
667
1074
  console.log(` MCP config unchanged: ${configPath}`);
668
1075
  return false;
669
1076
  }
@@ -673,6 +1080,7 @@ function ensureWorkbuddyMcpConfig() {
673
1080
  command: 'node',
674
1081
  args: [mcpPath],
675
1082
  env,
1083
+ timeout: 300000,
676
1084
  };
677
1085
  mkdirSync(dirname(configPath), { recursive: true });
678
1086
  writeFileSync(configPath, JSON.stringify(config, null, 2));
@@ -695,6 +1103,7 @@ async function installWorkBuddy() {
695
1103
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
696
1104
 
697
1105
  ensureWorkbuddyMcpConfig();
1106
+ installRuntimeDeps(pluginDest);
698
1107
  }
699
1108
 
700
1109
  // Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
@@ -714,6 +1123,7 @@ async function updateWorkBuddy() {
714
1123
  ensureWorkbuddyMcpConfig();
715
1124
  mkdirSync(pluginDest, { recursive: true });
716
1125
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1126
+ installRuntimeDeps(pluginDest);
717
1127
  }
718
1128
 
719
1129
  function uninstallWorkBuddy() {
@@ -736,7 +1146,9 @@ function uninstallWorkBuddy() {
736
1146
  const configPath = workbuddyMcpConfigFile();
737
1147
  if (existsSync(configPath)) {
738
1148
  let config = {};
739
- try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
1149
+ try {
1150
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
1151
+ } catch {}
740
1152
  if (config.mcpServers?.['huaweicloud-devkit']) {
741
1153
  delete config.mcpServers['huaweicloud-devkit'];
742
1154
  if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
@@ -749,19 +1161,28 @@ function uninstallWorkBuddy() {
749
1161
  function workbuddyStatus() {
750
1162
  const pluginDir = workbuddyPluginsDir();
751
1163
  const skillsDir = workbuddySkillsDir();
752
- console.log(` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
753
- console.log(` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
1164
+ console.log(
1165
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1166
+ );
1167
+ console.log(
1168
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1169
+ );
754
1170
  let skillCount = 0;
755
1171
  if (existsSync(skillsDir)) {
756
- skillCount = readdirSync(skillsDir, { withFileTypes: true })
757
- .filter((d) => d.isDirectory() && d.name.startsWith('huawei')).length;
1172
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1173
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1174
+ ).length;
758
1175
  }
759
- console.log(` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`);
1176
+ console.log(
1177
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1178
+ );
760
1179
  const configPath = workbuddyMcpConfigFile();
761
1180
  if (existsSync(configPath)) {
762
1181
  try {
763
1182
  const config = JSON.parse(readFileSync(configPath, 'utf8'));
764
- console.log(` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
1183
+ console.log(
1184
+ ` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
1185
+ );
765
1186
  } catch {
766
1187
  console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
767
1188
  }
@@ -774,10 +1195,7 @@ function dshMcpServerPath() {
774
1195
 
775
1196
  function dshPatchBlock() {
776
1197
  const hcloudBin = findHcloudBin();
777
- const envLines = [
778
- ' HUAWEICLOUD_AGENT_TOOLKIT_MODE: local',
779
- " HDKITSERVICE_ENDPOINT: ''",
780
- ];
1198
+ const envLines = [' HUAWEICLOUD_AGENT_TOOLKIT_MODE: local', " HDKITSERVICE_ENDPOINT: ''"];
781
1199
  if (hcloudBin) {
782
1200
  envLines.push(` HCLOUD_BIN: '${hcloudBin.replace(/\\/g, '/').replace(/'/g, "''")}'`);
783
1201
  }
@@ -804,8 +1222,14 @@ function escapeRegExp(value) {
804
1222
  }
805
1223
 
806
1224
  function removeManagedDshPatchBlock(content) {
807
- const pattern = new RegExp(`\\n?${escapeRegExp(DSH_MCP_PATCH_START)}[\\s\\S]*?${escapeRegExp(DSH_MCP_PATCH_END)}\\s*`, 'g');
808
- return String(content || '').replace(pattern, '\n').replace(/\n{3,}/g, '\n\n').trimEnd();
1225
+ const pattern = new RegExp(
1226
+ `\\n?${escapeRegExp(DSH_MCP_PATCH_START)}[\\s\\S]*?${escapeRegExp(DSH_MCP_PATCH_END)}\\s*`,
1227
+ 'g',
1228
+ );
1229
+ return String(content || '')
1230
+ .replace(pattern, '\n')
1231
+ .replace(/\n{3,}/g, '\n\n')
1232
+ .trimEnd();
809
1233
  }
810
1234
 
811
1235
  function dshPatchHasOnlyCommentsOrEmptyList(content) {
@@ -853,9 +1277,7 @@ function removeDshMcpPatch() {
853
1277
  .filter((line) => line.trim() !== '[]')
854
1278
  .join('\n')
855
1279
  .trimEnd();
856
- const next = dshPatchHasOnlyCommentsOrEmptyList(cleaned)
857
- ? `${prefix ? `${prefix}\n` : ''}[]\n`
858
- : `${cleaned}\n`;
1280
+ const next = dshPatchHasOnlyCommentsOrEmptyList(cleaned) ? `${prefix ? `${prefix}\n` : ''}[]\n` : `${cleaned}\n`;
859
1281
  writeFileSync(patchFile, next);
860
1282
  console.log(` DSH patch cleaned: ${patchFile}`);
861
1283
  return true;
@@ -866,9 +1288,11 @@ function dshPatchConfigured() {
866
1288
  if (!existsSync(patchFile)) return false;
867
1289
  try {
868
1290
  const patch = readFileSync(patchFile, 'utf8');
869
- return patch.includes('id: mcp-huaweicloud')
870
- && patch.includes("@deepseek-ai/dsh-mcp-client")
871
- && patch.includes('serverName: huaweicloud');
1291
+ return (
1292
+ patch.includes('id: mcp-huaweicloud') &&
1293
+ patch.includes('@deepseek-ai/dsh-mcp-client') &&
1294
+ patch.includes('serverName: huaweicloud')
1295
+ );
872
1296
  } catch {
873
1297
  return false;
874
1298
  }
@@ -900,8 +1324,9 @@ function dshMcpClientAvailable() {
900
1324
  if (!existsSync(pkgPath)) return false;
901
1325
  try {
902
1326
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
903
- return Boolean(pkg.dependencies?.['@deepseek-ai/dsh-mcp-client']
904
- || pkg.devDependencies?.['@deepseek-ai/dsh-mcp-client']);
1327
+ return Boolean(
1328
+ pkg.dependencies?.['@deepseek-ai/dsh-mcp-client'] || pkg.devDependencies?.['@deepseek-ai/dsh-mcp-client'],
1329
+ );
905
1330
  } catch {
906
1331
  return false;
907
1332
  }
@@ -963,6 +1388,7 @@ async function installDsh() {
963
1388
  tryInstallDshMcpClient();
964
1389
  mkdirSync(pluginDest, { recursive: true });
965
1390
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1391
+ installRuntimeDeps(pluginDest);
966
1392
  }
967
1393
 
968
1394
  async function updateDsh() {
@@ -982,6 +1408,7 @@ async function updateDsh() {
982
1408
  tryInstallDshMcpClient();
983
1409
  mkdirSync(pluginDest, { recursive: true });
984
1410
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1411
+ installRuntimeDeps(pluginDest);
985
1412
  }
986
1413
 
987
1414
  function uninstallDsh() {
@@ -1011,51 +1438,423 @@ function uninstallDsh() {
1011
1438
  function dshStatus() {
1012
1439
  const pluginDir = dshPluginsDir();
1013
1440
  const skillsDir = dshSkillsDir();
1014
- console.log(` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
1015
- console.log(` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
1441
+ console.log(
1442
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1443
+ );
1444
+ console.log(
1445
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1446
+ );
1016
1447
  let skillCount = 0;
1017
1448
  if (existsSync(skillsDir)) {
1018
- skillCount = readdirSync(skillsDir, { withFileTypes: true })
1019
- .filter((d) => d.isDirectory() && d.name.startsWith('huawei')).length;
1449
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1450
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1451
+ ).length;
1020
1452
  }
1021
- console.log(` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`);
1453
+ console.log(
1454
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1455
+ );
1022
1456
  console.log(` DSH patch: ${dshPatchConfigured() ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
1023
- console.log(` DSH MCP client package: ${dshMcpClientAvailable() ? '\x1b[32mDetected\x1b[0m' : '\x1b[33mCheck DSH profile\x1b[0m'}`);
1457
+ console.log(
1458
+ ` DSH MCP client package: ${dshMcpClientAvailable() ? '\x1b[32mDetected\x1b[0m' : '\x1b[33mCheck DSH profile\x1b[0m'}`,
1459
+ );
1460
+ }
1461
+
1462
+ async function promptOfficeaceInstallDir() {
1463
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1464
+ const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
1465
+ while (true) {
1466
+ const path = (await ask(' Install directory: ')).trim();
1467
+ if (!path) {
1468
+ console.log(' \x1b[33mPath cannot be empty.\x1b[0m');
1469
+ continue;
1470
+ }
1471
+ const capFile = join(path, '.office-claw', 'capabilities.json');
1472
+ if (existsSync(capFile)) {
1473
+ rl.close();
1474
+ return path;
1475
+ }
1476
+ console.log(` \x1b[33mcapabilities.json not found at: ${capFile}\x1b[0m`);
1477
+ console.log(' Please verify the path and try again.');
1478
+ }
1479
+ }
1480
+
1481
+ async function installOfficeAce() {
1482
+ if (!officeaceCapabilitiesDir()) {
1483
+ if (process.stdin.isTTY) {
1484
+ console.log(' \x1b[33mOfficeAce install directory not found automatically.\x1b[0m');
1485
+ console.log(' Please enter the OfficeAce install directory.');
1486
+ const entered = await promptOfficeaceInstallDir();
1487
+ process.env.OFFICE_CLAW_CONFIG_ROOT = join(entered, '.office-claw');
1488
+ } else {
1489
+ console.log(
1490
+ ' \x1b[31mOfficeAce install directory not found. Please set OFFICE_CLAW_CONFIG_ROOT env var and retry.\x1b[0m',
1491
+ );
1492
+ return;
1493
+ }
1494
+ }
1495
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1496
+ const srcDir = join(PLUGIN_ROOT, 'src');
1497
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1498
+ const pluginDest = officeacePluginsDir();
1499
+
1500
+ copyDir(skillsSrc, officeaceSkillsDir());
1501
+ console.log(` Skills -> ${officeaceSkillsDir()}`);
1502
+
1503
+ copyDir(srcDir, join(pluginDest, 'src'));
1504
+ console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1505
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1506
+ console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1507
+
1508
+ installRuntimeDeps(pluginDest);
1509
+ ensureOfficeaceMcpInSqlite();
1510
+ registerOfficeaceSkillEntries();
1511
+ }
1512
+
1513
+ async function updateOfficeAce() {
1514
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1515
+ const srcDir = join(PLUGIN_ROOT, 'src');
1516
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1517
+ const pluginDest = officeacePluginsDir();
1518
+
1519
+ copyDir(skillsSrc, officeaceSkillsDir());
1520
+ const stale = pruneStale(officeaceSkillsDir(), skillsSrc);
1521
+ console.log(` Skills updated -> ${officeaceSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
1522
+ copyDir(srcDir, join(pluginDest, 'src'));
1523
+ console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1524
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1525
+ console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1526
+ installRuntimeDeps(pluginDest);
1527
+ ensureOfficeaceMcpInSqlite();
1528
+ registerOfficeaceSkillEntries();
1529
+ mkdirSync(pluginDest, { recursive: true });
1530
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1531
+ }
1532
+
1533
+ function uninstallOfficeAce() {
1534
+ const skillsDir = officeaceSkillsDir();
1535
+ let removed = 0;
1536
+ if (existsSync(skillsDir)) {
1537
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
1538
+ if (entry.name.startsWith('huawei')) {
1539
+ removeIfExists(join(skillsDir, entry.name));
1540
+ removed++;
1541
+ }
1542
+ }
1543
+ if (removed > 0) console.log(` Removed ${removed} skills`);
1544
+ try {
1545
+ if (readdirSync(skillsDir).length === 0) {
1546
+ rmSync(skillsDir, { recursive: true, force: true });
1547
+ console.log(` Removed empty skills directory: ${skillsDir}`);
1548
+ }
1549
+ } catch {}
1550
+ }
1551
+
1552
+ if (removeIfExists(officeacePluginsDir())) {
1553
+ console.log(' Removed MCP server and safety policy');
1554
+ }
1555
+ removeOfficeaceSkillCapabilities();
1556
+ removeOfficeaceMcpFromSqlite();
1557
+ }
1558
+
1559
+ function officeaceStatus() {
1560
+ const pluginDir = officeacePluginsDir();
1561
+ const skillsDir = officeaceSkillsDir();
1562
+ console.log(
1563
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1564
+ );
1565
+ console.log(
1566
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1567
+ );
1568
+ let skillCount = 0;
1569
+ if (existsSync(skillsDir)) {
1570
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1571
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1572
+ ).length;
1573
+ }
1574
+ console.log(
1575
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1576
+ );
1577
+ const dbPath = officeaceSqlitePath();
1578
+ if (existsSync(dbPath)) {
1579
+ try {
1580
+ const db = openOfficeaceDb();
1581
+ const row = db.prepare("SELECT enabled, status FROM mcp_connectors WHERE name = 'huaweicloud-devkit'").get();
1582
+ db.close();
1583
+ if (row) {
1584
+ const statusIcon = row.status === 'connected' ? '\x1b[32m' : '\x1b[33m';
1585
+ console.log(` MCP config: ${statusIcon}${row.status}\x1b[0m (enabled: ${row.enabled ? 'yes' : 'no'})`);
1586
+ } else {
1587
+ console.log(` MCP config: \x1b[31mNot configured\x1b[0m`);
1588
+ }
1589
+ } catch {
1590
+ console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
1591
+ }
1592
+ } else {
1593
+ console.log(` MCP config: \x1b[31mDatabase not found\x1b[0m`);
1594
+ }
1595
+ }
1596
+
1597
+ // ── Hermes Agent ──
1598
+
1599
+ function hermesHomeDir() {
1600
+ if (process.env.HERMES_HOME) return process.env.HERMES_HOME;
1601
+ // Hermes on Windows stores under LOCALAPPDATA, not ~/.hermes
1602
+ if (platform() === 'win32' && process.env.LOCALAPPDATA) {
1603
+ return join(process.env.LOCALAPPDATA, 'hermes');
1604
+ }
1605
+ return join(homedir(), '.hermes');
1606
+ }
1607
+
1608
+ function hermesSkillsDir() {
1609
+ return join(hermesHomeDir(), 'skills');
1610
+ }
1611
+
1612
+ function hermesPluginsDir() {
1613
+ return join(hermesHomeDir(), 'huaweicloud-plugins');
1614
+ }
1615
+
1616
+ function hermesConfigFile() {
1617
+ return join(hermesHomeDir(), 'config.yaml');
1618
+ }
1619
+
1620
+ // Returns true when the config file was written, false when it was already correct.
1621
+ function ensureHermesMcpConfig() {
1622
+ const configPath = hermesConfigFile();
1623
+ const mcpPath = join(hermesPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
1624
+ const hcloudBin = findHcloudBin();
1625
+
1626
+ const blockLines = [
1627
+ 'mcp_servers:',
1628
+ ' huaweicloud-devkit:',
1629
+ ' command: "node"',
1630
+ ` args: ["${mcpPath}"]`,
1631
+ ' env:',
1632
+ ' HUAWEICLOUD_AGENT_TOOLKIT_MODE: "local"',
1633
+ ];
1634
+ if (hcloudBin) {
1635
+ blockLines.push(` HCLOUD_BIN: "${hcloudBin.replace(/\\/g, '/')}"`);
1636
+ }
1637
+ const block = blockLines.join('\n');
1638
+
1639
+ let existing = '';
1640
+ if (existsSync(configPath)) {
1641
+ try {
1642
+ existing = readFileSync(configPath, 'utf8');
1643
+ } catch {}
1644
+ if (existing.includes('mcp_servers:') && existing.includes('huaweicloud-devkit')) {
1645
+ if (existing.includes(`args: ["${mcpPath}"]`)) {
1646
+ console.log(` MCP config unchanged: ${configPath}`);
1647
+ return false;
1648
+ }
1649
+ removeHermesMcpConfigBlock();
1650
+ existing = '';
1651
+ if (existsSync(configPath)) {
1652
+ try {
1653
+ existing = readFileSync(configPath, 'utf8');
1654
+ } catch {}
1655
+ }
1656
+ }
1657
+ }
1658
+ mkdirSync(dirname(configPath), { recursive: true });
1659
+ const newContent = existing ? `${existing.trimEnd()}\n\n${block}\n` : `${block}\n`;
1660
+ writeFileSync(configPath, newContent);
1661
+ console.log(` MCP config updated: ${configPath}`);
1662
+ return true;
1663
+ }
1664
+
1665
+ function removeHermesMcpConfigBlock() {
1666
+ const configPath = hermesConfigFile();
1667
+ if (!existsSync(configPath)) return;
1668
+ const lines = readFileSync(configPath, 'utf8').split(/\r?\n/);
1669
+ const out = [];
1670
+ let skip = false;
1671
+ for (const line of lines) {
1672
+ if (/^\s*huaweicloud-devkit\s*:/.test(line)) {
1673
+ skip = true;
1674
+ continue;
1675
+ }
1676
+ if (skip) {
1677
+ // Continue skipping indented lines (the server config block)
1678
+ if (/^\s{2,}/.test(line) && line.trim() !== '') continue;
1679
+ // Stop skipping at a top-level key or empty line
1680
+ skip = false;
1681
+ }
1682
+ // If we just skipped the only server under mcp_servers, remove the key too
1683
+ if (line.trim() === 'mcp_servers:') {
1684
+ // Peek ahead to check if this mcp_servers block only has huaweicloud-devkit
1685
+ const remaining = out.join('\n');
1686
+ // Remove trailing mcp_servers: if it's the last meaningful line
1687
+ continue; // skip the mcp_servers key for now, re-add if other servers exist
1688
+ }
1689
+ out.push(line);
1690
+ }
1691
+ // Reconstruct: add mcp_servers back if there are other servers
1692
+ const cleaned = out.join('\n').trimEnd();
1693
+ writeFileSync(configPath, cleaned ? `${cleaned}\n` : '');
1694
+ console.log(' MCP config cleaned');
1695
+ }
1696
+
1697
+ async function installHermes() {
1698
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1699
+ const srcDir = join(PLUGIN_ROOT, 'src');
1700
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1701
+ const pluginDest = hermesPluginsDir();
1702
+
1703
+ copyDir(skillsSrc, hermesSkillsDir());
1704
+ console.log(` Skills -> ${hermesSkillsDir()}`);
1705
+
1706
+ copyDir(srcDir, join(pluginDest, 'src'));
1707
+ console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1708
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1709
+ console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1710
+
1711
+ ensureHermesMcpConfig();
1712
+ installRuntimeDeps(pluginDest);
1713
+ }
1714
+
1715
+ async function updateHermes() {
1716
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1717
+ const srcDir = join(PLUGIN_ROOT, 'src');
1718
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1719
+ const pluginDest = hermesPluginsDir();
1720
+
1721
+ copyDir(skillsSrc, hermesSkillsDir());
1722
+ const stale = pruneStale(hermesSkillsDir(), skillsSrc);
1723
+ console.log(` Skills updated -> ${hermesSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
1724
+ copyDir(srcDir, join(pluginDest, 'src'));
1725
+ console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1726
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1727
+ console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1728
+ ensureHermesMcpConfig();
1729
+ mkdirSync(pluginDest, { recursive: true });
1730
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1731
+ installRuntimeDeps(pluginDest);
1732
+ }
1733
+
1734
+ function uninstallHermes() {
1735
+ const skillsDir = hermesSkillsDir();
1736
+ let removed = 0;
1737
+ if (existsSync(skillsDir)) {
1738
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
1739
+ if (entry.name.startsWith('huawei')) {
1740
+ removeIfExists(join(skillsDir, entry.name));
1741
+ removed++;
1742
+ }
1743
+ }
1744
+ if (removed > 0) console.log(` Removed ${removed} skills`);
1745
+ try {
1746
+ if (readdirSync(skillsDir).length === 0) {
1747
+ rmSync(skillsDir, { recursive: true, force: true });
1748
+ console.log(` Removed empty skills directory: ${skillsDir}`);
1749
+ }
1750
+ } catch {}
1751
+ }
1752
+
1753
+ if (removeIfExists(hermesPluginsDir())) {
1754
+ console.log(' Removed MCP server and safety policy');
1755
+ }
1756
+ removeHermesMcpConfigBlock();
1757
+ }
1758
+
1759
+ function hermesStatus() {
1760
+ const pluginDir = hermesPluginsDir();
1761
+ const skillsDir = hermesSkillsDir();
1762
+ console.log(
1763
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1764
+ );
1765
+ console.log(
1766
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1767
+ );
1768
+ let skillCount = 0;
1769
+ if (existsSync(skillsDir)) {
1770
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1771
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1772
+ ).length;
1773
+ }
1774
+ console.log(
1775
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1776
+ );
1777
+ const configPath = hermesConfigFile();
1778
+ if (existsSync(configPath)) {
1779
+ try {
1780
+ const config = readFileSync(configPath, 'utf8');
1781
+ const configured = config.includes('mcp_servers:') && config.includes('huaweicloud-devkit');
1782
+ console.log(` MCP config: ${configured ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
1783
+ } catch {
1784
+ console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
1785
+ }
1786
+ } else {
1787
+ console.log(` MCP config: \x1b[31mNot found\x1b[0m`);
1788
+ }
1024
1789
  }
1025
1790
 
1026
1791
  function opencodeStatus() {
1027
1792
  const pluginDir = opencodePluginsDir();
1028
1793
  const skillsDir = opencodeSkillsDir();
1029
- console.log(` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
1030
- console.log(` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
1794
+ console.log(
1795
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1796
+ );
1797
+ console.log(
1798
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1799
+ );
1031
1800
  let skillCount = 0;
1032
1801
  if (existsSync(skillsDir)) {
1033
- skillCount = readdirSync(skillsDir, { withFileTypes: true })
1034
- .filter((d) => d.isDirectory() && d.name.startsWith('huawei')).length;
1802
+ skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
1803
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1804
+ ).length;
1035
1805
  }
1036
- console.log(` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`);
1806
+ console.log(
1807
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1808
+ );
1037
1809
  const configPath = opencodeConfigFile();
1038
1810
  if (existsSync(configPath)) {
1039
1811
  try {
1040
1812
  const config = JSON.parse(readFileSync(configPath, 'utf8'));
1041
- console.log(` MCP config: ${config.mcp?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
1813
+ console.log(
1814
+ ` MCP config: ${config.mcp?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
1815
+ );
1042
1816
  } catch {
1043
1817
  console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
1044
1818
  }
1045
1819
  }
1046
1820
  }
1047
1821
 
1822
+ function autoDetectTarget() {
1823
+ const checks = [
1824
+ ['opencode', () => existsSync(join(homedir(), '.config', 'opencode'))],
1825
+ ['codex-desktop', () => existsSync(join(homedir(), '.codex'))],
1826
+ ['codearts', () => existsSync(join(homedir(), '.codeartsdoer'))],
1827
+ ['workbuddy', () => existsSync(join(homedir(), '.workbuddy'))],
1828
+ [
1829
+ 'dsh',
1830
+ () => {
1831
+ const dsh = process.env.DSH_HOME || join(homedir(), '.dsh');
1832
+ return existsSync(dsh);
1833
+ },
1834
+ ],
1835
+ ['officeace', () => existsSync(officeaceCapabilitiesDir())],
1836
+ ['hermes', () => existsSync(hermesHomeDir())],
1837
+ ['openclaw', () => existsSync(join(homedir(), '.openclaw'))],
1838
+ ];
1839
+ const detected = checks.filter(([, check]) => check()).map(([name]) => name);
1840
+ if (detected.length === 0) {
1841
+ console.error('No supported agent detected.');
1842
+ console.error(`Supported: ${SUPPORTED_AGENT_TARGETS.join(', ')} (or "all")`);
1843
+ console.error('Use --target <agent> to specify.');
1844
+ process.exit(1);
1845
+ }
1846
+ if (detected.length === 1) return detected[0];
1847
+ return 'all';
1848
+ }
1849
+
1048
1850
  function parseTarget() {
1049
1851
  const idx = process.argv.indexOf('--target');
1050
- if (idx < 0) return 'opencode';
1852
+ if (idx < 0) return autoDetectTarget();
1051
1853
  const val = (process.argv[idx + 1] || '').toLowerCase();
1052
- if (val === 'codex') return 'codex';
1053
- if (val === 'codex-desktop') return 'codex-desktop';
1054
- if (val === 'codearts') return 'codearts';
1055
- if (val === 'workbuddy') return 'workbuddy';
1056
- if (val === 'dsh') return 'dsh';
1057
- if (val === 'all') return 'all';
1058
- return 'opencode';
1854
+ if (val === 'all' || SUPPORTED_AGENT_TARGETS.includes(val)) return val;
1855
+ console.error(`Unknown target: ${val}`);
1856
+ console.error(`Supported: ${SUPPORTED_AGENT_TARGETS.join(', ')} (or "all")`);
1857
+ process.exit(1);
1059
1858
  }
1060
1859
 
1061
1860
  async function cmdInstall() {
@@ -1084,6 +1883,18 @@ async function cmdInstall() {
1084
1883
  console.log('\n[DSH]');
1085
1884
  await installDsh();
1086
1885
  }
1886
+ if (target === 'officeace' || target === 'all') {
1887
+ console.log('\n[OfficeAce]');
1888
+ await installOfficeAce();
1889
+ }
1890
+ if (target === 'hermes' || target === 'all') {
1891
+ console.log('\n[Hermes Agent]');
1892
+ await installHermes();
1893
+ }
1894
+ if (target === 'openclaw' || target === 'all') {
1895
+ console.log('\n[OpenClaw]');
1896
+ await installCodexDesktop();
1897
+ }
1087
1898
  if (target === 'codex' || target === 'all') {
1088
1899
  console.log('\n[Codex]');
1089
1900
  if (!hasCodexCLI()) {
@@ -1108,18 +1919,37 @@ async function cmdInstall() {
1108
1919
  }
1109
1920
  }
1110
1921
  console.log(`\n\x1b[32mInstallation complete!\x1b[0m`);
1111
- const appName = target === 'codearts' ? 'CodeArts'
1112
- : target === 'codex-desktop' ? 'Codex Desktop'
1113
- : target === 'codex' ? 'Codex'
1114
- : target === 'workbuddy' ? 'WorkBuddy'
1115
- : target === 'dsh' ? 'DSH'
1116
- : 'OpenCode';
1922
+ const appName =
1923
+ target === 'codearts'
1924
+ ? 'CodeArts'
1925
+ : target === 'codex-desktop'
1926
+ ? 'Codex Desktop'
1927
+ : target === 'codex'
1928
+ ? 'Codex'
1929
+ : target === 'workbuddy'
1930
+ ? 'WorkBuddy'
1931
+ : target === 'dsh'
1932
+ ? 'DSH'
1933
+ : target === 'officeace'
1934
+ ? 'OfficeAce'
1935
+ : target === 'hermes'
1936
+ ? 'Hermes Agent'
1937
+ : target === 'openclaw'
1938
+ ? 'OpenClaw'
1939
+ : '当前 agent';
1117
1940
  const pad = ' '.repeat(24 - appName.length);
1118
- console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════╗`);
1119
- console.log(`\x1b[1m\x1b[33m║ MCP 工具在重启 ${appName} 会话后才生效${pad}║`);
1120
- console.log(`\x1b[1m\x1b[33m║ 关闭当前会话重新打开,直接描述华为云任务即可 ║`);
1121
- console.log(`\x1b[1m\x1b[33m║ 重启前请勿执行 hcloud 命令,避免 AK/SK 泄露 ║`);
1122
- console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
1941
+ if (target === 'officeace') {
1942
+ console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════╗`);
1943
+ console.log(`\x1b[1m\x1b[33m║ 打开连接器我的连接器 → huaweicloud-devkit ║`);
1944
+ console.log(`\x1b[1m\x1b[33m║ 连接 回到对话 → 输入框开启连接器 ║`);
1945
+ console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
1946
+ } else {
1947
+ console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════╗`);
1948
+ console.log(`\x1b[1m\x1b[33m║ MCP 工具在重启 ${appName} 会话后才生效${pad}║`);
1949
+ console.log(`\x1b[1m\x1b[33m║ 关闭当前会话 → 重新打开,直接描述华为云任务即可 ║`);
1950
+ console.log(`\x1b[1m\x1b[33m║ 重启前请勿执行 hcloud 命令,避免 AK/SK 泄露 ║`);
1951
+ console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
1952
+ }
1123
1953
 
1124
1954
  const hcloudOk = checkHcloud();
1125
1955
  if (!hcloudOk) {
@@ -1131,15 +1961,29 @@ async function cmdInstall() {
1131
1961
 
1132
1962
  console.log(`\n\x1b[1m下一步:\x1b[0m`);
1133
1963
  console.log(` 1. 配置统一凭据:npx huaweicloud-devkit auth init`);
1134
- console.log(` 2. 重启 ${appName} 会话(MCP 工具重启后生效)`);
1135
- console.log(` 3. 运行自检:npx huaweicloud-devkit doctor`);
1964
+ console.log(` 2. 配置代理(企业内网):npx huaweicloud-devkit proxy init`);
1965
+ if (target === 'officeace') {
1966
+ console.log(` 3. 打开连接器 → 我的连接器 → huaweicloud-devkit → 连接 → 回到对话 → 输入框开启连接器`);
1967
+ } else {
1968
+ console.log(` 3. 重启 ${appName} 会话(MCP 工具重启后生效)`);
1969
+ }
1970
+ console.log(` 4. 运行自检:npx huaweicloud-devkit doctor`);
1136
1971
 
1137
1972
  // Write install marker for doctor to detect
1138
- const markerDir = target === 'dsh' ? dshPluginsDir()
1139
- : target === 'codearts' ? codeartsPluginsDir()
1140
- : target === 'workbuddy' ? workbuddyPluginsDir()
1141
- : target === 'codex-desktop' ? codexDesktopPluginsDir()
1142
- : opencodePluginsDir();
1973
+ const markerDir =
1974
+ target === 'dsh'
1975
+ ? dshPluginsDir()
1976
+ : target === 'codearts'
1977
+ ? codeartsPluginsDir()
1978
+ : target === 'workbuddy'
1979
+ ? workbuddyPluginsDir()
1980
+ : target === 'officeace'
1981
+ ? officeacePluginsDir()
1982
+ : target === 'openclaw'
1983
+ ? codexDesktopPluginsDir()
1984
+ : target === 'codex-desktop'
1985
+ ? codexDesktopPluginsDir()
1986
+ : opencodePluginsDir();
1143
1987
  mkdirSync(markerDir, { recursive: true });
1144
1988
  writeFileSync(join(markerDir, '.installed'), new Date().toISOString());
1145
1989
  if (target === 'opencode' || target === 'all') {
@@ -1157,6 +2001,12 @@ async function cmdInstall() {
1157
2001
  if (target === 'dsh' || target === 'all') {
1158
2002
  console.log('Or describe your Huawei Cloud task in DSH');
1159
2003
  }
2004
+ if (target === 'officeace' || target === 'all') {
2005
+ console.log('Or describe your Huawei Cloud task in OfficeAce');
2006
+ }
2007
+ if (target === 'openclaw' || target === 'all') {
2008
+ console.log('Or describe your Huawei Cloud task in OpenClaw');
2009
+ }
1160
2010
  }
1161
2011
 
1162
2012
  async function cmdUninstall() {
@@ -1180,6 +2030,18 @@ async function cmdUninstall() {
1180
2030
  console.log('\n[DSH]');
1181
2031
  uninstallDsh();
1182
2032
  }
2033
+ if (target === 'officeace' || target === 'all') {
2034
+ console.log('\n[OfficeAce]');
2035
+ uninstallOfficeAce();
2036
+ }
2037
+ if (target === 'hermes' || target === 'all') {
2038
+ console.log('\n[Hermes Agent]');
2039
+ uninstallHermes();
2040
+ }
2041
+ if (target === 'openclaw' || target === 'all') {
2042
+ console.log('\n[OpenClaw]');
2043
+ uninstallCodexDesktop();
2044
+ }
1183
2045
  if (target === 'codex-desktop' || target === 'codex' || target === 'all') {
1184
2046
  console.log('\n[Codex]');
1185
2047
  uninstallCodexDesktop();
@@ -1228,6 +2090,34 @@ async function cmdStatus() {
1228
2090
  console.log('\n[DSH]');
1229
2091
  dshStatus();
1230
2092
  }
2093
+ if (target === 'officeace' || target === 'all') {
2094
+ console.log('\n[OfficeAce]');
2095
+ officeaceStatus();
2096
+ }
2097
+ if (target === 'hermes' || target === 'all') {
2098
+ console.log('\n[Hermes Agent]');
2099
+ hermesStatus();
2100
+ }
2101
+ if (target === 'openclaw' || target === 'all') {
2102
+ console.log('\n[OpenClaw]');
2103
+ const cdPluginDir = codexDesktopPluginsDir();
2104
+ const cdSkillsDir = codexDesktopSkillsDir();
2105
+ console.log(
2106
+ ` MCP Server: ${existsSync(join(cdPluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
2107
+ );
2108
+ console.log(
2109
+ ` Safety Policy: ${existsSync(join(cdPluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
2110
+ );
2111
+ let cdSkillCount = 0;
2112
+ if (existsSync(cdSkillsDir)) {
2113
+ cdSkillCount = readdirSync(cdSkillsDir, { withFileTypes: true }).filter(
2114
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
2115
+ ).length;
2116
+ }
2117
+ console.log(
2118
+ ` Skills: ${cdSkillCount > 0 ? `\x1b[32m${cdSkillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
2119
+ );
2120
+ }
1231
2121
  if (target === 'codex' || target === 'all') {
1232
2122
  console.log('\n[Codex]');
1233
2123
  if (!hasCodexCLI()) {
@@ -1245,43 +2135,68 @@ async function cmdDoctor() {
1245
2135
  console.log(BANNER);
1246
2136
  console.log('HuaweiCloud DevKit Doctor\n');
1247
2137
 
1248
- let pass = 0, warn = 0, fail = 0;
2138
+ let pass = 0,
2139
+ warn = 0,
2140
+ fail = 0;
1249
2141
 
1250
2142
  function check(label, ok, msg) {
1251
- if (ok) { console.log(` \x1b[32m[PASS]\x1b[0m ${label}`); pass++; }
1252
- else { console.log(` \x1b[31m[FAIL]\x1b[0m ${label} — ${msg}`); fail++; }
2143
+ if (ok) {
2144
+ console.log(` \x1b[32m[PASS]\x1b[0m ${label}`);
2145
+ pass++;
2146
+ } else {
2147
+ console.log(` \x1b[31m[FAIL]\x1b[0m ${label} — ${msg}`);
2148
+ fail++;
2149
+ }
1253
2150
  }
1254
2151
 
1255
2152
  // Node.js
1256
- check('Node.js >= 20', process.versions.node.split('.')[0] >= 20, 'Run: nvm install 20 && nvm use 20');
2153
+ check('Node.js >= 22', process.versions.node.split('.')[0] >= 22, 'Run: nvm install 22 && nvm use 22');
1257
2154
 
1258
- // MCP server — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH paths
2155
+ // MCP server — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH paths
1259
2156
  const opencodePluginDir = opencodePluginsDir();
1260
2157
  const codexPluginDir = codexDesktopPluginsDir();
1261
2158
  const codeartsPluginDir = codeartsPluginsDir();
1262
2159
  const workbuddyPluginDir = workbuddyPluginsDir();
1263
2160
  const dshPluginDir = dshPluginsDir();
1264
- const mcpOk = existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs'))
1265
- || existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs'))
1266
- || existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs'))
1267
- || existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs'))
1268
- || existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs'));
1269
- const mcpTarget = existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs')) ? 'OpenCode'
1270
- : existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs')) ? 'Codex Desktop'
1271
- : existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs')) ? 'CodeArts'
1272
- : existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs')) ? 'WorkBuddy'
1273
- : existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs')) ? 'DSH' : '';
2161
+ const officeacePluginDir = officeacePluginsDir();
2162
+ const hermesPluginDir = hermesPluginsDir();
2163
+ const mcpOk =
2164
+ existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs')) ||
2165
+ existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs')) ||
2166
+ existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs')) ||
2167
+ existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs')) ||
2168
+ existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs')) ||
2169
+ existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs')) ||
2170
+ existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'));
2171
+ const mcpTarget = existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs'))
2172
+ ? 'OpenCode'
2173
+ : existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs'))
2174
+ ? 'Codex Desktop'
2175
+ : existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs'))
2176
+ ? 'CodeArts'
2177
+ : existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs'))
2178
+ ? 'WorkBuddy'
2179
+ : existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs'))
2180
+ ? 'DSH'
2181
+ : existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs'))
2182
+ ? 'OfficeAce'
2183
+ : existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'))
2184
+ ? 'Hermes Agent'
2185
+ : '';
1274
2186
  check('MCP server installed', mcpOk, 'Run: npx huaweicloud-devkit install');
1275
2187
 
1276
2188
  if (mcpOk) {
1277
2189
  check(`MCP server can start (${mcpTarget})`, true, '');
1278
2190
  }
1279
2191
 
1280
- const safetyOk = existsSync(join(opencodePluginDir, 'safety', 'policy.json'))
1281
- || existsSync(join(codexPluginDir, 'safety', 'policy.json'))
1282
- || existsSync(join(codeartsPluginDir, 'safety', 'policy.json'))
1283
- || existsSync(join(workbuddyPluginDir, 'safety', 'policy.json'))
1284
- || existsSync(join(dshPluginDir, 'safety', 'policy.json'));
2192
+ const safetyOk =
2193
+ existsSync(join(opencodePluginDir, 'safety', 'policy.json')) ||
2194
+ existsSync(join(codexPluginDir, 'safety', 'policy.json')) ||
2195
+ existsSync(join(codeartsPluginDir, 'safety', 'policy.json')) ||
2196
+ existsSync(join(workbuddyPluginDir, 'safety', 'policy.json')) ||
2197
+ existsSync(join(dshPluginDir, 'safety', 'policy.json')) ||
2198
+ existsSync(join(officeacePluginDir, 'safety', 'policy.json')) ||
2199
+ existsSync(join(hermesPluginDir, 'safety', 'policy.json'));
1285
2200
  check('Safety policy installed', safetyOk, 'Run: npx huaweicloud-devkit install');
1286
2201
 
1287
2202
  // MCP config — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH
@@ -1291,39 +2206,87 @@ async function cmdDoctor() {
1291
2206
  if (existsSync(opencodeCfg)) {
1292
2207
  try {
1293
2208
  const cfg = JSON.parse(readFileSync(opencodeCfg, 'utf8'));
1294
- if (cfg.mcp && cfg.mcp['huaweicloud-devkit']) { mcpConfigured = true; mcpCfgTarget = 'OpenCode'; }
2209
+ if (cfg.mcp && cfg.mcp['huaweicloud-devkit']) {
2210
+ mcpConfigured = true;
2211
+ mcpCfgTarget = 'OpenCode';
2212
+ }
1295
2213
  } catch {}
1296
2214
  }
1297
2215
  const codexCfg = codexConfigToml();
1298
2216
  if (!mcpConfigured && existsSync(codexCfg)) {
1299
2217
  try {
1300
2218
  const cfg = readFileSync(codexCfg, 'utf8');
1301
- if (cfg.includes('[mcp_servers.huaweicloud-devkit]')) { mcpConfigured = true; mcpCfgTarget = 'Codex Desktop'; }
2219
+ if (cfg.includes('[mcp_servers.huaweicloud-devkit]')) {
2220
+ mcpConfigured = true;
2221
+ mcpCfgTarget = 'Codex Desktop';
2222
+ }
1302
2223
  } catch {}
1303
2224
  }
1304
2225
  const codeartsCfg = codeartsMcpSettingsFile();
1305
2226
  if (!mcpConfigured && existsSync(codeartsCfg)) {
1306
2227
  try {
1307
2228
  const cfg = JSON.parse(readFileSync(codeartsCfg, 'utf8'));
1308
- if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) { mcpConfigured = true; mcpCfgTarget = 'CodeArts'; }
2229
+ if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
2230
+ mcpConfigured = true;
2231
+ mcpCfgTarget = 'CodeArts';
2232
+ }
1309
2233
  } catch {}
1310
2234
  }
1311
2235
  const workbuddyCfg = workbuddyMcpConfigFile();
1312
2236
  if (!mcpConfigured && existsSync(workbuddyCfg)) {
1313
2237
  try {
1314
2238
  const cfg = JSON.parse(readFileSync(workbuddyCfg, 'utf8'));
1315
- if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) { mcpConfigured = true; mcpCfgTarget = 'WorkBuddy'; }
2239
+ if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
2240
+ mcpConfigured = true;
2241
+ mcpCfgTarget = 'WorkBuddy';
2242
+ }
1316
2243
  } catch {}
1317
2244
  }
1318
2245
  if (!mcpConfigured && dshPatchConfigured()) {
1319
2246
  mcpConfigured = true;
1320
2247
  mcpCfgTarget = 'DSH';
1321
2248
  }
1322
- check('MCP configured', mcpConfigured, mcpCfgTarget ? `Found in ${mcpCfgTarget} config` : 'Run: npx huaweicloud-devkit install');
2249
+ if (!mcpConfigured) {
2250
+ const officeaceCfg = officeaceCapabilitiesFile();
2251
+ if (existsSync(officeaceCfg)) {
2252
+ try {
2253
+ const cfg = JSON.parse(readFileSync(officeaceCfg, 'utf8'));
2254
+ if (
2255
+ Array.isArray(cfg.capabilities) &&
2256
+ cfg.capabilities.some((c) => c.id === 'huaweicloud-devkit' && c.type === 'mcp')
2257
+ ) {
2258
+ mcpConfigured = true;
2259
+ mcpCfgTarget = 'OfficeAce';
2260
+ }
2261
+ } catch {}
2262
+ }
2263
+ }
2264
+ if (!mcpConfigured) {
2265
+ const hermesCfg = hermesConfigFile();
2266
+ if (existsSync(hermesCfg)) {
2267
+ try {
2268
+ const cfg = readFileSync(hermesCfg, 'utf8');
2269
+ if (cfg.includes('mcp_servers:') && cfg.includes('huaweicloud-devkit')) {
2270
+ mcpConfigured = true;
2271
+ mcpCfgTarget = 'Hermes Agent';
2272
+ }
2273
+ } catch {}
2274
+ }
2275
+ }
2276
+ check(
2277
+ 'MCP configured',
2278
+ mcpConfigured,
2279
+ mcpCfgTarget ? `Found in ${mcpCfgTarget} config` : 'Run: npx huaweicloud-devkit install',
2280
+ );
1323
2281
 
1324
2282
  // hcloud CLI
1325
- const hcloudBin = findHcloudBin() || (process.env.HCLOUD_BIN || 'hcloud');
1326
- const hcloudCheck = spawnSync(`"${hcloudBin}" version`, [], { shell: true, windowsHide: true, stdio: 'pipe', timeout: 5000 });
2283
+ const hcloudBin = findHcloudBin() || process.env.HCLOUD_BIN || 'hcloud';
2284
+ const hcloudCheck = spawnSync(`"${hcloudBin}" version`, [], {
2285
+ shell: true,
2286
+ windowsHide: true,
2287
+ stdio: 'pipe',
2288
+ timeout: 5000,
2289
+ });
1327
2290
  const hcloudOut = (hcloudCheck.stdout || '').toString() + (hcloudCheck.stderr || '').toString();
1328
2291
  const hcloudOk = hcloudCheck.status === 0 && /KooCLI|Current.*version|当前KooCLI/i.test(hcloudOut);
1329
2292
  check('hcloud CLI installed', hcloudOk, 'Run: npx huaweicloud-devkit install-hcloud');
@@ -1342,20 +2305,37 @@ async function cmdDoctor() {
1342
2305
  console.log(` Version: ${ver}`);
1343
2306
 
1344
2307
  // Check auth
1345
- const authCheck = spawnSync(`"${hcloudBin}" configure list`, [], { shell: true, windowsHide: true, stdio: 'pipe', timeout: 5000 });
2308
+ const authCheck = spawnSync(`"${hcloudBin}" configure list`, [], {
2309
+ shell: true,
2310
+ windowsHide: true,
2311
+ stdio: 'pipe',
2312
+ timeout: 5000,
2313
+ });
1346
2314
  const hasAuth = authCheck.status === 0 && /access.?key/i.test(authCheck.stdout.toString());
1347
2315
  check('hcloud credentials configured', hasAuth, 'Run: npx huaweicloud-devkit auth init');
1348
2316
  }
1349
2317
 
1350
2318
  // Skills
1351
- const skillsOptions = [opencodeSkillsDir(), codexDesktopSkillsDir(), codeartsSkillsDir(), workbuddySkillsDir(), dshSkillsDir()];
1352
- let skillCount = 0, skillsDir = '', missingSkills = [];
2319
+ const skillsOptions = [
2320
+ opencodeSkillsDir(),
2321
+ codexDesktopSkillsDir(),
2322
+ codeartsSkillsDir(),
2323
+ workbuddySkillsDir(),
2324
+ dshSkillsDir(),
2325
+ officeaceSkillsDir(),
2326
+ hermesSkillsDir(),
2327
+ ];
2328
+ let skillCount = 0;
2329
+ const missingSkills = [];
1353
2330
  for (const dir of skillsOptions) {
1354
2331
  if (!existsSync(dir)) continue;
1355
- const entries = readdirSync(dir, { withFileTypes: true })
1356
- .filter((d) => d.isDirectory() && d.name.startsWith('huawei'));
2332
+ const entries = readdirSync(dir, { withFileTypes: true }).filter(
2333
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
2334
+ );
1357
2335
  const count = entries.length;
1358
- if (count > skillCount) { skillCount = count; skillsDir = dir; }
2336
+ if (count > skillCount) {
2337
+ skillCount = count;
2338
+ }
1359
2339
  for (const d of entries) {
1360
2340
  if (!existsSync(join(dir, d.name, 'SKILL.md'))) missingSkills.push(d.name);
1361
2341
  }
@@ -1363,12 +2343,15 @@ async function cmdDoctor() {
1363
2343
  const skillsOk = skillCount >= 6;
1364
2344
  check(`Skills installed (${skillCount})`, skillsOk, 'Run: npx huaweicloud-devkit install');
1365
2345
  if (missingSkills.length > 0) {
1366
- console.log(` \x1b[33m[WARN]\x1b[0m ${missingSkills.length} skill(s) missing SKILL.md: ${missingSkills.join(', ')} — Run: npx huaweicloud-devkit install`);
2346
+ console.log(
2347
+ ` \x1b[33m[WARN]\x1b[0m ${missingSkills.length} skill(s) missing SKILL.md: ${missingSkills.join(', ')} — Run: npx huaweicloud-devkit install`,
2348
+ );
1367
2349
  warn++;
1368
2350
  }
1369
2351
 
1370
2352
  const proxyConfig = readProxyConfig();
1371
- const proxyEnv = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
2353
+ const proxyEnv =
2354
+ process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
1372
2355
  if (proxyConfig || proxyEnv) {
1373
2356
  const source = proxyEnv ? 'env' : 'file';
1374
2357
  const proxyUrl = proxyEnv || proxyConfig.https_proxy || proxyConfig.http_proxy;
@@ -1378,7 +2361,9 @@ async function cmdDoctor() {
1378
2361
  console.log(`\nResults: ${pass} pass, ${warn} warn, ${fail} fail`);
1379
2362
 
1380
2363
  if (mcpConfigured && !hcloudOk) {
1381
- console.log('\n\x1b[33mMCP is configured but hcloud is not installed. Install hcloud then restart your session.\x1b[0m');
2364
+ console.log(
2365
+ '\n\x1b[33mMCP is configured but hcloud is not installed. Install hcloud then restart your session.\x1b[0m',
2366
+ );
1382
2367
  }
1383
2368
  if (fail > 0) {
1384
2369
  console.log('\x1b[33mFix failures above, then restart your session.\x1b[0m');
@@ -1394,6 +2379,7 @@ async function cmdDoctor() {
1394
2379
  { path: join(workbuddyPluginsDir(), '.installed'), name: 'WorkBuddy' },
1395
2380
  { path: join(codeartsPluginsDir(), '.installed'), name: 'CodeArts' },
1396
2381
  { path: join(dshPluginsDir(), '.installed'), name: 'DSH' },
2382
+ { path: join(officeacePluginsDir(), '.installed'), name: 'OfficeAce' },
1397
2383
  ];
1398
2384
  for (const marker of installedMarkers) {
1399
2385
  if (existsSync(marker.path)) {
@@ -1487,6 +2473,42 @@ async function cmdUpdate() {
1487
2473
  return;
1488
2474
  }
1489
2475
 
2476
+ if (target === 'officeace') {
2477
+ if (!existsSync(join(officeacePluginsDir(), 'src', 'mcp-server.mjs'))) {
2478
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
2479
+ return;
2480
+ }
2481
+ console.log('[OfficeAce]');
2482
+ await updateOfficeAce();
2483
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
2484
+ console.log(`\x1b[33m打开连接器 → 我的连接器 → huaweicloud-devkit → 连接 → 回到对话 → 输入框开启连接器\x1b[0m`);
2485
+ return;
2486
+ }
2487
+
2488
+ if (target === 'hermes') {
2489
+ if (!existsSync(join(hermesPluginsDir(), 'src', 'mcp-server.mjs'))) {
2490
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
2491
+ return;
2492
+ }
2493
+ console.log('[Hermes Agent]');
2494
+ await updateHermes();
2495
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
2496
+ console.log(`\x1b[33mRestart Hermes Agent for changes to take effect.\x1b[0m`);
2497
+ return;
2498
+ }
2499
+
2500
+ if (target === 'openclaw') {
2501
+ if (!existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
2502
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
2503
+ return;
2504
+ }
2505
+ console.log('[OpenClaw]');
2506
+ await updateCodexDesktop();
2507
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
2508
+ console.log(`\x1b[33mRestart OpenClaw for changes to take effect.\x1b[0m`);
2509
+ return;
2510
+ }
2511
+
1490
2512
  if (target === 'all') {
1491
2513
  let updatedAny = false;
1492
2514
  if (existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))) {
@@ -1514,6 +2536,21 @@ async function cmdUpdate() {
1514
2536
  await updateDsh();
1515
2537
  updatedAny = true;
1516
2538
  }
2539
+ if (existsSync(join(officeacePluginsDir(), 'src', 'mcp-server.mjs'))) {
2540
+ console.log('\n[OfficeAce]');
2541
+ await updateOfficeAce();
2542
+ updatedAny = true;
2543
+ }
2544
+ if (existsSync(join(hermesPluginsDir(), 'src', 'mcp-server.mjs'))) {
2545
+ console.log('\n[Hermes Agent]');
2546
+ await updateHermes();
2547
+ updatedAny = true;
2548
+ }
2549
+ if (existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
2550
+ console.log('\n[OpenClaw]');
2551
+ await updateCodexDesktop();
2552
+ updatedAny = true;
2553
+ }
1517
2554
  if (codexStatus()) {
1518
2555
  console.log('\n[Codex]');
1519
2556
  installCodex();
@@ -1564,9 +2601,7 @@ async function cmdInstallHcloud() {
1564
2601
  const os = platform();
1565
2602
  const arch = process.arch;
1566
2603
  const baseUrl = 'https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/latest';
1567
- const installDir = os === 'win32'
1568
- ? join(homedir(), 'hcloud')
1569
- : join(homedir(), '.local', 'bin');
2604
+ const installDir = os === 'win32' ? join(homedir(), 'hcloud') : join(homedir(), '.local', 'bin');
1570
2605
 
1571
2606
  if (os === 'win32') {
1572
2607
  const url = `${baseUrl}/huaweicloud-cli-windows-amd64.zip`;
@@ -1580,12 +2615,20 @@ async function cmdInstallHcloud() {
1580
2615
  // Download
1581
2616
  console.log(` Downloading ${url}...`);
1582
2617
  const psCmd = `[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '${url}' -OutFile '${zipPath}' -UseBasicParsing`;
1583
- const dl = spawnSync('powershell', ['-NoProfile', '-Command', psCmd], { stdio: 'inherit', windowsHide: true, timeout: 180000 });
2618
+ const dl = spawnSync('powershell', ['-NoProfile', '-Command', psCmd], {
2619
+ stdio: 'inherit',
2620
+ windowsHide: true,
2621
+ timeout: 180000,
2622
+ });
1584
2623
  if (dl.status !== 0) throw new Error(`下载失败 (PowerShell exit ${dl.status})`);
1585
2624
 
1586
2625
  // Extract
1587
2626
  console.log(' Extracting...');
1588
- const ex = spawnSync('powershell', ['-NoProfile', '-Command', `Expand-Archive -Path '${zipPath}' -DestinationPath '${installDir}' -Force`], { stdio: 'inherit', windowsHide: true, timeout: 60000 });
2627
+ const ex = spawnSync(
2628
+ 'powershell',
2629
+ ['-NoProfile', '-Command', `Expand-Archive -Path '${zipPath}' -DestinationPath '${installDir}' -Force`],
2630
+ { stdio: 'inherit', windowsHide: true, timeout: 60000 },
2631
+ );
1589
2632
  if (ex.status !== 0) throw new Error(`解压失败 (PowerShell exit ${ex.status})`);
1590
2633
  if (!existsSync(join(installDir, 'hcloud.exe'))) throw new Error('hcloud.exe 未生成,可能已被安全软件拦截');
1591
2634
 
@@ -1609,7 +2652,11 @@ async function cmdInstallHcloud() {
1609
2652
  ' Write-Output " Already in user PATH: $target"',
1610
2653
  '}',
1611
2654
  ].join('; ');
1612
- spawnSync('powershell', ['-NoProfile', '-Command', pathPs], { stdio: 'inherit', windowsHide: true, timeout: 30000 });
2655
+ spawnSync('powershell', ['-NoProfile', '-Command', pathPs], {
2656
+ stdio: 'inherit',
2657
+ windowsHide: true,
2658
+ timeout: 30000,
2659
+ });
1613
2660
 
1614
2661
  console.log(`\n\x1b[32mInstall complete.\x1b[0m`);
1615
2662
  console.log(` Verify: ${join(installDir, 'hcloud.exe')} version`);
@@ -1625,7 +2672,12 @@ async function cmdInstallHcloud() {
1625
2672
  });
1626
2673
  });
1627
2674
  if (agree) {
1628
- const r = spawnSync(hcloudBin, ['version'], { input: 'y\n', encoding: 'utf8', timeout: 10000, windowsHide: true });
2675
+ const r = spawnSync(hcloudBin, ['version'], {
2676
+ input: 'y\n',
2677
+ encoding: 'utf8',
2678
+ timeout: 10000,
2679
+ windowsHide: true,
2680
+ });
1629
2681
  if (r.status === 0) {
1630
2682
  console.log(' \x1b[32mPrivacy agreement accepted. KooCLI ready.\x1b[0m');
1631
2683
  } else {
@@ -1646,7 +2698,9 @@ async function cmdInstallHcloud() {
1646
2698
  }
1647
2699
  } else if (os === 'linux') {
1648
2700
  console.log('[Linux] One-liner install:');
1649
- 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');
2701
+ console.log(
2702
+ ' 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',
2703
+ );
1650
2704
  console.log(`\nOr manual: ${arch === 'arm64' ? 'ARM64' : 'AMD64'}`);
1651
2705
  const pkg = arch === 'arm64' ? 'linux-arm64' : 'linux-amd64';
1652
2706
  console.log(` curl -LO "${baseUrl}/huaweicloud-cli-${pkg}.tar.gz"`);
@@ -1656,7 +2710,9 @@ async function cmdInstallHcloud() {
1656
2710
  console.log(`\nFull guide: https://support.huaweicloud.com/qs-hcli/hcli_02_003_02.html`);
1657
2711
  } else if (os === 'darwin') {
1658
2712
  console.log('[macOS] One-liner install:');
1659
- 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');
2713
+ console.log(
2714
+ ' 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',
2715
+ );
1660
2716
  console.log(`\nOr manual: ${arch === 'arm64' ? 'ARM64 (Apple Silicon)' : 'AMD64 (Intel)'}`);
1661
2717
  const pkg = arch === 'arm64' ? 'mac-arm64' : 'mac-amd64';
1662
2718
  console.log(` curl -LO "${baseUrl}/huaweicloud-cli-${pkg}.tar.gz"`);
@@ -1687,7 +2743,7 @@ function readLineQuestion(prompt) {
1687
2743
  async function readSecret(prompt) {
1688
2744
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
1689
2745
  throw new Error(
1690
- `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.`
2746
+ `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.`,
1691
2747
  );
1692
2748
  }
1693
2749
 
@@ -1727,7 +2783,7 @@ async function readSecret(prompt) {
1727
2783
  }
1728
2784
 
1729
2785
  function configureHcloud(credentials) {
1730
- const hcloudBin = findHcloudBin() || (process.env.HCLOUD_BIN || 'hcloud');
2786
+ const hcloudBin = findHcloudBin() || process.env.HCLOUD_BIN || 'hcloud';
1731
2787
  const args = [
1732
2788
  'configure',
1733
2789
  'set',
@@ -1744,7 +2800,9 @@ function configureHcloud(credentials) {
1744
2800
  return {
1745
2801
  ok: r.status === 0,
1746
2802
  code: r.status,
1747
- error: String(r.stderr || '').trim().slice(0, 240),
2803
+ error: String(r.stderr || '')
2804
+ .trim()
2805
+ .slice(0, 240),
1748
2806
  };
1749
2807
  }
1750
2808
 
@@ -1755,7 +2813,9 @@ function printAuthAgents(agents = {}) {
1755
2813
  }
1756
2814
 
1757
2815
  function printAuthStatus(status) {
1758
- console.log(`Credentials vault: ${status.credentialsConfigured ? 'configured' : 'missing'} (${status.credentialsPath})`);
2816
+ console.log(
2817
+ `Credentials vault: ${status.credentialsConfigured ? 'configured' : 'missing'} (${status.credentialsPath})`,
2818
+ );
1759
2819
  console.log(`OBS config: ${status.obsConfigured ? 'configured' : 'missing'} (${status.obsConfigPath})`);
1760
2820
  console.log(`KooCLI: ${status.kooCliInstalled ? 'installed' : 'missing'}`);
1761
2821
  console.log('Agent MCP registration:');
@@ -1779,7 +2839,9 @@ async function cmdAuthInit() {
1779
2839
 
1780
2840
  const interactive = process.stdin.isTTY && process.stdout.isTTY;
1781
2841
  if (!interactive && (!ak || !sk)) {
1782
- console.error('\x1b[31mNon-interactive session detected. Provide credentials via environment variables instead:\x1b[0m');
2842
+ console.error(
2843
+ '\x1b[31mNon-interactive session detected. Provide credentials via environment variables instead:\x1b[0m',
2844
+ );
1783
2845
  console.error(' HW_ACCESS_KEY, HW_SECRET_KEY');
1784
2846
  console.error(' (Or run "npx huaweicloud-devkit auth init" in a real terminal.)');
1785
2847
  process.exitCode = 1;
@@ -1788,7 +2850,8 @@ async function cmdAuthInit() {
1788
2850
 
1789
2851
  if (!ak) ak = await readLineQuestion('Access Key ID (AK): ');
1790
2852
  if (!sk) sk = await readSecret('Secret Access Key (SK): ');
1791
- if (interactive && !securityToken) securityToken = await readLineQuestion('Security Token (optional, press Enter to skip): ');
2853
+ if (interactive && !securityToken)
2854
+ securityToken = await readLineQuestion('Security Token (optional, press Enter to skip): ');
1792
2855
  if (!region) region = 'cn-north-4';
1793
2856
 
1794
2857
  if (!ak || !sk) {
@@ -1797,8 +2860,7 @@ async function cmdAuthInit() {
1797
2860
  return;
1798
2861
  }
1799
2862
 
1800
-
1801
- const vaultPath = writeGlobalCredentials({ ak, sk, securityToken, region });
2863
+ writeGlobalCredentials({ ak, sk, securityToken, region });
1802
2864
 
1803
2865
  try {
1804
2866
  writeObsConfig({ ak, sk, securityToken, region });
@@ -1874,12 +2936,14 @@ async function cmdProxyInit() {
1874
2936
 
1875
2937
  const httpsProxy = await readLineQuestion(`HTTPS proxy [${existing.https_proxy || 'none'}]: `);
1876
2938
  const httpProxy = await readLineQuestion(`HTTP proxy [${existing.http_proxy || 'none'}]: `);
1877
- const noProxy = await readLineQuestion(`NO_PROXY hosts [${existing.no_proxy || 'localhost,127.0.0.1'}]: `);
2939
+ const noProxy = await readLineQuestion(
2940
+ `NO_PROXY hosts [${existing.no_proxy || '127.0.0.1,localhost,.huawei.com'}]: `,
2941
+ );
1878
2942
 
1879
2943
  const config = {
1880
2944
  https_proxy: httpsProxy || existing.https_proxy || '',
1881
2945
  http_proxy: httpProxy || existing.http_proxy || '',
1882
- no_proxy: noProxy || existing.no_proxy || 'localhost,127.0.0.1',
2946
+ no_proxy: noProxy || existing.no_proxy || '127.0.0.1,localhost,.huawei.com',
1883
2947
  };
1884
2948
 
1885
2949
  const path = writeProxyConfig(config);
@@ -1981,7 +3045,9 @@ async function main() {
1981
3045
  case '-h':
1982
3046
  default:
1983
3047
  console.log(BANNER);
1984
- console.log('Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|dsh|all>]\n');
3048
+ console.log(
3049
+ 'Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|dsh|officeace|hermes|openclaw|all>]\n',
3050
+ );
1985
3051
  console.log('Commands:');
1986
3052
  console.log(' install Install skills, MCP server, safety policy');
1987
3053
  console.log(' uninstall Remove installed files');
@@ -1994,13 +3060,17 @@ async function main() {
1994
3060
  console.log(' proxy Manage proxy config: init | show | clear');
1995
3061
  console.log(' help Show this help');
1996
3062
  console.log('\nOptions:');
1997
- console.log(' --target Target agent: opencode (default), codex, codearts, workbuddy, dsh, all');
3063
+ console.log(
3064
+ ' --target Target agent: opencode (default), codex, codearts, workbuddy, dsh, officeace, hermes, openclaw, all',
3065
+ );
1998
3066
  console.log('\nExamples:');
1999
3067
  console.log(' npx huaweicloud-devkit install');
2000
3068
  console.log(' npx huaweicloud-devkit install --target codex');
2001
3069
  console.log(' npx huaweicloud-devkit install --target codearts');
2002
3070
  console.log(' npx huaweicloud-devkit install --target workbuddy');
2003
3071
  console.log(' npx huaweicloud-devkit install --target dsh');
3072
+ console.log(' npx huaweicloud-devkit install --target officeace');
3073
+ console.log(' npx huaweicloud-devkit install --target hermes');
2004
3074
  console.log(' npx huaweicloud-devkit install --target all');
2005
3075
  console.log(' npx huaweicloud-devkit auth init');
2006
3076
  console.log(' npx huaweicloud-devkit auth sync --target all');