huaweicloud-devkit 1.0.2-next.8 → 1.0.2
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/.agents/plugins/marketplace.json +1 -1
- package/LICENSE +201 -201
- package/README.md +71 -52
- package/README.zh-CN.md +71 -50
- package/bin/setup.cjs +0 -0
- package/package.json +9 -5
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +2 -2
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +2 -2
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +2 -2
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +30 -0
- package/plugins/huaweicloud-core/.mcp.json +1 -2
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +2 -3
- package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +159 -136
- package/plugins/huaweicloud-core/openclaw.plugin.json +20 -0
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +1 -1
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +67 -20
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +11 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +16 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +27 -24
- package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +12 -0
- package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +79 -11
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +65 -0
- package/plugins/huaweicloud-core/src/mcp-server.mjs +31 -4
- package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +6 -1
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +7 -2
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +14 -4
- package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +432 -10
- package/plugins/huaweicloud-core/src/setup-cli.mjs +581 -87
- package/plugins/huaweicloud-core/src/tools.mjs +225 -33
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +18 -4
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +9 -0
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +9 -0
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +9 -0
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +9 -0
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +267 -0
- package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
- package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +9 -5
|
@@ -13,6 +13,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
13
13
|
import { homedir, platform } from 'node:os';
|
|
14
14
|
import { createInterface } from 'node:readline';
|
|
15
15
|
import { spawnSync } from 'node:child_process';
|
|
16
|
+
import { randomUUID } from 'node:crypto';
|
|
17
|
+
import Database from 'better-sqlite3';
|
|
16
18
|
import { getAuthStatus, syncAuth } from './auth/service.mjs';
|
|
17
19
|
import { SUPPORTED_AGENT_TARGETS } from './auth/agent-registration.mjs';
|
|
18
20
|
import {
|
|
@@ -126,93 +128,177 @@ function dshPluginsDir() {
|
|
|
126
128
|
return join(dshRoot(), 'huaweicloud-plugins');
|
|
127
129
|
}
|
|
128
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
|
+
|
|
129
147
|
function officeaceCapabilitiesDir() {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
if (
|
|
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
|
+
}
|
|
134
155
|
if (platform() === 'win32') {
|
|
135
|
-
|
|
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) {
|
|
136
159
|
if (!base) continue;
|
|
137
160
|
const dir = join(base, 'OfficeAce', '.office-claw');
|
|
138
161
|
if (existsSync(join(dir, 'capabilities.json'))) return dir;
|
|
139
162
|
}
|
|
140
163
|
}
|
|
141
|
-
return
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function officeaceCapabilitiesDirSafe() {
|
|
168
|
+
return officeaceCapabilitiesDir() || join(homedir(), '.office-claw');
|
|
142
169
|
}
|
|
143
170
|
function officeaceCapabilitiesFile() {
|
|
144
|
-
return join(
|
|
171
|
+
return join(officeaceCapabilitiesDirSafe(), 'capabilities.json');
|
|
145
172
|
}
|
|
146
173
|
function officeaceSkillsDir() {
|
|
147
|
-
return join(
|
|
174
|
+
return join(officeaceCapabilitiesDirSafe(), 'skills');
|
|
148
175
|
}
|
|
149
176
|
function officeacePluginsDir() {
|
|
150
|
-
return join(
|
|
177
|
+
return join(officeaceCapabilitiesDirSafe(), 'huaweicloud-plugins');
|
|
151
178
|
}
|
|
152
179
|
|
|
153
|
-
function
|
|
154
|
-
const
|
|
155
|
-
|
|
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;
|
|
156
191
|
try {
|
|
157
|
-
|
|
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;
|
|
158
196
|
} catch {
|
|
159
|
-
return
|
|
197
|
+
return null;
|
|
160
198
|
}
|
|
161
199
|
}
|
|
162
200
|
|
|
163
|
-
function
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
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
|
+
}
|
|
167
208
|
|
|
168
|
-
function ensureOfficeaceCapabilities() {
|
|
169
|
-
const capFile = officeaceCapabilitiesFile();
|
|
170
209
|
const mcpPath = join(officeacePluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
171
|
-
const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
|
|
210
|
+
const env = [{ key: 'HUAWEICLOUD_AGENT_TOOLKIT_MODE', value: 'local', sensitive: false }];
|
|
172
211
|
const hcloudBin = findHcloudBin();
|
|
173
|
-
if (hcloudBin) env.HCLOUD_BIN
|
|
212
|
+
if (hcloudBin) env.push({ key: 'HCLOUD_BIN', value: hcloudBin.replace(/\\/g, '/'), sensitive: false });
|
|
174
213
|
|
|
175
|
-
const
|
|
176
|
-
let
|
|
214
|
+
const now = Date.now();
|
|
215
|
+
let db;
|
|
216
|
+
try {
|
|
217
|
+
db = openOfficeaceDb();
|
|
177
218
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
Array.isArray(existing.mcpServer?.args) &&
|
|
196
|
-
existing.mcpServer.args[0] === mcpPath
|
|
197
|
-
) {
|
|
198
|
-
console.log(` MCP config unchanged: ${capFile}`);
|
|
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}`);
|
|
199
236
|
} else {
|
|
200
|
-
|
|
201
|
-
|
|
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}`);
|
|
202
248
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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;
|
|
206
259
|
}
|
|
260
|
+
}
|
|
207
261
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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: [] };
|
|
211
293
|
}
|
|
212
|
-
return changed;
|
|
213
294
|
}
|
|
214
295
|
|
|
215
|
-
function
|
|
296
|
+
function writeCapabilitiesJson(config) {
|
|
297
|
+
mkdirSync(officeaceCapabilitiesDirSafe(), { recursive: true });
|
|
298
|
+
writeFileSync(officeaceCapabilitiesFile(), JSON.stringify(config, null, 2));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function removeOfficeaceSkillCapabilities() {
|
|
216
302
|
const capFile = officeaceCapabilitiesFile();
|
|
217
303
|
if (!existsSync(capFile)) return;
|
|
218
304
|
let config;
|
|
@@ -226,14 +312,13 @@ function removeOfficeaceCapabilities() {
|
|
|
226
312
|
config.capabilities = config.capabilities.filter(
|
|
227
313
|
(c) =>
|
|
228
314
|
!(
|
|
229
|
-
(c.id === 'huaweicloud-devkit' && c.type === 'mcp') ||
|
|
230
315
|
(c.id === 'huaweicloud-core' && c.type === 'skill') ||
|
|
231
316
|
(c.source === 'custom' && c.id && c.id.startsWith('huawei'))
|
|
232
317
|
),
|
|
233
318
|
);
|
|
234
319
|
if (config.capabilities.length !== origLen) {
|
|
235
320
|
writeCapabilitiesJson(config);
|
|
236
|
-
console.log(`
|
|
321
|
+
console.log(` Skill capabilities cleaned: ${capFile}`);
|
|
237
322
|
}
|
|
238
323
|
}
|
|
239
324
|
|
|
@@ -317,14 +402,14 @@ function printSandboxWarning(reason) {
|
|
|
317
402
|
console.log(`\x1b[31m 请任选其一继续:\x1b[0m`);
|
|
318
403
|
console.log(`\x1b[31m A. 在码道外的终端安装并使用 KooCLI (推荐):`);
|
|
319
404
|
console.log(`\x1b[31m https://support.huaweicloud.com/qs-hcli/hcli_02_003.html`);
|
|
320
|
-
console.log(`\x1b[31m B. 在码道设置中关闭沙箱模式后重试 (设置 →
|
|
405
|
+
console.log(`\x1b[31m B. 在码道设置中关闭沙箱模式后重试 (设置 → 对话流 → 智能体 终端命令运行模式 → 自动运行)`);
|
|
321
406
|
console.log(`\x1b[31m 关闭沙箱后重新运行: npx huaweicloud-devkit install-hcloud\x1b[0m`);
|
|
322
407
|
}
|
|
323
408
|
|
|
324
409
|
function checkNode() {
|
|
325
410
|
const v = process.versions.node.split('.').map(Number);
|
|
326
|
-
if (v[0] <
|
|
327
|
-
console.error(`\x1b[31mNode.js >=
|
|
411
|
+
if (v[0] < 22) {
|
|
412
|
+
console.error(`\x1b[31mNode.js >= 22 required (current: ${process.version})\x1b[0m`);
|
|
328
413
|
process.exit(1);
|
|
329
414
|
}
|
|
330
415
|
console.log(` Node.js ${process.version} \x1b[32mOK\x1b[0m`);
|
|
@@ -344,6 +429,35 @@ function copyDir(src, dest) {
|
|
|
344
429
|
}
|
|
345
430
|
}
|
|
346
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
|
+
|
|
347
461
|
function removeIfExists(p) {
|
|
348
462
|
if (existsSync(p)) {
|
|
349
463
|
try {
|
|
@@ -376,7 +490,8 @@ function updateOpenCodeConfig(pluginDir) {
|
|
|
376
490
|
existing.type === 'local' &&
|
|
377
491
|
Array.isArray(existing.command) &&
|
|
378
492
|
existing.command[0] === 'node' &&
|
|
379
|
-
existing.command[1] === mcpPath
|
|
493
|
+
existing.command[1] === mcpPath &&
|
|
494
|
+
existing.timeout === 300000
|
|
380
495
|
) {
|
|
381
496
|
console.log(` OpenCode MCP config unchanged: ${configPath}`);
|
|
382
497
|
return;
|
|
@@ -387,6 +502,7 @@ function updateOpenCodeConfig(pluginDir) {
|
|
|
387
502
|
type: 'local',
|
|
388
503
|
command: ['node', mcpPath],
|
|
389
504
|
enabled: true,
|
|
505
|
+
timeout: 300000,
|
|
390
506
|
};
|
|
391
507
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
392
508
|
console.log(` OpenCode MCP config updated: ${configPath}`);
|
|
@@ -526,6 +642,7 @@ async function installOpenCode() {
|
|
|
526
642
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
527
643
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
528
644
|
updateOpenCodeConfig(pluginDest);
|
|
645
|
+
installRuntimeDeps(pluginDest);
|
|
529
646
|
}
|
|
530
647
|
|
|
531
648
|
function uninstallOpenCode() {
|
|
@@ -598,6 +715,7 @@ async function updateOpenCode() {
|
|
|
598
715
|
updateOpenCodeConfig(pluginDest);
|
|
599
716
|
mkdirSync(pluginDest, { recursive: true });
|
|
600
717
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
718
|
+
installRuntimeDeps(pluginDest);
|
|
601
719
|
}
|
|
602
720
|
|
|
603
721
|
function codexMcpServerPath() {
|
|
@@ -702,6 +820,7 @@ async function installCodexDesktop() {
|
|
|
702
820
|
}
|
|
703
821
|
|
|
704
822
|
ensureCodexConfigSection(mcpServerAbsPath);
|
|
823
|
+
installRuntimeDeps(codexDesktopPluginsDir());
|
|
705
824
|
}
|
|
706
825
|
|
|
707
826
|
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
@@ -749,6 +868,7 @@ async function updateCodexDesktop() {
|
|
|
749
868
|
|
|
750
869
|
ensureCodexConfigSection(mcpServerAbsPath);
|
|
751
870
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
871
|
+
installRuntimeDeps(pluginDest);
|
|
752
872
|
}
|
|
753
873
|
|
|
754
874
|
function uninstallCodexDesktop() {
|
|
@@ -798,7 +918,13 @@ function registerCodeartsMcp(configPath) {
|
|
|
798
918
|
return;
|
|
799
919
|
}
|
|
800
920
|
const existing = config.mcpServers?.['huaweicloud-devkit'];
|
|
801
|
-
if (
|
|
921
|
+
if (
|
|
922
|
+
existing &&
|
|
923
|
+
existing.command === 'node' &&
|
|
924
|
+
Array.isArray(existing.args) &&
|
|
925
|
+
existing.args[0] === mcpPath &&
|
|
926
|
+
existing.timeout === 300000
|
|
927
|
+
) {
|
|
802
928
|
console.log(` MCP config unchanged: ${configPath}`);
|
|
803
929
|
return;
|
|
804
930
|
}
|
|
@@ -809,6 +935,7 @@ function registerCodeartsMcp(configPath) {
|
|
|
809
935
|
args: [mcpPath],
|
|
810
936
|
env,
|
|
811
937
|
enabled: true,
|
|
938
|
+
timeout: 300000,
|
|
812
939
|
};
|
|
813
940
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
814
941
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
@@ -833,6 +960,7 @@ async function installCodeArts() {
|
|
|
833
960
|
|
|
834
961
|
registerCodeartsMcp(codeartsMcpSettingsFile());
|
|
835
962
|
registerCodeartsMcp(codeartsProjectMcpSettingsFile());
|
|
963
|
+
installRuntimeDeps(pluginDest);
|
|
836
964
|
}
|
|
837
965
|
|
|
838
966
|
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
@@ -855,6 +983,7 @@ async function updateCodeArts() {
|
|
|
855
983
|
registerCodeartsMcp(codeartsProjectMcpSettingsFile());
|
|
856
984
|
mkdirSync(pluginDest, { recursive: true });
|
|
857
985
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
986
|
+
installRuntimeDeps(pluginDest);
|
|
858
987
|
}
|
|
859
988
|
|
|
860
989
|
function uninstallCodeArts() {
|
|
@@ -935,7 +1064,13 @@ function ensureWorkbuddyMcpConfig() {
|
|
|
935
1064
|
return false;
|
|
936
1065
|
}
|
|
937
1066
|
const existing = config.mcpServers?.['huaweicloud-devkit'];
|
|
938
|
-
if (
|
|
1067
|
+
if (
|
|
1068
|
+
existing &&
|
|
1069
|
+
existing.command === 'node' &&
|
|
1070
|
+
Array.isArray(existing.args) &&
|
|
1071
|
+
existing.args[0] === mcpPath &&
|
|
1072
|
+
existing.timeout === 300000
|
|
1073
|
+
) {
|
|
939
1074
|
console.log(` MCP config unchanged: ${configPath}`);
|
|
940
1075
|
return false;
|
|
941
1076
|
}
|
|
@@ -945,6 +1080,7 @@ function ensureWorkbuddyMcpConfig() {
|
|
|
945
1080
|
command: 'node',
|
|
946
1081
|
args: [mcpPath],
|
|
947
1082
|
env,
|
|
1083
|
+
timeout: 300000,
|
|
948
1084
|
};
|
|
949
1085
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
950
1086
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
@@ -967,6 +1103,7 @@ async function installWorkBuddy() {
|
|
|
967
1103
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
968
1104
|
|
|
969
1105
|
ensureWorkbuddyMcpConfig();
|
|
1106
|
+
installRuntimeDeps(pluginDest);
|
|
970
1107
|
}
|
|
971
1108
|
|
|
972
1109
|
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
@@ -986,6 +1123,7 @@ async function updateWorkBuddy() {
|
|
|
986
1123
|
ensureWorkbuddyMcpConfig();
|
|
987
1124
|
mkdirSync(pluginDest, { recursive: true });
|
|
988
1125
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
1126
|
+
installRuntimeDeps(pluginDest);
|
|
989
1127
|
}
|
|
990
1128
|
|
|
991
1129
|
function uninstallWorkBuddy() {
|
|
@@ -1250,6 +1388,7 @@ async function installDsh() {
|
|
|
1250
1388
|
tryInstallDshMcpClient();
|
|
1251
1389
|
mkdirSync(pluginDest, { recursive: true });
|
|
1252
1390
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
1391
|
+
installRuntimeDeps(pluginDest);
|
|
1253
1392
|
}
|
|
1254
1393
|
|
|
1255
1394
|
async function updateDsh() {
|
|
@@ -1269,6 +1408,7 @@ async function updateDsh() {
|
|
|
1269
1408
|
tryInstallDshMcpClient();
|
|
1270
1409
|
mkdirSync(pluginDest, { recursive: true });
|
|
1271
1410
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
1411
|
+
installRuntimeDeps(pluginDest);
|
|
1272
1412
|
}
|
|
1273
1413
|
|
|
1274
1414
|
function uninstallDsh() {
|
|
@@ -1319,7 +1459,39 @@ function dshStatus() {
|
|
|
1319
1459
|
);
|
|
1320
1460
|
}
|
|
1321
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
|
+
|
|
1322
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
|
+
}
|
|
1323
1495
|
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
1324
1496
|
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
1325
1497
|
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
@@ -1333,7 +1505,8 @@ async function installOfficeAce() {
|
|
|
1333
1505
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1334
1506
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
1335
1507
|
|
|
1336
|
-
|
|
1508
|
+
installRuntimeDeps(pluginDest);
|
|
1509
|
+
ensureOfficeaceMcpInSqlite();
|
|
1337
1510
|
registerOfficeaceSkillEntries();
|
|
1338
1511
|
}
|
|
1339
1512
|
|
|
@@ -1350,7 +1523,8 @@ async function updateOfficeAce() {
|
|
|
1350
1523
|
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
1351
1524
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1352
1525
|
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
1353
|
-
|
|
1526
|
+
installRuntimeDeps(pluginDest);
|
|
1527
|
+
ensureOfficeaceMcpInSqlite();
|
|
1354
1528
|
registerOfficeaceSkillEntries();
|
|
1355
1529
|
mkdirSync(pluginDest, { recursive: true });
|
|
1356
1530
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
@@ -1378,7 +1552,8 @@ function uninstallOfficeAce() {
|
|
|
1378
1552
|
if (removeIfExists(officeacePluginsDir())) {
|
|
1379
1553
|
console.log(' Removed MCP server and safety policy');
|
|
1380
1554
|
}
|
|
1381
|
-
|
|
1555
|
+
removeOfficeaceSkillCapabilities();
|
|
1556
|
+
removeOfficeaceMcpFromSqlite();
|
|
1382
1557
|
}
|
|
1383
1558
|
|
|
1384
1559
|
function officeaceStatus() {
|
|
@@ -1399,15 +1574,217 @@ function officeaceStatus() {
|
|
|
1399
1574
|
console.log(
|
|
1400
1575
|
` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1401
1576
|
);
|
|
1402
|
-
const
|
|
1403
|
-
if (existsSync(
|
|
1577
|
+
const dbPath = officeaceSqlitePath();
|
|
1578
|
+
if (existsSync(dbPath)) {
|
|
1404
1579
|
try {
|
|
1405
|
-
const
|
|
1406
|
-
const
|
|
1407
|
-
|
|
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
|
+
}
|
|
1408
1589
|
} catch {
|
|
1409
1590
|
console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
|
|
1410
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`);
|
|
1411
1788
|
}
|
|
1412
1789
|
}
|
|
1413
1790
|
|
|
@@ -1456,6 +1833,8 @@ function autoDetectTarget() {
|
|
|
1456
1833
|
},
|
|
1457
1834
|
],
|
|
1458
1835
|
['officeace', () => existsSync(officeaceCapabilitiesDir())],
|
|
1836
|
+
['hermes', () => existsSync(hermesHomeDir())],
|
|
1837
|
+
['openclaw', () => existsSync(join(homedir(), '.openclaw'))],
|
|
1459
1838
|
];
|
|
1460
1839
|
const detected = checks.filter(([, check]) => check()).map(([name]) => name);
|
|
1461
1840
|
if (detected.length === 0) {
|
|
@@ -1508,6 +1887,14 @@ async function cmdInstall() {
|
|
|
1508
1887
|
console.log('\n[OfficeAce]');
|
|
1509
1888
|
await installOfficeAce();
|
|
1510
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
|
+
}
|
|
1511
1898
|
if (target === 'codex' || target === 'all') {
|
|
1512
1899
|
console.log('\n[Codex]');
|
|
1513
1900
|
if (!hasCodexCLI()) {
|
|
@@ -1545,13 +1932,24 @@ async function cmdInstall() {
|
|
|
1545
1932
|
? 'DSH'
|
|
1546
1933
|
: target === 'officeace'
|
|
1547
1934
|
? 'OfficeAce'
|
|
1548
|
-
: '
|
|
1935
|
+
: target === 'hermes'
|
|
1936
|
+
? 'Hermes Agent'
|
|
1937
|
+
: target === 'openclaw'
|
|
1938
|
+
? 'OpenClaw'
|
|
1939
|
+
: '当前 agent';
|
|
1549
1940
|
const pad = ' '.repeat(24 - appName.length);
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
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
|
+
}
|
|
1555
1953
|
|
|
1556
1954
|
const hcloudOk = checkHcloud();
|
|
1557
1955
|
if (!hcloudOk) {
|
|
@@ -1564,7 +1962,11 @@ async function cmdInstall() {
|
|
|
1564
1962
|
console.log(`\n\x1b[1m下一步:\x1b[0m`);
|
|
1565
1963
|
console.log(` 1. 配置统一凭据:npx huaweicloud-devkit auth init`);
|
|
1566
1964
|
console.log(` 2. 配置代理(企业内网):npx huaweicloud-devkit proxy init`);
|
|
1567
|
-
|
|
1965
|
+
if (target === 'officeace') {
|
|
1966
|
+
console.log(` 3. 打开连接器 → 我的连接器 → huaweicloud-devkit → 连接 → 回到对话 → 输入框开启连接器`);
|
|
1967
|
+
} else {
|
|
1968
|
+
console.log(` 3. 重启 ${appName} 会话(MCP 工具重启后生效)`);
|
|
1969
|
+
}
|
|
1568
1970
|
console.log(` 4. 运行自检:npx huaweicloud-devkit doctor`);
|
|
1569
1971
|
|
|
1570
1972
|
// Write install marker for doctor to detect
|
|
@@ -1577,9 +1979,11 @@ async function cmdInstall() {
|
|
|
1577
1979
|
? workbuddyPluginsDir()
|
|
1578
1980
|
: target === 'officeace'
|
|
1579
1981
|
? officeacePluginsDir()
|
|
1580
|
-
: target === '
|
|
1982
|
+
: target === 'openclaw'
|
|
1581
1983
|
? codexDesktopPluginsDir()
|
|
1582
|
-
:
|
|
1984
|
+
: target === 'codex-desktop'
|
|
1985
|
+
? codexDesktopPluginsDir()
|
|
1986
|
+
: opencodePluginsDir();
|
|
1583
1987
|
mkdirSync(markerDir, { recursive: true });
|
|
1584
1988
|
writeFileSync(join(markerDir, '.installed'), new Date().toISOString());
|
|
1585
1989
|
if (target === 'opencode' || target === 'all') {
|
|
@@ -1600,6 +2004,9 @@ async function cmdInstall() {
|
|
|
1600
2004
|
if (target === 'officeace' || target === 'all') {
|
|
1601
2005
|
console.log('Or describe your Huawei Cloud task in OfficeAce');
|
|
1602
2006
|
}
|
|
2007
|
+
if (target === 'openclaw' || target === 'all') {
|
|
2008
|
+
console.log('Or describe your Huawei Cloud task in OpenClaw');
|
|
2009
|
+
}
|
|
1603
2010
|
}
|
|
1604
2011
|
|
|
1605
2012
|
async function cmdUninstall() {
|
|
@@ -1627,6 +2034,14 @@ async function cmdUninstall() {
|
|
|
1627
2034
|
console.log('\n[OfficeAce]');
|
|
1628
2035
|
uninstallOfficeAce();
|
|
1629
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
|
+
}
|
|
1630
2045
|
if (target === 'codex-desktop' || target === 'codex' || target === 'all') {
|
|
1631
2046
|
console.log('\n[Codex]');
|
|
1632
2047
|
uninstallCodexDesktop();
|
|
@@ -1679,6 +2094,30 @@ async function cmdStatus() {
|
|
|
1679
2094
|
console.log('\n[OfficeAce]');
|
|
1680
2095
|
officeaceStatus();
|
|
1681
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
|
+
}
|
|
1682
2121
|
if (target === 'codex' || target === 'all') {
|
|
1683
2122
|
console.log('\n[Codex]');
|
|
1684
2123
|
if (!hasCodexCLI()) {
|
|
@@ -1711,7 +2150,7 @@ async function cmdDoctor() {
|
|
|
1711
2150
|
}
|
|
1712
2151
|
|
|
1713
2152
|
// Node.js
|
|
1714
|
-
check('Node.js >=
|
|
2153
|
+
check('Node.js >= 22', process.versions.node.split('.')[0] >= 22, 'Run: nvm install 22 && nvm use 22');
|
|
1715
2154
|
|
|
1716
2155
|
// MCP server — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH paths
|
|
1717
2156
|
const opencodePluginDir = opencodePluginsDir();
|
|
@@ -1720,13 +2159,15 @@ async function cmdDoctor() {
|
|
|
1720
2159
|
const workbuddyPluginDir = workbuddyPluginsDir();
|
|
1721
2160
|
const dshPluginDir = dshPluginsDir();
|
|
1722
2161
|
const officeacePluginDir = officeacePluginsDir();
|
|
2162
|
+
const hermesPluginDir = hermesPluginsDir();
|
|
1723
2163
|
const mcpOk =
|
|
1724
2164
|
existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs')) ||
|
|
1725
2165
|
existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
1726
2166
|
existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
1727
2167
|
existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
1728
2168
|
existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
1729
|
-
existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs'))
|
|
2169
|
+
existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2170
|
+
existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'));
|
|
1730
2171
|
const mcpTarget = existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs'))
|
|
1731
2172
|
? 'OpenCode'
|
|
1732
2173
|
: existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs'))
|
|
@@ -1739,7 +2180,9 @@ async function cmdDoctor() {
|
|
|
1739
2180
|
? 'DSH'
|
|
1740
2181
|
: existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs'))
|
|
1741
2182
|
? 'OfficeAce'
|
|
1742
|
-
: ''
|
|
2183
|
+
: existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'))
|
|
2184
|
+
? 'Hermes Agent'
|
|
2185
|
+
: '';
|
|
1743
2186
|
check('MCP server installed', mcpOk, 'Run: npx huaweicloud-devkit install');
|
|
1744
2187
|
|
|
1745
2188
|
if (mcpOk) {
|
|
@@ -1752,7 +2195,8 @@ async function cmdDoctor() {
|
|
|
1752
2195
|
existsSync(join(codeartsPluginDir, 'safety', 'policy.json')) ||
|
|
1753
2196
|
existsSync(join(workbuddyPluginDir, 'safety', 'policy.json')) ||
|
|
1754
2197
|
existsSync(join(dshPluginDir, 'safety', 'policy.json')) ||
|
|
1755
|
-
existsSync(join(officeacePluginDir, 'safety', 'policy.json'))
|
|
2198
|
+
existsSync(join(officeacePluginDir, 'safety', 'policy.json')) ||
|
|
2199
|
+
existsSync(join(hermesPluginDir, 'safety', 'policy.json'));
|
|
1756
2200
|
check('Safety policy installed', safetyOk, 'Run: npx huaweicloud-devkit install');
|
|
1757
2201
|
|
|
1758
2202
|
// MCP config — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH
|
|
@@ -1817,6 +2261,18 @@ async function cmdDoctor() {
|
|
|
1817
2261
|
} catch {}
|
|
1818
2262
|
}
|
|
1819
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
|
+
}
|
|
1820
2276
|
check(
|
|
1821
2277
|
'MCP configured',
|
|
1822
2278
|
mcpConfigured,
|
|
@@ -1867,6 +2323,7 @@ async function cmdDoctor() {
|
|
|
1867
2323
|
workbuddySkillsDir(),
|
|
1868
2324
|
dshSkillsDir(),
|
|
1869
2325
|
officeaceSkillsDir(),
|
|
2326
|
+
hermesSkillsDir(),
|
|
1870
2327
|
];
|
|
1871
2328
|
let skillCount = 0;
|
|
1872
2329
|
const missingSkills = [];
|
|
@@ -2024,7 +2481,31 @@ async function cmdUpdate() {
|
|
|
2024
2481
|
console.log('[OfficeAce]');
|
|
2025
2482
|
await updateOfficeAce();
|
|
2026
2483
|
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
2027
|
-
console.log(`\x1b[
|
|
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`);
|
|
2028
2509
|
return;
|
|
2029
2510
|
}
|
|
2030
2511
|
|
|
@@ -2060,6 +2541,16 @@ async function cmdUpdate() {
|
|
|
2060
2541
|
await updateOfficeAce();
|
|
2061
2542
|
updatedAny = true;
|
|
2062
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
|
+
}
|
|
2063
2554
|
if (codexStatus()) {
|
|
2064
2555
|
console.log('\n[Codex]');
|
|
2065
2556
|
installCodex();
|
|
@@ -2555,7 +3046,7 @@ async function main() {
|
|
|
2555
3046
|
default:
|
|
2556
3047
|
console.log(BANNER);
|
|
2557
3048
|
console.log(
|
|
2558
|
-
'Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|dsh|officeace|all>]\n',
|
|
3049
|
+
'Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|dsh|officeace|hermes|openclaw|all>]\n',
|
|
2559
3050
|
);
|
|
2560
3051
|
console.log('Commands:');
|
|
2561
3052
|
console.log(' install Install skills, MCP server, safety policy');
|
|
@@ -2569,7 +3060,9 @@ async function main() {
|
|
|
2569
3060
|
console.log(' proxy Manage proxy config: init | show | clear');
|
|
2570
3061
|
console.log(' help Show this help');
|
|
2571
3062
|
console.log('\nOptions:');
|
|
2572
|
-
console.log(
|
|
3063
|
+
console.log(
|
|
3064
|
+
' --target Target agent: opencode (default), codex, codearts, workbuddy, dsh, officeace, hermes, openclaw, all',
|
|
3065
|
+
);
|
|
2573
3066
|
console.log('\nExamples:');
|
|
2574
3067
|
console.log(' npx huaweicloud-devkit install');
|
|
2575
3068
|
console.log(' npx huaweicloud-devkit install --target codex');
|
|
@@ -2577,6 +3070,7 @@ async function main() {
|
|
|
2577
3070
|
console.log(' npx huaweicloud-devkit install --target workbuddy');
|
|
2578
3071
|
console.log(' npx huaweicloud-devkit install --target dsh');
|
|
2579
3072
|
console.log(' npx huaweicloud-devkit install --target officeace');
|
|
3073
|
+
console.log(' npx huaweicloud-devkit install --target hermes');
|
|
2580
3074
|
console.log(' npx huaweicloud-devkit install --target all');
|
|
2581
3075
|
console.log(' npx huaweicloud-devkit auth init');
|
|
2582
3076
|
console.log(' npx huaweicloud-devkit auth sync --target all');
|