huaweicloud-devkit 1.0.3-dev.0 → 1.1.0-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 +114 -41
- package/README.zh-CN.md +114 -41
- package/bin/setup.cjs +7 -4
- package/integrations/opencode/opencode.json +1 -4
- package/package.json +63 -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 +134 -2
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +77 -4
- package/plugins/huaweicloud-core/src/auth/service.mjs +2 -1
- package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +27 -8
- package/plugins/huaweicloud-core/src/icon-library.mjs +176 -0
- package/plugins/huaweicloud-core/src/mcp-server.mjs +31 -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 +123 -68
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +14 -10
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +35 -27
- package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +526 -8
- package/plugins/huaweicloud-core/src/search-market.mjs +55 -16
- package/plugins/huaweicloud-core/src/setup-cli.mjs +1942 -206
- package/plugins/huaweicloud-core/src/tools.mjs +678 -131
- 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 +13 -16
- 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 +268 -0
- package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
- package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +11 -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,36 @@
|
|
|
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';
|
|
18
|
+
|
|
9
19
|
import { getAuthStatus, syncAuth } from './auth/service.mjs';
|
|
10
|
-
import {
|
|
20
|
+
import { SUPPORTED_AGENT_TARGETS } from './auth/agent-registration.mjs';
|
|
21
|
+
import {
|
|
22
|
+
globalCredentialsPath,
|
|
23
|
+
readGlobalCredentials,
|
|
24
|
+
writeGlobalCredentials,
|
|
25
|
+
writeObsConfig,
|
|
26
|
+
} from './auth/credentials.mjs';
|
|
27
|
+
import {
|
|
28
|
+
proxyConfigPath,
|
|
29
|
+
readProxyConfig,
|
|
30
|
+
writeProxyConfig,
|
|
31
|
+
clearProxyConfig,
|
|
32
|
+
getProxySettings,
|
|
33
|
+
} from './proxy/proxy-config.mjs';
|
|
11
34
|
|
|
12
35
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
36
|
const PLUGIN_ROOT = resolve(__dirname, '..');
|
|
@@ -30,31 +53,333 @@ function configRoot(target = 'opencode') {
|
|
|
30
53
|
return join(home, '.config', target);
|
|
31
54
|
}
|
|
32
55
|
|
|
33
|
-
function opencodeSkillsDir() {
|
|
34
|
-
|
|
35
|
-
|
|
56
|
+
function opencodeSkillsDir() {
|
|
57
|
+
return join(configRoot('opencode'), 'skills');
|
|
58
|
+
}
|
|
59
|
+
function opencodeCommandsDir() {
|
|
60
|
+
return join(configRoot('opencode'), 'commands');
|
|
61
|
+
}
|
|
62
|
+
function opencodePluginsDir() {
|
|
63
|
+
return join(configRoot('opencode'), 'huaweicloud-plugins');
|
|
64
|
+
}
|
|
36
65
|
function opencodeConfigFile() {
|
|
37
66
|
const jsonc = join(configRoot('opencode'), 'opencode.jsonc');
|
|
38
67
|
if (existsSync(jsonc)) return jsonc;
|
|
39
68
|
return join(configRoot('opencode'), 'opencode.json');
|
|
40
69
|
}
|
|
41
70
|
|
|
42
|
-
function codexDesktopSkillsDir() {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
function
|
|
71
|
+
function codexDesktopSkillsDir() {
|
|
72
|
+
return join(homedir(), '.agents', 'skills');
|
|
73
|
+
}
|
|
74
|
+
function codexDesktopCommandsDir() {
|
|
75
|
+
return join(homedir(), '.agents', 'commands');
|
|
76
|
+
}
|
|
77
|
+
function codexDesktopPluginsDir() {
|
|
78
|
+
return join(homedir(), '.agents', 'huaweicloud-plugins');
|
|
79
|
+
}
|
|
80
|
+
function codexConfigToml() {
|
|
81
|
+
return join(homedir(), '.codex', 'config.toml');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function codeartsSkillsDir() {
|
|
85
|
+
return join(homedir(), '.codeartsdoer', 'skills');
|
|
86
|
+
}
|
|
87
|
+
function codeartsMcpSettingsDir() {
|
|
88
|
+
return join(homedir(), '.codeartsdoer', 'mcp');
|
|
89
|
+
}
|
|
90
|
+
function codeartsMcpSettingsFile() {
|
|
91
|
+
return join(codeartsMcpSettingsDir(), 'mcp_settings.json');
|
|
92
|
+
}
|
|
93
|
+
function codeartsProjectDir() {
|
|
94
|
+
return join(process.cwd(), '.codeartsdoer');
|
|
95
|
+
}
|
|
96
|
+
function codeartsProjectSkillsDir() {
|
|
97
|
+
return join(codeartsProjectDir(), 'skills');
|
|
98
|
+
}
|
|
99
|
+
function codeartsProjectMcpSettingsFile() {
|
|
100
|
+
return join(codeartsProjectDir(), 'mcp', 'mcp_settings.json');
|
|
101
|
+
}
|
|
102
|
+
function codeartsPluginsDir() {
|
|
103
|
+
return join(homedir(), '.codeartsdoer', 'huaweicloud-plugins');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function workbuddySkillsDir() {
|
|
107
|
+
return join(homedir(), '.workbuddy', 'skills');
|
|
108
|
+
}
|
|
109
|
+
function workbuddyMcpConfigFile() {
|
|
110
|
+
return join(homedir(), '.workbuddy', 'mcp.json');
|
|
111
|
+
}
|
|
112
|
+
function workbuddyPluginsDir() {
|
|
113
|
+
return join(homedir(), '.workbuddy', 'huaweicloud-plugins');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function atomcodeHome() {
|
|
117
|
+
return process.env.ATOMCODE_HOME || join(homedir(), '.atomcode');
|
|
118
|
+
}
|
|
119
|
+
function atomcodeSkillsDir() {
|
|
120
|
+
return join(atomcodeHome(), 'skills');
|
|
121
|
+
}
|
|
122
|
+
function atomcodeMcpConfigFile() {
|
|
123
|
+
return join(atomcodeHome(), 'mcp.json');
|
|
124
|
+
}
|
|
125
|
+
function atomcodePluginsDir() {
|
|
126
|
+
return join(atomcodeHome(), 'huaweicloud-plugins');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function dshRoot() {
|
|
130
|
+
return process.env.DSH_HOME || join(homedir(), '.dsh');
|
|
131
|
+
}
|
|
132
|
+
function dshSkillsDir() {
|
|
133
|
+
return join(dshRoot(), 'skills');
|
|
134
|
+
}
|
|
135
|
+
function dshProfileDir() {
|
|
136
|
+
return join(dshRoot(), 'profiles', 'web');
|
|
137
|
+
}
|
|
138
|
+
function dshPatchFile() {
|
|
139
|
+
return join(dshProfileDir(), 'cordis.patch.yml');
|
|
140
|
+
}
|
|
141
|
+
function dshPluginsDir() {
|
|
142
|
+
return join(dshRoot(), 'huaweicloud-plugins');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function readOfficeaceRegistryInstallDir() {
|
|
146
|
+
if (platform() !== 'win32') return null;
|
|
147
|
+
try {
|
|
148
|
+
const r = spawnSync('reg', ['query', 'HKCU\\SOFTWARE\\OfficeAce\\OfficeAce', '/v', 'InstallDir'], {
|
|
149
|
+
encoding: 'utf8',
|
|
150
|
+
windowsHide: true,
|
|
151
|
+
timeout: 5000,
|
|
152
|
+
});
|
|
153
|
+
if (r.status === 0) {
|
|
154
|
+
const m = r.stdout.match(/InstallDir\s+REG_SZ\s+(.+)/);
|
|
155
|
+
if (m) return m[1].trim();
|
|
156
|
+
}
|
|
157
|
+
} catch {}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function officeaceCapabilitiesDir() {
|
|
162
|
+
const configRoot = process.env.OFFICE_CLAW_CONFIG_ROOT;
|
|
163
|
+
if (configRoot && existsSync(join(configRoot, 'capabilities.json'))) return configRoot;
|
|
164
|
+
const regDir = readOfficeaceRegistryInstallDir();
|
|
165
|
+
if (regDir) {
|
|
166
|
+
const dir = join(regDir, '.office-claw');
|
|
167
|
+
if (existsSync(join(dir, 'capabilities.json'))) return dir;
|
|
168
|
+
}
|
|
169
|
+
if (platform() === 'win32') {
|
|
170
|
+
const bases = [process.env.ProgramFiles, 'C:\\Program Files', 'D:\\Program Files'];
|
|
171
|
+
if (process.env.LOCALAPPDATA) bases.push(join(process.env.LOCALAPPDATA, 'Programs'));
|
|
172
|
+
for (const base of bases) {
|
|
173
|
+
if (!base) continue;
|
|
174
|
+
const dir = join(base, 'OfficeAce', '.office-claw');
|
|
175
|
+
if (existsSync(join(dir, 'capabilities.json'))) return dir;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function officeaceCapabilitiesDirSafe() {
|
|
182
|
+
return officeaceCapabilitiesDir() || join(homedir(), '.office-claw');
|
|
183
|
+
}
|
|
184
|
+
function officeaceCapabilitiesFile() {
|
|
185
|
+
return join(officeaceCapabilitiesDirSafe(), 'capabilities.json');
|
|
186
|
+
}
|
|
187
|
+
function officeaceSkillsDir() {
|
|
188
|
+
return join(officeaceCapabilitiesDirSafe(), 'skills');
|
|
189
|
+
}
|
|
190
|
+
function officeacePluginsDir() {
|
|
191
|
+
return join(officeaceCapabilitiesDirSafe(), 'huaweicloud-plugins');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function officeaceSqlitePath() {
|
|
195
|
+
const capDir = officeaceCapabilitiesDirSafe();
|
|
196
|
+
return join(resolve(capDir, '..'), 'data', 'mcp-connectors.sqlite');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function openOfficeaceDb() {
|
|
200
|
+
return new Database(officeaceSqlitePath());
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function officeaceGetOwnerUserId() {
|
|
204
|
+
if (!existsSync(officeaceSqlitePath())) return null;
|
|
205
|
+
try {
|
|
206
|
+
const db = openOfficeaceDb();
|
|
207
|
+
const row = db.prepare('SELECT owner_user_id FROM mcp_connectors WHERE owner_user_id IS NOT NULL LIMIT 1').get();
|
|
208
|
+
db.close();
|
|
209
|
+
return row?.owner_user_id || null;
|
|
210
|
+
} catch {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function ensureOfficeaceMcpInSqlite() {
|
|
216
|
+
const dbPath = officeaceSqlitePath();
|
|
217
|
+
if (!existsSync(dbPath)) {
|
|
218
|
+
console.log(` \x1b[31mOfficeAce database not found: ${dbPath}\x1b[0m`);
|
|
219
|
+
console.log(` \x1b[33mPlease ensure OfficeAce is installed and has been launched at least once.\x1b[0m`);
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const mcpPath = join(officeacePluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
224
|
+
const env = [{ key: 'HUAWEICLOUD_AGENT_TOOLKIT_MODE', value: 'local', sensitive: false }];
|
|
225
|
+
const hcloudBin = findHcloudBin();
|
|
226
|
+
if (hcloudBin) env.push({ key: 'HCLOUD_BIN', value: hcloudBin.replace(/\\/g, '/'), sensitive: false });
|
|
227
|
+
|
|
228
|
+
const now = Date.now();
|
|
229
|
+
let db;
|
|
230
|
+
try {
|
|
231
|
+
db = openOfficeaceDb();
|
|
232
|
+
|
|
233
|
+
const existing = db
|
|
234
|
+
.prepare("SELECT id, command, args_json FROM mcp_connectors WHERE name = 'huaweicloud-devkit'")
|
|
235
|
+
.get();
|
|
236
|
+
|
|
237
|
+
const argsJson = JSON.stringify([mcpPath]);
|
|
238
|
+
const envJson = JSON.stringify(env);
|
|
239
|
+
|
|
240
|
+
if (existing) {
|
|
241
|
+
if (existing.command === 'node' && existing.args_json === argsJson) {
|
|
242
|
+
console.log(` MCP config unchanged: ${dbPath}`);
|
|
243
|
+
db.close();
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
db.prepare(
|
|
247
|
+
'UPDATE mcp_connectors SET command = ?, args_json = ?, env_json = ?, updated_at = ?, status = ?, enabled = 1 WHERE id = ?',
|
|
248
|
+
).run('node', argsJson, envJson, now, 'disconnected', existing.id);
|
|
249
|
+
console.log(` MCP config updated: ${dbPath}`);
|
|
250
|
+
} else {
|
|
251
|
+
const ownerUserId = officeaceGetOwnerUserId();
|
|
252
|
+
if (!ownerUserId) {
|
|
253
|
+
console.log(` \x1b[31mCannot determine owner_user_id from database\x1b[0m`);
|
|
254
|
+
db.close();
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
db.prepare(
|
|
258
|
+
`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)
|
|
259
|
+
VALUES (?, ?, 'custom', 'huaweicloud-devkit', 'huaweicloud-devkit', 'stdio', 60000, 'node', ?, ?, 1, 'disconnected', ?, ?, 1, 0)`,
|
|
260
|
+
).run(randomUUID(), ownerUserId, argsJson, envJson, now, now);
|
|
261
|
+
console.log(` MCP config created: ${dbPath}`);
|
|
262
|
+
}
|
|
263
|
+
db.close();
|
|
264
|
+
return true;
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if (db) {
|
|
267
|
+
try {
|
|
268
|
+
db.close();
|
|
269
|
+
} catch {}
|
|
270
|
+
}
|
|
271
|
+
console.log(` \x1b[31mFailed to write MCP config: ${error.message}\x1b[0m`);
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function removeOfficeaceMcpFromSqlite() {
|
|
277
|
+
const dbPath = officeaceSqlitePath();
|
|
278
|
+
if (!existsSync(dbPath)) return;
|
|
279
|
+
let db;
|
|
280
|
+
try {
|
|
281
|
+
db = openOfficeaceDb();
|
|
282
|
+
db.prepare(
|
|
283
|
+
"DELETE FROM mcp_connector_tools WHERE connector_id IN (SELECT id FROM mcp_connectors WHERE name = 'huaweicloud-devkit')",
|
|
284
|
+
).run();
|
|
285
|
+
const result2 = db.prepare("DELETE FROM mcp_connectors WHERE name = 'huaweicloud-devkit'").run();
|
|
286
|
+
if (result2.changes > 0) {
|
|
287
|
+
console.log(` MCP config removed: ${dbPath}`);
|
|
288
|
+
}
|
|
289
|
+
db.close();
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if (db) {
|
|
292
|
+
try {
|
|
293
|
+
db.close();
|
|
294
|
+
} catch {}
|
|
295
|
+
}
|
|
296
|
+
console.log(` \x1b[31mFailed to remove MCP config: ${error.message}\x1b[0m`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function readCapabilitiesJson() {
|
|
301
|
+
const capFile = officeaceCapabilitiesFile();
|
|
302
|
+
if (!existsSync(capFile)) return { capabilities: [] };
|
|
303
|
+
try {
|
|
304
|
+
return JSON.parse(readFileSync(capFile, 'utf8'));
|
|
305
|
+
} catch {
|
|
306
|
+
return { capabilities: [] };
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function writeCapabilitiesJson(config) {
|
|
311
|
+
mkdirSync(officeaceCapabilitiesDirSafe(), { recursive: true });
|
|
312
|
+
writeFileSync(officeaceCapabilitiesFile(), JSON.stringify(config, null, 2));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function removeOfficeaceSkillCapabilities() {
|
|
316
|
+
const capFile = officeaceCapabilitiesFile();
|
|
317
|
+
if (!existsSync(capFile)) return;
|
|
318
|
+
let config;
|
|
319
|
+
try {
|
|
320
|
+
config = JSON.parse(readFileSync(capFile, 'utf8'));
|
|
321
|
+
} catch {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (!Array.isArray(config.capabilities)) return;
|
|
325
|
+
const origLen = config.capabilities.length;
|
|
326
|
+
config.capabilities = config.capabilities.filter(
|
|
327
|
+
(c) =>
|
|
328
|
+
!(
|
|
329
|
+
(c.id === 'huaweicloud-core' && c.type === 'skill') ||
|
|
330
|
+
(c.source === 'custom' && c.id && c.id.startsWith('huawei'))
|
|
331
|
+
),
|
|
332
|
+
);
|
|
333
|
+
if (config.capabilities.length !== origLen) {
|
|
334
|
+
writeCapabilitiesJson(config);
|
|
335
|
+
console.log(` Skill capabilities cleaned: ${capFile}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function registerOfficeaceSkillEntries() {
|
|
340
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
341
|
+
if (!existsSync(skillsSrc)) return;
|
|
342
|
+
const skillNames = readdirSync(skillsSrc, { withFileTypes: true })
|
|
343
|
+
.filter((d) => d.isDirectory() && d.name.startsWith('huawei'))
|
|
344
|
+
.map((d) => d.name);
|
|
345
|
+
|
|
346
|
+
const config = readCapabilitiesJson();
|
|
347
|
+
let changed = false;
|
|
348
|
+
|
|
349
|
+
for (const name of skillNames) {
|
|
350
|
+
const existingIdx = config.capabilities.findIndex((c) => c.id === name && c.type === 'skill');
|
|
351
|
+
if (existingIdx >= 0) continue;
|
|
352
|
+
config.capabilities.push({
|
|
353
|
+
id: name,
|
|
354
|
+
type: 'skill',
|
|
355
|
+
enabled: true,
|
|
356
|
+
source: 'custom',
|
|
357
|
+
selfEvolution: 'suggest',
|
|
358
|
+
followGlobalSelfEvolution: true,
|
|
359
|
+
});
|
|
360
|
+
changed = true;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (!config.capabilities.some((c) => c.id === 'huaweicloud-core' && c.type === 'skill')) {
|
|
364
|
+
config.capabilities.push({
|
|
365
|
+
id: 'huaweicloud-core',
|
|
366
|
+
type: 'skill',
|
|
367
|
+
enabled: true,
|
|
368
|
+
source: 'custom',
|
|
369
|
+
selfEvolution: 'suggest',
|
|
370
|
+
followGlobalSelfEvolution: true,
|
|
371
|
+
});
|
|
372
|
+
changed = true;
|
|
373
|
+
}
|
|
46
374
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
function codeartsProjectMcpSettingsFile() { return join(codeartsProjectDir(), 'mcp', 'mcp_settings.json'); }
|
|
53
|
-
function codeartsPluginsDir() { return join(homedir(), '.codeartsdoer', 'huaweicloud-plugins'); }
|
|
375
|
+
if (changed) {
|
|
376
|
+
writeCapabilitiesJson(config);
|
|
377
|
+
console.log(` Skill entries registered: ${officeaceCapabilitiesFile()}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
54
380
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
function workbuddyPluginsDir() { return join(homedir(), '.workbuddy', 'huaweicloud-plugins'); }
|
|
381
|
+
const DSH_MCP_PATCH_START = '# HuaweiCloud DevKit DSH integration start';
|
|
382
|
+
const DSH_MCP_PATCH_END = '# HuaweiCloud DevKit DSH integration end';
|
|
58
383
|
|
|
59
384
|
// Detect CodeArts sandbox mode (bash_mode in permission config).
|
|
60
385
|
function detectCodeartsSandbox() {
|
|
@@ -74,7 +399,11 @@ function findHcloudBin() {
|
|
|
74
399
|
const isWin = platform() === 'win32';
|
|
75
400
|
const candidates = isWin
|
|
76
401
|
? [join(homedir(), 'hcloud', 'hcloud.exe')]
|
|
77
|
-
: [
|
|
402
|
+
: [
|
|
403
|
+
join(homedir(), '.local', 'bin', 'hcloud'),
|
|
404
|
+
join(homedir(), 'hcloud', 'hcloud'),
|
|
405
|
+
join(homedir(), 'hcloud', 'hcloud.exe'),
|
|
406
|
+
];
|
|
78
407
|
for (const c of candidates) {
|
|
79
408
|
if (existsSync(c)) return c;
|
|
80
409
|
}
|
|
@@ -87,14 +416,14 @@ function printSandboxWarning(reason) {
|
|
|
87
416
|
console.log(`\x1b[31m 请任选其一继续:\x1b[0m`);
|
|
88
417
|
console.log(`\x1b[31m A. 在码道外的终端安装并使用 KooCLI (推荐):`);
|
|
89
418
|
console.log(`\x1b[31m https://support.huaweicloud.com/qs-hcli/hcli_02_003.html`);
|
|
90
|
-
console.log(`\x1b[31m B. 在码道设置中关闭沙箱模式后重试 (设置 →
|
|
419
|
+
console.log(`\x1b[31m B. 在码道设置中关闭沙箱模式后重试 (设置 → 对话流 → 智能体 终端命令运行模式 → 自动运行)`);
|
|
91
420
|
console.log(`\x1b[31m 关闭沙箱后重新运行: npx huaweicloud-devkit install-hcloud\x1b[0m`);
|
|
92
421
|
}
|
|
93
422
|
|
|
94
423
|
function checkNode() {
|
|
95
424
|
const v = process.versions.node.split('.').map(Number);
|
|
96
|
-
if (v[0] <
|
|
97
|
-
console.error(`\x1b[31mNode.js >=
|
|
425
|
+
if (v[0] < 22) {
|
|
426
|
+
console.error(`\x1b[31mNode.js >= 22 required (current: ${process.version})\x1b[0m`);
|
|
98
427
|
process.exit(1);
|
|
99
428
|
}
|
|
100
429
|
console.log(` Node.js ${process.version} \x1b[32mOK\x1b[0m`);
|
|
@@ -114,13 +443,55 @@ function copyDir(src, dest) {
|
|
|
114
443
|
}
|
|
115
444
|
}
|
|
116
445
|
|
|
446
|
+
function installRuntimeDeps(pluginsDir) {
|
|
447
|
+
const pkgJson = {
|
|
448
|
+
name: 'huaweicloud-devkit',
|
|
449
|
+
version: pkgVersion,
|
|
450
|
+
type: 'module',
|
|
451
|
+
dependencies: { undici: '^8.10.0' },
|
|
452
|
+
};
|
|
453
|
+
mkdirSync(pluginsDir, { recursive: true });
|
|
454
|
+
writeFileSync(join(pluginsDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
|
|
455
|
+
const spawnOpts = {
|
|
456
|
+
cwd: pluginsDir,
|
|
457
|
+
shell: true,
|
|
458
|
+
windowsHide: true,
|
|
459
|
+
stdio: 'pipe',
|
|
460
|
+
timeout: 120000,
|
|
461
|
+
};
|
|
462
|
+
let r = spawnSync('npm', ['install', '--omit=dev'], spawnOpts);
|
|
463
|
+
const isRetryable = (res) => {
|
|
464
|
+
const stderr = (res.stderr || '').toString();
|
|
465
|
+
return res.error?.code === 'EPERM' || res.error?.code === 'EBUSY' || /EPERM|EBUSY/.test(stderr);
|
|
466
|
+
};
|
|
467
|
+
if (r.status !== 0 && isRetryable(r)) {
|
|
468
|
+
console.log(` \x1b[33m[WARN]\x1b[0m npm install hit file-lock error, retrying in 2s...`);
|
|
469
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2000);
|
|
470
|
+
r = spawnSync('npm', ['install', '--omit=dev'], spawnOpts);
|
|
471
|
+
}
|
|
472
|
+
const undiciDir = join(pluginsDir, 'node_modules', 'undici');
|
|
473
|
+
if (r.status === 0 && existsSync(undiciDir)) {
|
|
474
|
+
console.log(` Runtime deps installed -> ${join(pluginsDir, 'node_modules')}`);
|
|
475
|
+
} else {
|
|
476
|
+
const errCode = r.error?.code;
|
|
477
|
+
const err = (r.stderr || '').toString().trim().split(/\r?\n/).slice(-2).join(' ');
|
|
478
|
+
const hint = errCode === 'ENOENT' ? ' (npm not found — ensure Node.js/npm is in PATH)' : '';
|
|
479
|
+
console.log(` \x1b[33m[WARN]\x1b[0m npm install failed in ${pluginsDir}${err ? `: ${err}` : ''}${hint}`);
|
|
480
|
+
console.log(' Manual fix: cd %s && npm install', pluginsDir);
|
|
481
|
+
if (!existsSync(undiciDir)) {
|
|
482
|
+
console.log(` \x1b[31m[ERROR]\x1b[0m undici is NOT installed. MCP server will fail to start.`);
|
|
483
|
+
console.log(` Run manually: cd "${pluginsDir}" && npm install undici@^8.10.0`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
117
488
|
function removeIfExists(p) {
|
|
118
489
|
if (existsSync(p)) {
|
|
119
490
|
try {
|
|
120
491
|
rmSync(p, { recursive: true, force: true });
|
|
121
492
|
return true;
|
|
122
|
-
} catch (
|
|
123
|
-
console.log(` \x1b[33m[WARN]\x1b[0m Could not remove ${p}: ${
|
|
493
|
+
} catch (error) {
|
|
494
|
+
console.log(` \x1b[33m[WARN]\x1b[0m Could not remove ${p}: ${error.message}`);
|
|
124
495
|
return false;
|
|
125
496
|
}
|
|
126
497
|
}
|
|
@@ -132,14 +503,23 @@ function updateOpenCodeConfig(pluginDir) {
|
|
|
132
503
|
const mcpPath = join(pluginDir, 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
133
504
|
let config = {};
|
|
134
505
|
if (existsSync(configPath)) {
|
|
135
|
-
try {
|
|
136
|
-
|
|
506
|
+
try {
|
|
507
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
508
|
+
} catch {
|
|
509
|
+
console.log(
|
|
510
|
+
` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath} (jsonc comments?). Skipping MCP config write; ensure "mcp.huaweicloud-devkit" points to ${mcpPath}.`,
|
|
511
|
+
);
|
|
137
512
|
return;
|
|
138
513
|
}
|
|
139
514
|
const existing = config.mcp?.['huaweicloud-devkit'];
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
|
|
515
|
+
if (
|
|
516
|
+
existing &&
|
|
517
|
+
existing.type === 'local' &&
|
|
518
|
+
Array.isArray(existing.command) &&
|
|
519
|
+
existing.command[0] === 'node' &&
|
|
520
|
+
existing.command[1] === mcpPath &&
|
|
521
|
+
existing.timeout === 300000
|
|
522
|
+
) {
|
|
143
523
|
console.log(` OpenCode MCP config unchanged: ${configPath}`);
|
|
144
524
|
return;
|
|
145
525
|
}
|
|
@@ -149,6 +529,7 @@ function updateOpenCodeConfig(pluginDir) {
|
|
|
149
529
|
type: 'local',
|
|
150
530
|
command: ['node', mcpPath],
|
|
151
531
|
enabled: true,
|
|
532
|
+
timeout: 300000,
|
|
152
533
|
};
|
|
153
534
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
154
535
|
console.log(` OpenCode MCP config updated: ${configPath}`);
|
|
@@ -157,8 +538,12 @@ function updateOpenCodeConfig(pluginDir) {
|
|
|
157
538
|
function removeOpenCodeConfig() {
|
|
158
539
|
const configPath = opencodeConfigFile();
|
|
159
540
|
if (!existsSync(configPath)) return;
|
|
160
|
-
let config
|
|
161
|
-
try {
|
|
541
|
+
let config;
|
|
542
|
+
try {
|
|
543
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
544
|
+
} catch {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
162
547
|
if (!config.mcp?.['huaweicloud-devkit']) return;
|
|
163
548
|
delete config.mcp['huaweicloud-devkit'];
|
|
164
549
|
if (Object.keys(config.mcp).length === 0) delete config.mcp;
|
|
@@ -179,11 +564,13 @@ function hasCodexCLI() {
|
|
|
179
564
|
}
|
|
180
565
|
|
|
181
566
|
function checkHcloud() {
|
|
182
|
-
const bin = findHcloudBin() ||
|
|
567
|
+
const bin = findHcloudBin() || process.env.HCLOUD_BIN || 'hcloud';
|
|
183
568
|
if (!existsSync(bin)) return false;
|
|
184
569
|
try {
|
|
185
570
|
if (statSync(bin).size < 1024) return false;
|
|
186
|
-
} catch {
|
|
571
|
+
} catch {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
187
574
|
try {
|
|
188
575
|
const r = spawnSync(`"${bin}" version`, [], { shell: true, windowsHide: true, stdio: 'pipe', timeout: 5000 });
|
|
189
576
|
const out = (r.stdout ? r.stdout.toString() : '') + (r.stderr ? r.stderr.toString() : '');
|
|
@@ -209,7 +596,9 @@ function installCodex() {
|
|
|
209
596
|
|
|
210
597
|
console.log(` Registering Codex marketplace: ${marketplaceRoot}`);
|
|
211
598
|
const r1 = spawnSync(`codex plugin marketplace add "${marketplaceRoot}"`, [], {
|
|
212
|
-
shell: true,
|
|
599
|
+
shell: true,
|
|
600
|
+
windowsHide: true,
|
|
601
|
+
stdio: 'pipe',
|
|
213
602
|
});
|
|
214
603
|
console.log(` ${r1.stdout ? r1.stdout.toString().trim() : r1.stderr.toString().trim()}`);
|
|
215
604
|
|
|
@@ -221,7 +610,9 @@ function installCodex() {
|
|
|
221
610
|
|
|
222
611
|
console.log(` Installing plugin: ${pluginName}@${marketplaceName}`);
|
|
223
612
|
const r2 = spawnSync(`codex plugin add "${pluginName}@${marketplaceName}"`, [], {
|
|
224
|
-
shell: true,
|
|
613
|
+
shell: true,
|
|
614
|
+
windowsHide: true,
|
|
615
|
+
stdio: 'pipe',
|
|
225
616
|
});
|
|
226
617
|
console.log(` ${r2.stdout ? r2.stdout.toString().trim() : r2.stderr.toString().trim()}`);
|
|
227
618
|
|
|
@@ -239,14 +630,18 @@ function uninstallCodex() {
|
|
|
239
630
|
const marketplaceName = getMarketplaceName();
|
|
240
631
|
console.log(` Removing Codex plugin: ${pluginName}@${marketplaceName}`);
|
|
241
632
|
const r = spawnSync(`codex plugin remove "${pluginName}@${marketplaceName}"`, [], {
|
|
242
|
-
shell: true,
|
|
633
|
+
shell: true,
|
|
634
|
+
windowsHide: true,
|
|
635
|
+
stdio: 'pipe',
|
|
243
636
|
});
|
|
244
637
|
console.log(` ${r.stdout ? r.stdout.toString().trim() : r.stderr.toString().trim()}`);
|
|
245
638
|
|
|
246
639
|
for (const name of new Set([marketplaceName, 'HuaweiCloud-Devkit'])) {
|
|
247
640
|
console.log(` Removing Codex marketplace: ${name}`);
|
|
248
641
|
const r2 = spawnSync(`codex plugin marketplace remove "${name}"`, [], {
|
|
249
|
-
shell: true,
|
|
642
|
+
shell: true,
|
|
643
|
+
windowsHide: true,
|
|
644
|
+
stdio: 'pipe',
|
|
250
645
|
});
|
|
251
646
|
console.log(` ${r2.stdout ? r2.stdout.toString().trim() : r2.stderr.toString().trim()}`);
|
|
252
647
|
}
|
|
@@ -274,6 +669,7 @@ async function installOpenCode() {
|
|
|
274
669
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
275
670
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
276
671
|
updateOpenCodeConfig(pluginDest);
|
|
672
|
+
installRuntimeDeps(pluginDest);
|
|
277
673
|
}
|
|
278
674
|
|
|
279
675
|
function uninstallOpenCode() {
|
|
@@ -336,7 +732,9 @@ async function updateOpenCode() {
|
|
|
336
732
|
console.log(` Skills updated -> ${opencodeSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`);
|
|
337
733
|
copyDir(commandsSrc, opencodeCommandsDir());
|
|
338
734
|
const staleCommands = pruneStale(opencodeCommandsDir(), commandsSrc);
|
|
339
|
-
console.log(
|
|
735
|
+
console.log(
|
|
736
|
+
` Commands updated -> ${opencodeCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`,
|
|
737
|
+
);
|
|
340
738
|
copyDir(srcDir, join(pluginDest, 'src'));
|
|
341
739
|
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
342
740
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
@@ -344,6 +742,7 @@ async function updateOpenCode() {
|
|
|
344
742
|
updateOpenCodeConfig(pluginDest);
|
|
345
743
|
mkdirSync(pluginDest, { recursive: true });
|
|
346
744
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
745
|
+
installRuntimeDeps(pluginDest);
|
|
347
746
|
}
|
|
348
747
|
|
|
349
748
|
function codexMcpServerPath() {
|
|
@@ -367,7 +766,9 @@ function ensureCodexConfigSection(mcpPath) {
|
|
|
367
766
|
const configPath = codexConfigToml();
|
|
368
767
|
let existing = '';
|
|
369
768
|
if (existsSync(configPath)) {
|
|
370
|
-
try {
|
|
769
|
+
try {
|
|
770
|
+
existing = readFileSync(configPath, 'utf8');
|
|
771
|
+
} catch {}
|
|
371
772
|
if (existing.includes('[mcp_servers.huaweicloud-devkit]')) {
|
|
372
773
|
if (existing.includes(`args = ["${mcpPath}"]`)) {
|
|
373
774
|
console.log(` Config unchanged: ${configPath}`);
|
|
@@ -376,7 +777,9 @@ function ensureCodexConfigSection(mcpPath) {
|
|
|
376
777
|
removeCodexConfigSection();
|
|
377
778
|
existing = '';
|
|
378
779
|
if (existsSync(configPath)) {
|
|
379
|
-
try {
|
|
780
|
+
try {
|
|
781
|
+
existing = readFileSync(configPath, 'utf8');
|
|
782
|
+
} catch {}
|
|
380
783
|
}
|
|
381
784
|
}
|
|
382
785
|
}
|
|
@@ -394,7 +797,10 @@ function removeCodexConfigSection() {
|
|
|
394
797
|
const out = [];
|
|
395
798
|
let skip = false;
|
|
396
799
|
for (const line of lines) {
|
|
397
|
-
if (/^\[mcp_servers\.huaweicloud-devkit(\]|\.)/.test(line)) {
|
|
800
|
+
if (/^\[mcp_servers\.huaweicloud-devkit(\]|\.)/.test(line)) {
|
|
801
|
+
skip = true;
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
398
804
|
if (skip && line.startsWith('[')) skip = false;
|
|
399
805
|
if (!skip) out.push(line);
|
|
400
806
|
}
|
|
@@ -441,6 +847,7 @@ async function installCodexDesktop() {
|
|
|
441
847
|
}
|
|
442
848
|
|
|
443
849
|
ensureCodexConfigSection(mcpServerAbsPath);
|
|
850
|
+
installRuntimeDeps(codexDesktopPluginsDir());
|
|
444
851
|
}
|
|
445
852
|
|
|
446
853
|
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
@@ -453,10 +860,14 @@ async function updateCodexDesktop() {
|
|
|
453
860
|
|
|
454
861
|
copyDir(skillsSrc, codexDesktopSkillsDir());
|
|
455
862
|
const staleSkills = pruneStale(codexDesktopSkillsDir(), skillsSrc);
|
|
456
|
-
console.log(
|
|
863
|
+
console.log(
|
|
864
|
+
` Skills updated -> ${codexDesktopSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`,
|
|
865
|
+
);
|
|
457
866
|
copyDir(commandsSrc, codexDesktopCommandsDir());
|
|
458
867
|
const staleCommands = pruneStale(codexDesktopCommandsDir(), commandsSrc);
|
|
459
|
-
console.log(
|
|
868
|
+
console.log(
|
|
869
|
+
` Commands updated -> ${codexDesktopCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`,
|
|
870
|
+
);
|
|
460
871
|
mkdirSync(pluginDest, { recursive: true });
|
|
461
872
|
copyDir(srcDir, join(pluginDest, 'src'));
|
|
462
873
|
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
@@ -484,6 +895,7 @@ async function updateCodexDesktop() {
|
|
|
484
895
|
|
|
485
896
|
ensureCodexConfigSection(mcpServerAbsPath);
|
|
486
897
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
898
|
+
installRuntimeDeps(pluginDest);
|
|
487
899
|
}
|
|
488
900
|
|
|
489
901
|
function uninstallCodexDesktop() {
|
|
@@ -524,13 +936,22 @@ function registerCodeartsMcp(configPath) {
|
|
|
524
936
|
if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
|
|
525
937
|
let config = {};
|
|
526
938
|
if (existsSync(configPath)) {
|
|
527
|
-
try {
|
|
528
|
-
|
|
939
|
+
try {
|
|
940
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
941
|
+
} catch {
|
|
942
|
+
console.log(
|
|
943
|
+
` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`,
|
|
944
|
+
);
|
|
529
945
|
return;
|
|
530
946
|
}
|
|
531
947
|
const existing = config.mcpServers?.['huaweicloud-devkit'];
|
|
532
|
-
if (
|
|
533
|
-
|
|
948
|
+
if (
|
|
949
|
+
existing &&
|
|
950
|
+
existing.command === 'node' &&
|
|
951
|
+
Array.isArray(existing.args) &&
|
|
952
|
+
existing.args[0] === mcpPath &&
|
|
953
|
+
existing.timeout === 300000
|
|
954
|
+
) {
|
|
534
955
|
console.log(` MCP config unchanged: ${configPath}`);
|
|
535
956
|
return;
|
|
536
957
|
}
|
|
@@ -541,6 +962,7 @@ function registerCodeartsMcp(configPath) {
|
|
|
541
962
|
args: [mcpPath],
|
|
542
963
|
env,
|
|
543
964
|
enabled: true,
|
|
965
|
+
timeout: 300000,
|
|
544
966
|
};
|
|
545
967
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
546
968
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
@@ -565,6 +987,7 @@ async function installCodeArts() {
|
|
|
565
987
|
|
|
566
988
|
registerCodeartsMcp(codeartsMcpSettingsFile());
|
|
567
989
|
registerCodeartsMcp(codeartsProjectMcpSettingsFile());
|
|
990
|
+
installRuntimeDeps(pluginDest);
|
|
568
991
|
}
|
|
569
992
|
|
|
570
993
|
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
@@ -587,6 +1010,7 @@ async function updateCodeArts() {
|
|
|
587
1010
|
registerCodeartsMcp(codeartsProjectMcpSettingsFile());
|
|
588
1011
|
mkdirSync(pluginDest, { recursive: true });
|
|
589
1012
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
1013
|
+
installRuntimeDeps(pluginDest);
|
|
590
1014
|
}
|
|
591
1015
|
|
|
592
1016
|
function uninstallCodeArts() {
|
|
@@ -608,7 +1032,9 @@ function uninstallCodeArts() {
|
|
|
608
1032
|
for (const configPath of [codeartsMcpSettingsFile(), codeartsProjectMcpSettingsFile()]) {
|
|
609
1033
|
if (!existsSync(configPath)) continue;
|
|
610
1034
|
let config = {};
|
|
611
|
-
try {
|
|
1035
|
+
try {
|
|
1036
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1037
|
+
} catch {}
|
|
612
1038
|
if (config.mcpServers?.['huaweicloud-devkit']) {
|
|
613
1039
|
delete config.mcpServers['huaweicloud-devkit'];
|
|
614
1040
|
if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
|
|
@@ -618,96 +1044,861 @@ function uninstallCodeArts() {
|
|
|
618
1044
|
}
|
|
619
1045
|
}
|
|
620
1046
|
|
|
621
|
-
function codeartsStatus() {
|
|
622
|
-
const pluginDir = codeartsPluginsDir();
|
|
623
|
-
console.log(
|
|
624
|
-
|
|
1047
|
+
function codeartsStatus() {
|
|
1048
|
+
const pluginDir = codeartsPluginsDir();
|
|
1049
|
+
console.log(
|
|
1050
|
+
` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1051
|
+
);
|
|
1052
|
+
console.log(
|
|
1053
|
+
` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1054
|
+
);
|
|
1055
|
+
let skillCount = 0;
|
|
1056
|
+
if (existsSync(codeartsSkillsDir())) {
|
|
1057
|
+
skillCount = readdirSync(codeartsSkillsDir(), { withFileTypes: true }).filter(
|
|
1058
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
1059
|
+
).length;
|
|
1060
|
+
}
|
|
1061
|
+
console.log(
|
|
1062
|
+
` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1063
|
+
);
|
|
1064
|
+
if (existsSync(codeartsMcpSettingsFile())) {
|
|
1065
|
+
try {
|
|
1066
|
+
const config = JSON.parse(readFileSync(codeartsMcpSettingsFile(), 'utf8'));
|
|
1067
|
+
console.log(
|
|
1068
|
+
` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
|
|
1069
|
+
);
|
|
1070
|
+
} catch {
|
|
1071
|
+
console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// Returns true when the config file was written, false when it was already correct.
|
|
1077
|
+
function ensureWorkbuddyMcpConfig() {
|
|
1078
|
+
const configPath = workbuddyMcpConfigFile();
|
|
1079
|
+
const mcpPath = join(workbuddyPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
1080
|
+
const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
|
|
1081
|
+
const hcloudBin = findHcloudBin();
|
|
1082
|
+
if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
|
|
1083
|
+
let config = {};
|
|
1084
|
+
if (existsSync(configPath)) {
|
|
1085
|
+
try {
|
|
1086
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1087
|
+
} catch {
|
|
1088
|
+
console.log(
|
|
1089
|
+
` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`,
|
|
1090
|
+
);
|
|
1091
|
+
return false;
|
|
1092
|
+
}
|
|
1093
|
+
const existing = config.mcpServers?.['huaweicloud-devkit'];
|
|
1094
|
+
if (
|
|
1095
|
+
existing &&
|
|
1096
|
+
existing.command === 'node' &&
|
|
1097
|
+
Array.isArray(existing.args) &&
|
|
1098
|
+
existing.args[0] === mcpPath &&
|
|
1099
|
+
existing.timeout === 300000
|
|
1100
|
+
) {
|
|
1101
|
+
console.log(` MCP config unchanged: ${configPath}`);
|
|
1102
|
+
return false;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
config.mcpServers = config.mcpServers || {};
|
|
1106
|
+
config.mcpServers['huaweicloud-devkit'] = {
|
|
1107
|
+
command: 'node',
|
|
1108
|
+
args: [mcpPath],
|
|
1109
|
+
env,
|
|
1110
|
+
timeout: 300000,
|
|
1111
|
+
};
|
|
1112
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
1113
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1114
|
+
console.log(` MCP config updated: ${configPath}`);
|
|
1115
|
+
return true;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
async function installWorkBuddy() {
|
|
1119
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
1120
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
1121
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
1122
|
+
const pluginDest = workbuddyPluginsDir();
|
|
1123
|
+
|
|
1124
|
+
copyDir(skillsSrc, workbuddySkillsDir());
|
|
1125
|
+
console.log(` Skills -> ${workbuddySkillsDir()}`);
|
|
1126
|
+
|
|
1127
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
1128
|
+
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
1129
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1130
|
+
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
1131
|
+
|
|
1132
|
+
ensureWorkbuddyMcpConfig();
|
|
1133
|
+
installRuntimeDeps(pluginDest);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
1137
|
+
async function updateWorkBuddy() {
|
|
1138
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
1139
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
1140
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
1141
|
+
const pluginDest = workbuddyPluginsDir();
|
|
1142
|
+
|
|
1143
|
+
copyDir(skillsSrc, workbuddySkillsDir());
|
|
1144
|
+
const stale = pruneStale(workbuddySkillsDir(), skillsSrc);
|
|
1145
|
+
console.log(` Skills updated -> ${workbuddySkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
|
|
1146
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
1147
|
+
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
1148
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1149
|
+
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
1150
|
+
ensureWorkbuddyMcpConfig();
|
|
1151
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
1152
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
1153
|
+
installRuntimeDeps(pluginDest);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function uninstallWorkBuddy() {
|
|
1157
|
+
const skillsDir = workbuddySkillsDir();
|
|
1158
|
+
let removed = 0;
|
|
1159
|
+
if (existsSync(skillsDir)) {
|
|
1160
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
1161
|
+
if (entry.name.startsWith('huawei')) {
|
|
1162
|
+
removeIfExists(join(skillsDir, entry.name));
|
|
1163
|
+
removed++;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
if (removed > 0) console.log(` Removed ${removed} skills`);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
if (removeIfExists(workbuddyPluginsDir())) {
|
|
1170
|
+
console.log(' Removed MCP server and safety policy');
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
const configPath = workbuddyMcpConfigFile();
|
|
1174
|
+
if (existsSync(configPath)) {
|
|
1175
|
+
let config = {};
|
|
1176
|
+
try {
|
|
1177
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1178
|
+
} catch {}
|
|
1179
|
+
if (config.mcpServers?.['huaweicloud-devkit']) {
|
|
1180
|
+
delete config.mcpServers['huaweicloud-devkit'];
|
|
1181
|
+
if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
|
|
1182
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1183
|
+
console.log(` MCP config cleaned: ${configPath}`);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
function workbuddyStatus() {
|
|
1189
|
+
const pluginDir = workbuddyPluginsDir();
|
|
1190
|
+
const skillsDir = workbuddySkillsDir();
|
|
1191
|
+
console.log(
|
|
1192
|
+
` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1193
|
+
);
|
|
1194
|
+
console.log(
|
|
1195
|
+
` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1196
|
+
);
|
|
1197
|
+
let skillCount = 0;
|
|
1198
|
+
if (existsSync(skillsDir)) {
|
|
1199
|
+
skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
|
|
1200
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
1201
|
+
).length;
|
|
1202
|
+
}
|
|
1203
|
+
console.log(
|
|
1204
|
+
` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1205
|
+
);
|
|
1206
|
+
const configPath = workbuddyMcpConfigFile();
|
|
1207
|
+
if (existsSync(configPath)) {
|
|
1208
|
+
try {
|
|
1209
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1210
|
+
console.log(
|
|
1211
|
+
` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
|
|
1212
|
+
);
|
|
1213
|
+
} catch {
|
|
1214
|
+
console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function ensureAtomcodeMcpConfig() {
|
|
1220
|
+
const configPath = atomcodeMcpConfigFile();
|
|
1221
|
+
const mcpPath = join(atomcodePluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
1222
|
+
const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
|
|
1223
|
+
const hcloudBin = findHcloudBin();
|
|
1224
|
+
if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
|
|
1225
|
+
let config = {};
|
|
1226
|
+
if (existsSync(configPath)) {
|
|
1227
|
+
try {
|
|
1228
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1229
|
+
} catch {
|
|
1230
|
+
console.log(
|
|
1231
|
+
` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`,
|
|
1232
|
+
);
|
|
1233
|
+
return false;
|
|
1234
|
+
}
|
|
1235
|
+
const existing = config.mcpServers?.['huaweicloud-devkit'];
|
|
1236
|
+
if (
|
|
1237
|
+
existing &&
|
|
1238
|
+
existing.command === 'node' &&
|
|
1239
|
+
Array.isArray(existing.args) &&
|
|
1240
|
+
existing.args[0] === mcpPath &&
|
|
1241
|
+
existing.timeout === 300000
|
|
1242
|
+
) {
|
|
1243
|
+
console.log(` MCP config unchanged: ${configPath}`);
|
|
1244
|
+
return false;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
config.mcpServers = config.mcpServers || {};
|
|
1248
|
+
config.mcpServers['huaweicloud-devkit'] = {
|
|
1249
|
+
command: 'node',
|
|
1250
|
+
args: [mcpPath],
|
|
1251
|
+
env,
|
|
1252
|
+
timeout: 300000,
|
|
1253
|
+
};
|
|
1254
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
1255
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1256
|
+
console.log(` MCP config updated: ${configPath}`);
|
|
1257
|
+
return true;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
async function installAtomCode() {
|
|
1261
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
1262
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
1263
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
1264
|
+
const pluginDest = atomcodePluginsDir();
|
|
1265
|
+
|
|
1266
|
+
copyDir(skillsSrc, atomcodeSkillsDir());
|
|
1267
|
+
console.log(` Skills -> ${atomcodeSkillsDir()}`);
|
|
1268
|
+
|
|
1269
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
1270
|
+
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
1271
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1272
|
+
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
1273
|
+
|
|
1274
|
+
ensureAtomcodeMcpConfig();
|
|
1275
|
+
installRuntimeDeps(pluginDest);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
1279
|
+
async function updateAtomCode() {
|
|
1280
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
1281
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
1282
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
1283
|
+
const pluginDest = atomcodePluginsDir();
|
|
1284
|
+
|
|
1285
|
+
copyDir(skillsSrc, atomcodeSkillsDir());
|
|
1286
|
+
const stale = pruneStale(atomcodeSkillsDir(), skillsSrc);
|
|
1287
|
+
console.log(` Skills updated -> ${atomcodeSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
|
|
1288
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
1289
|
+
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
1290
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1291
|
+
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
1292
|
+
ensureAtomcodeMcpConfig();
|
|
1293
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
1294
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
1295
|
+
installRuntimeDeps(pluginDest);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
function uninstallAtomCode() {
|
|
1299
|
+
const skillsDir = atomcodeSkillsDir();
|
|
1300
|
+
let removed = 0;
|
|
1301
|
+
if (existsSync(skillsDir)) {
|
|
1302
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
1303
|
+
if (entry.name.startsWith('huawei')) {
|
|
1304
|
+
removeIfExists(join(skillsDir, entry.name));
|
|
1305
|
+
removed++;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
if (removed > 0) console.log(` Removed ${removed} skills`);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
if (removeIfExists(atomcodePluginsDir())) {
|
|
1312
|
+
console.log(' Removed MCP server and safety policy');
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
const configPath = atomcodeMcpConfigFile();
|
|
1316
|
+
if (existsSync(configPath)) {
|
|
1317
|
+
let config = {};
|
|
1318
|
+
try {
|
|
1319
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1320
|
+
} catch {}
|
|
1321
|
+
if (config.mcpServers?.['huaweicloud-devkit']) {
|
|
1322
|
+
delete config.mcpServers['huaweicloud-devkit'];
|
|
1323
|
+
if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
|
|
1324
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1325
|
+
console.log(` MCP config cleaned: ${configPath}`);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
function atomcodeStatus() {
|
|
1331
|
+
const pluginDir = atomcodePluginsDir();
|
|
1332
|
+
const skillsDir = atomcodeSkillsDir();
|
|
1333
|
+
console.log(
|
|
1334
|
+
` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1335
|
+
);
|
|
1336
|
+
console.log(
|
|
1337
|
+
` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1338
|
+
);
|
|
1339
|
+
let skillCount = 0;
|
|
1340
|
+
if (existsSync(skillsDir)) {
|
|
1341
|
+
skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
|
|
1342
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
1343
|
+
).length;
|
|
1344
|
+
}
|
|
1345
|
+
console.log(
|
|
1346
|
+
` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1347
|
+
);
|
|
1348
|
+
const configPath = atomcodeMcpConfigFile();
|
|
1349
|
+
if (existsSync(configPath)) {
|
|
1350
|
+
try {
|
|
1351
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1352
|
+
console.log(
|
|
1353
|
+
` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
|
|
1354
|
+
);
|
|
1355
|
+
} catch {
|
|
1356
|
+
console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
function dshMcpServerPath() {
|
|
1362
|
+
return join(dshPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
function dshPatchBlock() {
|
|
1366
|
+
const hcloudBin = findHcloudBin();
|
|
1367
|
+
const envLines = [' HUAWEICLOUD_AGENT_TOOLKIT_MODE: local', " HDKITSERVICE_ENDPOINT: ''"];
|
|
1368
|
+
if (hcloudBin) {
|
|
1369
|
+
envLines.push(` HCLOUD_BIN: '${hcloudBin.replace(/\\/g, '/').replace(/'/g, "''")}'`);
|
|
1370
|
+
}
|
|
1371
|
+
return [
|
|
1372
|
+
DSH_MCP_PATCH_START,
|
|
1373
|
+
'- insert:',
|
|
1374
|
+
' - id: mcp-huaweicloud',
|
|
1375
|
+
" name: '@deepseek-ai/dsh-mcp-client'",
|
|
1376
|
+
' config:',
|
|
1377
|
+
' serverName: huaweicloud',
|
|
1378
|
+
' transport: stdio',
|
|
1379
|
+
' command: node',
|
|
1380
|
+
' args:',
|
|
1381
|
+
` - '${dshMcpServerPath().replace(/'/g, "''")}'`,
|
|
1382
|
+
' env:',
|
|
1383
|
+
...envLines,
|
|
1384
|
+
' failOnStartupError: false',
|
|
1385
|
+
DSH_MCP_PATCH_END,
|
|
1386
|
+
].join('\n');
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
function escapeRegExp(value) {
|
|
1390
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
function removeManagedDshPatchBlock(content) {
|
|
1394
|
+
const pattern = new RegExp(
|
|
1395
|
+
`\\n?${escapeRegExp(DSH_MCP_PATCH_START)}[\\s\\S]*?${escapeRegExp(DSH_MCP_PATCH_END)}\\s*`,
|
|
1396
|
+
'g',
|
|
1397
|
+
);
|
|
1398
|
+
return String(content || '')
|
|
1399
|
+
.replace(pattern, '\n')
|
|
1400
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
1401
|
+
.trimEnd();
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
function dshPatchHasOnlyCommentsOrEmptyList(content) {
|
|
1405
|
+
const meaningful = String(content || '')
|
|
1406
|
+
.split(/\r?\n/)
|
|
1407
|
+
.map((line) => line.trim())
|
|
1408
|
+
.filter((line) => line && !line.startsWith('#'));
|
|
1409
|
+
return meaningful.length === 0 || (meaningful.length === 1 && meaningful[0] === '[]');
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
function ensureDshMcpPatch() {
|
|
1413
|
+
const patchFile = dshPatchFile();
|
|
1414
|
+
const existing = existsSync(patchFile) ? readFileSync(patchFile, 'utf8') : '';
|
|
1415
|
+
const cleaned = removeManagedDshPatchBlock(existing);
|
|
1416
|
+
const block = dshPatchBlock();
|
|
1417
|
+
let next;
|
|
1418
|
+
if (dshPatchHasOnlyCommentsOrEmptyList(cleaned)) {
|
|
1419
|
+
const prefix = cleaned
|
|
1420
|
+
.split(/\r?\n/)
|
|
1421
|
+
.filter((line) => line.trim() !== '[]')
|
|
1422
|
+
.join('\n')
|
|
1423
|
+
.trimEnd();
|
|
1424
|
+
next = `${prefix ? `${prefix}\n` : ''}${block}\n`;
|
|
1425
|
+
} else {
|
|
1426
|
+
next = `${cleaned}\n\n${block}\n`;
|
|
1427
|
+
}
|
|
1428
|
+
if (existing.replace(/\r\n/g, '\n') === next) {
|
|
1429
|
+
console.log(` DSH patch unchanged: ${patchFile}`);
|
|
1430
|
+
return false;
|
|
1431
|
+
}
|
|
1432
|
+
mkdirSync(dirname(patchFile), { recursive: true });
|
|
1433
|
+
writeFileSync(patchFile, next);
|
|
1434
|
+
console.log(` DSH patch updated: ${patchFile}`);
|
|
1435
|
+
return true;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
function removeDshMcpPatch() {
|
|
1439
|
+
const patchFile = dshPatchFile();
|
|
1440
|
+
if (!existsSync(patchFile)) return false;
|
|
1441
|
+
const existing = readFileSync(patchFile, 'utf8');
|
|
1442
|
+
const cleaned = removeManagedDshPatchBlock(existing);
|
|
1443
|
+
if (cleaned === existing.trimEnd()) return false;
|
|
1444
|
+
const prefix = cleaned
|
|
1445
|
+
.split(/\r?\n/)
|
|
1446
|
+
.filter((line) => line.trim() !== '[]')
|
|
1447
|
+
.join('\n')
|
|
1448
|
+
.trimEnd();
|
|
1449
|
+
const next = dshPatchHasOnlyCommentsOrEmptyList(cleaned) ? `${prefix ? `${prefix}\n` : ''}[]\n` : `${cleaned}\n`;
|
|
1450
|
+
writeFileSync(patchFile, next);
|
|
1451
|
+
console.log(` DSH patch cleaned: ${patchFile}`);
|
|
1452
|
+
return true;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
function dshPatchConfigured() {
|
|
1456
|
+
const patchFile = dshPatchFile();
|
|
1457
|
+
if (!existsSync(patchFile)) return false;
|
|
1458
|
+
try {
|
|
1459
|
+
const patch = readFileSync(patchFile, 'utf8');
|
|
1460
|
+
return (
|
|
1461
|
+
patch.includes('id: mcp-huaweicloud') &&
|
|
1462
|
+
patch.includes('@deepseek-ai/dsh-mcp-client') &&
|
|
1463
|
+
patch.includes('serverName: huaweicloud')
|
|
1464
|
+
);
|
|
1465
|
+
} catch {
|
|
1466
|
+
return false;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
function commandAvailable(command, args = ['--version']) {
|
|
1471
|
+
try {
|
|
1472
|
+
const r = spawnSync(command, args, { shell: false, windowsHide: true, stdio: 'pipe', timeout: 10000 });
|
|
1473
|
+
if (r.status === 0) return true;
|
|
1474
|
+
} catch {}
|
|
1475
|
+
if (process.platform === 'win32') {
|
|
1476
|
+
try {
|
|
1477
|
+
const w = spawnSync('where.exe', [command], { windowsHide: true, stdio: 'pipe', timeout: 10000 });
|
|
1478
|
+
return w.status === 0 && w.stdout.toString().trim().length > 0;
|
|
1479
|
+
} catch {}
|
|
1480
|
+
}
|
|
1481
|
+
return false;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
function dshMcpClientAvailable() {
|
|
1485
|
+
const modulePath = join('node_modules', '@deepseek-ai', 'dsh-mcp-client', 'package.json');
|
|
1486
|
+
const candidates = [
|
|
1487
|
+
join(dshProfileDir(), modulePath),
|
|
1488
|
+
join(dshRoot(), 'profiles', modulePath),
|
|
1489
|
+
join(dshRoot(), modulePath),
|
|
1490
|
+
];
|
|
1491
|
+
if (candidates.some((p) => existsSync(p))) return true;
|
|
1492
|
+
const pkgPath = join(dshProfileDir(), 'package.json');
|
|
1493
|
+
if (!existsSync(pkgPath)) return false;
|
|
1494
|
+
try {
|
|
1495
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
1496
|
+
return Boolean(
|
|
1497
|
+
pkg.dependencies?.['@deepseek-ai/dsh-mcp-client'] || pkg.devDependencies?.['@deepseek-ai/dsh-mcp-client'],
|
|
1498
|
+
);
|
|
1499
|
+
} catch {
|
|
1500
|
+
return false;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
function tryInstallDshMcpClient() {
|
|
1505
|
+
if (process.env.HUAWEICLOUD_DEVKIT_SKIP_DSH_PLUGIN_INSTALL === '1') {
|
|
1506
|
+
console.log(' DSH MCP client install skipped by environment');
|
|
1507
|
+
return false;
|
|
1508
|
+
}
|
|
1509
|
+
if (dshMcpClientAvailable()) {
|
|
1510
|
+
console.log(' DSH MCP client package detected');
|
|
1511
|
+
return true;
|
|
1512
|
+
}
|
|
1513
|
+
if (commandAvailable('dsh')) {
|
|
1514
|
+
const r = spawnSync('dsh', ['plugin', '--profile', 'web', 'add', '@deepseek-ai/dsh-mcp-client'], {
|
|
1515
|
+
env: { ...process.env, DSH_HOME: dshRoot() },
|
|
1516
|
+
windowsHide: true,
|
|
1517
|
+
stdio: 'pipe',
|
|
1518
|
+
timeout: 60000,
|
|
1519
|
+
});
|
|
1520
|
+
if (r.status === 0) {
|
|
1521
|
+
console.log(' DSH MCP client package installed via dsh');
|
|
1522
|
+
return true;
|
|
1523
|
+
}
|
|
1524
|
+
const err = `${r.stderr || ''}${r.stdout || ''}`.trim().split(/\r?\n/).slice(-2).join(' ');
|
|
1525
|
+
console.log(` \x1b[33m[WARN]\x1b[0m DSH MCP client auto-install failed${err ? `: ${err}` : ''}`);
|
|
1526
|
+
}
|
|
1527
|
+
if (commandAvailable('pnpm') && existsSync(join(dshProfileDir(), 'package.json'))) {
|
|
1528
|
+
const r = spawnSync('pnpm', ['--dir', dshProfileDir(), 'add', '@deepseek-ai/dsh-mcp-client'], {
|
|
1529
|
+
windowsHide: true,
|
|
1530
|
+
stdio: 'pipe',
|
|
1531
|
+
timeout: 60000,
|
|
1532
|
+
});
|
|
1533
|
+
if (r.status === 0) {
|
|
1534
|
+
console.log(' DSH MCP client package installed via pnpm');
|
|
1535
|
+
return true;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
console.log(' \x1b[33m[WARN]\x1b[0m DSH MCP client package not detected.');
|
|
1539
|
+
console.log(' Manual: npx @deepseek-ai/dsh plugin --profile web add @deepseek-ai/dsh-mcp-client');
|
|
1540
|
+
console.log(' If pnpm is missing, run: corepack enable pnpm');
|
|
1541
|
+
return false;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
async function installDsh() {
|
|
1545
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
1546
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
1547
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
1548
|
+
const pluginDest = dshPluginsDir();
|
|
1549
|
+
|
|
1550
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
1551
|
+
copyDir(skillsSrc, dshSkillsDir());
|
|
1552
|
+
console.log(` Skills -> ${dshSkillsDir()}`);
|
|
1553
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
1554
|
+
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
1555
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1556
|
+
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
1557
|
+
ensureDshMcpPatch();
|
|
1558
|
+
tryInstallDshMcpClient();
|
|
1559
|
+
installRuntimeDeps(pluginDest);
|
|
1560
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
async function updateDsh() {
|
|
1564
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
1565
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
1566
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
1567
|
+
const pluginDest = dshPluginsDir();
|
|
1568
|
+
|
|
1569
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
1570
|
+
copyDir(skillsSrc, dshSkillsDir());
|
|
1571
|
+
const stale = pruneStale(dshSkillsDir(), skillsSrc);
|
|
1572
|
+
console.log(` Skills updated -> ${dshSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
|
|
1573
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
1574
|
+
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
1575
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1576
|
+
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
1577
|
+
ensureDshMcpPatch();
|
|
1578
|
+
tryInstallDshMcpClient();
|
|
1579
|
+
installRuntimeDeps(pluginDest);
|
|
1580
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
function uninstallDsh() {
|
|
1584
|
+
const skillsDir = dshSkillsDir();
|
|
1585
|
+
let removed = 0;
|
|
1586
|
+
if (existsSync(skillsDir)) {
|
|
1587
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
1588
|
+
if (entry.name.startsWith('huawei')) {
|
|
1589
|
+
removeIfExists(join(skillsDir, entry.name));
|
|
1590
|
+
removed++;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
if (removed > 0) console.log(` Removed ${removed} skills`);
|
|
1594
|
+
try {
|
|
1595
|
+
if (readdirSync(skillsDir).length === 0) {
|
|
1596
|
+
rmSync(skillsDir, { recursive: true, force: true });
|
|
1597
|
+
console.log(` Removed empty skills directory: ${skillsDir}`);
|
|
1598
|
+
}
|
|
1599
|
+
} catch {}
|
|
1600
|
+
}
|
|
1601
|
+
if (removeIfExists(dshPluginsDir())) {
|
|
1602
|
+
console.log(' Removed MCP server and safety policy');
|
|
1603
|
+
}
|
|
1604
|
+
removeDshMcpPatch();
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
function dshStatus() {
|
|
1608
|
+
const pluginDir = dshPluginsDir();
|
|
1609
|
+
const skillsDir = dshSkillsDir();
|
|
1610
|
+
console.log(
|
|
1611
|
+
` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1612
|
+
);
|
|
1613
|
+
console.log(
|
|
1614
|
+
` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1615
|
+
);
|
|
1616
|
+
let skillCount = 0;
|
|
1617
|
+
if (existsSync(skillsDir)) {
|
|
1618
|
+
skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
|
|
1619
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
1620
|
+
).length;
|
|
1621
|
+
}
|
|
1622
|
+
console.log(
|
|
1623
|
+
` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1624
|
+
);
|
|
1625
|
+
console.log(` DSH patch: ${dshPatchConfigured() ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
|
|
1626
|
+
console.log(
|
|
1627
|
+
` DSH MCP client package: ${dshMcpClientAvailable() ? '\x1b[32mDetected\x1b[0m' : '\x1b[33mCheck DSH profile\x1b[0m'}`,
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
async function promptOfficeaceInstallDir() {
|
|
1632
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1633
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
1634
|
+
while (true) {
|
|
1635
|
+
const path = (await ask(' Install directory: ')).trim();
|
|
1636
|
+
if (!path) {
|
|
1637
|
+
console.log(' \x1b[33mPath cannot be empty.\x1b[0m');
|
|
1638
|
+
continue;
|
|
1639
|
+
}
|
|
1640
|
+
const capFile = join(path, '.office-claw', 'capabilities.json');
|
|
1641
|
+
if (existsSync(capFile)) {
|
|
1642
|
+
rl.close();
|
|
1643
|
+
return path;
|
|
1644
|
+
}
|
|
1645
|
+
console.log(` \x1b[33mcapabilities.json not found at: ${capFile}\x1b[0m`);
|
|
1646
|
+
console.log(' Please verify the path and try again.');
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
async function installOfficeAce() {
|
|
1651
|
+
if (!officeaceCapabilitiesDir()) {
|
|
1652
|
+
if (process.stdin.isTTY) {
|
|
1653
|
+
console.log(' \x1b[33mOfficeAce install directory not found automatically.\x1b[0m');
|
|
1654
|
+
console.log(' Please enter the OfficeAce install directory.');
|
|
1655
|
+
const entered = await promptOfficeaceInstallDir();
|
|
1656
|
+
process.env.OFFICE_CLAW_CONFIG_ROOT = join(entered, '.office-claw');
|
|
1657
|
+
} else {
|
|
1658
|
+
console.log(
|
|
1659
|
+
' \x1b[31mOfficeAce install directory not found. Please set OFFICE_CLAW_CONFIG_ROOT env var and retry.\x1b[0m',
|
|
1660
|
+
);
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
1665
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
1666
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
1667
|
+
const pluginDest = officeacePluginsDir();
|
|
1668
|
+
|
|
1669
|
+
copyDir(skillsSrc, officeaceSkillsDir());
|
|
1670
|
+
console.log(` Skills -> ${officeaceSkillsDir()}`);
|
|
1671
|
+
|
|
1672
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
1673
|
+
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
1674
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1675
|
+
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
1676
|
+
|
|
1677
|
+
installRuntimeDeps(pluginDest);
|
|
1678
|
+
ensureOfficeaceMcpInSqlite();
|
|
1679
|
+
registerOfficeaceSkillEntries();
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
async function updateOfficeAce() {
|
|
1683
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
1684
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
1685
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
1686
|
+
const pluginDest = officeacePluginsDir();
|
|
1687
|
+
|
|
1688
|
+
copyDir(skillsSrc, officeaceSkillsDir());
|
|
1689
|
+
const stale = pruneStale(officeaceSkillsDir(), skillsSrc);
|
|
1690
|
+
console.log(` Skills updated -> ${officeaceSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
|
|
1691
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
1692
|
+
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
1693
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1694
|
+
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
1695
|
+
installRuntimeDeps(pluginDest);
|
|
1696
|
+
ensureOfficeaceMcpInSqlite();
|
|
1697
|
+
registerOfficeaceSkillEntries();
|
|
1698
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
1699
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
function uninstallOfficeAce() {
|
|
1703
|
+
const skillsDir = officeaceSkillsDir();
|
|
1704
|
+
let removed = 0;
|
|
1705
|
+
if (existsSync(skillsDir)) {
|
|
1706
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
1707
|
+
if (entry.name.startsWith('huawei')) {
|
|
1708
|
+
removeIfExists(join(skillsDir, entry.name));
|
|
1709
|
+
removed++;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
if (removed > 0) console.log(` Removed ${removed} skills`);
|
|
1713
|
+
try {
|
|
1714
|
+
if (readdirSync(skillsDir).length === 0) {
|
|
1715
|
+
rmSync(skillsDir, { recursive: true, force: true });
|
|
1716
|
+
console.log(` Removed empty skills directory: ${skillsDir}`);
|
|
1717
|
+
}
|
|
1718
|
+
} catch {}
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
if (removeIfExists(officeacePluginsDir())) {
|
|
1722
|
+
console.log(' Removed MCP server and safety policy');
|
|
1723
|
+
}
|
|
1724
|
+
removeOfficeaceSkillCapabilities();
|
|
1725
|
+
removeOfficeaceMcpFromSqlite();
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
function officeaceStatus() {
|
|
1729
|
+
const pluginDir = officeacePluginsDir();
|
|
1730
|
+
const skillsDir = officeaceSkillsDir();
|
|
1731
|
+
console.log(
|
|
1732
|
+
` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1733
|
+
);
|
|
1734
|
+
console.log(
|
|
1735
|
+
` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1736
|
+
);
|
|
625
1737
|
let skillCount = 0;
|
|
626
|
-
if (existsSync(
|
|
627
|
-
skillCount = readdirSync(
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
1738
|
+
if (existsSync(skillsDir)) {
|
|
1739
|
+
skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
|
|
1740
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
1741
|
+
).length;
|
|
1742
|
+
}
|
|
1743
|
+
console.log(
|
|
1744
|
+
` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1745
|
+
);
|
|
1746
|
+
const dbPath = officeaceSqlitePath();
|
|
1747
|
+
if (existsSync(dbPath)) {
|
|
632
1748
|
try {
|
|
633
|
-
const
|
|
634
|
-
|
|
1749
|
+
const db = openOfficeaceDb();
|
|
1750
|
+
const row = db.prepare("SELECT enabled, status FROM mcp_connectors WHERE name = 'huaweicloud-devkit'").get();
|
|
1751
|
+
db.close();
|
|
1752
|
+
if (row) {
|
|
1753
|
+
const statusIcon = row.status === 'connected' ? '\x1b[32m' : '\x1b[33m';
|
|
1754
|
+
console.log(` MCP config: ${statusIcon}${row.status}\x1b[0m (enabled: ${row.enabled ? 'yes' : 'no'})`);
|
|
1755
|
+
} else {
|
|
1756
|
+
console.log(` MCP config: \x1b[31mNot configured\x1b[0m`);
|
|
1757
|
+
}
|
|
635
1758
|
} catch {
|
|
636
1759
|
console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
|
|
637
1760
|
}
|
|
1761
|
+
} else {
|
|
1762
|
+
console.log(` MCP config: \x1b[31mDatabase not found\x1b[0m`);
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
// ── Hermes Agent ──
|
|
1767
|
+
|
|
1768
|
+
function hermesHomeDir() {
|
|
1769
|
+
if (process.env.HERMES_HOME) return process.env.HERMES_HOME;
|
|
1770
|
+
// Hermes on Windows stores under LOCALAPPDATA, not ~/.hermes
|
|
1771
|
+
if (platform() === 'win32' && process.env.LOCALAPPDATA) {
|
|
1772
|
+
return join(process.env.LOCALAPPDATA, 'hermes');
|
|
638
1773
|
}
|
|
1774
|
+
return join(homedir(), '.hermes');
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
function hermesSkillsDir() {
|
|
1778
|
+
return join(hermesHomeDir(), 'skills');
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
function hermesPluginsDir() {
|
|
1782
|
+
return join(hermesHomeDir(), 'huaweicloud-plugins');
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
function hermesConfigFile() {
|
|
1786
|
+
return join(hermesHomeDir(), 'config.yaml');
|
|
639
1787
|
}
|
|
640
1788
|
|
|
641
1789
|
// 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' };
|
|
1790
|
+
function ensureHermesMcpConfig() {
|
|
1791
|
+
const configPath = hermesConfigFile();
|
|
1792
|
+
const mcpPath = join(hermesPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
646
1793
|
const hcloudBin = findHcloudBin();
|
|
647
|
-
|
|
648
|
-
|
|
1794
|
+
|
|
1795
|
+
const blockLines = [
|
|
1796
|
+
'mcp_servers:',
|
|
1797
|
+
' huaweicloud-devkit:',
|
|
1798
|
+
' command: "node"',
|
|
1799
|
+
` args: ["${mcpPath}"]`,
|
|
1800
|
+
' env:',
|
|
1801
|
+
' HUAWEICLOUD_AGENT_TOOLKIT_MODE: "local"',
|
|
1802
|
+
];
|
|
1803
|
+
if (hcloudBin) {
|
|
1804
|
+
blockLines.push(` HCLOUD_BIN: "${hcloudBin.replace(/\\/g, '/')}"`);
|
|
1805
|
+
}
|
|
1806
|
+
const block = blockLines.join('\n');
|
|
1807
|
+
|
|
1808
|
+
let existing = '';
|
|
649
1809
|
if (existsSync(configPath)) {
|
|
650
|
-
try {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1810
|
+
try {
|
|
1811
|
+
existing = readFileSync(configPath, 'utf8');
|
|
1812
|
+
} catch {}
|
|
1813
|
+
if (existing.includes('mcp_servers:') && existing.includes('huaweicloud-devkit')) {
|
|
1814
|
+
if (existing.includes(`args: ["${mcpPath}"]`)) {
|
|
1815
|
+
console.log(` MCP config unchanged: ${configPath}`);
|
|
1816
|
+
return false;
|
|
1817
|
+
}
|
|
1818
|
+
removeHermesMcpConfigBlock();
|
|
1819
|
+
existing = '';
|
|
1820
|
+
if (existsSync(configPath)) {
|
|
1821
|
+
try {
|
|
1822
|
+
existing = readFileSync(configPath, 'utf8');
|
|
1823
|
+
} catch {}
|
|
1824
|
+
}
|
|
659
1825
|
}
|
|
660
1826
|
}
|
|
661
|
-
config.mcpServers = config.mcpServers || {};
|
|
662
|
-
config.mcpServers['huaweicloud-devkit'] = {
|
|
663
|
-
command: 'node',
|
|
664
|
-
args: [mcpPath],
|
|
665
|
-
env,
|
|
666
|
-
};
|
|
667
1827
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
668
|
-
|
|
1828
|
+
const newContent = existing ? `${existing.trimEnd()}\n\n${block}\n` : `${block}\n`;
|
|
1829
|
+
writeFileSync(configPath, newContent);
|
|
669
1830
|
console.log(` MCP config updated: ${configPath}`);
|
|
670
1831
|
return true;
|
|
671
1832
|
}
|
|
672
1833
|
|
|
673
|
-
|
|
1834
|
+
function removeHermesMcpConfigBlock() {
|
|
1835
|
+
const configPath = hermesConfigFile();
|
|
1836
|
+
if (!existsSync(configPath)) return;
|
|
1837
|
+
const lines = readFileSync(configPath, 'utf8').split(/\r?\n/);
|
|
1838
|
+
const out = [];
|
|
1839
|
+
let skip = false;
|
|
1840
|
+
for (const line of lines) {
|
|
1841
|
+
if (/^\s*huaweicloud-devkit\s*:/.test(line)) {
|
|
1842
|
+
skip = true;
|
|
1843
|
+
continue;
|
|
1844
|
+
}
|
|
1845
|
+
if (skip) {
|
|
1846
|
+
// Continue skipping indented lines (the server config block)
|
|
1847
|
+
if (/^\s{2,}/.test(line) && line.trim() !== '') continue;
|
|
1848
|
+
// Stop skipping at a top-level key or empty line
|
|
1849
|
+
skip = false;
|
|
1850
|
+
}
|
|
1851
|
+
// If we just skipped the only server under mcp_servers, remove the key too
|
|
1852
|
+
if (line.trim() === 'mcp_servers:') {
|
|
1853
|
+
continue; // skip the mcp_servers key for now, re-add if other servers exist
|
|
1854
|
+
}
|
|
1855
|
+
out.push(line);
|
|
1856
|
+
}
|
|
1857
|
+
// Reconstruct: add mcp_servers back if there are other servers
|
|
1858
|
+
const cleaned = out.join('\n').trimEnd();
|
|
1859
|
+
writeFileSync(configPath, cleaned ? `${cleaned}\n` : '');
|
|
1860
|
+
console.log(' MCP config cleaned');
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
async function installHermes() {
|
|
674
1864
|
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
675
1865
|
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
676
1866
|
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
677
|
-
const pluginDest =
|
|
1867
|
+
const pluginDest = hermesPluginsDir();
|
|
678
1868
|
|
|
679
|
-
copyDir(skillsSrc,
|
|
680
|
-
console.log(` Skills -> ${
|
|
1869
|
+
copyDir(skillsSrc, hermesSkillsDir());
|
|
1870
|
+
console.log(` Skills -> ${hermesSkillsDir()}`);
|
|
681
1871
|
|
|
682
1872
|
copyDir(srcDir, join(pluginDest, 'src'));
|
|
683
1873
|
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
684
1874
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
685
1875
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
686
1876
|
|
|
687
|
-
|
|
1877
|
+
ensureHermesMcpConfig();
|
|
1878
|
+
installRuntimeDeps(pluginDest);
|
|
688
1879
|
}
|
|
689
1880
|
|
|
690
|
-
|
|
691
|
-
async function updateWorkBuddy() {
|
|
1881
|
+
async function updateHermes() {
|
|
692
1882
|
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
693
1883
|
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
694
1884
|
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
695
|
-
const pluginDest =
|
|
1885
|
+
const pluginDest = hermesPluginsDir();
|
|
696
1886
|
|
|
697
|
-
copyDir(skillsSrc,
|
|
698
|
-
const stale = pruneStale(
|
|
699
|
-
console.log(` Skills updated -> ${
|
|
1887
|
+
copyDir(skillsSrc, hermesSkillsDir());
|
|
1888
|
+
const stale = pruneStale(hermesSkillsDir(), skillsSrc);
|
|
1889
|
+
console.log(` Skills updated -> ${hermesSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
|
|
700
1890
|
copyDir(srcDir, join(pluginDest, 'src'));
|
|
701
1891
|
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
702
1892
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
703
1893
|
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
704
|
-
|
|
1894
|
+
ensureHermesMcpConfig();
|
|
705
1895
|
mkdirSync(pluginDest, { recursive: true });
|
|
706
1896
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
1897
|
+
installRuntimeDeps(pluginDest);
|
|
707
1898
|
}
|
|
708
1899
|
|
|
709
|
-
function
|
|
710
|
-
const skillsDir =
|
|
1900
|
+
function uninstallHermes() {
|
|
1901
|
+
const skillsDir = hermesSkillsDir();
|
|
711
1902
|
let removed = 0;
|
|
712
1903
|
if (existsSync(skillsDir)) {
|
|
713
1904
|
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
@@ -717,79 +1908,120 @@ function uninstallWorkBuddy() {
|
|
|
717
1908
|
}
|
|
718
1909
|
}
|
|
719
1910
|
if (removed > 0) console.log(` Removed ${removed} skills`);
|
|
1911
|
+
try {
|
|
1912
|
+
if (readdirSync(skillsDir).length === 0) {
|
|
1913
|
+
rmSync(skillsDir, { recursive: true, force: true });
|
|
1914
|
+
console.log(` Removed empty skills directory: ${skillsDir}`);
|
|
1915
|
+
}
|
|
1916
|
+
} catch {}
|
|
720
1917
|
}
|
|
721
1918
|
|
|
722
|
-
if (removeIfExists(
|
|
1919
|
+
if (removeIfExists(hermesPluginsDir())) {
|
|
723
1920
|
console.log(' Removed MCP server and safety policy');
|
|
724
1921
|
}
|
|
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
|
-
}
|
|
1922
|
+
removeHermesMcpConfigBlock();
|
|
737
1923
|
}
|
|
738
1924
|
|
|
739
|
-
function
|
|
740
|
-
const pluginDir =
|
|
741
|
-
const skillsDir =
|
|
742
|
-
console.log(
|
|
743
|
-
|
|
1925
|
+
function hermesStatus() {
|
|
1926
|
+
const pluginDir = hermesPluginsDir();
|
|
1927
|
+
const skillsDir = hermesSkillsDir();
|
|
1928
|
+
console.log(
|
|
1929
|
+
` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1930
|
+
);
|
|
1931
|
+
console.log(
|
|
1932
|
+
` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1933
|
+
);
|
|
744
1934
|
let skillCount = 0;
|
|
745
1935
|
if (existsSync(skillsDir)) {
|
|
746
|
-
skillCount = readdirSync(skillsDir, { withFileTypes: true })
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
1936
|
+
skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
|
|
1937
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
1938
|
+
).length;
|
|
1939
|
+
}
|
|
1940
|
+
console.log(
|
|
1941
|
+
` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1942
|
+
);
|
|
1943
|
+
const configPath = hermesConfigFile();
|
|
751
1944
|
if (existsSync(configPath)) {
|
|
752
1945
|
try {
|
|
753
|
-
const config =
|
|
754
|
-
|
|
1946
|
+
const config = readFileSync(configPath, 'utf8');
|
|
1947
|
+
const configured = config.includes('mcp_servers:') && config.includes('huaweicloud-devkit');
|
|
1948
|
+
console.log(` MCP config: ${configured ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
|
|
755
1949
|
} catch {
|
|
756
1950
|
console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
|
|
757
1951
|
}
|
|
1952
|
+
} else {
|
|
1953
|
+
console.log(` MCP config: \x1b[31mNot found\x1b[0m`);
|
|
758
1954
|
}
|
|
759
1955
|
}
|
|
760
1956
|
|
|
761
1957
|
function opencodeStatus() {
|
|
762
1958
|
const pluginDir = opencodePluginsDir();
|
|
763
1959
|
const skillsDir = opencodeSkillsDir();
|
|
764
|
-
console.log(
|
|
765
|
-
|
|
1960
|
+
console.log(
|
|
1961
|
+
` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1962
|
+
);
|
|
1963
|
+
console.log(
|
|
1964
|
+
` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1965
|
+
);
|
|
766
1966
|
let skillCount = 0;
|
|
767
1967
|
if (existsSync(skillsDir)) {
|
|
768
|
-
skillCount = readdirSync(skillsDir, { withFileTypes: true })
|
|
769
|
-
|
|
1968
|
+
skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
|
|
1969
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
1970
|
+
).length;
|
|
770
1971
|
}
|
|
771
|
-
console.log(
|
|
1972
|
+
console.log(
|
|
1973
|
+
` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1974
|
+
);
|
|
772
1975
|
const configPath = opencodeConfigFile();
|
|
773
1976
|
if (existsSync(configPath)) {
|
|
774
1977
|
try {
|
|
775
1978
|
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
776
|
-
console.log(
|
|
1979
|
+
console.log(
|
|
1980
|
+
` MCP config: ${config.mcp?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
|
|
1981
|
+
);
|
|
777
1982
|
} catch {
|
|
778
1983
|
console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
|
|
779
1984
|
}
|
|
780
1985
|
}
|
|
781
1986
|
}
|
|
782
1987
|
|
|
1988
|
+
function autoDetectTarget() {
|
|
1989
|
+
const checks = [
|
|
1990
|
+
['opencode', () => existsSync(join(homedir(), '.config', 'opencode'))],
|
|
1991
|
+
['codex-desktop', () => existsSync(join(homedir(), '.codex'))],
|
|
1992
|
+
['codearts', () => existsSync(join(homedir(), '.codeartsdoer'))],
|
|
1993
|
+
['workbuddy', () => existsSync(join(homedir(), '.workbuddy'))],
|
|
1994
|
+
[
|
|
1995
|
+
'dsh',
|
|
1996
|
+
() => {
|
|
1997
|
+
const dsh = process.env.DSH_HOME || join(homedir(), '.dsh');
|
|
1998
|
+
return existsSync(dsh);
|
|
1999
|
+
},
|
|
2000
|
+
],
|
|
2001
|
+
['officeace', () => existsSync(officeaceCapabilitiesDir())],
|
|
2002
|
+
['hermes', () => existsSync(hermesHomeDir())],
|
|
2003
|
+
['openclaw', () => existsSync(join(homedir(), '.openclaw'))],
|
|
2004
|
+
['atomcode', () => existsSync(atomcodeHome())],
|
|
2005
|
+
];
|
|
2006
|
+
const detected = checks.filter(([, check]) => check()).map(([name]) => name);
|
|
2007
|
+
if (detected.length === 0) {
|
|
2008
|
+
console.error('No supported agent detected.');
|
|
2009
|
+
console.error(`Supported: ${SUPPORTED_AGENT_TARGETS.join(', ')} (or "all")`);
|
|
2010
|
+
console.error('Use --target <agent> to specify.');
|
|
2011
|
+
process.exit(1);
|
|
2012
|
+
}
|
|
2013
|
+
if (detected.length === 1) return detected[0];
|
|
2014
|
+
return 'all';
|
|
2015
|
+
}
|
|
2016
|
+
|
|
783
2017
|
function parseTarget() {
|
|
784
2018
|
const idx = process.argv.indexOf('--target');
|
|
785
|
-
if (idx < 0) return
|
|
2019
|
+
if (idx < 0) return autoDetectTarget();
|
|
786
2020
|
const val = (process.argv[idx + 1] || '').toLowerCase();
|
|
787
|
-
if (val === '
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
if (val === 'all') return 'all';
|
|
792
|
-
return 'opencode';
|
|
2021
|
+
if (val === 'all' || SUPPORTED_AGENT_TARGETS.includes(val)) return val;
|
|
2022
|
+
console.error(`Unknown target: ${val}`);
|
|
2023
|
+
console.error(`Supported: ${SUPPORTED_AGENT_TARGETS.join(', ')} (or "all")`);
|
|
2024
|
+
process.exit(1);
|
|
793
2025
|
}
|
|
794
2026
|
|
|
795
2027
|
async function cmdInstall() {
|
|
@@ -814,6 +2046,26 @@ async function cmdInstall() {
|
|
|
814
2046
|
console.log('\n[WorkBuddy]');
|
|
815
2047
|
await installWorkBuddy();
|
|
816
2048
|
}
|
|
2049
|
+
if (target === 'dsh' || target === 'all') {
|
|
2050
|
+
console.log('\n[DSH]');
|
|
2051
|
+
await installDsh();
|
|
2052
|
+
}
|
|
2053
|
+
if (target === 'officeace' || target === 'all') {
|
|
2054
|
+
console.log('\n[OfficeAce]');
|
|
2055
|
+
await installOfficeAce();
|
|
2056
|
+
}
|
|
2057
|
+
if (target === 'hermes' || target === 'all') {
|
|
2058
|
+
console.log('\n[Hermes Agent]');
|
|
2059
|
+
await installHermes();
|
|
2060
|
+
}
|
|
2061
|
+
if (target === 'openclaw' || target === 'all') {
|
|
2062
|
+
console.log('\n[OpenClaw]');
|
|
2063
|
+
await installCodexDesktop();
|
|
2064
|
+
}
|
|
2065
|
+
if (target === 'atomcode' || target === 'all') {
|
|
2066
|
+
console.log('\n[AtomCode]');
|
|
2067
|
+
await installAtomCode();
|
|
2068
|
+
}
|
|
817
2069
|
if (target === 'codex' || target === 'all') {
|
|
818
2070
|
console.log('\n[Codex]');
|
|
819
2071
|
if (!hasCodexCLI()) {
|
|
@@ -836,18 +2088,47 @@ async function cmdInstall() {
|
|
|
836
2088
|
} else {
|
|
837
2089
|
installCodex();
|
|
838
2090
|
}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
2091
|
+
}
|
|
2092
|
+
console.log(`\n\x1b[32mInstallation complete!\x1b[0m`);
|
|
2093
|
+
const appName =
|
|
2094
|
+
target === 'codearts'
|
|
2095
|
+
? 'CodeArts'
|
|
2096
|
+
: target === 'codex-desktop'
|
|
2097
|
+
? 'Codex Desktop'
|
|
2098
|
+
: target === 'codex'
|
|
2099
|
+
? 'Codex'
|
|
2100
|
+
: target === 'workbuddy'
|
|
2101
|
+
? 'WorkBuddy'
|
|
2102
|
+
: target === 'dsh'
|
|
2103
|
+
? 'DSH'
|
|
2104
|
+
: target === 'officeace'
|
|
2105
|
+
? 'OfficeAce'
|
|
2106
|
+
: target === 'hermes'
|
|
2107
|
+
? 'Hermes Agent'
|
|
2108
|
+
: target === 'openclaw'
|
|
2109
|
+
? 'OpenClaw'
|
|
2110
|
+
: target === 'atomcode'
|
|
2111
|
+
? 'AtomCode'
|
|
2112
|
+
: '当前 agent';
|
|
845
2113
|
const pad = ' '.repeat(24 - appName.length);
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
2114
|
+
if (target === 'officeace') {
|
|
2115
|
+
console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════════╗`);
|
|
2116
|
+
console.log(`\x1b[1m\x1b[33m║ 打开连接器 → 我的连接器 → huaweicloud-devkit �`);
|
|
2117
|
+
console.log(`\x1b[1m\x1b[33m║ → 连接 → 回到对话 → 输入框开启连接器 �`);
|
|
2118
|
+
console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
|
|
2119
|
+
} else if (target === 'workbuddy') {
|
|
2120
|
+
console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════════╗`);
|
|
2121
|
+
console.log(`\x1b[1m\x1b[33m║ MCP 工具即时生效,无需重启会话 �`);
|
|
2122
|
+
console.log(`\x1b[1m\x1b[33m║ 前往连接器 → 自定义连接器,确认 huaweicloud-devkit �`);
|
|
2123
|
+
console.log(`\x1b[1m\x1b[33m║ 已添加信任并启用 �`);
|
|
2124
|
+
console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
|
|
2125
|
+
} else {
|
|
2126
|
+
console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════╗`);
|
|
2127
|
+
console.log(`\x1b[1m\x1b[33m║ MCP 工具在重启 ${appName} 会话后才生效${pad}║`);
|
|
2128
|
+
console.log(`\x1b[1m\x1b[33m║ 关闭当前会话 → 重新打开,直接描述华为云任务即可 ║`);
|
|
2129
|
+
console.log(`\x1b[1m\x1b[33m║ 重启前请勿执行 hcloud 命令,避免 AK/SK 泄露 ║`);
|
|
2130
|
+
console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
|
|
2131
|
+
}
|
|
851
2132
|
|
|
852
2133
|
const hcloudOk = checkHcloud();
|
|
853
2134
|
if (!hcloudOk) {
|
|
@@ -859,14 +2140,33 @@ async function cmdInstall() {
|
|
|
859
2140
|
|
|
860
2141
|
console.log(`\n\x1b[1m下一步:\x1b[0m`);
|
|
861
2142
|
console.log(` 1. 配置统一凭据:npx huaweicloud-devkit auth init`);
|
|
862
|
-
console.log(` 2.
|
|
863
|
-
|
|
2143
|
+
console.log(` 2. 配置代理(企业内网):npx huaweicloud-devkit proxy init`);
|
|
2144
|
+
if (target === 'officeace') {
|
|
2145
|
+
console.log(` 3. 打开连接器 → 我的连接器 → huaweicloud-devkit → 连接 → 回到对话 → 输入框开启连接器`);
|
|
2146
|
+
} else if (target === 'workbuddy') {
|
|
2147
|
+
console.log(` 3. 前往连接器 → 自定义连接器,确认 huaweicloud-devkit 已添加信任并启用`);
|
|
2148
|
+
} else {
|
|
2149
|
+
console.log(` 3. 重启 ${appName} 会话(MCP 工具重启后生效)`);
|
|
2150
|
+
}
|
|
2151
|
+
console.log(` 4. 运行自检:npx huaweicloud-devkit doctor`);
|
|
864
2152
|
|
|
865
2153
|
// Write install marker for doctor to detect
|
|
866
|
-
const markerDir =
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
2154
|
+
const markerDir =
|
|
2155
|
+
target === 'dsh'
|
|
2156
|
+
? dshPluginsDir()
|
|
2157
|
+
: target === 'codearts'
|
|
2158
|
+
? codeartsPluginsDir()
|
|
2159
|
+
: target === 'workbuddy'
|
|
2160
|
+
? workbuddyPluginsDir()
|
|
2161
|
+
: target === 'officeace'
|
|
2162
|
+
? officeacePluginsDir()
|
|
2163
|
+
: target === 'openclaw'
|
|
2164
|
+
? codexDesktopPluginsDir()
|
|
2165
|
+
: target === 'atomcode'
|
|
2166
|
+
? atomcodePluginsDir()
|
|
2167
|
+
: target === 'codex-desktop'
|
|
2168
|
+
? codexDesktopPluginsDir()
|
|
2169
|
+
: opencodePluginsDir();
|
|
870
2170
|
mkdirSync(markerDir, { recursive: true });
|
|
871
2171
|
writeFileSync(join(markerDir, '.installed'), new Date().toISOString());
|
|
872
2172
|
if (target === 'opencode' || target === 'all') {
|
|
@@ -881,6 +2181,18 @@ async function cmdInstall() {
|
|
|
881
2181
|
if (target === 'workbuddy' || target === 'all') {
|
|
882
2182
|
console.log('Or describe your Huawei Cloud task in WorkBuddy');
|
|
883
2183
|
}
|
|
2184
|
+
if (target === 'dsh' || target === 'all') {
|
|
2185
|
+
console.log('Or describe your Huawei Cloud task in DSH');
|
|
2186
|
+
}
|
|
2187
|
+
if (target === 'officeace' || target === 'all') {
|
|
2188
|
+
console.log('Or describe your Huawei Cloud task in OfficeAce');
|
|
2189
|
+
}
|
|
2190
|
+
if (target === 'openclaw' || target === 'all') {
|
|
2191
|
+
console.log('Or describe your Huawei Cloud task in OpenClaw');
|
|
2192
|
+
}
|
|
2193
|
+
if (target === 'atomcode' || target === 'all') {
|
|
2194
|
+
console.log('Or describe your Huawei Cloud task in AtomCode');
|
|
2195
|
+
}
|
|
884
2196
|
}
|
|
885
2197
|
|
|
886
2198
|
async function cmdUninstall() {
|
|
@@ -900,6 +2212,26 @@ async function cmdUninstall() {
|
|
|
900
2212
|
console.log('\n[WorkBuddy]');
|
|
901
2213
|
uninstallWorkBuddy();
|
|
902
2214
|
}
|
|
2215
|
+
if (target === 'dsh' || target === 'all') {
|
|
2216
|
+
console.log('\n[DSH]');
|
|
2217
|
+
uninstallDsh();
|
|
2218
|
+
}
|
|
2219
|
+
if (target === 'officeace' || target === 'all') {
|
|
2220
|
+
console.log('\n[OfficeAce]');
|
|
2221
|
+
uninstallOfficeAce();
|
|
2222
|
+
}
|
|
2223
|
+
if (target === 'hermes' || target === 'all') {
|
|
2224
|
+
console.log('\n[Hermes Agent]');
|
|
2225
|
+
uninstallHermes();
|
|
2226
|
+
}
|
|
2227
|
+
if (target === 'openclaw' || target === 'all') {
|
|
2228
|
+
console.log('\n[OpenClaw]');
|
|
2229
|
+
uninstallCodexDesktop();
|
|
2230
|
+
}
|
|
2231
|
+
if (target === 'atomcode' || target === 'all') {
|
|
2232
|
+
console.log('\n[AtomCode]');
|
|
2233
|
+
uninstallAtomCode();
|
|
2234
|
+
}
|
|
903
2235
|
if (target === 'codex-desktop' || target === 'codex' || target === 'all') {
|
|
904
2236
|
console.log('\n[Codex]');
|
|
905
2237
|
uninstallCodexDesktop();
|
|
@@ -944,6 +2276,42 @@ async function cmdStatus() {
|
|
|
944
2276
|
console.log('\n[WorkBuddy]');
|
|
945
2277
|
workbuddyStatus();
|
|
946
2278
|
}
|
|
2279
|
+
if (target === 'dsh' || target === 'all') {
|
|
2280
|
+
console.log('\n[DSH]');
|
|
2281
|
+
dshStatus();
|
|
2282
|
+
}
|
|
2283
|
+
if (target === 'officeace' || target === 'all') {
|
|
2284
|
+
console.log('\n[OfficeAce]');
|
|
2285
|
+
officeaceStatus();
|
|
2286
|
+
}
|
|
2287
|
+
if (target === 'hermes' || target === 'all') {
|
|
2288
|
+
console.log('\n[Hermes Agent]');
|
|
2289
|
+
hermesStatus();
|
|
2290
|
+
}
|
|
2291
|
+
if (target === 'openclaw' || target === 'all') {
|
|
2292
|
+
console.log('\n[OpenClaw]');
|
|
2293
|
+
const cdPluginDir = codexDesktopPluginsDir();
|
|
2294
|
+
const cdSkillsDir = codexDesktopSkillsDir();
|
|
2295
|
+
console.log(
|
|
2296
|
+
` MCP Server: ${existsSync(join(cdPluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
2297
|
+
);
|
|
2298
|
+
console.log(
|
|
2299
|
+
` Safety Policy: ${existsSync(join(cdPluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
2300
|
+
);
|
|
2301
|
+
let cdSkillCount = 0;
|
|
2302
|
+
if (existsSync(cdSkillsDir)) {
|
|
2303
|
+
cdSkillCount = readdirSync(cdSkillsDir, { withFileTypes: true }).filter(
|
|
2304
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
2305
|
+
).length;
|
|
2306
|
+
}
|
|
2307
|
+
console.log(
|
|
2308
|
+
` Skills: ${cdSkillCount > 0 ? `\x1b[32m${cdSkillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
2309
|
+
);
|
|
2310
|
+
}
|
|
2311
|
+
if (target === 'atomcode' || target === 'all') {
|
|
2312
|
+
console.log('\n[AtomCode]');
|
|
2313
|
+
atomcodeStatus();
|
|
2314
|
+
}
|
|
947
2315
|
if (target === 'codex' || target === 'all') {
|
|
948
2316
|
console.log('\n[Codex]');
|
|
949
2317
|
if (!hasCodexCLI()) {
|
|
@@ -961,66 +2329,187 @@ async function cmdDoctor() {
|
|
|
961
2329
|
console.log(BANNER);
|
|
962
2330
|
console.log('HuaweiCloud DevKit Doctor\n');
|
|
963
2331
|
|
|
964
|
-
let pass = 0,
|
|
2332
|
+
let pass = 0,
|
|
2333
|
+
warn = 0,
|
|
2334
|
+
fail = 0;
|
|
965
2335
|
|
|
966
2336
|
function check(label, ok, msg) {
|
|
967
|
-
if (ok) {
|
|
968
|
-
|
|
2337
|
+
if (ok) {
|
|
2338
|
+
console.log(` \x1b[32m[PASS]\x1b[0m ${label}`);
|
|
2339
|
+
pass++;
|
|
2340
|
+
} else {
|
|
2341
|
+
console.log(` \x1b[31m[FAIL]\x1b[0m ${label} — ${msg}`);
|
|
2342
|
+
fail++;
|
|
2343
|
+
}
|
|
969
2344
|
}
|
|
970
2345
|
|
|
971
2346
|
// Node.js
|
|
972
|
-
check('Node.js >=
|
|
2347
|
+
check('Node.js >= 22', process.versions.node.split('.')[0] >= 22, 'Run: nvm install 22 && nvm use 22');
|
|
973
2348
|
|
|
974
|
-
|
|
2349
|
+
// MCP server — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH paths
|
|
975
2350
|
const opencodePluginDir = opencodePluginsDir();
|
|
976
2351
|
const codexPluginDir = codexDesktopPluginsDir();
|
|
2352
|
+
const codeartsPluginDir = codeartsPluginsDir();
|
|
977
2353
|
const workbuddyPluginDir = workbuddyPluginsDir();
|
|
978
|
-
const
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
2354
|
+
const dshPluginDir = dshPluginsDir();
|
|
2355
|
+
const officeacePluginDir = officeacePluginsDir();
|
|
2356
|
+
const hermesPluginDir = hermesPluginsDir();
|
|
2357
|
+
const atomcodePluginDir = atomcodePluginsDir();
|
|
2358
|
+
const mcpOk =
|
|
2359
|
+
existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2360
|
+
existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2361
|
+
existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2362
|
+
existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2363
|
+
existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2364
|
+
existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2365
|
+
existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2366
|
+
existsSync(join(atomcodePluginDir, 'src', 'mcp-server.mjs'));
|
|
2367
|
+
const mcpTarget = existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs'))
|
|
2368
|
+
? 'OpenCode'
|
|
2369
|
+
: existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs'))
|
|
2370
|
+
? 'Codex Desktop'
|
|
2371
|
+
: existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs'))
|
|
2372
|
+
? 'CodeArts'
|
|
2373
|
+
: existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs'))
|
|
2374
|
+
? 'WorkBuddy'
|
|
2375
|
+
: existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs'))
|
|
2376
|
+
? 'DSH'
|
|
2377
|
+
: existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs'))
|
|
2378
|
+
? 'OfficeAce'
|
|
2379
|
+
: existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'))
|
|
2380
|
+
? 'Hermes Agent'
|
|
2381
|
+
: existsSync(join(atomcodePluginDir, 'src', 'mcp-server.mjs'))
|
|
2382
|
+
? 'AtomCode'
|
|
2383
|
+
: '';
|
|
984
2384
|
check('MCP server installed', mcpOk, 'Run: npx huaweicloud-devkit install');
|
|
985
2385
|
|
|
986
2386
|
if (mcpOk) {
|
|
987
2387
|
check(`MCP server can start (${mcpTarget})`, true, '');
|
|
988
2388
|
}
|
|
989
2389
|
|
|
990
|
-
const
|
|
991
|
-
|
|
992
|
-
|
|
2390
|
+
const undiciPluginDirs = [
|
|
2391
|
+
opencodePluginDir,
|
|
2392
|
+
codexPluginDir,
|
|
2393
|
+
codeartsPluginDir,
|
|
2394
|
+
workbuddyPluginDir,
|
|
2395
|
+
dshPluginDir,
|
|
2396
|
+
officeacePluginDir,
|
|
2397
|
+
hermesPluginDir,
|
|
2398
|
+
];
|
|
2399
|
+
const undiciOk = undiciPluginDirs.some((d) => existsSync(join(d, 'node_modules', 'undici')));
|
|
2400
|
+
check('Runtime deps (undici) installed', undiciOk, 'Run: npx huaweicloud-devkit install');
|
|
2401
|
+
|
|
2402
|
+
const safetyOk =
|
|
2403
|
+
existsSync(join(opencodePluginDir, 'safety', 'policy.json')) ||
|
|
2404
|
+
existsSync(join(codexPluginDir, 'safety', 'policy.json')) ||
|
|
2405
|
+
existsSync(join(codeartsPluginDir, 'safety', 'policy.json')) ||
|
|
2406
|
+
existsSync(join(workbuddyPluginDir, 'safety', 'policy.json')) ||
|
|
2407
|
+
existsSync(join(dshPluginDir, 'safety', 'policy.json')) ||
|
|
2408
|
+
existsSync(join(officeacePluginDir, 'safety', 'policy.json')) ||
|
|
2409
|
+
existsSync(join(hermesPluginDir, 'safety', 'policy.json')) ||
|
|
2410
|
+
existsSync(join(atomcodePluginDir, 'safety', 'policy.json'));
|
|
993
2411
|
check('Safety policy installed', safetyOk, 'Run: npx huaweicloud-devkit install');
|
|
994
2412
|
|
|
995
|
-
// MCP config — check OpenCode, Codex Desktop, and
|
|
2413
|
+
// MCP config — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH
|
|
996
2414
|
let mcpConfigured = false;
|
|
997
2415
|
let mcpCfgTarget = '';
|
|
998
2416
|
const opencodeCfg = opencodeConfigFile();
|
|
999
2417
|
if (existsSync(opencodeCfg)) {
|
|
1000
2418
|
try {
|
|
1001
2419
|
const cfg = JSON.parse(readFileSync(opencodeCfg, 'utf8'));
|
|
1002
|
-
if (cfg.mcp && cfg.mcp['huaweicloud-devkit']) {
|
|
2420
|
+
if (cfg.mcp && cfg.mcp['huaweicloud-devkit']) {
|
|
2421
|
+
mcpConfigured = true;
|
|
2422
|
+
mcpCfgTarget = 'OpenCode';
|
|
2423
|
+
}
|
|
1003
2424
|
} catch {}
|
|
1004
2425
|
}
|
|
1005
2426
|
const codexCfg = codexConfigToml();
|
|
1006
2427
|
if (!mcpConfigured && existsSync(codexCfg)) {
|
|
1007
2428
|
try {
|
|
1008
2429
|
const cfg = readFileSync(codexCfg, 'utf8');
|
|
1009
|
-
if (cfg.includes('[mcp_servers.huaweicloud-devkit]')) {
|
|
2430
|
+
if (cfg.includes('[mcp_servers.huaweicloud-devkit]')) {
|
|
2431
|
+
mcpConfigured = true;
|
|
2432
|
+
mcpCfgTarget = 'Codex Desktop';
|
|
2433
|
+
}
|
|
2434
|
+
} catch {}
|
|
2435
|
+
}
|
|
2436
|
+
const codeartsCfg = codeartsMcpSettingsFile();
|
|
2437
|
+
if (!mcpConfigured && existsSync(codeartsCfg)) {
|
|
2438
|
+
try {
|
|
2439
|
+
const cfg = JSON.parse(readFileSync(codeartsCfg, 'utf8'));
|
|
2440
|
+
if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
|
|
2441
|
+
mcpConfigured = true;
|
|
2442
|
+
mcpCfgTarget = 'CodeArts';
|
|
2443
|
+
}
|
|
1010
2444
|
} catch {}
|
|
1011
2445
|
}
|
|
1012
2446
|
const workbuddyCfg = workbuddyMcpConfigFile();
|
|
1013
2447
|
if (!mcpConfigured && existsSync(workbuddyCfg)) {
|
|
1014
2448
|
try {
|
|
1015
2449
|
const cfg = JSON.parse(readFileSync(workbuddyCfg, 'utf8'));
|
|
1016
|
-
if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
|
|
2450
|
+
if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
|
|
2451
|
+
mcpConfigured = true;
|
|
2452
|
+
mcpCfgTarget = 'WorkBuddy';
|
|
2453
|
+
}
|
|
1017
2454
|
} catch {}
|
|
1018
2455
|
}
|
|
1019
|
-
|
|
2456
|
+
if (!mcpConfigured && dshPatchConfigured()) {
|
|
2457
|
+
mcpConfigured = true;
|
|
2458
|
+
mcpCfgTarget = 'DSH';
|
|
2459
|
+
}
|
|
2460
|
+
if (!mcpConfigured) {
|
|
2461
|
+
const officeaceCfg = officeaceCapabilitiesFile();
|
|
2462
|
+
if (existsSync(officeaceCfg)) {
|
|
2463
|
+
try {
|
|
2464
|
+
const cfg = JSON.parse(readFileSync(officeaceCfg, 'utf8'));
|
|
2465
|
+
if (
|
|
2466
|
+
Array.isArray(cfg.capabilities) &&
|
|
2467
|
+
cfg.capabilities.some((c) => c.id === 'huaweicloud-devkit' && c.type === 'mcp')
|
|
2468
|
+
) {
|
|
2469
|
+
mcpConfigured = true;
|
|
2470
|
+
mcpCfgTarget = 'OfficeAce';
|
|
2471
|
+
}
|
|
2472
|
+
} catch {}
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
if (!mcpConfigured) {
|
|
2476
|
+
const hermesCfg = hermesConfigFile();
|
|
2477
|
+
if (existsSync(hermesCfg)) {
|
|
2478
|
+
try {
|
|
2479
|
+
const cfg = readFileSync(hermesCfg, 'utf8');
|
|
2480
|
+
if (cfg.includes('mcp_servers:') && cfg.includes('huaweicloud-devkit')) {
|
|
2481
|
+
mcpConfigured = true;
|
|
2482
|
+
mcpCfgTarget = 'Hermes Agent';
|
|
2483
|
+
}
|
|
2484
|
+
} catch {}
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
if (!mcpConfigured) {
|
|
2488
|
+
const atomcodeCfg = atomcodeMcpConfigFile();
|
|
2489
|
+
if (existsSync(atomcodeCfg)) {
|
|
2490
|
+
try {
|
|
2491
|
+
const cfg = JSON.parse(readFileSync(atomcodeCfg, 'utf8'));
|
|
2492
|
+
if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
|
|
2493
|
+
mcpConfigured = true;
|
|
2494
|
+
mcpCfgTarget = 'AtomCode';
|
|
2495
|
+
}
|
|
2496
|
+
} catch {}
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
check(
|
|
2500
|
+
'MCP configured',
|
|
2501
|
+
mcpConfigured,
|
|
2502
|
+
mcpCfgTarget ? `Found in ${mcpCfgTarget} config` : 'Run: npx huaweicloud-devkit install',
|
|
2503
|
+
);
|
|
1020
2504
|
|
|
1021
2505
|
// hcloud CLI
|
|
1022
|
-
const hcloudBin = findHcloudBin() ||
|
|
1023
|
-
const hcloudCheck = spawnSync(`"${hcloudBin}" version`, [], {
|
|
2506
|
+
const hcloudBin = findHcloudBin() || process.env.HCLOUD_BIN || 'hcloud';
|
|
2507
|
+
const hcloudCheck = spawnSync(`"${hcloudBin}" version`, [], {
|
|
2508
|
+
shell: true,
|
|
2509
|
+
windowsHide: true,
|
|
2510
|
+
stdio: 'pipe',
|
|
2511
|
+
timeout: 5000,
|
|
2512
|
+
});
|
|
1024
2513
|
const hcloudOut = (hcloudCheck.stdout || '').toString() + (hcloudCheck.stderr || '').toString();
|
|
1025
2514
|
const hcloudOk = hcloudCheck.status === 0 && /KooCLI|Current.*version|当前KooCLI/i.test(hcloudOut);
|
|
1026
2515
|
check('hcloud CLI installed', hcloudOk, 'Run: npx huaweicloud-devkit install-hcloud');
|
|
@@ -1039,20 +2528,38 @@ async function cmdDoctor() {
|
|
|
1039
2528
|
console.log(` Version: ${ver}`);
|
|
1040
2529
|
|
|
1041
2530
|
// Check auth
|
|
1042
|
-
const authCheck = spawnSync(`"${hcloudBin}" configure list`, [], {
|
|
2531
|
+
const authCheck = spawnSync(`"${hcloudBin}" configure list`, [], {
|
|
2532
|
+
shell: true,
|
|
2533
|
+
windowsHide: true,
|
|
2534
|
+
stdio: 'pipe',
|
|
2535
|
+
timeout: 5000,
|
|
2536
|
+
});
|
|
1043
2537
|
const hasAuth = authCheck.status === 0 && /access.?key/i.test(authCheck.stdout.toString());
|
|
1044
2538
|
check('hcloud credentials configured', hasAuth, 'Run: npx huaweicloud-devkit auth init');
|
|
1045
2539
|
}
|
|
1046
2540
|
|
|
1047
2541
|
// Skills
|
|
1048
|
-
const skillsOptions = [
|
|
1049
|
-
|
|
2542
|
+
const skillsOptions = [
|
|
2543
|
+
opencodeSkillsDir(),
|
|
2544
|
+
codexDesktopSkillsDir(),
|
|
2545
|
+
codeartsSkillsDir(),
|
|
2546
|
+
workbuddySkillsDir(),
|
|
2547
|
+
dshSkillsDir(),
|
|
2548
|
+
officeaceSkillsDir(),
|
|
2549
|
+
hermesSkillsDir(),
|
|
2550
|
+
atomcodeSkillsDir(),
|
|
2551
|
+
];
|
|
2552
|
+
let skillCount = 0;
|
|
2553
|
+
const missingSkills = [];
|
|
1050
2554
|
for (const dir of skillsOptions) {
|
|
1051
2555
|
if (!existsSync(dir)) continue;
|
|
1052
|
-
const entries = readdirSync(dir, { withFileTypes: true })
|
|
1053
|
-
|
|
2556
|
+
const entries = readdirSync(dir, { withFileTypes: true }).filter(
|
|
2557
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
2558
|
+
);
|
|
1054
2559
|
const count = entries.length;
|
|
1055
|
-
if (count > skillCount) {
|
|
2560
|
+
if (count > skillCount) {
|
|
2561
|
+
skillCount = count;
|
|
2562
|
+
}
|
|
1056
2563
|
for (const d of entries) {
|
|
1057
2564
|
if (!existsSync(join(dir, d.name, 'SKILL.md'))) missingSkills.push(d.name);
|
|
1058
2565
|
}
|
|
@@ -1060,14 +2567,27 @@ async function cmdDoctor() {
|
|
|
1060
2567
|
const skillsOk = skillCount >= 6;
|
|
1061
2568
|
check(`Skills installed (${skillCount})`, skillsOk, 'Run: npx huaweicloud-devkit install');
|
|
1062
2569
|
if (missingSkills.length > 0) {
|
|
1063
|
-
console.log(
|
|
2570
|
+
console.log(
|
|
2571
|
+
` \x1b[33m[WARN]\x1b[0m ${missingSkills.length} skill(s) missing SKILL.md: ${missingSkills.join(', ')} — Run: npx huaweicloud-devkit install`,
|
|
2572
|
+
);
|
|
1064
2573
|
warn++;
|
|
1065
2574
|
}
|
|
1066
2575
|
|
|
2576
|
+
const proxyConfig = readProxyConfig();
|
|
2577
|
+
const proxyEnv =
|
|
2578
|
+
process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
|
|
2579
|
+
if (proxyConfig || proxyEnv) {
|
|
2580
|
+
const source = proxyEnv ? 'env' : 'file';
|
|
2581
|
+
const proxyUrl = proxyEnv || proxyConfig.https_proxy || proxyConfig.http_proxy;
|
|
2582
|
+
console.log(` \x1b[36m[INFO]\x1b[0m Proxy configured (${source}): ${proxyUrl}`);
|
|
2583
|
+
}
|
|
2584
|
+
|
|
1067
2585
|
console.log(`\nResults: ${pass} pass, ${warn} warn, ${fail} fail`);
|
|
1068
2586
|
|
|
1069
2587
|
if (mcpConfigured && !hcloudOk) {
|
|
1070
|
-
console.log(
|
|
2588
|
+
console.log(
|
|
2589
|
+
'\n\x1b[33mMCP is configured but hcloud is not installed. Install hcloud then restart your session.\x1b[0m',
|
|
2590
|
+
);
|
|
1071
2591
|
}
|
|
1072
2592
|
if (fail > 0) {
|
|
1073
2593
|
console.log('\x1b[33mFix failures above, then restart your session.\x1b[0m');
|
|
@@ -1082,6 +2602,9 @@ async function cmdDoctor() {
|
|
|
1082
2602
|
{ path: join(codexDesktopPluginsDir(), '.installed'), name: 'Codex Desktop' },
|
|
1083
2603
|
{ path: join(workbuddyPluginsDir(), '.installed'), name: 'WorkBuddy' },
|
|
1084
2604
|
{ path: join(codeartsPluginsDir(), '.installed'), name: 'CodeArts' },
|
|
2605
|
+
{ path: join(dshPluginsDir(), '.installed'), name: 'DSH' },
|
|
2606
|
+
{ path: join(officeacePluginsDir(), '.installed'), name: 'OfficeAce' },
|
|
2607
|
+
{ path: join(atomcodePluginsDir(), '.installed'), name: 'AtomCode' },
|
|
1085
2608
|
];
|
|
1086
2609
|
for (const marker of installedMarkers) {
|
|
1087
2610
|
if (existsSync(marker.path)) {
|
|
@@ -1163,6 +2686,66 @@ async function cmdUpdate() {
|
|
|
1163
2686
|
return;
|
|
1164
2687
|
}
|
|
1165
2688
|
|
|
2689
|
+
if (target === 'dsh') {
|
|
2690
|
+
if (!existsSync(join(dshPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2691
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
2692
|
+
return;
|
|
2693
|
+
}
|
|
2694
|
+
console.log('[DSH]');
|
|
2695
|
+
await updateDsh();
|
|
2696
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
2697
|
+
console.log(`\x1b[33mRestart the DSH session for changes to take effect.\x1b[0m`);
|
|
2698
|
+
return;
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
if (target === 'officeace') {
|
|
2702
|
+
if (!existsSync(join(officeacePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2703
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
2704
|
+
return;
|
|
2705
|
+
}
|
|
2706
|
+
console.log('[OfficeAce]');
|
|
2707
|
+
await updateOfficeAce();
|
|
2708
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
2709
|
+
console.log(`\x1b[33m打开连接器 → 我的连接器 → huaweicloud-devkit → 连接 → 回到对话 → 输入框开启连接器\x1b[0m`);
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
if (target === 'hermes') {
|
|
2714
|
+
if (!existsSync(join(hermesPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2715
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
2716
|
+
return;
|
|
2717
|
+
}
|
|
2718
|
+
console.log('[Hermes Agent]');
|
|
2719
|
+
await updateHermes();
|
|
2720
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
2721
|
+
console.log(`\x1b[33mRestart Hermes Agent for changes to take effect.\x1b[0m`);
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
if (target === 'openclaw') {
|
|
2726
|
+
if (!existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2727
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
2728
|
+
return;
|
|
2729
|
+
}
|
|
2730
|
+
console.log('[OpenClaw]');
|
|
2731
|
+
await updateCodexDesktop();
|
|
2732
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
2733
|
+
console.log(`\x1b[33mRestart OpenClaw for changes to take effect.\x1b[0m`);
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
if (target === 'atomcode') {
|
|
2738
|
+
if (!existsSync(join(atomcodePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2739
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
2740
|
+
return;
|
|
2741
|
+
}
|
|
2742
|
+
console.log('[AtomCode]');
|
|
2743
|
+
await updateAtomCode();
|
|
2744
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
2745
|
+
console.log(`\x1b[33mRestart the AtomCode session for changes to take effect.\x1b[0m`);
|
|
2746
|
+
return;
|
|
2747
|
+
}
|
|
2748
|
+
|
|
1166
2749
|
if (target === 'all') {
|
|
1167
2750
|
let updatedAny = false;
|
|
1168
2751
|
if (existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
@@ -1185,6 +2768,31 @@ async function cmdUpdate() {
|
|
|
1185
2768
|
await updateWorkBuddy();
|
|
1186
2769
|
updatedAny = true;
|
|
1187
2770
|
}
|
|
2771
|
+
if (existsSync(join(dshPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2772
|
+
console.log('\n[DSH]');
|
|
2773
|
+
await updateDsh();
|
|
2774
|
+
updatedAny = true;
|
|
2775
|
+
}
|
|
2776
|
+
if (existsSync(join(officeacePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2777
|
+
console.log('\n[OfficeAce]');
|
|
2778
|
+
await updateOfficeAce();
|
|
2779
|
+
updatedAny = true;
|
|
2780
|
+
}
|
|
2781
|
+
if (existsSync(join(hermesPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2782
|
+
console.log('\n[Hermes Agent]');
|
|
2783
|
+
await updateHermes();
|
|
2784
|
+
updatedAny = true;
|
|
2785
|
+
}
|
|
2786
|
+
if (existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2787
|
+
console.log('\n[OpenClaw]');
|
|
2788
|
+
await updateCodexDesktop();
|
|
2789
|
+
updatedAny = true;
|
|
2790
|
+
}
|
|
2791
|
+
if (existsSync(join(atomcodePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2792
|
+
console.log('\n[AtomCode]');
|
|
2793
|
+
await updateAtomCode();
|
|
2794
|
+
updatedAny = true;
|
|
2795
|
+
}
|
|
1188
2796
|
if (codexStatus()) {
|
|
1189
2797
|
console.log('\n[Codex]');
|
|
1190
2798
|
installCodex();
|
|
@@ -1235,9 +2843,7 @@ async function cmdInstallHcloud() {
|
|
|
1235
2843
|
const os = platform();
|
|
1236
2844
|
const arch = process.arch;
|
|
1237
2845
|
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');
|
|
2846
|
+
const installDir = os === 'win32' ? join(homedir(), 'hcloud') : join(homedir(), '.local', 'bin');
|
|
1241
2847
|
|
|
1242
2848
|
if (os === 'win32') {
|
|
1243
2849
|
const url = `${baseUrl}/huaweicloud-cli-windows-amd64.zip`;
|
|
@@ -1251,12 +2857,20 @@ async function cmdInstallHcloud() {
|
|
|
1251
2857
|
// Download
|
|
1252
2858
|
console.log(` Downloading ${url}...`);
|
|
1253
2859
|
const psCmd = `[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '${url}' -OutFile '${zipPath}' -UseBasicParsing`;
|
|
1254
|
-
const dl = spawnSync('powershell', ['-NoProfile', '-Command', psCmd], {
|
|
2860
|
+
const dl = spawnSync('powershell', ['-NoProfile', '-Command', psCmd], {
|
|
2861
|
+
stdio: 'inherit',
|
|
2862
|
+
windowsHide: true,
|
|
2863
|
+
timeout: 180000,
|
|
2864
|
+
});
|
|
1255
2865
|
if (dl.status !== 0) throw new Error(`下载失败 (PowerShell exit ${dl.status})`);
|
|
1256
2866
|
|
|
1257
2867
|
// Extract
|
|
1258
2868
|
console.log(' Extracting...');
|
|
1259
|
-
const ex = spawnSync(
|
|
2869
|
+
const ex = spawnSync(
|
|
2870
|
+
'powershell',
|
|
2871
|
+
['-NoProfile', '-Command', `Expand-Archive -Path '${zipPath}' -DestinationPath '${installDir}' -Force`],
|
|
2872
|
+
{ stdio: 'inherit', windowsHide: true, timeout: 60000 },
|
|
2873
|
+
);
|
|
1260
2874
|
if (ex.status !== 0) throw new Error(`解压失败 (PowerShell exit ${ex.status})`);
|
|
1261
2875
|
if (!existsSync(join(installDir, 'hcloud.exe'))) throw new Error('hcloud.exe 未生成,可能已被安全软件拦截');
|
|
1262
2876
|
|
|
@@ -1280,7 +2894,11 @@ async function cmdInstallHcloud() {
|
|
|
1280
2894
|
' Write-Output " Already in user PATH: $target"',
|
|
1281
2895
|
'}',
|
|
1282
2896
|
].join('; ');
|
|
1283
|
-
spawnSync('powershell', ['-NoProfile', '-Command', pathPs], {
|
|
2897
|
+
spawnSync('powershell', ['-NoProfile', '-Command', pathPs], {
|
|
2898
|
+
stdio: 'inherit',
|
|
2899
|
+
windowsHide: true,
|
|
2900
|
+
timeout: 30000,
|
|
2901
|
+
});
|
|
1284
2902
|
|
|
1285
2903
|
console.log(`\n\x1b[32mInstall complete.\x1b[0m`);
|
|
1286
2904
|
console.log(` Verify: ${join(installDir, 'hcloud.exe')} version`);
|
|
@@ -1296,7 +2914,12 @@ async function cmdInstallHcloud() {
|
|
|
1296
2914
|
});
|
|
1297
2915
|
});
|
|
1298
2916
|
if (agree) {
|
|
1299
|
-
const r = spawnSync(hcloudBin, ['version'], {
|
|
2917
|
+
const r = spawnSync(hcloudBin, ['version'], {
|
|
2918
|
+
input: 'y\n',
|
|
2919
|
+
encoding: 'utf8',
|
|
2920
|
+
timeout: 10000,
|
|
2921
|
+
windowsHide: true,
|
|
2922
|
+
});
|
|
1300
2923
|
if (r.status === 0) {
|
|
1301
2924
|
console.log(' \x1b[32mPrivacy agreement accepted. KooCLI ready.\x1b[0m');
|
|
1302
2925
|
} else {
|
|
@@ -1307,8 +2930,8 @@ async function cmdInstallHcloud() {
|
|
|
1307
2930
|
}
|
|
1308
2931
|
|
|
1309
2932
|
console.log(' Or restart terminal and: hcloud version');
|
|
1310
|
-
} catch (
|
|
1311
|
-
console.log(`\n\x1b[33mAuto-install failed: ${
|
|
2933
|
+
} catch (error) {
|
|
2934
|
+
console.log(`\n\x1b[33mAuto-install failed: ${error.message}\x1b[0m`);
|
|
1312
2935
|
console.log(` Manual: download ${url}, unzip to ${installDir}, add to PATH`);
|
|
1313
2936
|
console.log(` Guide: https://support.huaweicloud.com/qs-hcli/hcli_02_003_01.html`);
|
|
1314
2937
|
if (detectCodeartsSandbox() === 'sandbox') {
|
|
@@ -1317,7 +2940,9 @@ async function cmdInstallHcloud() {
|
|
|
1317
2940
|
}
|
|
1318
2941
|
} else if (os === 'linux') {
|
|
1319
2942
|
console.log('[Linux] One-liner install:');
|
|
1320
|
-
console.log(
|
|
2943
|
+
console.log(
|
|
2944
|
+
' 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',
|
|
2945
|
+
);
|
|
1321
2946
|
console.log(`\nOr manual: ${arch === 'arm64' ? 'ARM64' : 'AMD64'}`);
|
|
1322
2947
|
const pkg = arch === 'arm64' ? 'linux-arm64' : 'linux-amd64';
|
|
1323
2948
|
console.log(` curl -LO "${baseUrl}/huaweicloud-cli-${pkg}.tar.gz"`);
|
|
@@ -1327,7 +2952,9 @@ async function cmdInstallHcloud() {
|
|
|
1327
2952
|
console.log(`\nFull guide: https://support.huaweicloud.com/qs-hcli/hcli_02_003_02.html`);
|
|
1328
2953
|
} else if (os === 'darwin') {
|
|
1329
2954
|
console.log('[macOS] One-liner install:');
|
|
1330
|
-
console.log(
|
|
2955
|
+
console.log(
|
|
2956
|
+
' 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',
|
|
2957
|
+
);
|
|
1331
2958
|
console.log(`\nOr manual: ${arch === 'arm64' ? 'ARM64 (Apple Silicon)' : 'AMD64 (Intel)'}`);
|
|
1332
2959
|
const pkg = arch === 'arm64' ? 'mac-arm64' : 'mac-amd64';
|
|
1333
2960
|
console.log(` curl -LO "${baseUrl}/huaweicloud-cli-${pkg}.tar.gz"`);
|
|
@@ -1358,7 +2985,7 @@ function readLineQuestion(prompt) {
|
|
|
1358
2985
|
async function readSecret(prompt) {
|
|
1359
2986
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1360
2987
|
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
|
|
2988
|
+
`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
2989
|
);
|
|
1363
2990
|
}
|
|
1364
2991
|
|
|
@@ -1398,7 +3025,7 @@ async function readSecret(prompt) {
|
|
|
1398
3025
|
}
|
|
1399
3026
|
|
|
1400
3027
|
function configureHcloud(credentials) {
|
|
1401
|
-
const hcloudBin = findHcloudBin() ||
|
|
3028
|
+
const hcloudBin = findHcloudBin() || process.env.HCLOUD_BIN || 'hcloud';
|
|
1402
3029
|
const args = [
|
|
1403
3030
|
'configure',
|
|
1404
3031
|
'set',
|
|
@@ -1415,7 +3042,9 @@ function configureHcloud(credentials) {
|
|
|
1415
3042
|
return {
|
|
1416
3043
|
ok: r.status === 0,
|
|
1417
3044
|
code: r.status,
|
|
1418
|
-
error: String(r.stderr || '')
|
|
3045
|
+
error: String(r.stderr || '')
|
|
3046
|
+
.trim()
|
|
3047
|
+
.slice(0, 240),
|
|
1419
3048
|
};
|
|
1420
3049
|
}
|
|
1421
3050
|
|
|
@@ -1426,7 +3055,9 @@ function printAuthAgents(agents = {}) {
|
|
|
1426
3055
|
}
|
|
1427
3056
|
|
|
1428
3057
|
function printAuthStatus(status) {
|
|
1429
|
-
console.log(
|
|
3058
|
+
console.log(
|
|
3059
|
+
`Credentials vault: ${status.credentialsConfigured ? 'configured' : 'missing'} (${status.credentialsPath})`,
|
|
3060
|
+
);
|
|
1430
3061
|
console.log(`OBS config: ${status.obsConfigured ? 'configured' : 'missing'} (${status.obsConfigPath})`);
|
|
1431
3062
|
console.log(`KooCLI: ${status.kooCliInstalled ? 'installed' : 'missing'}`);
|
|
1432
3063
|
console.log('Agent MCP registration:');
|
|
@@ -1450,7 +3081,9 @@ async function cmdAuthInit() {
|
|
|
1450
3081
|
|
|
1451
3082
|
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
1452
3083
|
if (!interactive && (!ak || !sk)) {
|
|
1453
|
-
console.error(
|
|
3084
|
+
console.error(
|
|
3085
|
+
'\x1b[31mNon-interactive session detected. Provide credentials via environment variables instead:\x1b[0m',
|
|
3086
|
+
);
|
|
1454
3087
|
console.error(' HW_ACCESS_KEY, HW_SECRET_KEY');
|
|
1455
3088
|
console.error(' (Or run "npx huaweicloud-devkit auth init" in a real terminal.)');
|
|
1456
3089
|
process.exitCode = 1;
|
|
@@ -1459,7 +3092,8 @@ async function cmdAuthInit() {
|
|
|
1459
3092
|
|
|
1460
3093
|
if (!ak) ak = await readLineQuestion('Access Key ID (AK): ');
|
|
1461
3094
|
if (!sk) sk = await readSecret('Secret Access Key (SK): ');
|
|
1462
|
-
if (interactive && !securityToken)
|
|
3095
|
+
if (interactive && !securityToken)
|
|
3096
|
+
securityToken = await readLineQuestion('Security Token (optional, press Enter to skip): ');
|
|
1463
3097
|
if (!region) region = 'cn-north-4';
|
|
1464
3098
|
|
|
1465
3099
|
if (!ak || !sk) {
|
|
@@ -1468,8 +3102,7 @@ async function cmdAuthInit() {
|
|
|
1468
3102
|
return;
|
|
1469
3103
|
}
|
|
1470
3104
|
|
|
1471
|
-
|
|
1472
|
-
const vaultPath = writeGlobalCredentials({ ak, sk, securityToken, region });
|
|
3105
|
+
writeGlobalCredentials({ ak, sk, securityToken, region });
|
|
1473
3106
|
|
|
1474
3107
|
try {
|
|
1475
3108
|
writeObsConfig({ ak, sk, securityToken, region });
|
|
@@ -1524,6 +3157,95 @@ async function cmdAuth() {
|
|
|
1524
3157
|
return cmdAuthStatus();
|
|
1525
3158
|
}
|
|
1526
3159
|
|
|
3160
|
+
async function cmdProxyInit() {
|
|
3161
|
+
console.log(BANNER);
|
|
3162
|
+
console.log('HuaweiCloud DevKit Proxy Configuration\n');
|
|
3163
|
+
console.log('Configure HTTP/HTTPS proxy for connections to Huawei Cloud services.');
|
|
3164
|
+
console.log('Proxy settings are saved to ~/.config/huaweicloud/proxy.json\n');
|
|
3165
|
+
|
|
3166
|
+
const existing = readProxyConfig() || {};
|
|
3167
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
3168
|
+
|
|
3169
|
+
if (!interactive) {
|
|
3170
|
+
console.error('\x1b[31mNon-interactive session. Set proxy via environment variables:\x1b[0m');
|
|
3171
|
+
console.error(' HTTPS_PROXY=http://proxy:port');
|
|
3172
|
+
console.error(' HTTP_PROXY=http://proxy:port');
|
|
3173
|
+
console.error(' NO_PROXY=localhost,127.0.0.1');
|
|
3174
|
+
console.error('\nOr run "npx huaweicloud-devkit proxy init" in a real terminal.');
|
|
3175
|
+
process.exitCode = 1;
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
const httpsProxy = await readLineQuestion(`HTTPS proxy [${existing.https_proxy || 'none'}]: `);
|
|
3180
|
+
const httpProxy = await readLineQuestion(`HTTP proxy [${existing.http_proxy || 'none'}]: `);
|
|
3181
|
+
const noProxy = await readLineQuestion(
|
|
3182
|
+
`NO_PROXY hosts [${existing.no_proxy || '127.0.0.1,localhost,.huawei.com'}]: `,
|
|
3183
|
+
);
|
|
3184
|
+
|
|
3185
|
+
const config = {
|
|
3186
|
+
https_proxy: httpsProxy || existing.https_proxy || '',
|
|
3187
|
+
http_proxy: httpProxy || existing.http_proxy || '',
|
|
3188
|
+
no_proxy: noProxy || existing.no_proxy || '127.0.0.1,localhost,.huawei.com',
|
|
3189
|
+
};
|
|
3190
|
+
|
|
3191
|
+
const path = writeProxyConfig(config);
|
|
3192
|
+
console.log(`\nProxy configuration saved to ${path}`);
|
|
3193
|
+
console.log('\nEffective settings:');
|
|
3194
|
+
console.log(` HTTPS_PROXY: ${config.https_proxy || '(none)'}`);
|
|
3195
|
+
console.log(` HTTP_PROXY: ${config.http_proxy || '(none)'}`);
|
|
3196
|
+
console.log(` NO_PROXY: ${config.no_proxy || '(none)'}`);
|
|
3197
|
+
console.log('\nEnvironment variables (HTTPS_PROXY, HTTP_PROXY, NO_PROXY) override file settings.');
|
|
3198
|
+
}
|
|
3199
|
+
|
|
3200
|
+
async function cmdProxyShow() {
|
|
3201
|
+
console.log(BANNER);
|
|
3202
|
+
console.log('HuaweiCloud DevKit Proxy Configuration\n');
|
|
3203
|
+
|
|
3204
|
+
const config = readProxyConfig();
|
|
3205
|
+
const configPath = proxyConfigPath();
|
|
3206
|
+
|
|
3207
|
+
console.log(`Config file: ${configPath}`);
|
|
3208
|
+
console.log(`File exists: ${config ? 'yes' : 'no'}\n`);
|
|
3209
|
+
|
|
3210
|
+
if (config) {
|
|
3211
|
+
console.log('File settings:');
|
|
3212
|
+
console.log(` https_proxy: ${config.https_proxy || '(empty)'}`);
|
|
3213
|
+
console.log(` http_proxy: ${config.http_proxy || '(empty)'}`);
|
|
3214
|
+
console.log(` no_proxy: ${config.no_proxy || '(empty)'}`);
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
console.log('\nEnvironment variables:');
|
|
3218
|
+
console.log(` HTTPS_PROXY: ${process.env.HTTPS_PROXY || process.env.https_proxy || '(not set)'}`);
|
|
3219
|
+
console.log(` HTTP_PROXY: ${process.env.HTTP_PROXY || process.env.http_proxy || '(not set)'}`);
|
|
3220
|
+
console.log(` NO_PROXY: ${process.env.NO_PROXY || process.env.no_proxy || '(not set)'}`);
|
|
3221
|
+
|
|
3222
|
+
const effective = getProxySettings();
|
|
3223
|
+
console.log('\nEffective (env > file):');
|
|
3224
|
+
if (effective) {
|
|
3225
|
+
console.log(` https_proxy: ${effective.https_proxy || '(none)'}`);
|
|
3226
|
+
console.log(` http_proxy: ${effective.http_proxy || '(none)'}`);
|
|
3227
|
+
console.log(` no_proxy: ${effective.no_proxy || '(none)'}`);
|
|
3228
|
+
} else {
|
|
3229
|
+
console.log(' (no proxy configured)');
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
async function cmdProxyClear() {
|
|
3234
|
+
const removed = clearProxyConfig();
|
|
3235
|
+
if (removed) {
|
|
3236
|
+
console.log('Proxy configuration removed.');
|
|
3237
|
+
} else {
|
|
3238
|
+
console.log('No proxy configuration file found.');
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
async function cmdProxy() {
|
|
3243
|
+
const sub = (process.argv[3] || 'show').toLowerCase();
|
|
3244
|
+
if (sub === 'init' || sub === 'setup') return cmdProxyInit();
|
|
3245
|
+
if (sub === 'clear' || sub === 'remove' || sub === 'reset') return cmdProxyClear();
|
|
3246
|
+
return cmdProxyShow();
|
|
3247
|
+
}
|
|
3248
|
+
|
|
1527
3249
|
async function main() {
|
|
1528
3250
|
const cmd = process.argv[2] || 'help';
|
|
1529
3251
|
|
|
@@ -1557,12 +3279,17 @@ async function main() {
|
|
|
1557
3279
|
case 'auth':
|
|
1558
3280
|
await cmdAuth();
|
|
1559
3281
|
break;
|
|
3282
|
+
case 'proxy':
|
|
3283
|
+
await cmdProxy();
|
|
3284
|
+
break;
|
|
1560
3285
|
case 'help':
|
|
1561
3286
|
case '--help':
|
|
1562
3287
|
case '-h':
|
|
1563
3288
|
default:
|
|
1564
3289
|
console.log(BANNER);
|
|
1565
|
-
console.log(
|
|
3290
|
+
console.log(
|
|
3291
|
+
'Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|dsh|officeace|hermes|openclaw|atomcode|all>]\n',
|
|
3292
|
+
);
|
|
1566
3293
|
console.log('Commands:');
|
|
1567
3294
|
console.log(' install Install skills, MCP server, safety policy');
|
|
1568
3295
|
console.log(' uninstall Remove installed files');
|
|
@@ -1572,23 +3299,32 @@ async function main() {
|
|
|
1572
3299
|
console.log(' doctor Self-check: hcloud, MCP, skills, auth');
|
|
1573
3300
|
console.log(' install-hcloud Show KooCLI install commands for your OS');
|
|
1574
3301
|
console.log(' auth Manage unified auth: init | sync | status');
|
|
3302
|
+
console.log(' proxy Manage proxy config: init | show | clear');
|
|
1575
3303
|
console.log(' help Show this help');
|
|
1576
3304
|
console.log('\nOptions:');
|
|
1577
|
-
console.log(
|
|
3305
|
+
console.log(
|
|
3306
|
+
' --target Target agent: opencode (default), codex, codearts, workbuddy, dsh, officeace, hermes, openclaw, atomcode, all',
|
|
3307
|
+
);
|
|
1578
3308
|
console.log('\nExamples:');
|
|
1579
3309
|
console.log(' npx huaweicloud-devkit install');
|
|
1580
3310
|
console.log(' npx huaweicloud-devkit install --target codex');
|
|
1581
3311
|
console.log(' npx huaweicloud-devkit install --target codearts');
|
|
1582
3312
|
console.log(' npx huaweicloud-devkit install --target workbuddy');
|
|
3313
|
+
console.log(' npx huaweicloud-devkit install --target dsh');
|
|
3314
|
+
console.log(' npx huaweicloud-devkit install --target officeace');
|
|
3315
|
+
console.log(' npx huaweicloud-devkit install --target hermes');
|
|
3316
|
+
console.log(' npx huaweicloud-devkit install --target atomcode');
|
|
1583
3317
|
console.log(' npx huaweicloud-devkit install --target all');
|
|
1584
3318
|
console.log(' npx huaweicloud-devkit auth init');
|
|
1585
3319
|
console.log(' npx huaweicloud-devkit auth sync --target all');
|
|
1586
3320
|
console.log(' npx huaweicloud-devkit auth status --target all');
|
|
3321
|
+
console.log(' npx huaweicloud-devkit proxy init');
|
|
3322
|
+
console.log(' npx huaweicloud-devkit proxy show');
|
|
1587
3323
|
break;
|
|
1588
3324
|
}
|
|
1589
3325
|
}
|
|
1590
3326
|
|
|
1591
|
-
main().catch((
|
|
1592
|
-
console.error(`\x1b[31mError: ${
|
|
3327
|
+
main().catch((error) => {
|
|
3328
|
+
console.error(`\x1b[31mError: ${error.message}\x1b[0m`);
|
|
1593
3329
|
process.exit(1);
|
|
1594
3330
|
});
|