huaweicloud-devkit 1.0.2-next.2 → 1.0.2-next.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +18 -18
- package/README.md +81 -51
- package/README.zh-CN.md +81 -49
- package/bin/setup.cjs +5 -3
- package/integrations/opencode/opencode.json +1 -4
- package/package.json +61 -44
- 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-cloud-find-skills/SKILL.md +13 -12
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +64 -17
- 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 +25 -25
- package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +45 -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 +127 -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 +17 -1
- 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 +56 -53
- 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 +108 -5
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +77 -4
- package/plugins/huaweicloud-core/src/auth/service.mjs +1 -1
- package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +25 -7
- package/plugins/huaweicloud-core/src/icon-library.mjs +171 -0
- package/plugins/huaweicloud-core/src/mcp-server.mjs +30 -3
- package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +20 -102
- package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +11 -4
- package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +4 -1
- package/plugins/huaweicloud-core/src/safety-policy.mjs +122 -68
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +9 -9
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +24 -26
- package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +510 -10
- package/plugins/huaweicloud-core/src/search-market.mjs +46 -12
- package/plugins/huaweicloud-core/src/setup-cli.mjs +1251 -181
- package/plugins/huaweicloud-core/src/tools.mjs +600 -122
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +72 -38
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +10 -5
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +12 -15
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +12 -16
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +46 -31
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +267 -0
- package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
- package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +9 -5
|
@@ -1,14 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
copyFileSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
statSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from 'node:fs';
|
|
4
11
|
import { join, dirname, resolve } from 'node:path';
|
|
5
12
|
import { fileURLToPath } from 'node:url';
|
|
6
13
|
import { homedir, platform } from 'node:os';
|
|
7
14
|
import { createInterface } from 'node:readline';
|
|
8
15
|
import { spawnSync } from 'node:child_process';
|
|
16
|
+
import { randomUUID } from 'node:crypto';
|
|
17
|
+
import Database from 'better-sqlite3';
|
|
9
18
|
import { getAuthStatus, syncAuth } from './auth/service.mjs';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
19
|
+
import { SUPPORTED_AGENT_TARGETS } from './auth/agent-registration.mjs';
|
|
20
|
+
import {
|
|
21
|
+
globalCredentialsPath,
|
|
22
|
+
readGlobalCredentials,
|
|
23
|
+
writeGlobalCredentials,
|
|
24
|
+
writeObsConfig,
|
|
25
|
+
} from './auth/credentials.mjs';
|
|
26
|
+
import {
|
|
27
|
+
proxyConfigPath,
|
|
28
|
+
readProxyConfig,
|
|
29
|
+
writeProxyConfig,
|
|
30
|
+
clearProxyConfig,
|
|
31
|
+
getProxySettings,
|
|
32
|
+
} from './proxy/proxy-config.mjs';
|
|
12
33
|
|
|
13
34
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
35
|
const PLUGIN_ROOT = resolve(__dirname, '..');
|
|
@@ -31,37 +52,317 @@ function configRoot(target = 'opencode') {
|
|
|
31
52
|
return join(home, '.config', target);
|
|
32
53
|
}
|
|
33
54
|
|
|
34
|
-
function opencodeSkillsDir() {
|
|
35
|
-
|
|
36
|
-
|
|
55
|
+
function opencodeSkillsDir() {
|
|
56
|
+
return join(configRoot('opencode'), 'skills');
|
|
57
|
+
}
|
|
58
|
+
function opencodeCommandsDir() {
|
|
59
|
+
return join(configRoot('opencode'), 'commands');
|
|
60
|
+
}
|
|
61
|
+
function opencodePluginsDir() {
|
|
62
|
+
return join(configRoot('opencode'), 'huaweicloud-plugins');
|
|
63
|
+
}
|
|
37
64
|
function opencodeConfigFile() {
|
|
38
65
|
const jsonc = join(configRoot('opencode'), 'opencode.jsonc');
|
|
39
66
|
if (existsSync(jsonc)) return jsonc;
|
|
40
67
|
return join(configRoot('opencode'), 'opencode.json');
|
|
41
68
|
}
|
|
42
69
|
|
|
43
|
-
function codexDesktopSkillsDir() {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
function
|
|
70
|
+
function codexDesktopSkillsDir() {
|
|
71
|
+
return join(homedir(), '.agents', 'skills');
|
|
72
|
+
}
|
|
73
|
+
function codexDesktopCommandsDir() {
|
|
74
|
+
return join(homedir(), '.agents', 'commands');
|
|
75
|
+
}
|
|
76
|
+
function codexDesktopPluginsDir() {
|
|
77
|
+
return join(homedir(), '.agents', 'huaweicloud-plugins');
|
|
78
|
+
}
|
|
79
|
+
function codexConfigToml() {
|
|
80
|
+
return join(homedir(), '.codex', 'config.toml');
|
|
81
|
+
}
|
|
47
82
|
|
|
48
|
-
function codeartsSkillsDir() {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
function
|
|
83
|
+
function codeartsSkillsDir() {
|
|
84
|
+
return join(homedir(), '.codeartsdoer', 'skills');
|
|
85
|
+
}
|
|
86
|
+
function codeartsMcpSettingsDir() {
|
|
87
|
+
return join(homedir(), '.codeartsdoer', 'mcp');
|
|
88
|
+
}
|
|
89
|
+
function codeartsMcpSettingsFile() {
|
|
90
|
+
return join(codeartsMcpSettingsDir(), 'mcp_settings.json');
|
|
91
|
+
}
|
|
92
|
+
function codeartsProjectDir() {
|
|
93
|
+
return join(process.cwd(), '.codeartsdoer');
|
|
94
|
+
}
|
|
95
|
+
function codeartsProjectSkillsDir() {
|
|
96
|
+
return join(codeartsProjectDir(), 'skills');
|
|
97
|
+
}
|
|
98
|
+
function codeartsProjectMcpSettingsFile() {
|
|
99
|
+
return join(codeartsProjectDir(), 'mcp', 'mcp_settings.json');
|
|
100
|
+
}
|
|
101
|
+
function codeartsPluginsDir() {
|
|
102
|
+
return join(homedir(), '.codeartsdoer', 'huaweicloud-plugins');
|
|
103
|
+
}
|
|
55
104
|
|
|
56
|
-
function workbuddySkillsDir() {
|
|
57
|
-
|
|
58
|
-
|
|
105
|
+
function workbuddySkillsDir() {
|
|
106
|
+
return join(homedir(), '.workbuddy', 'skills');
|
|
107
|
+
}
|
|
108
|
+
function workbuddyMcpConfigFile() {
|
|
109
|
+
return join(homedir(), '.workbuddy', 'mcp.json');
|
|
110
|
+
}
|
|
111
|
+
function workbuddyPluginsDir() {
|
|
112
|
+
return join(homedir(), '.workbuddy', 'huaweicloud-plugins');
|
|
113
|
+
}
|
|
59
114
|
|
|
60
|
-
function dshRoot() {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
function
|
|
64
|
-
|
|
115
|
+
function dshRoot() {
|
|
116
|
+
return process.env.DSH_HOME || join(homedir(), '.dsh');
|
|
117
|
+
}
|
|
118
|
+
function dshSkillsDir() {
|
|
119
|
+
return join(dshRoot(), 'skills');
|
|
120
|
+
}
|
|
121
|
+
function dshProfileDir() {
|
|
122
|
+
return join(dshRoot(), 'profiles', 'web');
|
|
123
|
+
}
|
|
124
|
+
function dshPatchFile() {
|
|
125
|
+
return join(dshProfileDir(), 'cordis.patch.yml');
|
|
126
|
+
}
|
|
127
|
+
function dshPluginsDir() {
|
|
128
|
+
return join(dshRoot(), 'huaweicloud-plugins');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function readOfficeaceRegistryInstallDir() {
|
|
132
|
+
if (platform() !== 'win32') return null;
|
|
133
|
+
try {
|
|
134
|
+
const r = spawnSync('reg', ['query', 'HKCU\\SOFTWARE\\OfficeAce\\OfficeAce', '/v', 'InstallDir'], {
|
|
135
|
+
encoding: 'utf8',
|
|
136
|
+
windowsHide: true,
|
|
137
|
+
timeout: 5000,
|
|
138
|
+
});
|
|
139
|
+
if (r.status === 0) {
|
|
140
|
+
const m = r.stdout.match(/InstallDir\s+REG_SZ\s+(.+)/);
|
|
141
|
+
if (m) return m[1].trim();
|
|
142
|
+
}
|
|
143
|
+
} catch {}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function officeaceCapabilitiesDir() {
|
|
148
|
+
const configRoot = process.env.OFFICE_CLAW_CONFIG_ROOT;
|
|
149
|
+
if (configRoot && existsSync(join(configRoot, 'capabilities.json'))) return configRoot;
|
|
150
|
+
const regDir = readOfficeaceRegistryInstallDir();
|
|
151
|
+
if (regDir) {
|
|
152
|
+
const dir = join(regDir, '.office-claw');
|
|
153
|
+
if (existsSync(join(dir, 'capabilities.json'))) return dir;
|
|
154
|
+
}
|
|
155
|
+
if (platform() === 'win32') {
|
|
156
|
+
const bases = [process.env.ProgramFiles, 'C:\\Program Files', 'D:\\Program Files'];
|
|
157
|
+
if (process.env.LOCALAPPDATA) bases.push(join(process.env.LOCALAPPDATA, 'Programs'));
|
|
158
|
+
for (const base of bases) {
|
|
159
|
+
if (!base) continue;
|
|
160
|
+
const dir = join(base, 'OfficeAce', '.office-claw');
|
|
161
|
+
if (existsSync(join(dir, 'capabilities.json'))) return dir;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function officeaceCapabilitiesDirSafe() {
|
|
168
|
+
return officeaceCapabilitiesDir() || join(homedir(), '.office-claw');
|
|
169
|
+
}
|
|
170
|
+
function officeaceCapabilitiesFile() {
|
|
171
|
+
return join(officeaceCapabilitiesDirSafe(), 'capabilities.json');
|
|
172
|
+
}
|
|
173
|
+
function officeaceSkillsDir() {
|
|
174
|
+
return join(officeaceCapabilitiesDirSafe(), 'skills');
|
|
175
|
+
}
|
|
176
|
+
function officeacePluginsDir() {
|
|
177
|
+
return join(officeaceCapabilitiesDirSafe(), 'huaweicloud-plugins');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function officeaceSqlitePath() {
|
|
181
|
+
const capDir = officeaceCapabilitiesDirSafe();
|
|
182
|
+
return join(resolve(capDir, '..'), 'data', 'mcp-connectors.sqlite');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function openOfficeaceDb() {
|
|
186
|
+
return new Database(officeaceSqlitePath());
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function officeaceGetOwnerUserId() {
|
|
190
|
+
if (!existsSync(officeaceSqlitePath())) return null;
|
|
191
|
+
try {
|
|
192
|
+
const db = openOfficeaceDb();
|
|
193
|
+
const row = db.prepare('SELECT owner_user_id FROM mcp_connectors WHERE owner_user_id IS NOT NULL LIMIT 1').get();
|
|
194
|
+
db.close();
|
|
195
|
+
return row?.owner_user_id || null;
|
|
196
|
+
} catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function ensureOfficeaceMcpInSqlite() {
|
|
202
|
+
const dbPath = officeaceSqlitePath();
|
|
203
|
+
if (!existsSync(dbPath)) {
|
|
204
|
+
console.log(` \x1b[31mOfficeAce database not found: ${dbPath}\x1b[0m`);
|
|
205
|
+
console.log(` \x1b[33mPlease ensure OfficeAce is installed and has been launched at least once.\x1b[0m`);
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const mcpPath = join(officeacePluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
210
|
+
const env = [{ key: 'HUAWEICLOUD_AGENT_TOOLKIT_MODE', value: 'local', sensitive: false }];
|
|
211
|
+
const hcloudBin = findHcloudBin();
|
|
212
|
+
if (hcloudBin) env.push({ key: 'HCLOUD_BIN', value: hcloudBin.replace(/\\/g, '/'), sensitive: false });
|
|
213
|
+
|
|
214
|
+
const now = Date.now();
|
|
215
|
+
let db;
|
|
216
|
+
try {
|
|
217
|
+
db = openOfficeaceDb();
|
|
218
|
+
|
|
219
|
+
const existing = db
|
|
220
|
+
.prepare("SELECT id, command, args_json FROM mcp_connectors WHERE name = 'huaweicloud-devkit'")
|
|
221
|
+
.get();
|
|
222
|
+
|
|
223
|
+
const argsJson = JSON.stringify([mcpPath]);
|
|
224
|
+
const envJson = JSON.stringify(env);
|
|
225
|
+
|
|
226
|
+
if (existing) {
|
|
227
|
+
if (existing.command === 'node' && existing.args_json === argsJson) {
|
|
228
|
+
console.log(` MCP config unchanged: ${dbPath}`);
|
|
229
|
+
db.close();
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
db.prepare(
|
|
233
|
+
'UPDATE mcp_connectors SET command = ?, args_json = ?, env_json = ?, updated_at = ?, status = ?, enabled = 1 WHERE id = ?',
|
|
234
|
+
).run('node', argsJson, envJson, now, 'disconnected', existing.id);
|
|
235
|
+
console.log(` MCP config updated: ${dbPath}`);
|
|
236
|
+
} else {
|
|
237
|
+
const ownerUserId = officeaceGetOwnerUserId();
|
|
238
|
+
if (!ownerUserId) {
|
|
239
|
+
console.log(` \x1b[31mCannot determine owner_user_id from database\x1b[0m`);
|
|
240
|
+
db.close();
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
db.prepare(
|
|
244
|
+
`INSERT INTO mcp_connectors (id, owner_user_id, type, name, normalized_name, transport, timeout_ms, command, args_json, env_json, enabled, status, created_at, updated_at, version, seeded)
|
|
245
|
+
VALUES (?, ?, 'custom', 'huaweicloud-devkit', 'huaweicloud-devkit', 'stdio', 60000, 'node', ?, ?, 1, 'disconnected', ?, ?, 1, 0)`,
|
|
246
|
+
).run(randomUUID(), ownerUserId, argsJson, envJson, now, now);
|
|
247
|
+
console.log(` MCP config created: ${dbPath}`);
|
|
248
|
+
}
|
|
249
|
+
db.close();
|
|
250
|
+
return true;
|
|
251
|
+
} catch (err) {
|
|
252
|
+
if (db) {
|
|
253
|
+
try {
|
|
254
|
+
db.close();
|
|
255
|
+
} catch {}
|
|
256
|
+
}
|
|
257
|
+
console.log(` \x1b[31mFailed to write MCP config: ${err.message}\x1b[0m`);
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function removeOfficeaceMcpFromSqlite() {
|
|
263
|
+
const dbPath = officeaceSqlitePath();
|
|
264
|
+
if (!existsSync(dbPath)) return;
|
|
265
|
+
let db;
|
|
266
|
+
try {
|
|
267
|
+
db = openOfficeaceDb();
|
|
268
|
+
db.prepare(
|
|
269
|
+
"DELETE FROM mcp_connector_tools WHERE connector_id IN (SELECT id FROM mcp_connectors WHERE name = 'huaweicloud-devkit')",
|
|
270
|
+
).run();
|
|
271
|
+
const result2 = db.prepare("DELETE FROM mcp_connectors WHERE name = 'huaweicloud-devkit'").run();
|
|
272
|
+
if (result2.changes > 0) {
|
|
273
|
+
console.log(` MCP config removed: ${dbPath}`);
|
|
274
|
+
}
|
|
275
|
+
db.close();
|
|
276
|
+
} catch (err) {
|
|
277
|
+
if (db) {
|
|
278
|
+
try {
|
|
279
|
+
db.close();
|
|
280
|
+
} catch {}
|
|
281
|
+
}
|
|
282
|
+
console.log(` \x1b[31mFailed to remove MCP config: ${err.message}\x1b[0m`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function readCapabilitiesJson() {
|
|
287
|
+
const capFile = officeaceCapabilitiesFile();
|
|
288
|
+
if (!existsSync(capFile)) return { capabilities: [] };
|
|
289
|
+
try {
|
|
290
|
+
return JSON.parse(readFileSync(capFile, 'utf8'));
|
|
291
|
+
} catch {
|
|
292
|
+
return { capabilities: [] };
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function writeCapabilitiesJson(config) {
|
|
297
|
+
mkdirSync(officeaceCapabilitiesDirSafe(), { recursive: true });
|
|
298
|
+
writeFileSync(officeaceCapabilitiesFile(), JSON.stringify(config, null, 2));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function removeOfficeaceSkillCapabilities() {
|
|
302
|
+
const capFile = officeaceCapabilitiesFile();
|
|
303
|
+
if (!existsSync(capFile)) return;
|
|
304
|
+
let config;
|
|
305
|
+
try {
|
|
306
|
+
config = JSON.parse(readFileSync(capFile, 'utf8'));
|
|
307
|
+
} catch {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (!Array.isArray(config.capabilities)) return;
|
|
311
|
+
const origLen = config.capabilities.length;
|
|
312
|
+
config.capabilities = config.capabilities.filter(
|
|
313
|
+
(c) =>
|
|
314
|
+
!(
|
|
315
|
+
(c.id === 'huaweicloud-core' && c.type === 'skill') ||
|
|
316
|
+
(c.source === 'custom' && c.id && c.id.startsWith('huawei'))
|
|
317
|
+
),
|
|
318
|
+
);
|
|
319
|
+
if (config.capabilities.length !== origLen) {
|
|
320
|
+
writeCapabilitiesJson(config);
|
|
321
|
+
console.log(` Skill capabilities cleaned: ${capFile}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function registerOfficeaceSkillEntries() {
|
|
326
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
327
|
+
if (!existsSync(skillsSrc)) return;
|
|
328
|
+
const skillNames = readdirSync(skillsSrc, { withFileTypes: true })
|
|
329
|
+
.filter((d) => d.isDirectory() && d.name.startsWith('huawei'))
|
|
330
|
+
.map((d) => d.name);
|
|
331
|
+
|
|
332
|
+
const config = readCapabilitiesJson();
|
|
333
|
+
let changed = false;
|
|
334
|
+
|
|
335
|
+
for (const name of skillNames) {
|
|
336
|
+
const existingIdx = config.capabilities.findIndex((c) => c.id === name && c.type === 'skill');
|
|
337
|
+
if (existingIdx >= 0) continue;
|
|
338
|
+
config.capabilities.push({
|
|
339
|
+
id: name,
|
|
340
|
+
type: 'skill',
|
|
341
|
+
enabled: true,
|
|
342
|
+
source: 'custom',
|
|
343
|
+
selfEvolution: 'suggest',
|
|
344
|
+
followGlobalSelfEvolution: true,
|
|
345
|
+
});
|
|
346
|
+
changed = true;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!config.capabilities.some((c) => c.id === 'huaweicloud-core' && c.type === 'skill')) {
|
|
350
|
+
config.capabilities.push({
|
|
351
|
+
id: 'huaweicloud-core',
|
|
352
|
+
type: 'skill',
|
|
353
|
+
enabled: true,
|
|
354
|
+
source: 'custom',
|
|
355
|
+
selfEvolution: 'suggest',
|
|
356
|
+
followGlobalSelfEvolution: true,
|
|
357
|
+
});
|
|
358
|
+
changed = true;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (changed) {
|
|
362
|
+
writeCapabilitiesJson(config);
|
|
363
|
+
console.log(` Skill entries registered: ${officeaceCapabilitiesFile()}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
65
366
|
|
|
66
367
|
const DSH_MCP_PATCH_START = '# HuaweiCloud DevKit DSH integration start';
|
|
67
368
|
const DSH_MCP_PATCH_END = '# HuaweiCloud DevKit DSH integration end';
|
|
@@ -84,7 +385,11 @@ function findHcloudBin() {
|
|
|
84
385
|
const isWin = platform() === 'win32';
|
|
85
386
|
const candidates = isWin
|
|
86
387
|
? [join(homedir(), 'hcloud', 'hcloud.exe')]
|
|
87
|
-
: [
|
|
388
|
+
: [
|
|
389
|
+
join(homedir(), '.local', 'bin', 'hcloud'),
|
|
390
|
+
join(homedir(), 'hcloud', 'hcloud'),
|
|
391
|
+
join(homedir(), 'hcloud', 'hcloud.exe'),
|
|
392
|
+
];
|
|
88
393
|
for (const c of candidates) {
|
|
89
394
|
if (existsSync(c)) return c;
|
|
90
395
|
}
|
|
@@ -97,14 +402,14 @@ function printSandboxWarning(reason) {
|
|
|
97
402
|
console.log(`\x1b[31m 请任选其一继续:\x1b[0m`);
|
|
98
403
|
console.log(`\x1b[31m A. 在码道外的终端安装并使用 KooCLI (推荐):`);
|
|
99
404
|
console.log(`\x1b[31m https://support.huaweicloud.com/qs-hcli/hcli_02_003.html`);
|
|
100
|
-
console.log(`\x1b[31m B. 在码道设置中关闭沙箱模式后重试 (设置 →
|
|
405
|
+
console.log(`\x1b[31m B. 在码道设置中关闭沙箱模式后重试 (设置 → 对话流 → 智能体 终端命令运行模式 → 自动运行)`);
|
|
101
406
|
console.log(`\x1b[31m 关闭沙箱后重新运行: npx huaweicloud-devkit install-hcloud\x1b[0m`);
|
|
102
407
|
}
|
|
103
408
|
|
|
104
409
|
function checkNode() {
|
|
105
410
|
const v = process.versions.node.split('.').map(Number);
|
|
106
|
-
if (v[0] <
|
|
107
|
-
console.error(`\x1b[31mNode.js >=
|
|
411
|
+
if (v[0] < 22) {
|
|
412
|
+
console.error(`\x1b[31mNode.js >= 22 required (current: ${process.version})\x1b[0m`);
|
|
108
413
|
process.exit(1);
|
|
109
414
|
}
|
|
110
415
|
console.log(` Node.js ${process.version} \x1b[32mOK\x1b[0m`);
|
|
@@ -124,6 +429,35 @@ function copyDir(src, dest) {
|
|
|
124
429
|
}
|
|
125
430
|
}
|
|
126
431
|
|
|
432
|
+
function installRuntimeDeps(pluginsDir) {
|
|
433
|
+
const pkgJson = {
|
|
434
|
+
name: 'huaweicloud-devkit',
|
|
435
|
+
version: pkgVersion,
|
|
436
|
+
type: 'module',
|
|
437
|
+
dependencies: { undici: '^8.10.0' },
|
|
438
|
+
};
|
|
439
|
+
mkdirSync(pluginsDir, { recursive: true });
|
|
440
|
+
writeFileSync(join(pluginsDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
|
|
441
|
+
const r = spawnSync('npm', ['install', '--omit=dev'], {
|
|
442
|
+
cwd: pluginsDir,
|
|
443
|
+
windowsHide: true,
|
|
444
|
+
stdio: 'pipe',
|
|
445
|
+
timeout: 120000,
|
|
446
|
+
});
|
|
447
|
+
const undiciDir = join(pluginsDir, 'node_modules', 'undici');
|
|
448
|
+
if (r.status === 0 && existsSync(undiciDir)) {
|
|
449
|
+
console.log(` Runtime deps installed -> ${join(pluginsDir, 'node_modules')}`);
|
|
450
|
+
} else {
|
|
451
|
+
const err = (r.stderr || '').toString().trim().split(/\r?\n/).slice(-2).join(' ');
|
|
452
|
+
console.log(` \x1b[33m[WARN]\x1b[0m npm install failed in ${pluginsDir}${err ? `: ${err}` : ''}`);
|
|
453
|
+
console.log(' Manual fix: cd %s && npm install', pluginsDir);
|
|
454
|
+
if (!existsSync(undiciDir)) {
|
|
455
|
+
console.log(` \x1b[31m[ERROR]\x1b[0m undici is NOT installed. MCP server will fail to start.`);
|
|
456
|
+
console.log(` Run manually: cd "${pluginsDir}" && npm install undici@^8.10.0`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
127
461
|
function removeIfExists(p) {
|
|
128
462
|
if (existsSync(p)) {
|
|
129
463
|
try {
|
|
@@ -142,14 +476,23 @@ function updateOpenCodeConfig(pluginDir) {
|
|
|
142
476
|
const mcpPath = join(pluginDir, 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
143
477
|
let config = {};
|
|
144
478
|
if (existsSync(configPath)) {
|
|
145
|
-
try {
|
|
146
|
-
|
|
479
|
+
try {
|
|
480
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
481
|
+
} catch {
|
|
482
|
+
console.log(
|
|
483
|
+
` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath} (jsonc comments?). Skipping MCP config write; ensure "mcp.huaweicloud-devkit" points to ${mcpPath}.`,
|
|
484
|
+
);
|
|
147
485
|
return;
|
|
148
486
|
}
|
|
149
487
|
const existing = config.mcp?.['huaweicloud-devkit'];
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
488
|
+
if (
|
|
489
|
+
existing &&
|
|
490
|
+
existing.type === 'local' &&
|
|
491
|
+
Array.isArray(existing.command) &&
|
|
492
|
+
existing.command[0] === 'node' &&
|
|
493
|
+
existing.command[1] === mcpPath &&
|
|
494
|
+
existing.timeout === 300000
|
|
495
|
+
) {
|
|
153
496
|
console.log(` OpenCode MCP config unchanged: ${configPath}`);
|
|
154
497
|
return;
|
|
155
498
|
}
|
|
@@ -159,6 +502,7 @@ function updateOpenCodeConfig(pluginDir) {
|
|
|
159
502
|
type: 'local',
|
|
160
503
|
command: ['node', mcpPath],
|
|
161
504
|
enabled: true,
|
|
505
|
+
timeout: 300000,
|
|
162
506
|
};
|
|
163
507
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
164
508
|
console.log(` OpenCode MCP config updated: ${configPath}`);
|
|
@@ -167,8 +511,12 @@ function updateOpenCodeConfig(pluginDir) {
|
|
|
167
511
|
function removeOpenCodeConfig() {
|
|
168
512
|
const configPath = opencodeConfigFile();
|
|
169
513
|
if (!existsSync(configPath)) return;
|
|
170
|
-
let config
|
|
171
|
-
try {
|
|
514
|
+
let config;
|
|
515
|
+
try {
|
|
516
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
517
|
+
} catch {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
172
520
|
if (!config.mcp?.['huaweicloud-devkit']) return;
|
|
173
521
|
delete config.mcp['huaweicloud-devkit'];
|
|
174
522
|
if (Object.keys(config.mcp).length === 0) delete config.mcp;
|
|
@@ -189,11 +537,13 @@ function hasCodexCLI() {
|
|
|
189
537
|
}
|
|
190
538
|
|
|
191
539
|
function checkHcloud() {
|
|
192
|
-
const bin = findHcloudBin() ||
|
|
540
|
+
const bin = findHcloudBin() || process.env.HCLOUD_BIN || 'hcloud';
|
|
193
541
|
if (!existsSync(bin)) return false;
|
|
194
542
|
try {
|
|
195
543
|
if (statSync(bin).size < 1024) return false;
|
|
196
|
-
} catch {
|
|
544
|
+
} catch {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
197
547
|
try {
|
|
198
548
|
const r = spawnSync(`"${bin}" version`, [], { shell: true, windowsHide: true, stdio: 'pipe', timeout: 5000 });
|
|
199
549
|
const out = (r.stdout ? r.stdout.toString() : '') + (r.stderr ? r.stderr.toString() : '');
|
|
@@ -219,7 +569,9 @@ function installCodex() {
|
|
|
219
569
|
|
|
220
570
|
console.log(` Registering Codex marketplace: ${marketplaceRoot}`);
|
|
221
571
|
const r1 = spawnSync(`codex plugin marketplace add "${marketplaceRoot}"`, [], {
|
|
222
|
-
shell: true,
|
|
572
|
+
shell: true,
|
|
573
|
+
windowsHide: true,
|
|
574
|
+
stdio: 'pipe',
|
|
223
575
|
});
|
|
224
576
|
console.log(` ${r1.stdout ? r1.stdout.toString().trim() : r1.stderr.toString().trim()}`);
|
|
225
577
|
|
|
@@ -231,7 +583,9 @@ function installCodex() {
|
|
|
231
583
|
|
|
232
584
|
console.log(` Installing plugin: ${pluginName}@${marketplaceName}`);
|
|
233
585
|
const r2 = spawnSync(`codex plugin add "${pluginName}@${marketplaceName}"`, [], {
|
|
234
|
-
shell: true,
|
|
586
|
+
shell: true,
|
|
587
|
+
windowsHide: true,
|
|
588
|
+
stdio: 'pipe',
|
|
235
589
|
});
|
|
236
590
|
console.log(` ${r2.stdout ? r2.stdout.toString().trim() : r2.stderr.toString().trim()}`);
|
|
237
591
|
|
|
@@ -249,14 +603,18 @@ function uninstallCodex() {
|
|
|
249
603
|
const marketplaceName = getMarketplaceName();
|
|
250
604
|
console.log(` Removing Codex plugin: ${pluginName}@${marketplaceName}`);
|
|
251
605
|
const r = spawnSync(`codex plugin remove "${pluginName}@${marketplaceName}"`, [], {
|
|
252
|
-
shell: true,
|
|
606
|
+
shell: true,
|
|
607
|
+
windowsHide: true,
|
|
608
|
+
stdio: 'pipe',
|
|
253
609
|
});
|
|
254
610
|
console.log(` ${r.stdout ? r.stdout.toString().trim() : r.stderr.toString().trim()}`);
|
|
255
611
|
|
|
256
612
|
for (const name of new Set([marketplaceName, 'HuaweiCloud-Devkit'])) {
|
|
257
613
|
console.log(` Removing Codex marketplace: ${name}`);
|
|
258
614
|
const r2 = spawnSync(`codex plugin marketplace remove "${name}"`, [], {
|
|
259
|
-
shell: true,
|
|
615
|
+
shell: true,
|
|
616
|
+
windowsHide: true,
|
|
617
|
+
stdio: 'pipe',
|
|
260
618
|
});
|
|
261
619
|
console.log(` ${r2.stdout ? r2.stdout.toString().trim() : r2.stderr.toString().trim()}`);
|
|
262
620
|
}
|
|
@@ -284,6 +642,7 @@ async function installOpenCode() {
|
|
|
284
642
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
285
643
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
286
644
|
updateOpenCodeConfig(pluginDest);
|
|
645
|
+
installRuntimeDeps(pluginDest);
|
|
287
646
|
}
|
|
288
647
|
|
|
289
648
|
function uninstallOpenCode() {
|
|
@@ -346,7 +705,9 @@ async function updateOpenCode() {
|
|
|
346
705
|
console.log(` Skills updated -> ${opencodeSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`);
|
|
347
706
|
copyDir(commandsSrc, opencodeCommandsDir());
|
|
348
707
|
const staleCommands = pruneStale(opencodeCommandsDir(), commandsSrc);
|
|
349
|
-
console.log(
|
|
708
|
+
console.log(
|
|
709
|
+
` Commands updated -> ${opencodeCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`,
|
|
710
|
+
);
|
|
350
711
|
copyDir(srcDir, join(pluginDest, 'src'));
|
|
351
712
|
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
352
713
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
@@ -354,6 +715,7 @@ async function updateOpenCode() {
|
|
|
354
715
|
updateOpenCodeConfig(pluginDest);
|
|
355
716
|
mkdirSync(pluginDest, { recursive: true });
|
|
356
717
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
718
|
+
installRuntimeDeps(pluginDest);
|
|
357
719
|
}
|
|
358
720
|
|
|
359
721
|
function codexMcpServerPath() {
|
|
@@ -377,7 +739,9 @@ function ensureCodexConfigSection(mcpPath) {
|
|
|
377
739
|
const configPath = codexConfigToml();
|
|
378
740
|
let existing = '';
|
|
379
741
|
if (existsSync(configPath)) {
|
|
380
|
-
try {
|
|
742
|
+
try {
|
|
743
|
+
existing = readFileSync(configPath, 'utf8');
|
|
744
|
+
} catch {}
|
|
381
745
|
if (existing.includes('[mcp_servers.huaweicloud-devkit]')) {
|
|
382
746
|
if (existing.includes(`args = ["${mcpPath}"]`)) {
|
|
383
747
|
console.log(` Config unchanged: ${configPath}`);
|
|
@@ -386,7 +750,9 @@ function ensureCodexConfigSection(mcpPath) {
|
|
|
386
750
|
removeCodexConfigSection();
|
|
387
751
|
existing = '';
|
|
388
752
|
if (existsSync(configPath)) {
|
|
389
|
-
try {
|
|
753
|
+
try {
|
|
754
|
+
existing = readFileSync(configPath, 'utf8');
|
|
755
|
+
} catch {}
|
|
390
756
|
}
|
|
391
757
|
}
|
|
392
758
|
}
|
|
@@ -404,7 +770,10 @@ function removeCodexConfigSection() {
|
|
|
404
770
|
const out = [];
|
|
405
771
|
let skip = false;
|
|
406
772
|
for (const line of lines) {
|
|
407
|
-
if (/^\[mcp_servers\.huaweicloud-devkit(\]|\.)/.test(line)) {
|
|
773
|
+
if (/^\[mcp_servers\.huaweicloud-devkit(\]|\.)/.test(line)) {
|
|
774
|
+
skip = true;
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
408
777
|
if (skip && line.startsWith('[')) skip = false;
|
|
409
778
|
if (!skip) out.push(line);
|
|
410
779
|
}
|
|
@@ -451,6 +820,7 @@ async function installCodexDesktop() {
|
|
|
451
820
|
}
|
|
452
821
|
|
|
453
822
|
ensureCodexConfigSection(mcpServerAbsPath);
|
|
823
|
+
installRuntimeDeps(codexDesktopPluginsDir());
|
|
454
824
|
}
|
|
455
825
|
|
|
456
826
|
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
@@ -463,10 +833,14 @@ async function updateCodexDesktop() {
|
|
|
463
833
|
|
|
464
834
|
copyDir(skillsSrc, codexDesktopSkillsDir());
|
|
465
835
|
const staleSkills = pruneStale(codexDesktopSkillsDir(), skillsSrc);
|
|
466
|
-
console.log(
|
|
836
|
+
console.log(
|
|
837
|
+
` Skills updated -> ${codexDesktopSkillsDir()}${staleSkills > 0 ? ` (removed ${staleSkills} stale)` : ''}`,
|
|
838
|
+
);
|
|
467
839
|
copyDir(commandsSrc, codexDesktopCommandsDir());
|
|
468
840
|
const staleCommands = pruneStale(codexDesktopCommandsDir(), commandsSrc);
|
|
469
|
-
console.log(
|
|
841
|
+
console.log(
|
|
842
|
+
` Commands updated -> ${codexDesktopCommandsDir()}${staleCommands > 0 ? ` (removed ${staleCommands} stale)` : ''}`,
|
|
843
|
+
);
|
|
470
844
|
mkdirSync(pluginDest, { recursive: true });
|
|
471
845
|
copyDir(srcDir, join(pluginDest, 'src'));
|
|
472
846
|
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
@@ -494,6 +868,7 @@ async function updateCodexDesktop() {
|
|
|
494
868
|
|
|
495
869
|
ensureCodexConfigSection(mcpServerAbsPath);
|
|
496
870
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
871
|
+
installRuntimeDeps(pluginDest);
|
|
497
872
|
}
|
|
498
873
|
|
|
499
874
|
function uninstallCodexDesktop() {
|
|
@@ -534,13 +909,22 @@ function registerCodeartsMcp(configPath) {
|
|
|
534
909
|
if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
|
|
535
910
|
let config = {};
|
|
536
911
|
if (existsSync(configPath)) {
|
|
537
|
-
try {
|
|
538
|
-
|
|
912
|
+
try {
|
|
913
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
914
|
+
} catch {
|
|
915
|
+
console.log(
|
|
916
|
+
` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`,
|
|
917
|
+
);
|
|
539
918
|
return;
|
|
540
919
|
}
|
|
541
920
|
const existing = config.mcpServers?.['huaweicloud-devkit'];
|
|
542
|
-
if (
|
|
543
|
-
|
|
921
|
+
if (
|
|
922
|
+
existing &&
|
|
923
|
+
existing.command === 'node' &&
|
|
924
|
+
Array.isArray(existing.args) &&
|
|
925
|
+
existing.args[0] === mcpPath &&
|
|
926
|
+
existing.timeout === 300000
|
|
927
|
+
) {
|
|
544
928
|
console.log(` MCP config unchanged: ${configPath}`);
|
|
545
929
|
return;
|
|
546
930
|
}
|
|
@@ -551,6 +935,7 @@ function registerCodeartsMcp(configPath) {
|
|
|
551
935
|
args: [mcpPath],
|
|
552
936
|
env,
|
|
553
937
|
enabled: true,
|
|
938
|
+
timeout: 300000,
|
|
554
939
|
};
|
|
555
940
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
556
941
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
@@ -575,6 +960,7 @@ async function installCodeArts() {
|
|
|
575
960
|
|
|
576
961
|
registerCodeartsMcp(codeartsMcpSettingsFile());
|
|
577
962
|
registerCodeartsMcp(codeartsProjectMcpSettingsFile());
|
|
963
|
+
installRuntimeDeps(pluginDest);
|
|
578
964
|
}
|
|
579
965
|
|
|
580
966
|
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
@@ -597,6 +983,7 @@ async function updateCodeArts() {
|
|
|
597
983
|
registerCodeartsMcp(codeartsProjectMcpSettingsFile());
|
|
598
984
|
mkdirSync(pluginDest, { recursive: true });
|
|
599
985
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
986
|
+
installRuntimeDeps(pluginDest);
|
|
600
987
|
}
|
|
601
988
|
|
|
602
989
|
function uninstallCodeArts() {
|
|
@@ -618,7 +1005,9 @@ function uninstallCodeArts() {
|
|
|
618
1005
|
for (const configPath of [codeartsMcpSettingsFile(), codeartsProjectMcpSettingsFile()]) {
|
|
619
1006
|
if (!existsSync(configPath)) continue;
|
|
620
1007
|
let config = {};
|
|
621
|
-
try {
|
|
1008
|
+
try {
|
|
1009
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1010
|
+
} catch {}
|
|
622
1011
|
if (config.mcpServers?.['huaweicloud-devkit']) {
|
|
623
1012
|
delete config.mcpServers['huaweicloud-devkit'];
|
|
624
1013
|
if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
|
|
@@ -630,18 +1019,27 @@ function uninstallCodeArts() {
|
|
|
630
1019
|
|
|
631
1020
|
function codeartsStatus() {
|
|
632
1021
|
const pluginDir = codeartsPluginsDir();
|
|
633
|
-
console.log(
|
|
634
|
-
|
|
1022
|
+
console.log(
|
|
1023
|
+
` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1024
|
+
);
|
|
1025
|
+
console.log(
|
|
1026
|
+
` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1027
|
+
);
|
|
635
1028
|
let skillCount = 0;
|
|
636
1029
|
if (existsSync(codeartsSkillsDir())) {
|
|
637
|
-
skillCount = readdirSync(codeartsSkillsDir(), { withFileTypes: true })
|
|
638
|
-
|
|
1030
|
+
skillCount = readdirSync(codeartsSkillsDir(), { withFileTypes: true }).filter(
|
|
1031
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
1032
|
+
).length;
|
|
639
1033
|
}
|
|
640
|
-
console.log(
|
|
1034
|
+
console.log(
|
|
1035
|
+
` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1036
|
+
);
|
|
641
1037
|
if (existsSync(codeartsMcpSettingsFile())) {
|
|
642
1038
|
try {
|
|
643
1039
|
const config = JSON.parse(readFileSync(codeartsMcpSettingsFile(), 'utf8'));
|
|
644
|
-
console.log(
|
|
1040
|
+
console.log(
|
|
1041
|
+
` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
|
|
1042
|
+
);
|
|
645
1043
|
} catch {
|
|
646
1044
|
console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
|
|
647
1045
|
}
|
|
@@ -657,13 +1055,22 @@ function ensureWorkbuddyMcpConfig() {
|
|
|
657
1055
|
if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
|
|
658
1056
|
let config = {};
|
|
659
1057
|
if (existsSync(configPath)) {
|
|
660
|
-
try {
|
|
661
|
-
|
|
1058
|
+
try {
|
|
1059
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1060
|
+
} catch {
|
|
1061
|
+
console.log(
|
|
1062
|
+
` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`,
|
|
1063
|
+
);
|
|
662
1064
|
return false;
|
|
663
1065
|
}
|
|
664
1066
|
const existing = config.mcpServers?.['huaweicloud-devkit'];
|
|
665
|
-
if (
|
|
666
|
-
|
|
1067
|
+
if (
|
|
1068
|
+
existing &&
|
|
1069
|
+
existing.command === 'node' &&
|
|
1070
|
+
Array.isArray(existing.args) &&
|
|
1071
|
+
existing.args[0] === mcpPath &&
|
|
1072
|
+
existing.timeout === 300000
|
|
1073
|
+
) {
|
|
667
1074
|
console.log(` MCP config unchanged: ${configPath}`);
|
|
668
1075
|
return false;
|
|
669
1076
|
}
|
|
@@ -673,6 +1080,7 @@ function ensureWorkbuddyMcpConfig() {
|
|
|
673
1080
|
command: 'node',
|
|
674
1081
|
args: [mcpPath],
|
|
675
1082
|
env,
|
|
1083
|
+
timeout: 300000,
|
|
676
1084
|
};
|
|
677
1085
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
678
1086
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
@@ -695,6 +1103,7 @@ async function installWorkBuddy() {
|
|
|
695
1103
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
696
1104
|
|
|
697
1105
|
ensureWorkbuddyMcpConfig();
|
|
1106
|
+
installRuntimeDeps(pluginDest);
|
|
698
1107
|
}
|
|
699
1108
|
|
|
700
1109
|
// Incremental update: overwrite copied files, prune stale ones, and only touch the config when necessary.
|
|
@@ -714,6 +1123,7 @@ async function updateWorkBuddy() {
|
|
|
714
1123
|
ensureWorkbuddyMcpConfig();
|
|
715
1124
|
mkdirSync(pluginDest, { recursive: true });
|
|
716
1125
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
1126
|
+
installRuntimeDeps(pluginDest);
|
|
717
1127
|
}
|
|
718
1128
|
|
|
719
1129
|
function uninstallWorkBuddy() {
|
|
@@ -736,7 +1146,9 @@ function uninstallWorkBuddy() {
|
|
|
736
1146
|
const configPath = workbuddyMcpConfigFile();
|
|
737
1147
|
if (existsSync(configPath)) {
|
|
738
1148
|
let config = {};
|
|
739
|
-
try {
|
|
1149
|
+
try {
|
|
1150
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1151
|
+
} catch {}
|
|
740
1152
|
if (config.mcpServers?.['huaweicloud-devkit']) {
|
|
741
1153
|
delete config.mcpServers['huaweicloud-devkit'];
|
|
742
1154
|
if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
|
|
@@ -749,19 +1161,28 @@ function uninstallWorkBuddy() {
|
|
|
749
1161
|
function workbuddyStatus() {
|
|
750
1162
|
const pluginDir = workbuddyPluginsDir();
|
|
751
1163
|
const skillsDir = workbuddySkillsDir();
|
|
752
|
-
console.log(
|
|
753
|
-
|
|
1164
|
+
console.log(
|
|
1165
|
+
` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1166
|
+
);
|
|
1167
|
+
console.log(
|
|
1168
|
+
` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1169
|
+
);
|
|
754
1170
|
let skillCount = 0;
|
|
755
1171
|
if (existsSync(skillsDir)) {
|
|
756
|
-
skillCount = readdirSync(skillsDir, { withFileTypes: true })
|
|
757
|
-
|
|
1172
|
+
skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
|
|
1173
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
1174
|
+
).length;
|
|
758
1175
|
}
|
|
759
|
-
console.log(
|
|
1176
|
+
console.log(
|
|
1177
|
+
` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1178
|
+
);
|
|
760
1179
|
const configPath = workbuddyMcpConfigFile();
|
|
761
1180
|
if (existsSync(configPath)) {
|
|
762
1181
|
try {
|
|
763
1182
|
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
764
|
-
console.log(
|
|
1183
|
+
console.log(
|
|
1184
|
+
` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
|
|
1185
|
+
);
|
|
765
1186
|
} catch {
|
|
766
1187
|
console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
|
|
767
1188
|
}
|
|
@@ -774,10 +1195,7 @@ function dshMcpServerPath() {
|
|
|
774
1195
|
|
|
775
1196
|
function dshPatchBlock() {
|
|
776
1197
|
const hcloudBin = findHcloudBin();
|
|
777
|
-
const envLines = [
|
|
778
|
-
' HUAWEICLOUD_AGENT_TOOLKIT_MODE: local',
|
|
779
|
-
" HDKITSERVICE_ENDPOINT: ''",
|
|
780
|
-
];
|
|
1198
|
+
const envLines = [' HUAWEICLOUD_AGENT_TOOLKIT_MODE: local', " HDKITSERVICE_ENDPOINT: ''"];
|
|
781
1199
|
if (hcloudBin) {
|
|
782
1200
|
envLines.push(` HCLOUD_BIN: '${hcloudBin.replace(/\\/g, '/').replace(/'/g, "''")}'`);
|
|
783
1201
|
}
|
|
@@ -804,8 +1222,14 @@ function escapeRegExp(value) {
|
|
|
804
1222
|
}
|
|
805
1223
|
|
|
806
1224
|
function removeManagedDshPatchBlock(content) {
|
|
807
|
-
const pattern = new RegExp(
|
|
808
|
-
|
|
1225
|
+
const pattern = new RegExp(
|
|
1226
|
+
`\\n?${escapeRegExp(DSH_MCP_PATCH_START)}[\\s\\S]*?${escapeRegExp(DSH_MCP_PATCH_END)}\\s*`,
|
|
1227
|
+
'g',
|
|
1228
|
+
);
|
|
1229
|
+
return String(content || '')
|
|
1230
|
+
.replace(pattern, '\n')
|
|
1231
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
1232
|
+
.trimEnd();
|
|
809
1233
|
}
|
|
810
1234
|
|
|
811
1235
|
function dshPatchHasOnlyCommentsOrEmptyList(content) {
|
|
@@ -853,9 +1277,7 @@ function removeDshMcpPatch() {
|
|
|
853
1277
|
.filter((line) => line.trim() !== '[]')
|
|
854
1278
|
.join('\n')
|
|
855
1279
|
.trimEnd();
|
|
856
|
-
const next = dshPatchHasOnlyCommentsOrEmptyList(cleaned)
|
|
857
|
-
? `${prefix ? `${prefix}\n` : ''}[]\n`
|
|
858
|
-
: `${cleaned}\n`;
|
|
1280
|
+
const next = dshPatchHasOnlyCommentsOrEmptyList(cleaned) ? `${prefix ? `${prefix}\n` : ''}[]\n` : `${cleaned}\n`;
|
|
859
1281
|
writeFileSync(patchFile, next);
|
|
860
1282
|
console.log(` DSH patch cleaned: ${patchFile}`);
|
|
861
1283
|
return true;
|
|
@@ -866,9 +1288,11 @@ function dshPatchConfigured() {
|
|
|
866
1288
|
if (!existsSync(patchFile)) return false;
|
|
867
1289
|
try {
|
|
868
1290
|
const patch = readFileSync(patchFile, 'utf8');
|
|
869
|
-
return
|
|
870
|
-
|
|
871
|
-
|
|
1291
|
+
return (
|
|
1292
|
+
patch.includes('id: mcp-huaweicloud') &&
|
|
1293
|
+
patch.includes('@deepseek-ai/dsh-mcp-client') &&
|
|
1294
|
+
patch.includes('serverName: huaweicloud')
|
|
1295
|
+
);
|
|
872
1296
|
} catch {
|
|
873
1297
|
return false;
|
|
874
1298
|
}
|
|
@@ -900,8 +1324,9 @@ function dshMcpClientAvailable() {
|
|
|
900
1324
|
if (!existsSync(pkgPath)) return false;
|
|
901
1325
|
try {
|
|
902
1326
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
903
|
-
return Boolean(
|
|
904
|
-
|| pkg.devDependencies?.['@deepseek-ai/dsh-mcp-client']
|
|
1327
|
+
return Boolean(
|
|
1328
|
+
pkg.dependencies?.['@deepseek-ai/dsh-mcp-client'] || pkg.devDependencies?.['@deepseek-ai/dsh-mcp-client'],
|
|
1329
|
+
);
|
|
905
1330
|
} catch {
|
|
906
1331
|
return false;
|
|
907
1332
|
}
|
|
@@ -963,6 +1388,7 @@ async function installDsh() {
|
|
|
963
1388
|
tryInstallDshMcpClient();
|
|
964
1389
|
mkdirSync(pluginDest, { recursive: true });
|
|
965
1390
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
1391
|
+
installRuntimeDeps(pluginDest);
|
|
966
1392
|
}
|
|
967
1393
|
|
|
968
1394
|
async function updateDsh() {
|
|
@@ -982,6 +1408,7 @@ async function updateDsh() {
|
|
|
982
1408
|
tryInstallDshMcpClient();
|
|
983
1409
|
mkdirSync(pluginDest, { recursive: true });
|
|
984
1410
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
1411
|
+
installRuntimeDeps(pluginDest);
|
|
985
1412
|
}
|
|
986
1413
|
|
|
987
1414
|
function uninstallDsh() {
|
|
@@ -1011,51 +1438,423 @@ function uninstallDsh() {
|
|
|
1011
1438
|
function dshStatus() {
|
|
1012
1439
|
const pluginDir = dshPluginsDir();
|
|
1013
1440
|
const skillsDir = dshSkillsDir();
|
|
1014
|
-
console.log(
|
|
1015
|
-
|
|
1441
|
+
console.log(
|
|
1442
|
+
` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1443
|
+
);
|
|
1444
|
+
console.log(
|
|
1445
|
+
` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1446
|
+
);
|
|
1016
1447
|
let skillCount = 0;
|
|
1017
1448
|
if (existsSync(skillsDir)) {
|
|
1018
|
-
skillCount = readdirSync(skillsDir, { withFileTypes: true })
|
|
1019
|
-
|
|
1449
|
+
skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
|
|
1450
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
1451
|
+
).length;
|
|
1020
1452
|
}
|
|
1021
|
-
console.log(
|
|
1453
|
+
console.log(
|
|
1454
|
+
` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1455
|
+
);
|
|
1022
1456
|
console.log(` DSH patch: ${dshPatchConfigured() ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
|
|
1023
|
-
console.log(
|
|
1457
|
+
console.log(
|
|
1458
|
+
` DSH MCP client package: ${dshMcpClientAvailable() ? '\x1b[32mDetected\x1b[0m' : '\x1b[33mCheck DSH profile\x1b[0m'}`,
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
async function promptOfficeaceInstallDir() {
|
|
1463
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1464
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
1465
|
+
while (true) {
|
|
1466
|
+
const path = (await ask(' Install directory: ')).trim();
|
|
1467
|
+
if (!path) {
|
|
1468
|
+
console.log(' \x1b[33mPath cannot be empty.\x1b[0m');
|
|
1469
|
+
continue;
|
|
1470
|
+
}
|
|
1471
|
+
const capFile = join(path, '.office-claw', 'capabilities.json');
|
|
1472
|
+
if (existsSync(capFile)) {
|
|
1473
|
+
rl.close();
|
|
1474
|
+
return path;
|
|
1475
|
+
}
|
|
1476
|
+
console.log(` \x1b[33mcapabilities.json not found at: ${capFile}\x1b[0m`);
|
|
1477
|
+
console.log(' Please verify the path and try again.');
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
async function installOfficeAce() {
|
|
1482
|
+
if (!officeaceCapabilitiesDir()) {
|
|
1483
|
+
if (process.stdin.isTTY) {
|
|
1484
|
+
console.log(' \x1b[33mOfficeAce install directory not found automatically.\x1b[0m');
|
|
1485
|
+
console.log(' Please enter the OfficeAce install directory.');
|
|
1486
|
+
const entered = await promptOfficeaceInstallDir();
|
|
1487
|
+
process.env.OFFICE_CLAW_CONFIG_ROOT = join(entered, '.office-claw');
|
|
1488
|
+
} else {
|
|
1489
|
+
console.log(
|
|
1490
|
+
' \x1b[31mOfficeAce install directory not found. Please set OFFICE_CLAW_CONFIG_ROOT env var and retry.\x1b[0m',
|
|
1491
|
+
);
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
1496
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
1497
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
1498
|
+
const pluginDest = officeacePluginsDir();
|
|
1499
|
+
|
|
1500
|
+
copyDir(skillsSrc, officeaceSkillsDir());
|
|
1501
|
+
console.log(` Skills -> ${officeaceSkillsDir()}`);
|
|
1502
|
+
|
|
1503
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
1504
|
+
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
1505
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1506
|
+
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
1507
|
+
|
|
1508
|
+
installRuntimeDeps(pluginDest);
|
|
1509
|
+
ensureOfficeaceMcpInSqlite();
|
|
1510
|
+
registerOfficeaceSkillEntries();
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
async function updateOfficeAce() {
|
|
1514
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
1515
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
1516
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
1517
|
+
const pluginDest = officeacePluginsDir();
|
|
1518
|
+
|
|
1519
|
+
copyDir(skillsSrc, officeaceSkillsDir());
|
|
1520
|
+
const stale = pruneStale(officeaceSkillsDir(), skillsSrc);
|
|
1521
|
+
console.log(` Skills updated -> ${officeaceSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
|
|
1522
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
1523
|
+
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
1524
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1525
|
+
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
1526
|
+
installRuntimeDeps(pluginDest);
|
|
1527
|
+
ensureOfficeaceMcpInSqlite();
|
|
1528
|
+
registerOfficeaceSkillEntries();
|
|
1529
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
1530
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
function uninstallOfficeAce() {
|
|
1534
|
+
const skillsDir = officeaceSkillsDir();
|
|
1535
|
+
let removed = 0;
|
|
1536
|
+
if (existsSync(skillsDir)) {
|
|
1537
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
1538
|
+
if (entry.name.startsWith('huawei')) {
|
|
1539
|
+
removeIfExists(join(skillsDir, entry.name));
|
|
1540
|
+
removed++;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
if (removed > 0) console.log(` Removed ${removed} skills`);
|
|
1544
|
+
try {
|
|
1545
|
+
if (readdirSync(skillsDir).length === 0) {
|
|
1546
|
+
rmSync(skillsDir, { recursive: true, force: true });
|
|
1547
|
+
console.log(` Removed empty skills directory: ${skillsDir}`);
|
|
1548
|
+
}
|
|
1549
|
+
} catch {}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
if (removeIfExists(officeacePluginsDir())) {
|
|
1553
|
+
console.log(' Removed MCP server and safety policy');
|
|
1554
|
+
}
|
|
1555
|
+
removeOfficeaceSkillCapabilities();
|
|
1556
|
+
removeOfficeaceMcpFromSqlite();
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
function officeaceStatus() {
|
|
1560
|
+
const pluginDir = officeacePluginsDir();
|
|
1561
|
+
const skillsDir = officeaceSkillsDir();
|
|
1562
|
+
console.log(
|
|
1563
|
+
` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1564
|
+
);
|
|
1565
|
+
console.log(
|
|
1566
|
+
` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1567
|
+
);
|
|
1568
|
+
let skillCount = 0;
|
|
1569
|
+
if (existsSync(skillsDir)) {
|
|
1570
|
+
skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
|
|
1571
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
1572
|
+
).length;
|
|
1573
|
+
}
|
|
1574
|
+
console.log(
|
|
1575
|
+
` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1576
|
+
);
|
|
1577
|
+
const dbPath = officeaceSqlitePath();
|
|
1578
|
+
if (existsSync(dbPath)) {
|
|
1579
|
+
try {
|
|
1580
|
+
const db = openOfficeaceDb();
|
|
1581
|
+
const row = db.prepare("SELECT enabled, status FROM mcp_connectors WHERE name = 'huaweicloud-devkit'").get();
|
|
1582
|
+
db.close();
|
|
1583
|
+
if (row) {
|
|
1584
|
+
const statusIcon = row.status === 'connected' ? '\x1b[32m' : '\x1b[33m';
|
|
1585
|
+
console.log(` MCP config: ${statusIcon}${row.status}\x1b[0m (enabled: ${row.enabled ? 'yes' : 'no'})`);
|
|
1586
|
+
} else {
|
|
1587
|
+
console.log(` MCP config: \x1b[31mNot configured\x1b[0m`);
|
|
1588
|
+
}
|
|
1589
|
+
} catch {
|
|
1590
|
+
console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
|
|
1591
|
+
}
|
|
1592
|
+
} else {
|
|
1593
|
+
console.log(` MCP config: \x1b[31mDatabase not found\x1b[0m`);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
// ── Hermes Agent ──
|
|
1598
|
+
|
|
1599
|
+
function hermesHomeDir() {
|
|
1600
|
+
if (process.env.HERMES_HOME) return process.env.HERMES_HOME;
|
|
1601
|
+
// Hermes on Windows stores under LOCALAPPDATA, not ~/.hermes
|
|
1602
|
+
if (platform() === 'win32' && process.env.LOCALAPPDATA) {
|
|
1603
|
+
return join(process.env.LOCALAPPDATA, 'hermes');
|
|
1604
|
+
}
|
|
1605
|
+
return join(homedir(), '.hermes');
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
function hermesSkillsDir() {
|
|
1609
|
+
return join(hermesHomeDir(), 'skills');
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
function hermesPluginsDir() {
|
|
1613
|
+
return join(hermesHomeDir(), 'huaweicloud-plugins');
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
function hermesConfigFile() {
|
|
1617
|
+
return join(hermesHomeDir(), 'config.yaml');
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
// Returns true when the config file was written, false when it was already correct.
|
|
1621
|
+
function ensureHermesMcpConfig() {
|
|
1622
|
+
const configPath = hermesConfigFile();
|
|
1623
|
+
const mcpPath = join(hermesPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
1624
|
+
const hcloudBin = findHcloudBin();
|
|
1625
|
+
|
|
1626
|
+
const blockLines = [
|
|
1627
|
+
'mcp_servers:',
|
|
1628
|
+
' huaweicloud-devkit:',
|
|
1629
|
+
' command: "node"',
|
|
1630
|
+
` args: ["${mcpPath}"]`,
|
|
1631
|
+
' env:',
|
|
1632
|
+
' HUAWEICLOUD_AGENT_TOOLKIT_MODE: "local"',
|
|
1633
|
+
];
|
|
1634
|
+
if (hcloudBin) {
|
|
1635
|
+
blockLines.push(` HCLOUD_BIN: "${hcloudBin.replace(/\\/g, '/')}"`);
|
|
1636
|
+
}
|
|
1637
|
+
const block = blockLines.join('\n');
|
|
1638
|
+
|
|
1639
|
+
let existing = '';
|
|
1640
|
+
if (existsSync(configPath)) {
|
|
1641
|
+
try {
|
|
1642
|
+
existing = readFileSync(configPath, 'utf8');
|
|
1643
|
+
} catch {}
|
|
1644
|
+
if (existing.includes('mcp_servers:') && existing.includes('huaweicloud-devkit')) {
|
|
1645
|
+
if (existing.includes(`args: ["${mcpPath}"]`)) {
|
|
1646
|
+
console.log(` MCP config unchanged: ${configPath}`);
|
|
1647
|
+
return false;
|
|
1648
|
+
}
|
|
1649
|
+
removeHermesMcpConfigBlock();
|
|
1650
|
+
existing = '';
|
|
1651
|
+
if (existsSync(configPath)) {
|
|
1652
|
+
try {
|
|
1653
|
+
existing = readFileSync(configPath, 'utf8');
|
|
1654
|
+
} catch {}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
1659
|
+
const newContent = existing ? `${existing.trimEnd()}\n\n${block}\n` : `${block}\n`;
|
|
1660
|
+
writeFileSync(configPath, newContent);
|
|
1661
|
+
console.log(` MCP config updated: ${configPath}`);
|
|
1662
|
+
return true;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
function removeHermesMcpConfigBlock() {
|
|
1666
|
+
const configPath = hermesConfigFile();
|
|
1667
|
+
if (!existsSync(configPath)) return;
|
|
1668
|
+
const lines = readFileSync(configPath, 'utf8').split(/\r?\n/);
|
|
1669
|
+
const out = [];
|
|
1670
|
+
let skip = false;
|
|
1671
|
+
for (const line of lines) {
|
|
1672
|
+
if (/^\s*huaweicloud-devkit\s*:/.test(line)) {
|
|
1673
|
+
skip = true;
|
|
1674
|
+
continue;
|
|
1675
|
+
}
|
|
1676
|
+
if (skip) {
|
|
1677
|
+
// Continue skipping indented lines (the server config block)
|
|
1678
|
+
if (/^\s{2,}/.test(line) && line.trim() !== '') continue;
|
|
1679
|
+
// Stop skipping at a top-level key or empty line
|
|
1680
|
+
skip = false;
|
|
1681
|
+
}
|
|
1682
|
+
// If we just skipped the only server under mcp_servers, remove the key too
|
|
1683
|
+
if (line.trim() === 'mcp_servers:') {
|
|
1684
|
+
// Peek ahead to check if this mcp_servers block only has huaweicloud-devkit
|
|
1685
|
+
const remaining = out.join('\n');
|
|
1686
|
+
// Remove trailing mcp_servers: if it's the last meaningful line
|
|
1687
|
+
continue; // skip the mcp_servers key for now, re-add if other servers exist
|
|
1688
|
+
}
|
|
1689
|
+
out.push(line);
|
|
1690
|
+
}
|
|
1691
|
+
// Reconstruct: add mcp_servers back if there are other servers
|
|
1692
|
+
const cleaned = out.join('\n').trimEnd();
|
|
1693
|
+
writeFileSync(configPath, cleaned ? `${cleaned}\n` : '');
|
|
1694
|
+
console.log(' MCP config cleaned');
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
async function installHermes() {
|
|
1698
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
1699
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
1700
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
1701
|
+
const pluginDest = hermesPluginsDir();
|
|
1702
|
+
|
|
1703
|
+
copyDir(skillsSrc, hermesSkillsDir());
|
|
1704
|
+
console.log(` Skills -> ${hermesSkillsDir()}`);
|
|
1705
|
+
|
|
1706
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
1707
|
+
console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
|
|
1708
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1709
|
+
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
1710
|
+
|
|
1711
|
+
ensureHermesMcpConfig();
|
|
1712
|
+
installRuntimeDeps(pluginDest);
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
async function updateHermes() {
|
|
1716
|
+
const skillsSrc = join(PLUGIN_ROOT, 'skills');
|
|
1717
|
+
const srcDir = join(PLUGIN_ROOT, 'src');
|
|
1718
|
+
const safetyDir = join(PLUGIN_ROOT, 'safety');
|
|
1719
|
+
const pluginDest = hermesPluginsDir();
|
|
1720
|
+
|
|
1721
|
+
copyDir(skillsSrc, hermesSkillsDir());
|
|
1722
|
+
const stale = pruneStale(hermesSkillsDir(), skillsSrc);
|
|
1723
|
+
console.log(` Skills updated -> ${hermesSkillsDir()}${stale > 0 ? ` (removed ${stale} stale)` : ''}`);
|
|
1724
|
+
copyDir(srcDir, join(pluginDest, 'src'));
|
|
1725
|
+
console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
|
|
1726
|
+
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
1727
|
+
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
1728
|
+
ensureHermesMcpConfig();
|
|
1729
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
1730
|
+
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
1731
|
+
installRuntimeDeps(pluginDest);
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
function uninstallHermes() {
|
|
1735
|
+
const skillsDir = hermesSkillsDir();
|
|
1736
|
+
let removed = 0;
|
|
1737
|
+
if (existsSync(skillsDir)) {
|
|
1738
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
1739
|
+
if (entry.name.startsWith('huawei')) {
|
|
1740
|
+
removeIfExists(join(skillsDir, entry.name));
|
|
1741
|
+
removed++;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
if (removed > 0) console.log(` Removed ${removed} skills`);
|
|
1745
|
+
try {
|
|
1746
|
+
if (readdirSync(skillsDir).length === 0) {
|
|
1747
|
+
rmSync(skillsDir, { recursive: true, force: true });
|
|
1748
|
+
console.log(` Removed empty skills directory: ${skillsDir}`);
|
|
1749
|
+
}
|
|
1750
|
+
} catch {}
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
if (removeIfExists(hermesPluginsDir())) {
|
|
1754
|
+
console.log(' Removed MCP server and safety policy');
|
|
1755
|
+
}
|
|
1756
|
+
removeHermesMcpConfigBlock();
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
function hermesStatus() {
|
|
1760
|
+
const pluginDir = hermesPluginsDir();
|
|
1761
|
+
const skillsDir = hermesSkillsDir();
|
|
1762
|
+
console.log(
|
|
1763
|
+
` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1764
|
+
);
|
|
1765
|
+
console.log(
|
|
1766
|
+
` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1767
|
+
);
|
|
1768
|
+
let skillCount = 0;
|
|
1769
|
+
if (existsSync(skillsDir)) {
|
|
1770
|
+
skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
|
|
1771
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
1772
|
+
).length;
|
|
1773
|
+
}
|
|
1774
|
+
console.log(
|
|
1775
|
+
` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1776
|
+
);
|
|
1777
|
+
const configPath = hermesConfigFile();
|
|
1778
|
+
if (existsSync(configPath)) {
|
|
1779
|
+
try {
|
|
1780
|
+
const config = readFileSync(configPath, 'utf8');
|
|
1781
|
+
const configured = config.includes('mcp_servers:') && config.includes('huaweicloud-devkit');
|
|
1782
|
+
console.log(` MCP config: ${configured ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`);
|
|
1783
|
+
} catch {
|
|
1784
|
+
console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
|
|
1785
|
+
}
|
|
1786
|
+
} else {
|
|
1787
|
+
console.log(` MCP config: \x1b[31mNot found\x1b[0m`);
|
|
1788
|
+
}
|
|
1024
1789
|
}
|
|
1025
1790
|
|
|
1026
1791
|
function opencodeStatus() {
|
|
1027
1792
|
const pluginDir = opencodePluginsDir();
|
|
1028
1793
|
const skillsDir = opencodeSkillsDir();
|
|
1029
|
-
console.log(
|
|
1030
|
-
|
|
1794
|
+
console.log(
|
|
1795
|
+
` MCP Server: ${existsSync(join(pluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1796
|
+
);
|
|
1797
|
+
console.log(
|
|
1798
|
+
` Safety Policy: ${existsSync(join(pluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1799
|
+
);
|
|
1031
1800
|
let skillCount = 0;
|
|
1032
1801
|
if (existsSync(skillsDir)) {
|
|
1033
|
-
skillCount = readdirSync(skillsDir, { withFileTypes: true })
|
|
1034
|
-
|
|
1802
|
+
skillCount = readdirSync(skillsDir, { withFileTypes: true }).filter(
|
|
1803
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
1804
|
+
).length;
|
|
1035
1805
|
}
|
|
1036
|
-
console.log(
|
|
1806
|
+
console.log(
|
|
1807
|
+
` Skills: ${skillCount > 0 ? `\x1b[32m${skillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
1808
|
+
);
|
|
1037
1809
|
const configPath = opencodeConfigFile();
|
|
1038
1810
|
if (existsSync(configPath)) {
|
|
1039
1811
|
try {
|
|
1040
1812
|
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1041
|
-
console.log(
|
|
1813
|
+
console.log(
|
|
1814
|
+
` MCP config: ${config.mcp?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
|
|
1815
|
+
);
|
|
1042
1816
|
} catch {
|
|
1043
1817
|
console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
|
|
1044
1818
|
}
|
|
1045
1819
|
}
|
|
1046
1820
|
}
|
|
1047
1821
|
|
|
1822
|
+
function autoDetectTarget() {
|
|
1823
|
+
const checks = [
|
|
1824
|
+
['opencode', () => existsSync(join(homedir(), '.config', 'opencode'))],
|
|
1825
|
+
['codex-desktop', () => existsSync(join(homedir(), '.codex'))],
|
|
1826
|
+
['codearts', () => existsSync(join(homedir(), '.codeartsdoer'))],
|
|
1827
|
+
['workbuddy', () => existsSync(join(homedir(), '.workbuddy'))],
|
|
1828
|
+
[
|
|
1829
|
+
'dsh',
|
|
1830
|
+
() => {
|
|
1831
|
+
const dsh = process.env.DSH_HOME || join(homedir(), '.dsh');
|
|
1832
|
+
return existsSync(dsh);
|
|
1833
|
+
},
|
|
1834
|
+
],
|
|
1835
|
+
['officeace', () => existsSync(officeaceCapabilitiesDir())],
|
|
1836
|
+
['hermes', () => existsSync(hermesHomeDir())],
|
|
1837
|
+
['openclaw', () => existsSync(join(homedir(), '.openclaw'))],
|
|
1838
|
+
];
|
|
1839
|
+
const detected = checks.filter(([, check]) => check()).map(([name]) => name);
|
|
1840
|
+
if (detected.length === 0) {
|
|
1841
|
+
console.error('No supported agent detected.');
|
|
1842
|
+
console.error(`Supported: ${SUPPORTED_AGENT_TARGETS.join(', ')} (or "all")`);
|
|
1843
|
+
console.error('Use --target <agent> to specify.');
|
|
1844
|
+
process.exit(1);
|
|
1845
|
+
}
|
|
1846
|
+
if (detected.length === 1) return detected[0];
|
|
1847
|
+
return 'all';
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1048
1850
|
function parseTarget() {
|
|
1049
1851
|
const idx = process.argv.indexOf('--target');
|
|
1050
|
-
if (idx < 0) return
|
|
1852
|
+
if (idx < 0) return autoDetectTarget();
|
|
1051
1853
|
const val = (process.argv[idx + 1] || '').toLowerCase();
|
|
1052
|
-
if (val === '
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
if (val === 'dsh') return 'dsh';
|
|
1057
|
-
if (val === 'all') return 'all';
|
|
1058
|
-
return 'opencode';
|
|
1854
|
+
if (val === 'all' || SUPPORTED_AGENT_TARGETS.includes(val)) return val;
|
|
1855
|
+
console.error(`Unknown target: ${val}`);
|
|
1856
|
+
console.error(`Supported: ${SUPPORTED_AGENT_TARGETS.join(', ')} (or "all")`);
|
|
1857
|
+
process.exit(1);
|
|
1059
1858
|
}
|
|
1060
1859
|
|
|
1061
1860
|
async function cmdInstall() {
|
|
@@ -1084,6 +1883,18 @@ async function cmdInstall() {
|
|
|
1084
1883
|
console.log('\n[DSH]');
|
|
1085
1884
|
await installDsh();
|
|
1086
1885
|
}
|
|
1886
|
+
if (target === 'officeace' || target === 'all') {
|
|
1887
|
+
console.log('\n[OfficeAce]');
|
|
1888
|
+
await installOfficeAce();
|
|
1889
|
+
}
|
|
1890
|
+
if (target === 'hermes' || target === 'all') {
|
|
1891
|
+
console.log('\n[Hermes Agent]');
|
|
1892
|
+
await installHermes();
|
|
1893
|
+
}
|
|
1894
|
+
if (target === 'openclaw' || target === 'all') {
|
|
1895
|
+
console.log('\n[OpenClaw]');
|
|
1896
|
+
await installCodexDesktop();
|
|
1897
|
+
}
|
|
1087
1898
|
if (target === 'codex' || target === 'all') {
|
|
1088
1899
|
console.log('\n[Codex]');
|
|
1089
1900
|
if (!hasCodexCLI()) {
|
|
@@ -1108,18 +1919,37 @@ async function cmdInstall() {
|
|
|
1108
1919
|
}
|
|
1109
1920
|
}
|
|
1110
1921
|
console.log(`\n\x1b[32mInstallation complete!\x1b[0m`);
|
|
1111
|
-
const appName =
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1922
|
+
const appName =
|
|
1923
|
+
target === 'codearts'
|
|
1924
|
+
? 'CodeArts'
|
|
1925
|
+
: target === 'codex-desktop'
|
|
1926
|
+
? 'Codex Desktop'
|
|
1927
|
+
: target === 'codex'
|
|
1928
|
+
? 'Codex'
|
|
1929
|
+
: target === 'workbuddy'
|
|
1930
|
+
? 'WorkBuddy'
|
|
1931
|
+
: target === 'dsh'
|
|
1932
|
+
? 'DSH'
|
|
1933
|
+
: target === 'officeace'
|
|
1934
|
+
? 'OfficeAce'
|
|
1935
|
+
: target === 'hermes'
|
|
1936
|
+
? 'Hermes Agent'
|
|
1937
|
+
: target === 'openclaw'
|
|
1938
|
+
? 'OpenClaw'
|
|
1939
|
+
: '当前 agent';
|
|
1117
1940
|
const pad = ' '.repeat(24 - appName.length);
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1941
|
+
if (target === 'officeace') {
|
|
1942
|
+
console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════╗`);
|
|
1943
|
+
console.log(`\x1b[1m\x1b[33m║ 打开连接器 → 我的连接器 → huaweicloud-devkit ║`);
|
|
1944
|
+
console.log(`\x1b[1m\x1b[33m║ → 连接 → 回到对话 → 输入框开启连接器 ║`);
|
|
1945
|
+
console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
|
|
1946
|
+
} else {
|
|
1947
|
+
console.log(`\n\x1b[1m\x1b[33m╔══════════════════════════════════════════════════════╗`);
|
|
1948
|
+
console.log(`\x1b[1m\x1b[33m║ MCP 工具在重启 ${appName} 会话后才生效${pad}║`);
|
|
1949
|
+
console.log(`\x1b[1m\x1b[33m║ 关闭当前会话 → 重新打开,直接描述华为云任务即可 ║`);
|
|
1950
|
+
console.log(`\x1b[1m\x1b[33m║ 重启前请勿执行 hcloud 命令,避免 AK/SK 泄露 ║`);
|
|
1951
|
+
console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
|
|
1952
|
+
}
|
|
1123
1953
|
|
|
1124
1954
|
const hcloudOk = checkHcloud();
|
|
1125
1955
|
if (!hcloudOk) {
|
|
@@ -1131,15 +1961,29 @@ async function cmdInstall() {
|
|
|
1131
1961
|
|
|
1132
1962
|
console.log(`\n\x1b[1m下一步:\x1b[0m`);
|
|
1133
1963
|
console.log(` 1. 配置统一凭据:npx huaweicloud-devkit auth init`);
|
|
1134
|
-
console.log(` 2.
|
|
1135
|
-
|
|
1964
|
+
console.log(` 2. 配置代理(企业内网):npx huaweicloud-devkit proxy init`);
|
|
1965
|
+
if (target === 'officeace') {
|
|
1966
|
+
console.log(` 3. 打开连接器 → 我的连接器 → huaweicloud-devkit → 连接 → 回到对话 → 输入框开启连接器`);
|
|
1967
|
+
} else {
|
|
1968
|
+
console.log(` 3. 重启 ${appName} 会话(MCP 工具重启后生效)`);
|
|
1969
|
+
}
|
|
1970
|
+
console.log(` 4. 运行自检:npx huaweicloud-devkit doctor`);
|
|
1136
1971
|
|
|
1137
1972
|
// Write install marker for doctor to detect
|
|
1138
|
-
const markerDir =
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1973
|
+
const markerDir =
|
|
1974
|
+
target === 'dsh'
|
|
1975
|
+
? dshPluginsDir()
|
|
1976
|
+
: target === 'codearts'
|
|
1977
|
+
? codeartsPluginsDir()
|
|
1978
|
+
: target === 'workbuddy'
|
|
1979
|
+
? workbuddyPluginsDir()
|
|
1980
|
+
: target === 'officeace'
|
|
1981
|
+
? officeacePluginsDir()
|
|
1982
|
+
: target === 'openclaw'
|
|
1983
|
+
? codexDesktopPluginsDir()
|
|
1984
|
+
: target === 'codex-desktop'
|
|
1985
|
+
? codexDesktopPluginsDir()
|
|
1986
|
+
: opencodePluginsDir();
|
|
1143
1987
|
mkdirSync(markerDir, { recursive: true });
|
|
1144
1988
|
writeFileSync(join(markerDir, '.installed'), new Date().toISOString());
|
|
1145
1989
|
if (target === 'opencode' || target === 'all') {
|
|
@@ -1157,6 +2001,12 @@ async function cmdInstall() {
|
|
|
1157
2001
|
if (target === 'dsh' || target === 'all') {
|
|
1158
2002
|
console.log('Or describe your Huawei Cloud task in DSH');
|
|
1159
2003
|
}
|
|
2004
|
+
if (target === 'officeace' || target === 'all') {
|
|
2005
|
+
console.log('Or describe your Huawei Cloud task in OfficeAce');
|
|
2006
|
+
}
|
|
2007
|
+
if (target === 'openclaw' || target === 'all') {
|
|
2008
|
+
console.log('Or describe your Huawei Cloud task in OpenClaw');
|
|
2009
|
+
}
|
|
1160
2010
|
}
|
|
1161
2011
|
|
|
1162
2012
|
async function cmdUninstall() {
|
|
@@ -1180,6 +2030,18 @@ async function cmdUninstall() {
|
|
|
1180
2030
|
console.log('\n[DSH]');
|
|
1181
2031
|
uninstallDsh();
|
|
1182
2032
|
}
|
|
2033
|
+
if (target === 'officeace' || target === 'all') {
|
|
2034
|
+
console.log('\n[OfficeAce]');
|
|
2035
|
+
uninstallOfficeAce();
|
|
2036
|
+
}
|
|
2037
|
+
if (target === 'hermes' || target === 'all') {
|
|
2038
|
+
console.log('\n[Hermes Agent]');
|
|
2039
|
+
uninstallHermes();
|
|
2040
|
+
}
|
|
2041
|
+
if (target === 'openclaw' || target === 'all') {
|
|
2042
|
+
console.log('\n[OpenClaw]');
|
|
2043
|
+
uninstallCodexDesktop();
|
|
2044
|
+
}
|
|
1183
2045
|
if (target === 'codex-desktop' || target === 'codex' || target === 'all') {
|
|
1184
2046
|
console.log('\n[Codex]');
|
|
1185
2047
|
uninstallCodexDesktop();
|
|
@@ -1228,6 +2090,34 @@ async function cmdStatus() {
|
|
|
1228
2090
|
console.log('\n[DSH]');
|
|
1229
2091
|
dshStatus();
|
|
1230
2092
|
}
|
|
2093
|
+
if (target === 'officeace' || target === 'all') {
|
|
2094
|
+
console.log('\n[OfficeAce]');
|
|
2095
|
+
officeaceStatus();
|
|
2096
|
+
}
|
|
2097
|
+
if (target === 'hermes' || target === 'all') {
|
|
2098
|
+
console.log('\n[Hermes Agent]');
|
|
2099
|
+
hermesStatus();
|
|
2100
|
+
}
|
|
2101
|
+
if (target === 'openclaw' || target === 'all') {
|
|
2102
|
+
console.log('\n[OpenClaw]');
|
|
2103
|
+
const cdPluginDir = codexDesktopPluginsDir();
|
|
2104
|
+
const cdSkillsDir = codexDesktopSkillsDir();
|
|
2105
|
+
console.log(
|
|
2106
|
+
` MCP Server: ${existsSync(join(cdPluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
2107
|
+
);
|
|
2108
|
+
console.log(
|
|
2109
|
+
` Safety Policy: ${existsSync(join(cdPluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
2110
|
+
);
|
|
2111
|
+
let cdSkillCount = 0;
|
|
2112
|
+
if (existsSync(cdSkillsDir)) {
|
|
2113
|
+
cdSkillCount = readdirSync(cdSkillsDir, { withFileTypes: true }).filter(
|
|
2114
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
2115
|
+
).length;
|
|
2116
|
+
}
|
|
2117
|
+
console.log(
|
|
2118
|
+
` Skills: ${cdSkillCount > 0 ? `\x1b[32m${cdSkillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
2119
|
+
);
|
|
2120
|
+
}
|
|
1231
2121
|
if (target === 'codex' || target === 'all') {
|
|
1232
2122
|
console.log('\n[Codex]');
|
|
1233
2123
|
if (!hasCodexCLI()) {
|
|
@@ -1245,43 +2135,68 @@ async function cmdDoctor() {
|
|
|
1245
2135
|
console.log(BANNER);
|
|
1246
2136
|
console.log('HuaweiCloud DevKit Doctor\n');
|
|
1247
2137
|
|
|
1248
|
-
let pass = 0,
|
|
2138
|
+
let pass = 0,
|
|
2139
|
+
warn = 0,
|
|
2140
|
+
fail = 0;
|
|
1249
2141
|
|
|
1250
2142
|
function check(label, ok, msg) {
|
|
1251
|
-
if (ok) {
|
|
1252
|
-
|
|
2143
|
+
if (ok) {
|
|
2144
|
+
console.log(` \x1b[32m[PASS]\x1b[0m ${label}`);
|
|
2145
|
+
pass++;
|
|
2146
|
+
} else {
|
|
2147
|
+
console.log(` \x1b[31m[FAIL]\x1b[0m ${label} — ${msg}`);
|
|
2148
|
+
fail++;
|
|
2149
|
+
}
|
|
1253
2150
|
}
|
|
1254
2151
|
|
|
1255
2152
|
// Node.js
|
|
1256
|
-
check('Node.js >=
|
|
2153
|
+
check('Node.js >= 22', process.versions.node.split('.')[0] >= 22, 'Run: nvm install 22 && nvm use 22');
|
|
1257
2154
|
|
|
1258
|
-
|
|
2155
|
+
// MCP server — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH paths
|
|
1259
2156
|
const opencodePluginDir = opencodePluginsDir();
|
|
1260
2157
|
const codexPluginDir = codexDesktopPluginsDir();
|
|
1261
2158
|
const codeartsPluginDir = codeartsPluginsDir();
|
|
1262
2159
|
const workbuddyPluginDir = workbuddyPluginsDir();
|
|
1263
2160
|
const dshPluginDir = dshPluginsDir();
|
|
1264
|
-
const
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
2161
|
+
const officeacePluginDir = officeacePluginsDir();
|
|
2162
|
+
const hermesPluginDir = hermesPluginsDir();
|
|
2163
|
+
const mcpOk =
|
|
2164
|
+
existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2165
|
+
existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2166
|
+
existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2167
|
+
existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2168
|
+
existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2169
|
+
existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs')) ||
|
|
2170
|
+
existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'));
|
|
2171
|
+
const mcpTarget = existsSync(join(opencodePluginDir, 'src', 'mcp-server.mjs'))
|
|
2172
|
+
? 'OpenCode'
|
|
2173
|
+
: existsSync(join(codexPluginDir, 'src', 'mcp-server.mjs'))
|
|
2174
|
+
? 'Codex Desktop'
|
|
2175
|
+
: existsSync(join(codeartsPluginDir, 'src', 'mcp-server.mjs'))
|
|
2176
|
+
? 'CodeArts'
|
|
2177
|
+
: existsSync(join(workbuddyPluginDir, 'src', 'mcp-server.mjs'))
|
|
2178
|
+
? 'WorkBuddy'
|
|
2179
|
+
: existsSync(join(dshPluginDir, 'src', 'mcp-server.mjs'))
|
|
2180
|
+
? 'DSH'
|
|
2181
|
+
: existsSync(join(officeacePluginDir, 'src', 'mcp-server.mjs'))
|
|
2182
|
+
? 'OfficeAce'
|
|
2183
|
+
: existsSync(join(hermesPluginDir, 'src', 'mcp-server.mjs'))
|
|
2184
|
+
? 'Hermes Agent'
|
|
2185
|
+
: '';
|
|
1274
2186
|
check('MCP server installed', mcpOk, 'Run: npx huaweicloud-devkit install');
|
|
1275
2187
|
|
|
1276
2188
|
if (mcpOk) {
|
|
1277
2189
|
check(`MCP server can start (${mcpTarget})`, true, '');
|
|
1278
2190
|
}
|
|
1279
2191
|
|
|
1280
|
-
const safetyOk =
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
2192
|
+
const safetyOk =
|
|
2193
|
+
existsSync(join(opencodePluginDir, 'safety', 'policy.json')) ||
|
|
2194
|
+
existsSync(join(codexPluginDir, 'safety', 'policy.json')) ||
|
|
2195
|
+
existsSync(join(codeartsPluginDir, 'safety', 'policy.json')) ||
|
|
2196
|
+
existsSync(join(workbuddyPluginDir, 'safety', 'policy.json')) ||
|
|
2197
|
+
existsSync(join(dshPluginDir, 'safety', 'policy.json')) ||
|
|
2198
|
+
existsSync(join(officeacePluginDir, 'safety', 'policy.json')) ||
|
|
2199
|
+
existsSync(join(hermesPluginDir, 'safety', 'policy.json'));
|
|
1285
2200
|
check('Safety policy installed', safetyOk, 'Run: npx huaweicloud-devkit install');
|
|
1286
2201
|
|
|
1287
2202
|
// MCP config — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH
|
|
@@ -1291,39 +2206,87 @@ async function cmdDoctor() {
|
|
|
1291
2206
|
if (existsSync(opencodeCfg)) {
|
|
1292
2207
|
try {
|
|
1293
2208
|
const cfg = JSON.parse(readFileSync(opencodeCfg, 'utf8'));
|
|
1294
|
-
if (cfg.mcp && cfg.mcp['huaweicloud-devkit']) {
|
|
2209
|
+
if (cfg.mcp && cfg.mcp['huaweicloud-devkit']) {
|
|
2210
|
+
mcpConfigured = true;
|
|
2211
|
+
mcpCfgTarget = 'OpenCode';
|
|
2212
|
+
}
|
|
1295
2213
|
} catch {}
|
|
1296
2214
|
}
|
|
1297
2215
|
const codexCfg = codexConfigToml();
|
|
1298
2216
|
if (!mcpConfigured && existsSync(codexCfg)) {
|
|
1299
2217
|
try {
|
|
1300
2218
|
const cfg = readFileSync(codexCfg, 'utf8');
|
|
1301
|
-
if (cfg.includes('[mcp_servers.huaweicloud-devkit]')) {
|
|
2219
|
+
if (cfg.includes('[mcp_servers.huaweicloud-devkit]')) {
|
|
2220
|
+
mcpConfigured = true;
|
|
2221
|
+
mcpCfgTarget = 'Codex Desktop';
|
|
2222
|
+
}
|
|
1302
2223
|
} catch {}
|
|
1303
2224
|
}
|
|
1304
2225
|
const codeartsCfg = codeartsMcpSettingsFile();
|
|
1305
2226
|
if (!mcpConfigured && existsSync(codeartsCfg)) {
|
|
1306
2227
|
try {
|
|
1307
2228
|
const cfg = JSON.parse(readFileSync(codeartsCfg, 'utf8'));
|
|
1308
|
-
if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
|
|
2229
|
+
if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
|
|
2230
|
+
mcpConfigured = true;
|
|
2231
|
+
mcpCfgTarget = 'CodeArts';
|
|
2232
|
+
}
|
|
1309
2233
|
} catch {}
|
|
1310
2234
|
}
|
|
1311
2235
|
const workbuddyCfg = workbuddyMcpConfigFile();
|
|
1312
2236
|
if (!mcpConfigured && existsSync(workbuddyCfg)) {
|
|
1313
2237
|
try {
|
|
1314
2238
|
const cfg = JSON.parse(readFileSync(workbuddyCfg, 'utf8'));
|
|
1315
|
-
if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
|
|
2239
|
+
if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
|
|
2240
|
+
mcpConfigured = true;
|
|
2241
|
+
mcpCfgTarget = 'WorkBuddy';
|
|
2242
|
+
}
|
|
1316
2243
|
} catch {}
|
|
1317
2244
|
}
|
|
1318
2245
|
if (!mcpConfigured && dshPatchConfigured()) {
|
|
1319
2246
|
mcpConfigured = true;
|
|
1320
2247
|
mcpCfgTarget = 'DSH';
|
|
1321
2248
|
}
|
|
1322
|
-
|
|
2249
|
+
if (!mcpConfigured) {
|
|
2250
|
+
const officeaceCfg = officeaceCapabilitiesFile();
|
|
2251
|
+
if (existsSync(officeaceCfg)) {
|
|
2252
|
+
try {
|
|
2253
|
+
const cfg = JSON.parse(readFileSync(officeaceCfg, 'utf8'));
|
|
2254
|
+
if (
|
|
2255
|
+
Array.isArray(cfg.capabilities) &&
|
|
2256
|
+
cfg.capabilities.some((c) => c.id === 'huaweicloud-devkit' && c.type === 'mcp')
|
|
2257
|
+
) {
|
|
2258
|
+
mcpConfigured = true;
|
|
2259
|
+
mcpCfgTarget = 'OfficeAce';
|
|
2260
|
+
}
|
|
2261
|
+
} catch {}
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
if (!mcpConfigured) {
|
|
2265
|
+
const hermesCfg = hermesConfigFile();
|
|
2266
|
+
if (existsSync(hermesCfg)) {
|
|
2267
|
+
try {
|
|
2268
|
+
const cfg = readFileSync(hermesCfg, 'utf8');
|
|
2269
|
+
if (cfg.includes('mcp_servers:') && cfg.includes('huaweicloud-devkit')) {
|
|
2270
|
+
mcpConfigured = true;
|
|
2271
|
+
mcpCfgTarget = 'Hermes Agent';
|
|
2272
|
+
}
|
|
2273
|
+
} catch {}
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
check(
|
|
2277
|
+
'MCP configured',
|
|
2278
|
+
mcpConfigured,
|
|
2279
|
+
mcpCfgTarget ? `Found in ${mcpCfgTarget} config` : 'Run: npx huaweicloud-devkit install',
|
|
2280
|
+
);
|
|
1323
2281
|
|
|
1324
2282
|
// hcloud CLI
|
|
1325
|
-
const hcloudBin = findHcloudBin() ||
|
|
1326
|
-
const hcloudCheck = spawnSync(`"${hcloudBin}" version`, [], {
|
|
2283
|
+
const hcloudBin = findHcloudBin() || process.env.HCLOUD_BIN || 'hcloud';
|
|
2284
|
+
const hcloudCheck = spawnSync(`"${hcloudBin}" version`, [], {
|
|
2285
|
+
shell: true,
|
|
2286
|
+
windowsHide: true,
|
|
2287
|
+
stdio: 'pipe',
|
|
2288
|
+
timeout: 5000,
|
|
2289
|
+
});
|
|
1327
2290
|
const hcloudOut = (hcloudCheck.stdout || '').toString() + (hcloudCheck.stderr || '').toString();
|
|
1328
2291
|
const hcloudOk = hcloudCheck.status === 0 && /KooCLI|Current.*version|当前KooCLI/i.test(hcloudOut);
|
|
1329
2292
|
check('hcloud CLI installed', hcloudOk, 'Run: npx huaweicloud-devkit install-hcloud');
|
|
@@ -1342,20 +2305,37 @@ async function cmdDoctor() {
|
|
|
1342
2305
|
console.log(` Version: ${ver}`);
|
|
1343
2306
|
|
|
1344
2307
|
// Check auth
|
|
1345
|
-
const authCheck = spawnSync(`"${hcloudBin}" configure list`, [], {
|
|
2308
|
+
const authCheck = spawnSync(`"${hcloudBin}" configure list`, [], {
|
|
2309
|
+
shell: true,
|
|
2310
|
+
windowsHide: true,
|
|
2311
|
+
stdio: 'pipe',
|
|
2312
|
+
timeout: 5000,
|
|
2313
|
+
});
|
|
1346
2314
|
const hasAuth = authCheck.status === 0 && /access.?key/i.test(authCheck.stdout.toString());
|
|
1347
2315
|
check('hcloud credentials configured', hasAuth, 'Run: npx huaweicloud-devkit auth init');
|
|
1348
2316
|
}
|
|
1349
2317
|
|
|
1350
2318
|
// Skills
|
|
1351
|
-
const skillsOptions = [
|
|
1352
|
-
|
|
2319
|
+
const skillsOptions = [
|
|
2320
|
+
opencodeSkillsDir(),
|
|
2321
|
+
codexDesktopSkillsDir(),
|
|
2322
|
+
codeartsSkillsDir(),
|
|
2323
|
+
workbuddySkillsDir(),
|
|
2324
|
+
dshSkillsDir(),
|
|
2325
|
+
officeaceSkillsDir(),
|
|
2326
|
+
hermesSkillsDir(),
|
|
2327
|
+
];
|
|
2328
|
+
let skillCount = 0;
|
|
2329
|
+
const missingSkills = [];
|
|
1353
2330
|
for (const dir of skillsOptions) {
|
|
1354
2331
|
if (!existsSync(dir)) continue;
|
|
1355
|
-
const entries = readdirSync(dir, { withFileTypes: true })
|
|
1356
|
-
|
|
2332
|
+
const entries = readdirSync(dir, { withFileTypes: true }).filter(
|
|
2333
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
2334
|
+
);
|
|
1357
2335
|
const count = entries.length;
|
|
1358
|
-
if (count > skillCount) {
|
|
2336
|
+
if (count > skillCount) {
|
|
2337
|
+
skillCount = count;
|
|
2338
|
+
}
|
|
1359
2339
|
for (const d of entries) {
|
|
1360
2340
|
if (!existsSync(join(dir, d.name, 'SKILL.md'))) missingSkills.push(d.name);
|
|
1361
2341
|
}
|
|
@@ -1363,12 +2343,15 @@ async function cmdDoctor() {
|
|
|
1363
2343
|
const skillsOk = skillCount >= 6;
|
|
1364
2344
|
check(`Skills installed (${skillCount})`, skillsOk, 'Run: npx huaweicloud-devkit install');
|
|
1365
2345
|
if (missingSkills.length > 0) {
|
|
1366
|
-
console.log(
|
|
2346
|
+
console.log(
|
|
2347
|
+
` \x1b[33m[WARN]\x1b[0m ${missingSkills.length} skill(s) missing SKILL.md: ${missingSkills.join(', ')} — Run: npx huaweicloud-devkit install`,
|
|
2348
|
+
);
|
|
1367
2349
|
warn++;
|
|
1368
2350
|
}
|
|
1369
2351
|
|
|
1370
2352
|
const proxyConfig = readProxyConfig();
|
|
1371
|
-
const proxyEnv =
|
|
2353
|
+
const proxyEnv =
|
|
2354
|
+
process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
|
|
1372
2355
|
if (proxyConfig || proxyEnv) {
|
|
1373
2356
|
const source = proxyEnv ? 'env' : 'file';
|
|
1374
2357
|
const proxyUrl = proxyEnv || proxyConfig.https_proxy || proxyConfig.http_proxy;
|
|
@@ -1378,7 +2361,9 @@ async function cmdDoctor() {
|
|
|
1378
2361
|
console.log(`\nResults: ${pass} pass, ${warn} warn, ${fail} fail`);
|
|
1379
2362
|
|
|
1380
2363
|
if (mcpConfigured && !hcloudOk) {
|
|
1381
|
-
console.log(
|
|
2364
|
+
console.log(
|
|
2365
|
+
'\n\x1b[33mMCP is configured but hcloud is not installed. Install hcloud then restart your session.\x1b[0m',
|
|
2366
|
+
);
|
|
1382
2367
|
}
|
|
1383
2368
|
if (fail > 0) {
|
|
1384
2369
|
console.log('\x1b[33mFix failures above, then restart your session.\x1b[0m');
|
|
@@ -1394,6 +2379,7 @@ async function cmdDoctor() {
|
|
|
1394
2379
|
{ path: join(workbuddyPluginsDir(), '.installed'), name: 'WorkBuddy' },
|
|
1395
2380
|
{ path: join(codeartsPluginsDir(), '.installed'), name: 'CodeArts' },
|
|
1396
2381
|
{ path: join(dshPluginsDir(), '.installed'), name: 'DSH' },
|
|
2382
|
+
{ path: join(officeacePluginsDir(), '.installed'), name: 'OfficeAce' },
|
|
1397
2383
|
];
|
|
1398
2384
|
for (const marker of installedMarkers) {
|
|
1399
2385
|
if (existsSync(marker.path)) {
|
|
@@ -1487,6 +2473,42 @@ async function cmdUpdate() {
|
|
|
1487
2473
|
return;
|
|
1488
2474
|
}
|
|
1489
2475
|
|
|
2476
|
+
if (target === 'officeace') {
|
|
2477
|
+
if (!existsSync(join(officeacePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2478
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
console.log('[OfficeAce]');
|
|
2482
|
+
await updateOfficeAce();
|
|
2483
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
2484
|
+
console.log(`\x1b[33m打开连接器 → 我的连接器 → huaweicloud-devkit → 连接 → 回到对话 → 输入框开启连接器\x1b[0m`);
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
if (target === 'hermes') {
|
|
2489
|
+
if (!existsSync(join(hermesPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2490
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
2491
|
+
return;
|
|
2492
|
+
}
|
|
2493
|
+
console.log('[Hermes Agent]');
|
|
2494
|
+
await updateHermes();
|
|
2495
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
2496
|
+
console.log(`\x1b[33mRestart Hermes Agent for changes to take effect.\x1b[0m`);
|
|
2497
|
+
return;
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
if (target === 'openclaw') {
|
|
2501
|
+
if (!existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2502
|
+
console.log('\x1b[33mNot installed. Use "install" command first.\x1b[0m');
|
|
2503
|
+
return;
|
|
2504
|
+
}
|
|
2505
|
+
console.log('[OpenClaw]');
|
|
2506
|
+
await updateCodexDesktop();
|
|
2507
|
+
console.log(`\n\x1b[32mUpdate complete.\x1b[0m`);
|
|
2508
|
+
console.log(`\x1b[33mRestart OpenClaw for changes to take effect.\x1b[0m`);
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2511
|
+
|
|
1490
2512
|
if (target === 'all') {
|
|
1491
2513
|
let updatedAny = false;
|
|
1492
2514
|
if (existsSync(join(opencodePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
@@ -1514,6 +2536,21 @@ async function cmdUpdate() {
|
|
|
1514
2536
|
await updateDsh();
|
|
1515
2537
|
updatedAny = true;
|
|
1516
2538
|
}
|
|
2539
|
+
if (existsSync(join(officeacePluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2540
|
+
console.log('\n[OfficeAce]');
|
|
2541
|
+
await updateOfficeAce();
|
|
2542
|
+
updatedAny = true;
|
|
2543
|
+
}
|
|
2544
|
+
if (existsSync(join(hermesPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2545
|
+
console.log('\n[Hermes Agent]');
|
|
2546
|
+
await updateHermes();
|
|
2547
|
+
updatedAny = true;
|
|
2548
|
+
}
|
|
2549
|
+
if (existsSync(join(codexDesktopPluginsDir(), 'src', 'mcp-server.mjs'))) {
|
|
2550
|
+
console.log('\n[OpenClaw]');
|
|
2551
|
+
await updateCodexDesktop();
|
|
2552
|
+
updatedAny = true;
|
|
2553
|
+
}
|
|
1517
2554
|
if (codexStatus()) {
|
|
1518
2555
|
console.log('\n[Codex]');
|
|
1519
2556
|
installCodex();
|
|
@@ -1564,9 +2601,7 @@ async function cmdInstallHcloud() {
|
|
|
1564
2601
|
const os = platform();
|
|
1565
2602
|
const arch = process.arch;
|
|
1566
2603
|
const baseUrl = 'https://cn-north-4-hdn-koocli.obs.cn-north-4.myhuaweicloud.com/cli/latest';
|
|
1567
|
-
const installDir = os === 'win32'
|
|
1568
|
-
? join(homedir(), 'hcloud')
|
|
1569
|
-
: join(homedir(), '.local', 'bin');
|
|
2604
|
+
const installDir = os === 'win32' ? join(homedir(), 'hcloud') : join(homedir(), '.local', 'bin');
|
|
1570
2605
|
|
|
1571
2606
|
if (os === 'win32') {
|
|
1572
2607
|
const url = `${baseUrl}/huaweicloud-cli-windows-amd64.zip`;
|
|
@@ -1580,12 +2615,20 @@ async function cmdInstallHcloud() {
|
|
|
1580
2615
|
// Download
|
|
1581
2616
|
console.log(` Downloading ${url}...`);
|
|
1582
2617
|
const psCmd = `[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '${url}' -OutFile '${zipPath}' -UseBasicParsing`;
|
|
1583
|
-
const dl = spawnSync('powershell', ['-NoProfile', '-Command', psCmd], {
|
|
2618
|
+
const dl = spawnSync('powershell', ['-NoProfile', '-Command', psCmd], {
|
|
2619
|
+
stdio: 'inherit',
|
|
2620
|
+
windowsHide: true,
|
|
2621
|
+
timeout: 180000,
|
|
2622
|
+
});
|
|
1584
2623
|
if (dl.status !== 0) throw new Error(`下载失败 (PowerShell exit ${dl.status})`);
|
|
1585
2624
|
|
|
1586
2625
|
// Extract
|
|
1587
2626
|
console.log(' Extracting...');
|
|
1588
|
-
const ex = spawnSync(
|
|
2627
|
+
const ex = spawnSync(
|
|
2628
|
+
'powershell',
|
|
2629
|
+
['-NoProfile', '-Command', `Expand-Archive -Path '${zipPath}' -DestinationPath '${installDir}' -Force`],
|
|
2630
|
+
{ stdio: 'inherit', windowsHide: true, timeout: 60000 },
|
|
2631
|
+
);
|
|
1589
2632
|
if (ex.status !== 0) throw new Error(`解压失败 (PowerShell exit ${ex.status})`);
|
|
1590
2633
|
if (!existsSync(join(installDir, 'hcloud.exe'))) throw new Error('hcloud.exe 未生成,可能已被安全软件拦截');
|
|
1591
2634
|
|
|
@@ -1609,7 +2652,11 @@ async function cmdInstallHcloud() {
|
|
|
1609
2652
|
' Write-Output " Already in user PATH: $target"',
|
|
1610
2653
|
'}',
|
|
1611
2654
|
].join('; ');
|
|
1612
|
-
spawnSync('powershell', ['-NoProfile', '-Command', pathPs], {
|
|
2655
|
+
spawnSync('powershell', ['-NoProfile', '-Command', pathPs], {
|
|
2656
|
+
stdio: 'inherit',
|
|
2657
|
+
windowsHide: true,
|
|
2658
|
+
timeout: 30000,
|
|
2659
|
+
});
|
|
1613
2660
|
|
|
1614
2661
|
console.log(`\n\x1b[32mInstall complete.\x1b[0m`);
|
|
1615
2662
|
console.log(` Verify: ${join(installDir, 'hcloud.exe')} version`);
|
|
@@ -1625,7 +2672,12 @@ async function cmdInstallHcloud() {
|
|
|
1625
2672
|
});
|
|
1626
2673
|
});
|
|
1627
2674
|
if (agree) {
|
|
1628
|
-
const r = spawnSync(hcloudBin, ['version'], {
|
|
2675
|
+
const r = spawnSync(hcloudBin, ['version'], {
|
|
2676
|
+
input: 'y\n',
|
|
2677
|
+
encoding: 'utf8',
|
|
2678
|
+
timeout: 10000,
|
|
2679
|
+
windowsHide: true,
|
|
2680
|
+
});
|
|
1629
2681
|
if (r.status === 0) {
|
|
1630
2682
|
console.log(' \x1b[32mPrivacy agreement accepted. KooCLI ready.\x1b[0m');
|
|
1631
2683
|
} else {
|
|
@@ -1646,7 +2698,9 @@ async function cmdInstallHcloud() {
|
|
|
1646
2698
|
}
|
|
1647
2699
|
} else if (os === 'linux') {
|
|
1648
2700
|
console.log('[Linux] One-liner install:');
|
|
1649
|
-
console.log(
|
|
2701
|
+
console.log(
|
|
2702
|
+
' 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',
|
|
2703
|
+
);
|
|
1650
2704
|
console.log(`\nOr manual: ${arch === 'arm64' ? 'ARM64' : 'AMD64'}`);
|
|
1651
2705
|
const pkg = arch === 'arm64' ? 'linux-arm64' : 'linux-amd64';
|
|
1652
2706
|
console.log(` curl -LO "${baseUrl}/huaweicloud-cli-${pkg}.tar.gz"`);
|
|
@@ -1656,7 +2710,9 @@ async function cmdInstallHcloud() {
|
|
|
1656
2710
|
console.log(`\nFull guide: https://support.huaweicloud.com/qs-hcli/hcli_02_003_02.html`);
|
|
1657
2711
|
} else if (os === 'darwin') {
|
|
1658
2712
|
console.log('[macOS] One-liner install:');
|
|
1659
|
-
console.log(
|
|
2713
|
+
console.log(
|
|
2714
|
+
' 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',
|
|
2715
|
+
);
|
|
1660
2716
|
console.log(`\nOr manual: ${arch === 'arm64' ? 'ARM64 (Apple Silicon)' : 'AMD64 (Intel)'}`);
|
|
1661
2717
|
const pkg = arch === 'arm64' ? 'mac-arm64' : 'mac-amd64';
|
|
1662
2718
|
console.log(` curl -LO "${baseUrl}/huaweicloud-cli-${pkg}.tar.gz"`);
|
|
@@ -1687,7 +2743,7 @@ function readLineQuestion(prompt) {
|
|
|
1687
2743
|
async function readSecret(prompt) {
|
|
1688
2744
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1689
2745
|
throw new Error(
|
|
1690
|
-
`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
|
|
2746
|
+
`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.`,
|
|
1691
2747
|
);
|
|
1692
2748
|
}
|
|
1693
2749
|
|
|
@@ -1727,7 +2783,7 @@ async function readSecret(prompt) {
|
|
|
1727
2783
|
}
|
|
1728
2784
|
|
|
1729
2785
|
function configureHcloud(credentials) {
|
|
1730
|
-
const hcloudBin = findHcloudBin() ||
|
|
2786
|
+
const hcloudBin = findHcloudBin() || process.env.HCLOUD_BIN || 'hcloud';
|
|
1731
2787
|
const args = [
|
|
1732
2788
|
'configure',
|
|
1733
2789
|
'set',
|
|
@@ -1744,7 +2800,9 @@ function configureHcloud(credentials) {
|
|
|
1744
2800
|
return {
|
|
1745
2801
|
ok: r.status === 0,
|
|
1746
2802
|
code: r.status,
|
|
1747
|
-
error: String(r.stderr || '')
|
|
2803
|
+
error: String(r.stderr || '')
|
|
2804
|
+
.trim()
|
|
2805
|
+
.slice(0, 240),
|
|
1748
2806
|
};
|
|
1749
2807
|
}
|
|
1750
2808
|
|
|
@@ -1755,7 +2813,9 @@ function printAuthAgents(agents = {}) {
|
|
|
1755
2813
|
}
|
|
1756
2814
|
|
|
1757
2815
|
function printAuthStatus(status) {
|
|
1758
|
-
console.log(
|
|
2816
|
+
console.log(
|
|
2817
|
+
`Credentials vault: ${status.credentialsConfigured ? 'configured' : 'missing'} (${status.credentialsPath})`,
|
|
2818
|
+
);
|
|
1759
2819
|
console.log(`OBS config: ${status.obsConfigured ? 'configured' : 'missing'} (${status.obsConfigPath})`);
|
|
1760
2820
|
console.log(`KooCLI: ${status.kooCliInstalled ? 'installed' : 'missing'}`);
|
|
1761
2821
|
console.log('Agent MCP registration:');
|
|
@@ -1779,7 +2839,9 @@ async function cmdAuthInit() {
|
|
|
1779
2839
|
|
|
1780
2840
|
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
1781
2841
|
if (!interactive && (!ak || !sk)) {
|
|
1782
|
-
console.error(
|
|
2842
|
+
console.error(
|
|
2843
|
+
'\x1b[31mNon-interactive session detected. Provide credentials via environment variables instead:\x1b[0m',
|
|
2844
|
+
);
|
|
1783
2845
|
console.error(' HW_ACCESS_KEY, HW_SECRET_KEY');
|
|
1784
2846
|
console.error(' (Or run "npx huaweicloud-devkit auth init" in a real terminal.)');
|
|
1785
2847
|
process.exitCode = 1;
|
|
@@ -1788,7 +2850,8 @@ async function cmdAuthInit() {
|
|
|
1788
2850
|
|
|
1789
2851
|
if (!ak) ak = await readLineQuestion('Access Key ID (AK): ');
|
|
1790
2852
|
if (!sk) sk = await readSecret('Secret Access Key (SK): ');
|
|
1791
|
-
if (interactive && !securityToken)
|
|
2853
|
+
if (interactive && !securityToken)
|
|
2854
|
+
securityToken = await readLineQuestion('Security Token (optional, press Enter to skip): ');
|
|
1792
2855
|
if (!region) region = 'cn-north-4';
|
|
1793
2856
|
|
|
1794
2857
|
if (!ak || !sk) {
|
|
@@ -1797,8 +2860,7 @@ async function cmdAuthInit() {
|
|
|
1797
2860
|
return;
|
|
1798
2861
|
}
|
|
1799
2862
|
|
|
1800
|
-
|
|
1801
|
-
const vaultPath = writeGlobalCredentials({ ak, sk, securityToken, region });
|
|
2863
|
+
writeGlobalCredentials({ ak, sk, securityToken, region });
|
|
1802
2864
|
|
|
1803
2865
|
try {
|
|
1804
2866
|
writeObsConfig({ ak, sk, securityToken, region });
|
|
@@ -1874,12 +2936,14 @@ async function cmdProxyInit() {
|
|
|
1874
2936
|
|
|
1875
2937
|
const httpsProxy = await readLineQuestion(`HTTPS proxy [${existing.https_proxy || 'none'}]: `);
|
|
1876
2938
|
const httpProxy = await readLineQuestion(`HTTP proxy [${existing.http_proxy || 'none'}]: `);
|
|
1877
|
-
const noProxy = await readLineQuestion(
|
|
2939
|
+
const noProxy = await readLineQuestion(
|
|
2940
|
+
`NO_PROXY hosts [${existing.no_proxy || '127.0.0.1,localhost,.huawei.com'}]: `,
|
|
2941
|
+
);
|
|
1878
2942
|
|
|
1879
2943
|
const config = {
|
|
1880
2944
|
https_proxy: httpsProxy || existing.https_proxy || '',
|
|
1881
2945
|
http_proxy: httpProxy || existing.http_proxy || '',
|
|
1882
|
-
no_proxy: noProxy || existing.no_proxy || '
|
|
2946
|
+
no_proxy: noProxy || existing.no_proxy || '127.0.0.1,localhost,.huawei.com',
|
|
1883
2947
|
};
|
|
1884
2948
|
|
|
1885
2949
|
const path = writeProxyConfig(config);
|
|
@@ -1981,7 +3045,9 @@ async function main() {
|
|
|
1981
3045
|
case '-h':
|
|
1982
3046
|
default:
|
|
1983
3047
|
console.log(BANNER);
|
|
1984
|
-
console.log(
|
|
3048
|
+
console.log(
|
|
3049
|
+
'Usage: npx huaweicloud-devkit <command> [--target <opencode|codex|codearts|workbuddy|dsh|officeace|hermes|openclaw|all>]\n',
|
|
3050
|
+
);
|
|
1985
3051
|
console.log('Commands:');
|
|
1986
3052
|
console.log(' install Install skills, MCP server, safety policy');
|
|
1987
3053
|
console.log(' uninstall Remove installed files');
|
|
@@ -1994,13 +3060,17 @@ async function main() {
|
|
|
1994
3060
|
console.log(' proxy Manage proxy config: init | show | clear');
|
|
1995
3061
|
console.log(' help Show this help');
|
|
1996
3062
|
console.log('\nOptions:');
|
|
1997
|
-
console.log(
|
|
3063
|
+
console.log(
|
|
3064
|
+
' --target Target agent: opencode (default), codex, codearts, workbuddy, dsh, officeace, hermes, openclaw, all',
|
|
3065
|
+
);
|
|
1998
3066
|
console.log('\nExamples:');
|
|
1999
3067
|
console.log(' npx huaweicloud-devkit install');
|
|
2000
3068
|
console.log(' npx huaweicloud-devkit install --target codex');
|
|
2001
3069
|
console.log(' npx huaweicloud-devkit install --target codearts');
|
|
2002
3070
|
console.log(' npx huaweicloud-devkit install --target workbuddy');
|
|
2003
3071
|
console.log(' npx huaweicloud-devkit install --target dsh');
|
|
3072
|
+
console.log(' npx huaweicloud-devkit install --target officeace');
|
|
3073
|
+
console.log(' npx huaweicloud-devkit install --target hermes');
|
|
2004
3074
|
console.log(' npx huaweicloud-devkit install --target all');
|
|
2005
3075
|
console.log(' npx huaweicloud-devkit auth init');
|
|
2006
3076
|
console.log(' npx huaweicloud-devkit auth sync --target all');
|