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.
- package/README.md +64 -1
- package/README.zh-CN.md +63 -1
- package/package.json +3 -1
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +46 -21
- package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
- package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +136 -24
- package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +15 -14
- package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +1 -1
- package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +5 -1
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/hc-activity.md +27 -0
- package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +11 -11
- package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +6 -5
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +596 -93
- package/plugins/huaweicloud-core/skills/huawei-sandbox/references/framework-commands.md +0 -1
- package/plugins/huaweicloud-core/skills/huawei-sandbox/references/nginx-templates.md +8 -4
- package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +19 -12
- package/plugins/huaweicloud-core/skills/huawei-voucher/SKILL.md +52 -0
- package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +26 -26
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +12 -6
- package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +7 -4
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +7 -7
- package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +2 -0
- package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +8 -0
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +30 -1
- package/plugins/huaweicloud-core/src/auth/service.mjs +33 -0
- package/plugins/huaweicloud-core/src/detect-framework.mjs +25 -3
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +67 -5
- package/plugins/huaweicloud-core/src/mcp-server.mjs +23 -0
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +11 -3
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +436 -21
- package/plugins/huaweicloud-core/src/search-market.mjs +1 -0
- package/plugins/huaweicloud-core/src/setup-cli.mjs +605 -50
- package/plugins/huaweicloud-core/src/tools.mjs +193 -18
- 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
|
-
|
|
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
|
-
|
|
2048
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
2123
|
-
|
|
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 === '
|
|
2272
|
-
? '
|
|
2273
|
-
: target === 'codex'
|
|
2274
|
-
? 'Codex'
|
|
2275
|
-
: target === '
|
|
2276
|
-
? '
|
|
2277
|
-
: target === '
|
|
2278
|
-
? '
|
|
2279
|
-
: target === '
|
|
2280
|
-
? '
|
|
2281
|
-
: target === '
|
|
2282
|
-
? '
|
|
2283
|
-
: target === '
|
|
2284
|
-
? '
|
|
2285
|
-
: target === '
|
|
2286
|
-
? '
|
|
2287
|
-
:
|
|
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 === '
|
|
2342
|
-
?
|
|
2343
|
-
: target === '
|
|
2344
|
-
?
|
|
2345
|
-
: target === '
|
|
2346
|
-
?
|
|
2347
|
-
: target === '
|
|
2348
|
-
?
|
|
2349
|
-
: target === '
|
|
2350
|
-
?
|
|
2351
|
-
:
|
|
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(
|
|
2556
|
-
? '
|
|
2557
|
-
: existsSync(join(
|
|
2558
|
-
? '
|
|
2559
|
-
: existsSync(join(
|
|
2560
|
-
? '
|
|
2561
|
-
: existsSync(join(
|
|
2562
|
-
? '
|
|
2563
|
-
: existsSync(join(
|
|
2564
|
-
? '
|
|
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');
|