huaweicloud-devkit 1.1.0-next.2 → 1.1.0-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 (39) hide show
  1. package/README.md +64 -1
  2. package/README.zh-CN.md +63 -1
  3. package/package.json +3 -1
  4. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
  5. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
  6. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
  7. package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
  8. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
  9. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +46 -21
  10. package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
  11. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +136 -24
  12. package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +15 -14
  13. package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +1 -1
  14. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +5 -1
  15. package/plugins/huaweicloud-core/skills/huawei-ecs/references/hc-activity.md +27 -0
  16. package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +11 -11
  17. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +6 -5
  18. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +596 -93
  19. package/plugins/huaweicloud-core/skills/huawei-sandbox/references/framework-commands.md +0 -1
  20. package/plugins/huaweicloud-core/skills/huawei-sandbox/references/nginx-templates.md +8 -4
  21. package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +19 -12
  22. package/plugins/huaweicloud-core/skills/huawei-voucher/SKILL.md +52 -0
  23. package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +26 -26
  24. package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +12 -6
  25. package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +7 -4
  26. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +7 -7
  27. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +2 -0
  28. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +8 -0
  29. package/plugins/huaweicloud-core/src/auth/credentials.mjs +30 -1
  30. package/plugins/huaweicloud-core/src/auth/service.mjs +33 -0
  31. package/plugins/huaweicloud-core/src/detect-framework.mjs +25 -3
  32. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +67 -5
  33. package/plugins/huaweicloud-core/src/mcp-server.mjs +23 -0
  34. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +11 -3
  35. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +436 -21
  36. package/plugins/huaweicloud-core/src/search-market.mjs +1 -0
  37. package/plugins/huaweicloud-core/src/setup-cli.mjs +605 -50
  38. package/plugins/huaweicloud-core/src/tools.mjs +193 -18
  39. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +29 -8
@@ -105,6 +105,17 @@ function codeartsPluginsDir() {
105
105
  return join(homedir(), '.codeartsdoer', 'huaweicloud-plugins');
106
106
  }
107
107
 
108
+ // CodeArts Work (CodeArts Space) — user-level only
109
+ function codeartsWorkSkillsDir() {
110
+ return join(homedir(), '.codeartswork', 'skills');
111
+ }
112
+ function codeartsWorkMcpSettingsFile() {
113
+ return join(homedir(), '.codeartswork', 'mcp', 'mcp_settings.json');
114
+ }
115
+ function codeartsWorkPluginsDir() {
116
+ return join(homedir(), '.codeartswork', 'huaweicloud-plugins');
117
+ }
118
+
108
119
  function workbuddySkillsDir() {
109
120
  return join(homedir(), '.workbuddy', 'skills');
110
121
  }
@@ -463,6 +474,7 @@ function installRuntimeDeps(pluginsDir) {
463
474
  };
464
475
  let r = spawnSync('npm', ['install', '--omit=dev'], spawnOpts);
465
476
  const isRetryable = (res) => {
477
+ if (res.status === 0) return false;
466
478
  const stderr = (res.stderr || '').toString();
467
479
  return res.error?.code === 'EPERM' || res.error?.code === 'EBUSY' || /EPERM|EBUSY/.test(stderr);
468
480
  };
@@ -471,6 +483,11 @@ function installRuntimeDeps(pluginsDir) {
471
483
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2000);
472
484
  r = spawnSync('npm', ['install', '--omit=dev'], spawnOpts);
473
485
  }
486
+ if (r.status !== 0 && !isRetryable(r)) {
487
+ console.log(` \x1b[33m[RETRY]\x1b[0m npm install failed (exit ${r.status}), retrying in 3s...`);
488
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 3000);
489
+ r = spawnSync('npm', ['install', '--omit=dev'], spawnOpts);
490
+ }
474
491
  const undiciDir = join(pluginsDir, 'node_modules', 'undici');
475
492
  if (r.status === 0 && existsSync(undiciDir)) {
476
493
  console.log(` Runtime deps installed -> ${join(pluginsDir, 'node_modules')}`);
@@ -478,12 +495,14 @@ function installRuntimeDeps(pluginsDir) {
478
495
  const errCode = r.error?.code;
479
496
  const err = (r.stderr || '').toString().trim().split(/\r?\n/).slice(-2).join(' ');
480
497
  const hint = errCode === 'ENOENT' ? ' (npm not found — ensure Node.js/npm is in PATH)' : '';
481
- console.log(` \x1b[33m[WARN]\x1b[0m npm install failed in ${pluginsDir}${err ? `: ${err}` : ''}${hint}`);
498
+ const msg = `npm install failed in ${pluginsDir}${err ? `: ${err}` : ''}${hint}`;
499
+ console.log(` \x1b[31m[ERROR]\x1b[0m ${msg}`);
482
500
  console.log(' Manual fix: cd %s && npm install', pluginsDir);
483
501
  if (!existsSync(undiciDir)) {
484
502
  console.log(` \x1b[31m[ERROR]\x1b[0m undici is NOT installed. MCP server will fail to start.`);
485
503
  console.log(` Run manually: cd "${pluginsDir}" && npm install undici@^8.10.0`);
486
504
  }
505
+ throw new Error(msg);
487
506
  }
488
507
  }
489
508
 
@@ -1248,6 +1267,146 @@ function codeartsStatus() {
1248
1267
  }
1249
1268
  }
1250
1269
 
1270
+ // --- CodeArts Work (CodeArts Space) ---
1271
+
1272
+ function registerCodeartsWorkMcp() {
1273
+ const configPath = codeartsWorkMcpSettingsFile();
1274
+ const mcpPath = join(codeartsWorkPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
1275
+ const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
1276
+ const hcloudBin = findHcloudBin();
1277
+ if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
1278
+ let config = {};
1279
+ if (existsSync(configPath)) {
1280
+ try {
1281
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
1282
+ } catch {
1283
+ console.log(
1284
+ ` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`,
1285
+ );
1286
+ return;
1287
+ }
1288
+ const existing = config.mcpServers?.['huaweicloud-devkit'];
1289
+ if (
1290
+ existing &&
1291
+ existing.command === 'node' &&
1292
+ Array.isArray(existing.args) &&
1293
+ existing.args[0] === mcpPath &&
1294
+ existing.timeout === 300000
1295
+ ) {
1296
+ console.log(` MCP config unchanged: ${configPath}`);
1297
+ return;
1298
+ }
1299
+ }
1300
+ config.mcpServers = config.mcpServers || {};
1301
+ config.mcpServers['huaweicloud-devkit'] = {
1302
+ command: 'node',
1303
+ args: [mcpPath],
1304
+ env,
1305
+ enabled: true,
1306
+ timeout: 300000,
1307
+ };
1308
+ mkdirSync(dirname(configPath), { recursive: true });
1309
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
1310
+ console.log(` MCP config updated: ${configPath}`);
1311
+ }
1312
+
1313
+ async function installCodeArtsWork() {
1314
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1315
+ const srcDir = join(PLUGIN_ROOT, 'src');
1316
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1317
+
1318
+ copyDir(skillsSrc, codeartsWorkSkillsDir());
1319
+ console.log(` Skills -> ${codeartsWorkSkillsDir()}`);
1320
+
1321
+ const pluginDest = codeartsWorkPluginsDir();
1322
+ copyDir(srcDir, join(pluginDest, 'src'));
1323
+ console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1324
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1325
+ console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1326
+
1327
+ registerCodeartsWorkMcp();
1328
+ installRuntimeDeps(pluginDest);
1329
+ }
1330
+
1331
+ async function updateCodeArtsWork() {
1332
+ const skillsSrc = join(PLUGIN_ROOT, 'skills');
1333
+ const srcDir = join(PLUGIN_ROOT, 'src');
1334
+ const safetyDir = join(PLUGIN_ROOT, 'safety');
1335
+ const pluginDest = codeartsWorkPluginsDir();
1336
+
1337
+ copyDir(skillsSrc, codeartsWorkSkillsDir());
1338
+ console.log(` Skills updated -> ${codeartsWorkSkillsDir()}`);
1339
+ copyDir(srcDir, join(pluginDest, 'src'));
1340
+ console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1341
+ copyDir(safetyDir, join(pluginDest, 'safety'));
1342
+ console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1343
+ registerCodeartsWorkMcp();
1344
+ mkdirSync(pluginDest, { recursive: true });
1345
+ writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
1346
+ installRuntimeDeps(pluginDest);
1347
+ }
1348
+
1349
+ function uninstallCodeArtsWork() {
1350
+ const skillsDir = codeartsWorkSkillsDir();
1351
+ if (existsSync(skillsDir)) {
1352
+ let removed = 0;
1353
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
1354
+ if (entry.name.startsWith('huawei')) {
1355
+ removeIfExists(join(skillsDir, entry.name));
1356
+ removed++;
1357
+ }
1358
+ }
1359
+ if (removed > 0) console.log(` Removed ${removed} skills`);
1360
+ }
1361
+
1362
+ if (removeIfExists(codeartsWorkPluginsDir())) {
1363
+ console.log(' Removed MCP server and safety policy');
1364
+ }
1365
+
1366
+ const configPath = codeartsWorkMcpSettingsFile();
1367
+ if (existsSync(configPath)) {
1368
+ let config = {};
1369
+ try {
1370
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
1371
+ } catch {}
1372
+ if (config.mcpServers?.['huaweicloud-devkit']) {
1373
+ delete config.mcpServers['huaweicloud-devkit'];
1374
+ if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
1375
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
1376
+ console.log(` Config cleaned: ${configPath}`);
1377
+ }
1378
+ }
1379
+ }
1380
+
1381
+ function codeartsWorkStatus() {
1382
+ const pluginDir = codeartsWorkPluginsDir();
1383
+ console.log(
1384
+ ` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1385
+ );
1386
+ console.log(
1387
+ ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
1388
+ );
1389
+ let skillCount = 0;
1390
+ if (existsSync(codeartsWorkSkillsDir())) {
1391
+ skillCount = readdirSync(codeartsWorkSkillsDir(), { withFileTypes: true }).filter(
1392
+ (d) => d.isDirectory() && d.name.startsWith('huawei'),
1393
+ ).length;
1394
+ }
1395
+ console.log(
1396
+ ` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
1397
+ );
1398
+ if (existsSync(codeartsWorkMcpSettingsFile())) {
1399
+ try {
1400
+ const config = JSON.parse(readFileSync(codeartsWorkMcpSettingsFile(), 'utf8'));
1401
+ console.log(
1402
+ ` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
1403
+ );
1404
+ } catch {
1405
+ console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
1406
+ }
1407
+ }
1408
+ }
1409
+
1251
1410
  // Returns true when the config file was written, false when it was already correct.
1252
1411
  function ensureWorkbuddyMcpConfig() {
1253
1412
  const configPath = workbuddyMcpConfigFile();
@@ -1957,6 +2116,14 @@ function hermesPluginsDir() {
1957
2116
  return join(hermesHomeDir(), 'huaweicloud-plugins');
1958
2117
  }
1959
2118
 
2119
+ function hermesPythonPluginsDir() {
2120
+ return join(hermesHomeDir(), 'plugins');
2121
+ }
2122
+
2123
+ function hermesSafetyPluginDir() {
2124
+ return join(hermesPythonPluginsDir(), 'huaweicloud-safety');
2125
+ }
2126
+
1960
2127
  function hermesConfigFile() {
1961
2128
  return join(hermesHomeDir(), 'config.yaml');
1962
2129
  }
@@ -2035,28 +2202,298 @@ function removeHermesMcpConfigBlock() {
2035
2202
  console.log(' MCP config cleaned');
2036
2203
  }
2037
2204
 
2205
+ function hermesHookScript() {
2206
+ return join(hermesPluginsDir(), 'hooks', 'huaweicloud-safety.py').replace(/\\/g, '/');
2207
+ }
2208
+
2209
+ function hermesHookCommand() {
2210
+ const pythonBin = process.platform === 'win32' ? 'python' : 'python3';
2211
+ return `${pythonBin} ${hermesHookScript()}`;
2212
+ }
2213
+
2214
+ function ensureHermesHooksConfig() {
2215
+ const configPath = hermesConfigFile();
2216
+ const hookCommand = hermesHookCommand();
2217
+
2218
+ const blockLines = [
2219
+ 'hooks:',
2220
+ ' pre_tool_call:',
2221
+ ' - matcher: "terminal"',
2222
+ ` command: "${hookCommand}"`,
2223
+ ' timeout: 5',
2224
+ ' fail_closed: true',
2225
+ ];
2226
+ const block = blockLines.join('\n');
2227
+
2228
+ let existing = '';
2229
+ if (existsSync(configPath)) {
2230
+ try {
2231
+ existing = readFileSync(configPath, 'utf8');
2232
+ } catch {}
2233
+ if (
2234
+ existing.includes('hooks:') &&
2235
+ existing.includes(`command: "${hookCommand}"`) &&
2236
+ existing.includes('fail_closed: true')
2237
+ ) {
2238
+ console.log(` Hooks config unchanged: ${configPath}`);
2239
+ return false;
2240
+ }
2241
+ removeHermesHooksConfigBlock();
2242
+ existing = '';
2243
+ if (existsSync(configPath)) {
2244
+ try {
2245
+ existing = readFileSync(configPath, 'utf8');
2246
+ } catch {}
2247
+ }
2248
+ }
2249
+ mkdirSync(dirname(configPath), { recursive: true });
2250
+ const newContent = existing ? `${existing.trimEnd()}\n\n${block}\n` : `${block}\n`;
2251
+ writeFileSync(configPath, newContent);
2252
+ console.log(` Hooks config updated: ${configPath}`);
2253
+ return true;
2254
+ }
2255
+
2256
+ function hermesHookScriptMtime() {
2257
+ const scriptPath = hermesHookScript();
2258
+ try {
2259
+ return statSync(scriptPath).mtime.toISOString();
2260
+ } catch {
2261
+ return null;
2262
+ }
2263
+ }
2264
+
2265
+ function hermesAllowlistPath() {
2266
+ return join(hermesHomeDir(), 'shell-hooks-allowlist.json');
2267
+ }
2268
+
2269
+ function ensureHermesHookAllowlist() {
2270
+ const hookCommand = hermesHookCommand();
2271
+ const allowlistPath = hermesAllowlistPath();
2272
+
2273
+ let data = { approvals: [] };
2274
+ if (existsSync(allowlistPath)) {
2275
+ try {
2276
+ const parsed = JSON.parse(readFileSync(allowlistPath, 'utf8'));
2277
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
2278
+ data = parsed;
2279
+ }
2280
+ } catch {}
2281
+ }
2282
+ const approvals = Array.isArray(data.approvals) ? data.approvals : [];
2283
+ const already = approvals.some(
2284
+ (a) => a && typeof a === 'object' && a.event === 'pre_tool_call' && a.command === hookCommand,
2285
+ );
2286
+ if (already) {
2287
+ console.log(` Hook allowlist unchanged: ${allowlistPath}`);
2288
+ return false;
2289
+ }
2290
+
2291
+ const entry = {
2292
+ event: 'pre_tool_call',
2293
+ command: hookCommand,
2294
+ approved_at: new Date().toISOString(),
2295
+ script_mtime_at_approval: hermesHookScriptMtime(),
2296
+ };
2297
+ data.approvals = [...approvals, entry];
2298
+ mkdirSync(dirname(allowlistPath), { recursive: true });
2299
+ writeFileSync(allowlistPath, JSON.stringify(data, null, 2));
2300
+ console.log(` Hook allowlist updated: ${allowlistPath}`);
2301
+ return true;
2302
+ }
2303
+
2304
+ function hermesHookAllowlisted() {
2305
+ const allowlistPath = hermesAllowlistPath();
2306
+ if (!existsSync(allowlistPath)) return false;
2307
+ try {
2308
+ const data = JSON.parse(readFileSync(allowlistPath, 'utf8'));
2309
+ const cmd = hermesHookCommand();
2310
+ return (
2311
+ Array.isArray(data?.approvals) &&
2312
+ data.approvals.some((a) => a && typeof a === 'object' && a.event === 'pre_tool_call' && a.command === cmd)
2313
+ );
2314
+ } catch {
2315
+ return false;
2316
+ }
2317
+ }
2318
+
2319
+ function hermesHookPluginInit() {
2320
+ const script = hermesHookScript();
2321
+ return [
2322
+ 'import importlib.util',
2323
+ '',
2324
+ `SAFETY_SCRIPT = ${JSON.stringify(script)}`,
2325
+ '',
2326
+ '',
2327
+ 'def _load_safety():',
2328
+ ' spec = importlib.util.spec_from_file_location("huaweicloud_safety_hook", SAFETY_SCRIPT)',
2329
+ ' module = importlib.util.module_from_spec(spec)',
2330
+ ' spec.loader.exec_module(module)',
2331
+ ' return module',
2332
+ '',
2333
+ '',
2334
+ '_safety = _load_safety()',
2335
+ '',
2336
+ '',
2337
+ 'def register(ctx):',
2338
+ ' ctx.register_hook("pre_tool_call", _check)',
2339
+ '',
2340
+ '',
2341
+ 'def _check(tool_name=None, args=None, **kwargs):',
2342
+ ' if tool_name != "terminal":',
2343
+ ' return None',
2344
+ ' if not (isinstance(args, dict) and "command" in args):',
2345
+ ' return None',
2346
+ ' reason = _safety.evaluate(tool_name, args)',
2347
+ ' if reason:',
2348
+ ' return {"action": "block", "message": _safety.DENY_PREFIX + reason}',
2349
+ ' return None',
2350
+ '',
2351
+ ].join('\n');
2352
+ }
2353
+
2354
+ function ensureHermesHookPlugin() {
2355
+ const pluginDir = hermesSafetyPluginDir();
2356
+ const manifestPath = join(pluginDir, 'plugin.yaml');
2357
+ const initPath = join(pluginDir, '__init__.py');
2358
+
2359
+ const manifest = [
2360
+ 'name: huaweicloud-safety',
2361
+ `version: "${pkgVersion}"`,
2362
+ 'description: Huawei Cloud safety pre_tool_call hook',
2363
+ 'author: HuaweiCloud Mate',
2364
+ 'provides_hooks:',
2365
+ ' - pre_tool_call',
2366
+ '',
2367
+ ].join('\n');
2368
+ const init = hermesHookPluginInit();
2369
+
2370
+ const manifestCurrent = existsSync(manifestPath) ? readFileSync(manifestPath, 'utf8') : '';
2371
+ const initCurrent = existsSync(initPath) ? readFileSync(initPath, 'utf8') : '';
2372
+ if (manifestCurrent === manifest && initCurrent === init) {
2373
+ console.log(` Hook plugin unchanged: ${pluginDir}`);
2374
+ return false;
2375
+ }
2376
+
2377
+ mkdirSync(pluginDir, { recursive: true });
2378
+ writeFileSync(manifestPath, manifest);
2379
+ writeFileSync(initPath, init);
2380
+ console.log(` Hook plugin installed: ${pluginDir}`);
2381
+ return true;
2382
+ }
2383
+
2384
+ function removeHermesHookPlugin() {
2385
+ const pluginDir = hermesSafetyPluginDir();
2386
+ if (!existsSync(pluginDir)) return false;
2387
+ rmSync(pluginDir, { recursive: true, force: true });
2388
+ console.log(` Removed hook plugin: ${pluginDir}`);
2389
+ const parent = hermesPythonPluginsDir();
2390
+ try {
2391
+ if (existsSync(parent) && readdirSync(parent).length === 0) {
2392
+ rmSync(parent, { recursive: true, force: true });
2393
+ }
2394
+ } catch {}
2395
+ return true;
2396
+ }
2397
+
2398
+ function hermesHookPluginInstalled() {
2399
+ return (
2400
+ existsSync(join(hermesSafetyPluginDir(), 'plugin.yaml')) && existsSync(join(hermesSafetyPluginDir(), '__init__.py'))
2401
+ );
2402
+ }
2403
+
2404
+ function removeHermesHooksConfigBlock() {
2405
+ const configPath = hermesConfigFile();
2406
+ if (!existsSync(configPath)) return;
2407
+ const lines = readFileSync(configPath, 'utf8').split(/\r?\n/);
2408
+ let inHooksBlock = false;
2409
+ const out = [];
2410
+ for (const line of lines) {
2411
+ const trimmed = line.trim();
2412
+ if (trimmed === 'hooks:' && !inHooksBlock) {
2413
+ inHooksBlock = true;
2414
+ continue;
2415
+ }
2416
+ if (inHooksBlock) {
2417
+ if (trimmed === '' || line.startsWith(' ') || line.startsWith('\t')) {
2418
+ continue;
2419
+ }
2420
+ inHooksBlock = false;
2421
+ }
2422
+ out.push(line);
2423
+ }
2424
+ const cleaned = out.join('\n').trimEnd();
2425
+ writeFileSync(configPath, cleaned ? `${cleaned}\n` : '');
2426
+ console.log(' Hooks config cleaned');
2427
+ }
2428
+
2429
+ function ensureHermesMcpSdk() {
2430
+ const pythonBin = process.platform === 'win32' ? 'python' : 'python3';
2431
+ try {
2432
+ const r = spawnSync(pythonBin, ['-c', 'import mcp; print("ok")'], { encoding: 'utf8', timeout: 5000 });
2433
+ if (r.status === 0 && r.stdout.trim() === 'ok') {
2434
+ console.log(' MCP Python SDK: \x1b[32mAlready installed\x1b[0m');
2435
+ return true;
2436
+ }
2437
+ } catch {}
2438
+ console.log(' Installing Hermes MCP Python SDK...');
2439
+ try {
2440
+ const pip = spawnSync(pythonBin, ['-m', 'pip', 'install', 'mcp', '--quiet'], { encoding: 'utf8', timeout: 60000 });
2441
+ if (pip.status === 0) {
2442
+ console.log(' MCP Python SDK: \x1b[32mInstalled\x1b[0m');
2443
+ return true;
2444
+ }
2445
+ console.log(` MCP Python SDK: \x1b[33mInstall failed\x1b[0m`);
2446
+ console.log(` \x1b[33m Run manually: ${pythonBin} -m pip install mcp\x1b[0m`);
2447
+ } catch (error) {
2448
+ console.log(` MCP Python SDK: \x1b[33m${error.message}\x1b[0m`);
2449
+ console.log(` \x1b[33m Run manually: ${pythonBin} -m pip install mcp\x1b[0m`);
2450
+ }
2451
+ return false;
2452
+ }
2453
+
2454
+ function hermesMcpSdkOk() {
2455
+ const pythonBin = process.platform === 'win32' ? 'python' : 'python3';
2456
+ try {
2457
+ const r = spawnSync(pythonBin, ['-c', 'import mcp; print("ok")'], { encoding: 'utf8', timeout: 5000 });
2458
+ return r.status === 0 && r.stdout.trim() === 'ok';
2459
+ } catch {
2460
+ return false;
2461
+ }
2462
+ }
2463
+
2038
2464
  async function installHermes() {
2039
2465
  const skillsSrc = join(PLUGIN_ROOT, 'skills');
2040
2466
  const srcDir = join(PLUGIN_ROOT, 'src');
2041
2467
  const safetyDir = join(PLUGIN_ROOT, 'safety');
2468
+ const hooksDir = join(PLUGIN_ROOT, 'hooks');
2042
2469
  const pluginDest = hermesPluginsDir();
2470
+ const skipMcp = process.argv.includes('--skip-mcp-server');
2043
2471
 
2044
2472
  copyDir(skillsSrc, hermesSkillsDir());
2045
2473
  console.log(` Skills -> ${hermesSkillsDir()}`);
2046
2474
 
2047
- copyDir(srcDir, join(pluginDest, 'src'));
2048
- console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
2475
+ if (!skipMcp) {
2476
+ copyDir(srcDir, join(pluginDest, 'src'));
2477
+ console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
2478
+ }
2049
2479
  copyDir(safetyDir, join(pluginDest, 'safety'));
2050
2480
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
2481
+ copyDir(hooksDir, join(pluginDest, 'hooks'));
2482
+ console.log(` Safety Hooks -> ${join(pluginDest, 'hooks')}`);
2051
2483
 
2052
- ensureHermesMcpConfig();
2053
- installRuntimeDeps(pluginDest);
2484
+ if (!skipMcp) ensureHermesMcpConfig();
2485
+ ensureHermesHooksConfig();
2486
+ ensureHermesHookAllowlist();
2487
+ ensureHermesHookPlugin();
2488
+ if (!skipMcp) installRuntimeDeps(pluginDest);
2489
+ if (!skipMcp) ensureHermesMcpSdk();
2054
2490
  }
2055
2491
 
2056
2492
  async function updateHermes() {
2057
2493
  const skillsSrc = join(PLUGIN_ROOT, 'skills');
2058
2494
  const srcDir = join(PLUGIN_ROOT, 'src');
2059
2495
  const safetyDir = join(PLUGIN_ROOT, 'safety');
2496
+ const hooksDir = join(PLUGIN_ROOT, 'hooks');
2060
2497
  const pluginDest = hermesPluginsDir();
2061
2498
 
2062
2499
  copyDir(skillsSrc, hermesSkillsDir());
@@ -2066,7 +2503,13 @@ async function updateHermes() {
2066
2503
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
2067
2504
  copyDir(safetyDir, join(pluginDest, 'safety'));
2068
2505
  console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
2506
+ copyDir(hooksDir, join(pluginDest, 'hooks'));
2507
+ console.log(` Safety Hooks updated -> ${join(pluginDest, 'hooks')}`);
2069
2508
  ensureHermesMcpConfig();
2509
+ ensureHermesHooksConfig();
2510
+ ensureHermesHookAllowlist();
2511
+ ensureHermesHookPlugin();
2512
+ ensureHermesMcpSdk();
2070
2513
  mkdirSync(pluginDest, { recursive: true });
2071
2514
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
2072
2515
  installRuntimeDeps(pluginDest);
@@ -2075,6 +2518,40 @@ async function updateHermes() {
2075
2518
  function uninstallHermes() {
2076
2519
  const skillsDir = hermesSkillsDir();
2077
2520
  let removed = 0;
2521
+
2522
+ // 1. Remove hook config from config.yaml (before deleting script files)
2523
+ removeHermesHooksConfigBlock();
2524
+ console.log(' Hooks config removed');
2525
+
2526
+ // 1b. Remove the Python safety hook plugin (v0.9.0+ enforcement path)
2527
+ removeHermesHookPlugin();
2528
+
2529
+ // 2. Clean shell-hooks-allowlist.json (remove approved hook references)
2530
+ const hermesHome = hermesHomeDir();
2531
+ const allowlistPath = join(hermesHome, 'shell-hooks-allowlist.json');
2532
+ if (existsSync(allowlistPath)) {
2533
+ try {
2534
+ const allowlist = JSON.parse(readFileSync(allowlistPath, 'utf8'));
2535
+ const before = (allowlist.approvals || []).length;
2536
+ allowlist.approvals = (allowlist.approvals || []).filter((a) => {
2537
+ const cmd = typeof a === 'string' ? a : a?.command;
2538
+ return !(typeof cmd === 'string' && cmd.includes('huaweicloud-safety.py'));
2539
+ });
2540
+ if (allowlist.approvals.length < before) {
2541
+ writeFileSync(allowlistPath, JSON.stringify(allowlist, null, 2));
2542
+ console.log(` Removed ${before - allowlist.approvals.length} hook approvals from allowlist`);
2543
+ }
2544
+ } catch {
2545
+ removeIfExists(allowlistPath);
2546
+ console.log(' Removed hook allowlist file');
2547
+ }
2548
+ }
2549
+
2550
+ // 3. Remove MCP config from config.yaml
2551
+ removeHermesMcpConfigBlock();
2552
+ console.log(' MCP config removed');
2553
+
2554
+ // 4. Remove skills (file deletion comes after config cleanup)
2078
2555
  if (existsSync(skillsDir)) {
2079
2556
  for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
2080
2557
  if (entry.name.startsWith('huawei')) {
@@ -2091,10 +2568,10 @@ function uninstallHermes() {
2091
2568
  } catch {}
2092
2569
  }
2093
2570
 
2571
+ // 5. Remove plugin directory (hook script files deleted last)
2094
2572
  if (removeIfExists(hermesPluginsDir())) {
2095
- console.log(' Removed MCP server and safety policy');
2573
+ console.log(' Removed MCP server, safety policy and hooks');
2096
2574
  }
2097
- removeHermesMcpConfigBlock();
2098
2575
  }
2099
2576
 
2100
2577
  function hermesStatus() {
@@ -2106,6 +2583,16 @@ function hermesStatus() {
2106
2583
  console.log(
2107
2584
  ` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
2108
2585
  );
2586
+ console.log(
2587
+ ` Safety Hooks: ${existsSync(join(pluginDir, 'hooks', 'huaweicloud-safety.py')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
2588
+ );
2589
+ console.log(
2590
+ ` Hook allowlist: ${hermesHookAllowlisted() ? '\x1b[32mApproved\x1b[0m' : '\x1b[31mNot allowlisted\x1b[0m'}`,
2591
+ );
2592
+ console.log(
2593
+ ` Hook plugin: ${hermesHookPluginInstalled() ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
2594
+ );
2595
+ console.log(` MCP Python SDK: ${hermesMcpSdkOk() ? '\x1b[32mReady\x1b[0m' : '\x1b[31mMissing\x1b[0m'}`);
2109
2596
  let skillCount = 0;
2110
2597
  if (existsSync(skillsDir)) {
2111
2598
  skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
@@ -2119,13 +2606,17 @@ function hermesStatus() {
2119
2606
  if (existsSync(configPath)) {
2120
2607
  try {
2121
2608
  const config = readFileSync(configPath, 'utf8');
2122
- const configured = config.includes('mcp_servers:') && config.includes('huaweicloud-devkit');
2123
- console.log(` MCP config: ${configured ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
2609
+ const mcpConfigured = config.includes('mcp_servers:') && config.includes('huaweicloud-devkit');
2610
+ const hooksConfigured = config.includes('hooks:') && config.includes('huaweicloud-safety.py');
2611
+ console.log(` MCP config: ${mcpConfigured ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
2612
+ console.log(` Hooks config: ${hooksConfigured ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
2124
2613
  } catch {
2125
2614
  console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
2615
+ console.log(` Hooks config: \x1b[31mInvalid\x1b[0m`);
2126
2616
  }
2127
2617
  } else {
2128
2618
  console.log(` MCP config: \x1b[31mNot found\x1b[0m`);
2619
+ console.log(` Hooks config: \x1b[31mNot found\x1b[0m`);
2129
2620
  }
2130
2621
  }
2131
2622
 
@@ -2165,6 +2656,7 @@ function autoDetectTarget() {
2165
2656
  ['opencode', () => existsSync(join(homedir(), '.config', 'opencode'))],
2166
2657
  ['codex-desktop', () => existsSync(join(homedir(), '.codex'))],
2167
2658
  ['codearts', () => existsSync(join(homedir(), '.codeartsdoer'))],
2659
+ ['codearts-work', () => existsSync(join(homedir(), '.codeartswork'))],
2168
2660
  ['workbuddy', () => existsSync(join(homedir(), '.workbuddy'))],
2169
2661
  [
2170
2662
  'dsh',
@@ -2217,6 +2709,10 @@ async function cmdInstall() {
2217
2709
  console.log('\n[CodeArts]');
2218
2710
  await installCodeArts();
2219
2711
  }
2712
+ if (target === 'codearts-work' || target === 'all') {
2713
+ console.log('\n[CodeArts Work]');
2714
+ await installCodeArtsWork();
2715
+ }
2220
2716
  if (target === 'workbuddy' || target === 'all') {
2221
2717
  console.log('\n[WorkBuddy]');
2222
2718
  await installWorkBuddy();
@@ -2268,23 +2764,25 @@ async function cmdInstall() {
2268
2764
  const appName =
2269
2765
  target === 'codearts'
2270
2766
  ? 'CodeArts'
2271
- : target === 'codex-desktop'
2272
- ? 'Codex Desktop'
2273
- : target === 'codex'
2274
- ? 'Codex'
2275
- : target === 'workbuddy'
2276
- ? 'WorkBuddy'
2277
- : target === 'dsh'
2278
- ? 'DSH'
2279
- : target === 'officeace'
2280
- ? 'OfficeAce'
2281
- : target === 'hermes'
2282
- ? 'Hermes Agent'
2283
- : target === 'openclaw'
2284
- ? 'OpenClaw'
2285
- : target === 'atomcode'
2286
- ? 'AtomCode'
2287
- : '当前 agent';
2767
+ : target === 'codearts-work'
2768
+ ? 'CodeArts Work'
2769
+ : target === 'codex-desktop'
2770
+ ? 'Codex Desktop'
2771
+ : target === 'codex'
2772
+ ? 'Codex'
2773
+ : target === 'workbuddy'
2774
+ ? 'WorkBuddy'
2775
+ : target === 'dsh'
2776
+ ? 'DSH'
2777
+ : target === 'officeace'
2778
+ ? 'OfficeAce'
2779
+ : target === 'hermes'
2780
+ ? 'Hermes Agent'
2781
+ : target === 'openclaw'
2782
+ ? 'OpenClaw'
2783
+ : target === 'atomcode'
2784
+ ? 'AtomCode'
2785
+ : '当前 agent';
2288
2786
  const pad = ' '.repeat(24 - appName.length);
2289
2787
  if (target === 'officeace') {
2290
2788
  console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════════╗`);
@@ -2338,17 +2836,19 @@ async function cmdInstall() {
2338
2836
  ? dshPluginsDir()
2339
2837
  : target === 'codearts'
2340
2838
  ? codeartsPluginsDir()
2341
- : target === 'workbuddy'
2342
- ? workbuddyPluginsDir()
2343
- : target === 'officeace'
2344
- ? officeacePluginsDir()
2345
- : target === 'openclaw'
2346
- ? codexDesktopPluginsDir()
2347
- : target === 'atomcode'
2348
- ? atomcodePluginsDir()
2349
- : target === 'codex-desktop'
2350
- ? codexDesktopPluginsDir()
2351
- : opencodePluginsDir();
2839
+ : target === 'codearts-work'
2840
+ ? codeartsWorkPluginsDir()
2841
+ : target === 'workbuddy'
2842
+ ? workbuddyPluginsDir()
2843
+ : target === 'officeace'
2844
+ ? officeacePluginsDir()
2845
+ : target === 'openclaw'
2846
+ ? codexDesktopPluginsDir()
2847
+ : target === 'atomcode'
2848
+ ? atomcodePluginsDir()
2849
+ : target === 'codex-desktop'
2850
+ ? codexDesktopPluginsDir()
2851
+ : opencodePluginsDir();
2352
2852
  mkdirSync(markerDir, { recursive: true });
2353
2853
  writeFileSync(join(markerDir, '.installed'), new Date().toISOString());
2354
2854
  if (target === 'opencode' || target === 'all') {
@@ -2357,6 +2857,9 @@ async function cmdInstall() {
2357
2857
  if (target === 'codearts' || target === 'all') {
2358
2858
  console.log('Or describe your Huawei Cloud task in CodeArts');
2359
2859
  }
2860
+ if (target === 'codearts-work' || target === 'all') {
2861
+ console.log('Or describe your Huawei Cloud task in CodeArts Work');
2862
+ }
2360
2863
  if (target === 'codex' || target === 'all') {
2361
2864
  console.log('Or mention @huaweicloud-core in Codex');
2362
2865
  }
@@ -2390,6 +2893,10 @@ async function cmdUninstall() {
2390
2893
  console.log('\n[CodeArts]');
2391
2894
  uninstallCodeArts();
2392
2895
  }
2896
+ if (target === 'codearts-work' || target === 'all') {
2897
+ console.log('\n[CodeArts Work]');
2898
+ uninstallCodeArtsWork();
2899
+ }
2393
2900
  if (target === 'workbuddy' || target === 'all') {
2394
2901
  console.log('\n[WorkBuddy]');
2395
2902
  uninstallWorkBuddy();
@@ -2454,6 +2961,10 @@ async function cmdStatus() {
2454
2961
  console.log('\n[CodeArts]');
2455
2962
  codeartsStatus();
2456
2963
  }
2964
+ if (target === 'codearts-work' || target === 'all') {
2965
+ console.log('\n[CodeArts Work]');
2966
+ codeartsWorkStatus();
2967
+ }
2457
2968
  if (target === 'workbuddy' || target === 'all') {
2458
2969
  console.log('\n[WorkBuddy]');
2459
2970
  workbuddyStatus();
@@ -2532,6 +3043,7 @@ async function cmdDoctor() {
2532
3043
  const opencodePluginDir = opencodePluginsDir();
2533
3044
  const codexPluginDir = codexDesktopPluginsDir();
2534
3045
  const codeartsPluginDir = codeartsPluginsDir();
3046
+ const codeartsWorkPluginDir = codeartsWorkPluginsDir();
2535
3047
  const workbuddyPluginDir = workbuddyPluginsDir();
2536
3048
  const dshPluginDir = dshPluginsDir();
2537
3049
  const officeacePluginDir = officeacePluginsDir();
@@ -2541,6 +3053,7 @@ async function cmdDoctor() {
2541
3053
  existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs')) ||
2542
3054
  existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs')) ||
2543
3055
  existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs')) ||
3056
+ existsSync(join(codeartsWorkPluginDir, 'src', 'mcp-server.mjs')) ||
2544
3057
  existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs')) ||
2545
3058
  existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs')) ||
2546
3059
  existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs')) ||
@@ -2552,17 +3065,19 @@ async function cmdDoctor() {
2552
3065
  ? 'Codex Desktop'
2553
3066
  : existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs'))
2554
3067
  ? 'CodeArts'
2555
- : existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs'))
2556
- ? 'WorkBuddy'
2557
- : existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs'))
2558
- ? 'DSH'
2559
- : existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs'))
2560
- ? 'OfficeAce'
2561
- : existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'))
2562
- ? 'Hermes Agent'
2563
- : existsSync(join(atomcodePluginDir, 'src', 'mcp-server.mjs'))
2564
- ? 'AtomCode'
2565
- : '';
3068
+ : existsSync(join(codeartsWorkPluginDir, 'src', 'mcp-server.mjs'))
3069
+ ? 'CodeArts Work'
3070
+ : existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs'))
3071
+ ? 'WorkBuddy'
3072
+ : existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs'))
3073
+ ? 'DSH'
3074
+ : existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs'))
3075
+ ? 'OfficeAce'
3076
+ : existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'))
3077
+ ? 'Hermes Agent'
3078
+ : existsSync(join(atomcodePluginDir, 'src', 'mcp-server.mjs'))
3079
+ ? 'AtomCode'
3080
+ : '';
2566
3081
  check('MCP server installed', mcpOk, 'Run: npx huaweicloud-devkit install');
2567
3082
 
2568
3083
  if (mcpOk) {
@@ -2573,6 +3088,7 @@ async function cmdDoctor() {
2573
3088
  opencodePluginDir,
2574
3089
  codexPluginDir,
2575
3090
  codeartsPluginDir,
3091
+ codeartsWorkPluginDir,
2576
3092
  workbuddyPluginDir,
2577
3093
  dshPluginDir,
2578
3094
  officeacePluginDir,
@@ -2585,6 +3101,7 @@ async function cmdDoctor() {
2585
3101
  existsSync(join(opencodePluginDir, 'safety', 'policy.json')) ||
2586
3102
  existsSync(join(codexPluginDir, 'safety', 'policy.json')) ||
2587
3103
  existsSync(join(codeartsPluginDir, 'safety', 'policy.json')) ||
3104
+ existsSync(join(codeartsWorkPluginDir, 'safety', 'policy.json')) ||
2588
3105
  existsSync(join(workbuddyPluginDir, 'safety', 'policy.json')) ||
2589
3106
  existsSync(join(dshPluginDir, 'safety', 'policy.json')) ||
2590
3107
  existsSync(join(officeacePluginDir, 'safety', 'policy.json')) ||
@@ -2625,6 +3142,16 @@ async function cmdDoctor() {
2625
3142
  }
2626
3143
  } catch {}
2627
3144
  }
3145
+ const codeartsWorkCfg = codeartsWorkMcpSettingsFile();
3146
+ if (!mcpConfigured && existsSync(codeartsWorkCfg)) {
3147
+ try {
3148
+ const cfg = JSON.parse(readFileSync(codeartsWorkCfg, 'utf8'));
3149
+ if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
3150
+ mcpConfigured = true;
3151
+ mcpCfgTarget = 'CodeArts Work';
3152
+ }
3153
+ } catch {}
3154
+ }
2628
3155
  const workbuddyCfg = workbuddyMcpConfigFile();
2629
3156
  if (!mcpConfigured && existsSync(workbuddyCfg)) {
2630
3157
  try {
@@ -2684,6 +3211,14 @@ async function cmdDoctor() {
2684
3211
  mcpCfgTarget ? `Found in ${mcpCfgTarget} config` : 'Run: npx huaweicloud-devkit install',
2685
3212
  );
2686
3213
 
3214
+ // Hermes MCP Python SDK (only checked when Hermes config is found)
3215
+ if (
3216
+ mcpCfgTarget === 'Hermes Agent' ||
3217
+ (existsSync(hermesConfigFile()) && readFileSync(hermesConfigFile(), 'utf8').includes('mcp_servers'))
3218
+ ) {
3219
+ check('Hermes MCP Python SDK', hermesMcpSdkOk(), 'Run: pip3 install mcp');
3220
+ }
3221
+
2687
3222
  // hcloud CLI
2688
3223
  const hcloudBin = findHcloudBin() || process.env.HCLOUD_BIN || 'hcloud';
2689
3224
  const hcloudCheck = spawnSync(`"${hcloudBin}" version`, [], {
@@ -2725,6 +3260,7 @@ async function cmdDoctor() {
2725
3260
  opencodeSkillsDir(),
2726
3261
  codexDesktopSkillsDir(),
2727
3262
  codeartsSkillsDir(),
3263
+ codeartsWorkSkillsDir(),
2728
3264
  workbuddySkillsDir(),
2729
3265
  dshSkillsDir(),
2730
3266
  officeaceSkillsDir(),
@@ -2784,6 +3320,7 @@ async function cmdDoctor() {
2784
3320
  { path: join(codexDesktopPluginsDir(), '.installed'), name: 'Codex Desktop' },
2785
3321
  { path: join(workbuddyPluginsDir(), '.installed'), name: 'WorkBuddy' },
2786
3322
  { path: join(codeartsPluginsDir(), '.installed'), name: 'CodeArts' },
3323
+ { path: join(codeartsWorkPluginsDir(), '.installed'), name: 'CodeArts Work' },
2787
3324
  { path: join(dshPluginsDir(), '.installed'), name: 'DSH' },
2788
3325
  { path: join(officeacePluginsDir(), '.installed'), name: 'OfficeAce' },
2789
3326
  { path: join(atomcodePluginsDir(), '.installed'), name: 'AtomCode' },
@@ -2856,6 +3393,18 @@ async function cmdUpdate() {
2856
3393
  return;
2857
3394
  }
2858
3395
 
3396
+ if (target === 'codearts-work') {
3397
+ if (!existsSync(join(codeartsWorkPluginsDir(), 'src', 'mcp-server.mjs'))) {
3398
+ console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
3399
+ return;
3400
+ }
3401
+ console.log('[CodeArts Work]');
3402
+ await updateCodeArtsWork();
3403
+ console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
3404
+ console.log(`\x1b[33mMCP 工具在重启 CodeArts Work 会话后才生效。\x1b[0m`);
3405
+ return;
3406
+ }
3407
+
2859
3408
  if (target === 'workbuddy') {
2860
3409
  if (!existsSync(join(workbuddyPluginsDir(), 'src', 'mcp-server.mjs'))) {
2861
3410
  console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
@@ -2945,6 +3494,11 @@ async function cmdUpdate() {
2945
3494
  await updateCodeArts();
2946
3495
  updatedAny = true;
2947
3496
  }
3497
+ if (existsSync(join(codeartsWorkPluginsDir(), 'src', 'mcp-server.mjs'))) {
3498
+ console.log('\n[CodeArts Work]');
3499
+ await updateCodeArtsWork();
3500
+ updatedAny = true;
3501
+ }
2948
3502
  if (existsSync(join(workbuddyPluginsDir(), 'src', 'mcp-server.mjs'))) {
2949
3503
  console.log('\n[WorkBuddy]');
2950
3504
  await updateWorkBuddy();
@@ -3470,7 +4024,7 @@ async function main() {
3470
4024
  default:
3471
4025
  console.log(BANNER);
3472
4026
  console.log(
3473
- 'Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|dsh|officeace|hermes|openclaw|atomcode|all>]\n',
4027
+ 'Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|codearts-work|workbuddy|dsh|officeace|hermes|openclaw|atomcode|all>]\n',
3474
4028
  );
3475
4029
  console.log('Commands:');
3476
4030
  console.log(' install Install skills, MCP server, safety policy');
@@ -3485,12 +4039,13 @@ async function main() {
3485
4039
  console.log(' help Show this help');
3486
4040
  console.log('\nOptions:');
3487
4041
  console.log(
3488
- ' --target Target agent: opencode (default), codex, codearts, workbuddy, dsh, officeace, hermes, openclaw, atomcode, all',
4042
+ ' --target Target agent: opencode (default), codex, codearts, codearts-work, workbuddy, dsh, officeace, hermes, openclaw, atomcode, all',
3489
4043
  );
3490
4044
  console.log('\nExamples:');
3491
4045
  console.log(' npx huaweicloud-devkit install');
3492
4046
  console.log(' npx huaweicloud-devkit install --target codex');
3493
4047
  console.log(' npx huaweicloud-devkit install --target codearts');
4048
+ console.log(' npx huaweicloud-devkit install --target codearts-work');
3494
4049
  console.log(' npx huaweicloud-devkit install --target workbuddy');
3495
4050
  console.log(' npx huaweicloud-devkit install --target dsh');
3496
4051
  console.log(' npx huaweicloud-devkit install --target officeace');