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