huaweicloud-devkit 1.1.7-next.1 → 1.1.7-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +106 -21
- package/plugins/huaweicloud-core/src/auth/service.mjs +14 -0
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +118 -1
- package/plugins/huaweicloud-core/src/mcp-config-merge.mjs +23 -0
- package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +1 -1
- package/plugins/huaweicloud-core/src/safety-policy.mjs +1 -1
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +12 -0
- package/plugins/huaweicloud-core/src/setup-cli.mjs +50 -18
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
3
|
"mcpName": "io.github.huaweicloud/huaweicloud-devkit",
|
|
4
|
-
"version": "1.1.7-next.
|
|
4
|
+
"version": "1.1.7-next.2",
|
|
5
5
|
"kooCliVersion": "7.2.12",
|
|
6
6
|
"description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
|
|
7
7
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"mcpServers": "./.mcp.json",
|
|
18
18
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
19
19
|
"skills": "./skills/",
|
|
20
|
-
"version": "1.1.7-next.
|
|
20
|
+
"version": "1.1.7-next.2",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"mcpServers": "./.mcp.json",
|
|
21
21
|
"description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
|
|
22
22
|
"skills": "./skills/",
|
|
23
|
-
"version": "1.1.7-next.
|
|
23
|
+
"version": "1.1.7-next.2",
|
|
24
24
|
"author": {
|
|
25
25
|
"name": "HuaweiCloud Mate",
|
|
26
26
|
"url": "https://github.com/huaweicloud"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"mcpServers": "./.mcp.json",
|
|
18
18
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
19
19
|
"skills": "./skills/",
|
|
20
|
-
"version": "1.1.7-next.
|
|
20
|
+
"version": "1.1.7-next.2",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.7-next.
|
|
3
|
+
"version": "1.1.7-next.2",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.7-next.
|
|
3
|
+
"version": "1.1.7-next.2",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
3
|
"id": "huaweicloud-devkit",
|
|
4
4
|
"displayName": "HuaweiCloud DevKit",
|
|
5
|
-
"version": "1.1.7-next.
|
|
5
|
+
"version": "1.1.7-next.2",
|
|
6
6
|
"family": "bundle-plugin",
|
|
7
7
|
"bundleFormat": "codex",
|
|
8
8
|
"description": "Guide coding agents to use Huawei Cloud safely — KooCLI, APIs, SDKs, 28 MCP tools, skills, and safety guardrails.",
|
|
@@ -85,6 +85,95 @@ function present(v) {
|
|
|
85
85
|
return typeof v === 'string' && v.length > 0 && !isPlaceholder(v);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
// Pick the Huawei Cloud DevKit MCP server entry from a map using prefix matching.
|
|
89
|
+
// CodeArts Work marketplace presets keyed entries like `huaweicloud-devkit_1`
|
|
90
|
+
// (suffix per installed instance); match by prefix so `_1`/`_2`/`HuaweiCloud DevKit`
|
|
91
|
+
// all resolve without hardcoding an instance number.
|
|
92
|
+
// Deterministically choose the Huawei Cloud DevKit MCP server entry from a map.
|
|
93
|
+
// Priority (stable, independent of object key order):
|
|
94
|
+
// 1. exact `huaweicloud-devkit` (installer-managed key)
|
|
95
|
+
// 2. `HuaweiCloud DevKit`
|
|
96
|
+
// 3. prefixed instances `huaweicloud-devkit_N` sorted by ascending N
|
|
97
|
+
export function pickDevkitMcpServer(mcpMap) {
|
|
98
|
+
if (!mcpMap || typeof mcpMap !== 'object') return null;
|
|
99
|
+
const keys = Object.keys(mcpMap);
|
|
100
|
+
const rank = (k) => {
|
|
101
|
+
if (k === 'huaweicloud-devkit') return -2;
|
|
102
|
+
if (k === 'HuaweiCloud DevKit') return -1;
|
|
103
|
+
const m = /^huaweicloud-devkit_(\d+)$/i.exec(k);
|
|
104
|
+
return m ? Number(m[1]) : Number.MAX_SAFE_INTEGER;
|
|
105
|
+
};
|
|
106
|
+
const candidates = keys
|
|
107
|
+
.filter((k) => /^huaweicloud-devkit(?:_|$)/i.test(k) || k === 'HuaweiCloud DevKit')
|
|
108
|
+
.sort((a, b) => rank(a) - rank(b));
|
|
109
|
+
for (const k of candidates) {
|
|
110
|
+
if (mcpMap[k]) return mcpMap[k];
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Derive the expiry (epoch ms) of a temporary STS credential set.
|
|
116
|
+
// Priority: 1) HW_STS_EXPIRES_AT env (ISO8601 or epoch seconds); 2) decode the
|
|
117
|
+
// security token (JWT payload or bare URL-safe base64 JSON) reading common
|
|
118
|
+
// expiry fields (exp, timeout_at, expires_at, id_expires_at; issued_at+duration).
|
|
119
|
+
// Returns null when unknown/unparseable. Never throws.
|
|
120
|
+
export function parseStsExpiry({ securityToken, expiresAtEnv = process.env.HW_STS_EXPIRES_AT } = {}) {
|
|
121
|
+
if (expiresAtEnv) {
|
|
122
|
+
const v = String(expiresAtEnv).trim();
|
|
123
|
+
if (!v) return null;
|
|
124
|
+
const asNum = Number(v);
|
|
125
|
+
if (Number.isFinite(asNum) && asNum > 0) return asNum > 1e12 ? asNum : asNum * 1000;
|
|
126
|
+
const t = Date.parse(v);
|
|
127
|
+
if (Number.isFinite(t)) return t;
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
if (!securityToken || typeof securityToken !== 'string') return null;
|
|
131
|
+
const token = String(securityToken).trim();
|
|
132
|
+
if (!token) return null;
|
|
133
|
+
|
|
134
|
+
const payload = (() => {
|
|
135
|
+
try {
|
|
136
|
+
if (token.includes('.')) {
|
|
137
|
+
const parts = token.split('.');
|
|
138
|
+
const b64 = parts.length >= 2 ? parts[1] : null;
|
|
139
|
+
if (!b64) return null;
|
|
140
|
+
const pad = b64 + '='.repeat((4 - (b64.length % 4)) % 4);
|
|
141
|
+
return JSON.parse(Buffer.from(pad.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8'));
|
|
142
|
+
}
|
|
143
|
+
const pad = token + '='.repeat((4 - (token.length % 4)) % 4);
|
|
144
|
+
return JSON.parse(Buffer.from(pad.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8'));
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
})();
|
|
149
|
+
if (!payload || typeof payload !== 'object') return null;
|
|
150
|
+
|
|
151
|
+
const exp = Number(payload.exp ?? payload.expires_at ?? payload.timeout_at ?? Number.NaN);
|
|
152
|
+
if (Number.isFinite(exp) && exp > 0) return exp * 1000;
|
|
153
|
+
const issued = Number(payload.issued_at ?? payload.iat ?? Number.NaN);
|
|
154
|
+
const duration = Number(payload.duration ?? payload.expires_in ?? payload.lifetime ?? Number.NaN);
|
|
155
|
+
if (Number.isFinite(issued) && issued > 0 && Number.isFinite(duration) && duration > 0) {
|
|
156
|
+
return (issued + duration) * 1000;
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function readWorkEnvironmentEntry(config) {
|
|
162
|
+
const server = pickDevkitMcpServer(config?.mcp);
|
|
163
|
+
if (!server?.environment) return null;
|
|
164
|
+
const ak = server.environment.HW_ACCESS_KEY;
|
|
165
|
+
const sk = server.environment.HW_SECRET_KEY;
|
|
166
|
+
if (present(ak) && present(sk)) {
|
|
167
|
+
return {
|
|
168
|
+
ak,
|
|
169
|
+
sk,
|
|
170
|
+
securityToken: present(server.environment.HW_SECURITY_TOKEN) ? server.environment.HW_SECURITY_TOKEN : '',
|
|
171
|
+
region: server.environment.HW_REGION || server.environment.HUAWEICLOUD_REGION || '',
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
88
177
|
export function writeGlobalCredentials(credentials = {}) {
|
|
89
178
|
const path = globalCredentialsPath();
|
|
90
179
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -136,6 +225,7 @@ export function resolveCredentials(options = {}) {
|
|
|
136
225
|
if (codeartsCreds) {
|
|
137
226
|
if (!ak && codeartsCreds.ak) ak = codeartsCreds.ak;
|
|
138
227
|
if (!sk && codeartsCreds.sk) sk = codeartsCreds.sk;
|
|
228
|
+
if (!securityToken && codeartsCreds.securityToken) securityToken = codeartsCreds.securityToken;
|
|
139
229
|
if (!region && codeartsCreds.region) region = codeartsCreds.region;
|
|
140
230
|
}
|
|
141
231
|
|
|
@@ -234,7 +324,8 @@ function isCodeArtsContext() {
|
|
|
234
324
|
return (
|
|
235
325
|
existsSync(join(process.cwd(), '.codeartsdoer')) ||
|
|
236
326
|
existsSync(join(homedir(), '.codeartsdoer')) ||
|
|
237
|
-
existsSync(join(homedir(), '.codeartswork'))
|
|
327
|
+
existsSync(join(homedir(), '.codeartswork')) ||
|
|
328
|
+
existsSync(join(homedir(), '.codearts'))
|
|
238
329
|
);
|
|
239
330
|
}
|
|
240
331
|
|
|
@@ -248,7 +339,7 @@ export function readCodeArtsCredentials() {
|
|
|
248
339
|
try {
|
|
249
340
|
if (!existsSync(path)) continue;
|
|
250
341
|
const config = JSON.parse(readFileSync(path, 'utf8'));
|
|
251
|
-
const server = config?.mcpServers
|
|
342
|
+
const server = pickDevkitMcpServer(config?.mcpServers);
|
|
252
343
|
if (!server?.env) continue;
|
|
253
344
|
|
|
254
345
|
const ak = server.env.HW_ACCESS_KEY;
|
|
@@ -266,26 +357,20 @@ export function readCodeArtsCredentials() {
|
|
|
266
357
|
}
|
|
267
358
|
}
|
|
268
359
|
|
|
269
|
-
// CodeArts Work — user-level only
|
|
270
|
-
|
|
271
|
-
|
|
360
|
+
// CodeArts Work — user-level only.
|
|
361
|
+
// New layout (post platform migration): ~/.codearts/mcp/mcp_settings.json with
|
|
362
|
+
// prefixed keys like `huaweicloud-devkit_1`; legacy ~/.codeartswork kept as
|
|
363
|
+
// fallback while still in service.
|
|
364
|
+
const workPaths = [
|
|
365
|
+
join(homedir(), '.codearts', 'mcp', 'mcp_settings.json'),
|
|
366
|
+
join(homedir(), '.codeartswork', 'mcp', 'mcp_settings.json'),
|
|
367
|
+
];
|
|
368
|
+
for (const path of workPaths) {
|
|
272
369
|
try {
|
|
273
|
-
if (existsSync(path))
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const ak = server.environment.HW_ACCESS_KEY;
|
|
278
|
-
const sk = server.environment.HW_SECRET_KEY;
|
|
279
|
-
if (present(ak) && present(sk)) {
|
|
280
|
-
return {
|
|
281
|
-
ak,
|
|
282
|
-
sk,
|
|
283
|
-
securityToken: present(server.environment.HW_SECURITY_TOKEN) ? server.environment.HW_SECURITY_TOKEN : '',
|
|
284
|
-
region: server.environment.HW_REGION || server.environment.HUAWEICLOUD_REGION || '',
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
370
|
+
if (!existsSync(path)) continue;
|
|
371
|
+
const config = JSON.parse(readFileSync(path, 'utf8'));
|
|
372
|
+
const entry = readWorkEnvironmentEntry(config);
|
|
373
|
+
if (entry) return entry;
|
|
289
374
|
} catch {
|
|
290
375
|
// mcp_settings.json missing or invalid — skip
|
|
291
376
|
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
globalCredentialsPath,
|
|
9
9
|
isPlaceholder,
|
|
10
10
|
obsConfigPath,
|
|
11
|
+
readCodeArtsCredentials,
|
|
11
12
|
readGlobalCredentials,
|
|
12
13
|
writeLastSync,
|
|
13
14
|
writeObsConfig,
|
|
@@ -47,6 +48,8 @@ export function computeOnboarding({ credentials, reconciled } = {}) {
|
|
|
47
48
|
const creds = credentials ?? readGlobalCredentials();
|
|
48
49
|
const scan = reconciled ?? exportStateForStatus();
|
|
49
50
|
const s1Has = Boolean(creds?.ak && creds?.sk && !isPlaceholder(creds.ak) && !isPlaceholder(creds.sk));
|
|
51
|
+
const s4Creds = readCodeArtsCredentials();
|
|
52
|
+
const s4Has = Boolean(s4Creds?.ak && s4Creds?.sk);
|
|
50
53
|
const injected = envHasRealTriplet();
|
|
51
54
|
// env has real non-triplet creds that are NOT placeholders (e.g. devspace AK/SK w/o token)
|
|
52
55
|
const envRealAk = !isPlaceholder(process.env.HW_ACCESS_KEY) && Boolean(process.env.HW_ACCESS_KEY);
|
|
@@ -72,6 +75,14 @@ export function computeOnboarding({ credentials, reconciled } = {}) {
|
|
|
72
75
|
message = 'Platform credentials are active; nothing to configure.';
|
|
73
76
|
return { needsSetup: false, scenario, reason, message, steps: [], accountHint };
|
|
74
77
|
}
|
|
78
|
+
if (s4Has && !s1Has && !envHasCreds) {
|
|
79
|
+
// CodeArts Work/IDE carries the user's temporary STS credentials in the MCP
|
|
80
|
+
// settings file only (not surfaced in the UI, not in S1/env).
|
|
81
|
+
scenario = 0;
|
|
82
|
+
reason = 'mcp-settings-injected';
|
|
83
|
+
message = '已从码道 MCP settings 读取到临时凭证,MCP 工具可直接使用。';
|
|
84
|
+
return { needsSetup: false, scenario, reason, message, steps: [], accountHint };
|
|
85
|
+
}
|
|
75
86
|
if (s1Has && !envHasCreds) {
|
|
76
87
|
scenario = 1;
|
|
77
88
|
reason = 's1-only';
|
|
@@ -155,11 +166,14 @@ export function getAuthStatus(target = 'all') {
|
|
|
155
166
|
const credentials = readGlobalCredentials();
|
|
156
167
|
const reconciled = { ...exportStateForStatus(), runtimeActive: hasRuntimeCredentials() };
|
|
157
168
|
const hcloud = probeHcloud();
|
|
169
|
+
const s4Creds = readCodeArtsCredentials();
|
|
158
170
|
const onboarding = computeOnboarding({ credentials, reconciled });
|
|
159
171
|
return {
|
|
160
172
|
target,
|
|
161
173
|
credentialsConfigured: Boolean(credentials?.ak && credentials?.sk),
|
|
162
174
|
credentialsPath: globalCredentialsPath(),
|
|
175
|
+
mcpSettingsConfigured: Boolean(s4Creds?.ak && s4Creds?.sk),
|
|
176
|
+
mcpSettingsSource: s4Creds ? 'codearts' : null,
|
|
163
177
|
obsConfigured: existsSync(obsConfigPath()),
|
|
164
178
|
obsConfigPath: obsConfigPath(),
|
|
165
179
|
kooCliInstalled: hcloud.installed,
|
|
@@ -7,6 +7,7 @@ import { dirname, join } from 'node:path';
|
|
|
7
7
|
import { classifyHcloudArgs, redactSecrets, assertAllowed } from './safety-policy.mjs';
|
|
8
8
|
import { getProxySettings } from './proxy/proxy-config.mjs';
|
|
9
9
|
import { findHcloudBin, resolveHcloudCommand } from './hcloud-probe.mjs';
|
|
10
|
+
import { parseStsExpiry, resolveCredentialsWithRuntime } from './auth/credentials.mjs';
|
|
10
11
|
|
|
11
12
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
12
13
|
const DEFAULT_FORCE_KILL_AFTER_MS = 2_000;
|
|
@@ -237,6 +238,105 @@ export function classifyUnsupported(service, metaDir) {
|
|
|
237
238
|
return 'unknown';
|
|
238
239
|
}
|
|
239
240
|
|
|
241
|
+
// Build command-line credential-injection args when the current credentials are
|
|
242
|
+
// temporary STS (carry a non-empty security token). KooCLI/obsutil do not read the
|
|
243
|
+
// platform-injected HW_* env (link B reads only S2/S3 config), so for a live STS
|
|
244
|
+
// credential set that R3 forbids writing to disk, we pass it per-command instead.
|
|
245
|
+
// Zero-persist: nothing touches S1/S2/S3 and every invocation re-reads the current
|
|
246
|
+
// value (stale as soon as the token is).
|
|
247
|
+
// Redact executed args for MCP-visible output. Besides the generic key=value
|
|
248
|
+
// redaction (--cli-access-key=...), obsutil-style standalone credential flags
|
|
249
|
+
// (-i AK / -k SK / -t TOKEN) carry the temporary STS as following array elements,
|
|
250
|
+
// which redactSecrets can't match. We scrub those explicitly.
|
|
251
|
+
export function redactArgsWithObs(rawArgs) {
|
|
252
|
+
const arr = Array.isArray(rawArgs) ? rawArgs.map(String) : [];
|
|
253
|
+
const out = [...arr];
|
|
254
|
+
for (let i = 0; i < out.length; i += 1) {
|
|
255
|
+
if (out[i] === '-i' || out[i] === '-k' || out[i] === '-t') {
|
|
256
|
+
if (i + 1 < out.length) out[i + 1] = '<redacted>';
|
|
257
|
+
i += 1;
|
|
258
|
+
} else if (/^-i[A-Za-z0-9]/.test(out[i])) {
|
|
259
|
+
out[i] = '-i<redacted>';
|
|
260
|
+
} else if (/^-k[A-Za-z0-9]/.test(out[i])) {
|
|
261
|
+
out[i] = '-k<redacted>';
|
|
262
|
+
} else if (/^-t[A-Za-z0-9]/.test(out[i])) {
|
|
263
|
+
out[i] = '-t<redacted>';
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return redactSecrets(out);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function resolveStsInjectArgs(rawArgs) {
|
|
270
|
+
const normalized = Array.isArray(rawArgs) ? rawArgs.map(String) : [];
|
|
271
|
+
if (normalized.length === 0) return [];
|
|
272
|
+
const flat = normalized.map(String);
|
|
273
|
+
|
|
274
|
+
// Explicit kill-switch (R2): CI/ops can disable argv-injection entirely so the
|
|
275
|
+
// temporary STS never appears in a process list.
|
|
276
|
+
const flag = process.env.HUAWEICLOUD_INJECT_STS_CMD;
|
|
277
|
+
if (flag === '0' || flag === 'false') return [];
|
|
278
|
+
|
|
279
|
+
// Never inject for help / metadata subcommands — the flags are meaningless there.
|
|
280
|
+
if (flat.some((a) => a === '--help' || a === '-h' || a === 'help')) return [];
|
|
281
|
+
|
|
282
|
+
// Wrapper invocation (bash -c / sudo / sh ...): our extra args would land in the
|
|
283
|
+
// wrapper's argv, not hcloud's — either ineffective or misleading. Skip them.
|
|
284
|
+
const WRAP = new Set([
|
|
285
|
+
'bash',
|
|
286
|
+
'sh',
|
|
287
|
+
'zsh',
|
|
288
|
+
'dash',
|
|
289
|
+
'bash.exe',
|
|
290
|
+
'sh.exe',
|
|
291
|
+
'/bin/bash',
|
|
292
|
+
'/bin/sh',
|
|
293
|
+
'/bin/zsh',
|
|
294
|
+
'/bin/dash',
|
|
295
|
+
'sudo',
|
|
296
|
+
]);
|
|
297
|
+
const first = String(flat[0] || '').toLowerCase();
|
|
298
|
+
if (WRAP.has(first)) return [];
|
|
299
|
+
|
|
300
|
+
// KooCLI profile-management subcommands don't take --cli-security-token.
|
|
301
|
+
if (first === 'configure' || first === 'config') return [];
|
|
302
|
+
|
|
303
|
+
// If the caller already passed explicit credential flags, do not override them.
|
|
304
|
+
if (
|
|
305
|
+
flat.some(
|
|
306
|
+
(a) =>
|
|
307
|
+
a.startsWith('--cli-access-key=') || a.startsWith('--cli-secret-key=') || a.startsWith('--cli-security-token='),
|
|
308
|
+
)
|
|
309
|
+
) {
|
|
310
|
+
return [];
|
|
311
|
+
}
|
|
312
|
+
// Explicit obsutil-style credentials already present — standalone (-i AK) or
|
|
313
|
+
// attached (-iAK) — should not be overridden by an extra injection.
|
|
314
|
+
if (flat.some((a) => a === '-i' || a === '-k' || a === '-t' || /^-[ikt][A-Za-z0-9]/.test(a))) return [];
|
|
315
|
+
|
|
316
|
+
let creds;
|
|
317
|
+
try {
|
|
318
|
+
creds = resolveCredentialsWithRuntime({ allowMissing: true });
|
|
319
|
+
} catch {
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
if (!creds || !creds.ak || !creds.sk || !creds.securityToken) return [];
|
|
323
|
+
|
|
324
|
+
// R3: if we can derive an expiry and the token is already at/within 60s of
|
|
325
|
+
// expiring, skip injection — using a dead token would make a doomed IAM round
|
|
326
|
+
// trip. If expiry is unknown/unparseable we keep the existing "inject anyway"
|
|
327
|
+
// behavior (can't prove it's stale).
|
|
328
|
+
const expiry = parseStsExpiry({ securityToken: creds.securityToken });
|
|
329
|
+
if (expiry !== null) {
|
|
330
|
+
const grace = 60 * 1000;
|
|
331
|
+
if (expiry <= Date.now() + grace) return [];
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const isObs = flat[0].toUpperCase() === 'OBS';
|
|
335
|
+
return isObs
|
|
336
|
+
? ['-i', creds.ak, '-k', creds.sk, '-t', creds.securityToken]
|
|
337
|
+
: ['--cli-access-key=' + creds.ak, '--cli-secret-key=' + creds.sk, '--cli-security-token=' + creds.securityToken];
|
|
338
|
+
}
|
|
339
|
+
|
|
240
340
|
export function planHcloudCommand(args, options = {}) {
|
|
241
341
|
const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
|
|
242
342
|
const classification = classifyHcloudArgs(normalizedArgs, options);
|
|
@@ -299,6 +399,16 @@ export async function runHcloud(args, options = {}) {
|
|
|
299
399
|
};
|
|
300
400
|
assertAllowed(plan.classification);
|
|
301
401
|
|
|
402
|
+
// Link B: when the runtime/environment carries live temporary STS credentials
|
|
403
|
+
// (security token set), KooCLI/obsutil would not see them (they read S2/S3 only
|
|
404
|
+
// and R3 forbids writing them to disk). Inject per-command so hcloud actually
|
|
405
|
+
// authenticates with the platform-provided STS. Injection args are appended to
|
|
406
|
+
// the EXECUTED args only; classification/approval used the original args.
|
|
407
|
+
const stsInject = resolveStsInjectArgs(normalizedArgs);
|
|
408
|
+
if (stsInject.length > 0) {
|
|
409
|
+
plan.rawArgs = [...plan.rawArgs, ...stsInject];
|
|
410
|
+
}
|
|
411
|
+
|
|
302
412
|
const metaDir = options.metaDir;
|
|
303
413
|
|
|
304
414
|
// KooCLI only switches language globally (`hcloud configure set --cli-lang=cn`); there is no
|
|
@@ -411,6 +521,13 @@ function runHcloudOnce(plan, options) {
|
|
|
411
521
|
result.stdout = String(result.stdout).slice(0, 2000) + `\n...(truncated, full output saved to ${outputFile})`;
|
|
412
522
|
}
|
|
413
523
|
}
|
|
524
|
+
// Never surface raw args (may include the injected temporary STS) to MCP
|
|
525
|
+
// clients / agent conversation — redact before resolve. The live `plan`
|
|
526
|
+
// object itself is left untouched so retries keep executing the real args.
|
|
527
|
+
if (result.plan && Array.isArray(result.plan.rawArgs)) {
|
|
528
|
+
const redactedPlan = { ...result.plan, rawArgs: redactArgsWithObs(result.plan.rawArgs) };
|
|
529
|
+
result.plan = redactedPlan;
|
|
530
|
+
}
|
|
414
531
|
resolve(result);
|
|
415
532
|
}
|
|
416
533
|
|
|
@@ -509,7 +626,7 @@ function quoteShellArg(value) {
|
|
|
509
626
|
function planningWarnings(args) {
|
|
510
627
|
const joined = args.join(' ');
|
|
511
628
|
const warnings = [];
|
|
512
|
-
if (/
|
|
629
|
+
if (/admin[_-]?pass|password|passwd|secret|token/i.test(joined)) {
|
|
513
630
|
warnings.push(
|
|
514
631
|
'This command appears to contain a password or secret field. Do not leave plaintext secrets in shell history; prefer local-only input or a runtime injection pattern.',
|
|
515
632
|
);
|
|
@@ -130,3 +130,26 @@ export function applyUserDelta(entry, delta, style) {
|
|
|
130
130
|
if (delta.enabled === false) merged.enabled = false;
|
|
131
131
|
return merged;
|
|
132
132
|
}
|
|
133
|
+
|
|
134
|
+
// Collect user-owned environment variables from peer DevKit server entries in the
|
|
135
|
+
// same MCP map (e.g. market-preset keys like `huaweicloud-devkit_1` that carry the
|
|
136
|
+
// user's temporary STS credentials HW_ACCESS_KEY / HW_SECRET_KEY / HW_SECURITY_TOKEN).
|
|
137
|
+
// When the installer writes a NEW `huaweicloud-devkit` entry these must be inherited
|
|
138
|
+
// so the freshly installed key also has the user's credentials (the CodeArts Work
|
|
139
|
+
// UI never surfaces them; they live only in mcp_settings.json).
|
|
140
|
+
export function inheritPeerUserEnv(mcpMap) {
|
|
141
|
+
if (!isPlainObject(mcpMap)) return null;
|
|
142
|
+
const collected = {};
|
|
143
|
+
for (const [key, entry] of Object.entries(mcpMap)) {
|
|
144
|
+
if (key === 'huaweicloud-devkit') continue;
|
|
145
|
+
if (!/^huaweicloud-devkit(?:_|$)/i.test(key) && key !== 'HuaweiCloud DevKit') continue;
|
|
146
|
+
const env = isPlainObject(entry?.environment) ? entry.environment : isPlainObject(entry?.env) ? entry.env : null;
|
|
147
|
+
if (!env) continue;
|
|
148
|
+
for (const [k, v] of Object.entries(env)) {
|
|
149
|
+
if (REQUIRED_ENV_KEYS.has(k)) continue;
|
|
150
|
+
if (typeof v !== 'string' || v === '') continue;
|
|
151
|
+
if (!(k in collected)) collected[k] = v;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return Object.keys(collected).length > 0 ? collected : null;
|
|
155
|
+
}
|
|
@@ -19,7 +19,7 @@ export function loadRiskRules(options = {}) {
|
|
|
19
19
|
function redactEvidence(text) {
|
|
20
20
|
return String(text)
|
|
21
21
|
.replace(
|
|
22
|
-
/((?:access[_-]?key|secret[_-]?key|security[_-]?token|x[_-]?auth[_-]?token|authorization|password|passwd|
|
|
22
|
+
/((?:access[_-]?key|secret[_-]?key|security[_-]?token|x[_-]?auth[_-]?token|token|authorization|password|passwd|admin[_-]?pass|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;]+)/gi,
|
|
23
23
|
'$1<redacted>',
|
|
24
24
|
)
|
|
25
25
|
.replace(/(AK|SK)\s*[:=]\s*("[^"]*"|'[^']*'|[^\s,;]+)/g, '$1=<redacted>');
|
|
@@ -39,7 +39,7 @@ function redactString(text) {
|
|
|
39
39
|
// Redact the ENTIRE value of this arg — not just the first whitespace token.
|
|
40
40
|
.replace(/((?:user[_-]?data|metadata|private[_-]?key)\s*[:=]\s*).*/gi, '$1<redacted>')
|
|
41
41
|
.replace(
|
|
42
|
-
/((?:access[_-]?key|secret[_-]?key|security[_-]?token|x[_-]?auth[_-]?token|authorization|password|passwd|
|
|
42
|
+
/((?:access[_-]?key|secret[_-]?key|security[_-]?token|x[_-]?auth[_-]?token|token|authorization|password|passwd|admin[_-]?pass|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;]+)/gi,
|
|
43
43
|
'$1<redacted>',
|
|
44
44
|
)
|
|
45
45
|
.replace(/(AK|SK)\s*[:=]\s*("[^"]*"|'[^']*'|[^\s,;]+)/g, '$1=<redacted>')
|
|
@@ -65,6 +65,16 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
|
|
|
65
65
|
err.code = code;
|
|
66
66
|
err.status = resp.status;
|
|
67
67
|
err.traceId = data.traceId;
|
|
68
|
+
if (code === 'HDKIT_CRED_INVALID') {
|
|
69
|
+
err.remediation = {
|
|
70
|
+
hint: '已保存的凭证(S1)可能已失效。请执行以下操作之一:',
|
|
71
|
+
steps: [
|
|
72
|
+
'运行 huaweicloud_auth_status 查看当前凭证状态(S1 指纹 vs 环境注入指纹)',
|
|
73
|
+
'若环境注入了有效凭证:运行 huaweicloud_auth_switch action=clear 清除 runtime 凭证,或删除 ~/.config/huaweicloud/credentials.json 让平台凭证接管',
|
|
74
|
+
'若需更新 S1:运行 npx huaweicloud-devkit auth init 重新配置有效 AK/SK',
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
68
78
|
throw err;
|
|
69
79
|
}
|
|
70
80
|
|
|
@@ -119,6 +129,7 @@ export async function hdkitVoucherStatus(domainId) {
|
|
|
119
129
|
claimed: false,
|
|
120
130
|
message: error?.message || 'Incentive service unavailable, please try again later',
|
|
121
131
|
code: error?.code,
|
|
132
|
+
...(error?.remediation ? { remediation: error.remediation } : {}),
|
|
122
133
|
};
|
|
123
134
|
}
|
|
124
135
|
}
|
|
@@ -132,6 +143,7 @@ export async function hdkitVoucherClaim(domainId) {
|
|
|
132
143
|
claimed: false,
|
|
133
144
|
message: error?.message || 'Incentive service unavailable, please try again later',
|
|
134
145
|
code: error?.code,
|
|
146
|
+
...(error?.remediation ? { remediation: error.remediation } : {}),
|
|
135
147
|
};
|
|
136
148
|
}
|
|
137
149
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
chmodSync,
|
|
2
3
|
copyFileSync,
|
|
3
4
|
existsSync,
|
|
4
5
|
mkdirSync,
|
|
@@ -36,7 +37,13 @@ import {
|
|
|
36
37
|
getProxySettings,
|
|
37
38
|
} from './proxy/proxy-config.mjs';
|
|
38
39
|
import { removeKooCli, removeObsConfig } from './sandbox/uninstall-cleanup.mjs';
|
|
39
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
mergeCommandStyle,
|
|
42
|
+
mergeArgsStyle,
|
|
43
|
+
extractUserDelta,
|
|
44
|
+
applyUserDelta,
|
|
45
|
+
inheritPeerUserEnv,
|
|
46
|
+
} from './mcp-config-merge.mjs';
|
|
40
47
|
import { readAgentDelta, saveAgentDelta, takeAgentDelta, purgeBackup } from './mcp-config-backup.mjs';
|
|
41
48
|
import { isUsableOfficeaceRoot, readOfficeaceRootMarker, writeOfficeaceRootMarker } from './officeace-paths.mjs';
|
|
42
49
|
import { queryDistTagsFetch, determineTarget, semverCompare } from './update-check.mjs';
|
|
@@ -123,7 +130,12 @@ function codeartsWorkSkillsDir() {
|
|
|
123
130
|
return join(homedir(), '.codeartswork', 'skills');
|
|
124
131
|
}
|
|
125
132
|
function codeartsWorkMcpSettingsFile() {
|
|
126
|
-
|
|
133
|
+
// Post platform migration the marketplace presets the plugin under ~/.codearts
|
|
134
|
+
// (new layout); legacy ~/.codeartswork is kept as fallback while still in
|
|
135
|
+
// service. Default to the new dir when it exists, otherwise legacy.
|
|
136
|
+
const newDir = join(homedir(), '.codearts', 'mcp');
|
|
137
|
+
const legacy = join(homedir(), '.codeartswork', 'mcp', 'mcp_settings.json');
|
|
138
|
+
return existsSync(newDir) ? join(newDir, 'mcp_settings.json') : legacy;
|
|
127
139
|
}
|
|
128
140
|
function codeartsWorkPluginsDir() {
|
|
129
141
|
return join(homedir(), '.codeartswork', 'huaweicloud-plugins');
|
|
@@ -524,6 +536,22 @@ function copyFileVerified(src, dest) {
|
|
|
524
536
|
);
|
|
525
537
|
}
|
|
526
538
|
|
|
539
|
+
// R5: write an MCP settings JSON file with least-privilege permissions
|
|
540
|
+
// (parent dir 0700, file 0600) so the platform-injected temporary credentials
|
|
541
|
+
// (HW_ACCESS_KEY/HW_SECRET_KEY/HW_SECURITY_TOKEN) are not world-readable.
|
|
542
|
+
function writeMcpSettingsFile(configPath, config) {
|
|
543
|
+
mkdirSync(dirname(configPath), { recursive: true, mode: 0o700 });
|
|
544
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
545
|
+
try {
|
|
546
|
+
chmodSync(configPath, 0o600);
|
|
547
|
+
// Re-assert the parent dir mode too — later un-mode'd mkdirSync calls can
|
|
548
|
+
// produce a broader default (e.g. 0775) on existing trees.
|
|
549
|
+
chmodSync(dirname(configPath), 0o700);
|
|
550
|
+
} catch {
|
|
551
|
+
// best-effort; unsupported on some platforms
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
527
555
|
function copyDir(src, dest) {
|
|
528
556
|
if (!existsSync(src)) return;
|
|
529
557
|
mkdirSync(dest, { recursive: true });
|
|
@@ -625,7 +653,7 @@ function updateOpenCodeConfig(pluginDir) {
|
|
|
625
653
|
}
|
|
626
654
|
if (existing && changed) {
|
|
627
655
|
config.mcp['huaweicloud-devkit'] = entry;
|
|
628
|
-
|
|
656
|
+
writeMcpSettingsFile(configPath, config);
|
|
629
657
|
console.log(` OpenCode MCP config merged (user fields preserved): ${configPath}`);
|
|
630
658
|
return;
|
|
631
659
|
}
|
|
@@ -635,7 +663,7 @@ function updateOpenCodeConfig(pluginDir) {
|
|
|
635
663
|
// Restore user fields saved by a previous uninstall (issue #615).
|
|
636
664
|
const delta = takeAgentDelta('opencode');
|
|
637
665
|
if (delta) config.mcp['huaweicloud-devkit'] = applyUserDelta(config.mcp['huaweicloud-devkit'], delta, 'command');
|
|
638
|
-
|
|
666
|
+
writeMcpSettingsFile(configPath, config);
|
|
639
667
|
console.log(` OpenCode config updated: ${configPath}`);
|
|
640
668
|
}
|
|
641
669
|
|
|
@@ -666,7 +694,7 @@ function writeMcpServersFile(pluginDest, mcpPath, agentKey) {
|
|
|
666
694
|
if (delta)
|
|
667
695
|
next.mcpServers['huaweicloud-devkit'] = applyUserDelta(next.mcpServers['huaweicloud-devkit'], delta, 'args');
|
|
668
696
|
}
|
|
669
|
-
|
|
697
|
+
writeMcpSettingsFile(configPath, next);
|
|
670
698
|
console.log(` MCP Config -> ${configPath}`);
|
|
671
699
|
}
|
|
672
700
|
|
|
@@ -695,7 +723,7 @@ function removeOpenCodeConfig() {
|
|
|
695
723
|
if (delta) saveAgentDelta('opencode', delta);
|
|
696
724
|
delete config.mcp['huaweicloud-devkit'];
|
|
697
725
|
if (Object.keys(config.mcp).length === 0) delete config.mcp;
|
|
698
|
-
|
|
726
|
+
writeMcpSettingsFile(configPath, config);
|
|
699
727
|
console.log(` OpenCode MCP config cleaned: ${configPath}`);
|
|
700
728
|
}
|
|
701
729
|
|
|
@@ -1267,7 +1295,7 @@ function registerCodeartsMcp(configPath, agentKey = 'codearts') {
|
|
|
1267
1295
|
}
|
|
1268
1296
|
config.mcpServers['huaweicloud-devkit'] = entry;
|
|
1269
1297
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
1270
|
-
|
|
1298
|
+
writeMcpSettingsFile(configPath, config);
|
|
1271
1299
|
console.log(` MCP config merged (user fields preserved): ${configPath}`);
|
|
1272
1300
|
return;
|
|
1273
1301
|
}
|
|
@@ -1280,7 +1308,7 @@ function registerCodeartsMcp(configPath, agentKey = 'codearts') {
|
|
|
1280
1308
|
if (delta) entry = applyUserDelta(entry, delta, 'args');
|
|
1281
1309
|
config.mcpServers['huaweicloud-devkit'] = entry;
|
|
1282
1310
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
1283
|
-
|
|
1311
|
+
writeMcpSettingsFile(configPath, config);
|
|
1284
1312
|
console.log(` MCP config updated: ${configPath}`);
|
|
1285
1313
|
}
|
|
1286
1314
|
|
|
@@ -1375,7 +1403,7 @@ function uninstallCodeArts() {
|
|
|
1375
1403
|
if (delta) saveAgentDelta('codearts', delta);
|
|
1376
1404
|
delete config.mcpServers['huaweicloud-devkit'];
|
|
1377
1405
|
if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
|
|
1378
|
-
|
|
1406
|
+
writeMcpSettingsFile(configPath, config);
|
|
1379
1407
|
console.log(` Config cleaned: ${configPath}`);
|
|
1380
1408
|
}
|
|
1381
1409
|
}
|
|
@@ -1446,7 +1474,7 @@ function registerCodeartsWorkMcp() {
|
|
|
1446
1474
|
}
|
|
1447
1475
|
config.mcp['huaweicloud-devkit'] = merged;
|
|
1448
1476
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
1449
|
-
|
|
1477
|
+
writeMcpSettingsFile(configPath, config);
|
|
1450
1478
|
console.log(` MCP config merged (user fields preserved): ${configPath}`);
|
|
1451
1479
|
return;
|
|
1452
1480
|
}
|
|
@@ -1454,12 +1482,16 @@ function registerCodeartsWorkMcp() {
|
|
|
1454
1482
|
config.mcp = config.mcp || {};
|
|
1455
1483
|
let entry = mergeCommandStyle(undefined, { mcpPath }).entry;
|
|
1456
1484
|
entry.environment = { ...environment };
|
|
1485
|
+
// Inherit user-owned env (e.g. market-preset `huaweicloud-devkit_1` carrying the
|
|
1486
|
+
// user's temporary STS credentials) so a freshly installed key also has them.
|
|
1487
|
+
const peerEnv = inheritPeerUserEnv(config.mcp);
|
|
1488
|
+
if (peerEnv) entry.environment = { ...peerEnv, ...entry.environment };
|
|
1457
1489
|
// Restore user fields saved by a previous uninstall (issue #615).
|
|
1458
1490
|
const delta = takeAgentDelta('codearts-work');
|
|
1459
1491
|
if (delta) entry = applyUserDelta(entry, delta, 'command');
|
|
1460
1492
|
config.mcp['huaweicloud-devkit'] = entry;
|
|
1461
1493
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
1462
|
-
|
|
1494
|
+
writeMcpSettingsFile(configPath, config);
|
|
1463
1495
|
console.log(` MCP config updated: ${configPath}`);
|
|
1464
1496
|
}
|
|
1465
1497
|
|
|
@@ -1530,7 +1562,7 @@ function uninstallCodeArtsWork() {
|
|
|
1530
1562
|
if (delta) saveAgentDelta('codearts-work', delta);
|
|
1531
1563
|
delete config.mcp['huaweicloud-devkit'];
|
|
1532
1564
|
if (Object.keys(config.mcp).length === 0) delete config.mcp;
|
|
1533
|
-
|
|
1565
|
+
writeMcpSettingsFile(configPath, config);
|
|
1534
1566
|
console.log(` Config cleaned: ${configPath}`);
|
|
1535
1567
|
}
|
|
1536
1568
|
}
|
|
@@ -1591,7 +1623,7 @@ function ensureWorkbuddyMcpConfig() {
|
|
|
1591
1623
|
}
|
|
1592
1624
|
config.mcpServers['huaweicloud-devkit'] = entry;
|
|
1593
1625
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
1594
|
-
|
|
1626
|
+
writeMcpSettingsFile(configPath, config);
|
|
1595
1627
|
console.log(` MCP config merged (user fields preserved): ${configPath}`);
|
|
1596
1628
|
return true;
|
|
1597
1629
|
}
|
|
@@ -1603,7 +1635,7 @@ function ensureWorkbuddyMcpConfig() {
|
|
|
1603
1635
|
if (delta) entry = applyUserDelta(entry, delta, 'args');
|
|
1604
1636
|
config.mcpServers['huaweicloud-devkit'] = entry;
|
|
1605
1637
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
1606
|
-
|
|
1638
|
+
writeMcpSettingsFile(configPath, config);
|
|
1607
1639
|
console.log(` MCP config updated: ${configPath}`);
|
|
1608
1640
|
return true;
|
|
1609
1641
|
}
|
|
@@ -1817,7 +1849,7 @@ function uninstallWorkBuddy() {
|
|
|
1817
1849
|
if (delta) saveAgentDelta('workbuddy', delta);
|
|
1818
1850
|
delete config.mcpServers['huaweicloud-devkit'];
|
|
1819
1851
|
if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
|
|
1820
|
-
|
|
1852
|
+
writeMcpSettingsFile(configPath, config);
|
|
1821
1853
|
console.log(` MCP config cleaned: ${configPath}`);
|
|
1822
1854
|
}
|
|
1823
1855
|
}
|
|
@@ -1923,7 +1955,7 @@ function ensureAtomcodeMcpConfig() {
|
|
|
1923
1955
|
}
|
|
1924
1956
|
config.mcpServers['huaweicloud-devkit'] = entry;
|
|
1925
1957
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
1926
|
-
|
|
1958
|
+
writeMcpSettingsFile(configPath, config);
|
|
1927
1959
|
console.log(` MCP config merged (user fields preserved): ${configPath}`);
|
|
1928
1960
|
return true;
|
|
1929
1961
|
}
|
|
@@ -1935,7 +1967,7 @@ function ensureAtomcodeMcpConfig() {
|
|
|
1935
1967
|
if (delta) entry = applyUserDelta(entry, delta, 'args');
|
|
1936
1968
|
config.mcpServers['huaweicloud-devkit'] = entry;
|
|
1937
1969
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
1938
|
-
|
|
1970
|
+
writeMcpSettingsFile(configPath, config);
|
|
1939
1971
|
console.log(` MCP config updated: ${configPath}`);
|
|
1940
1972
|
return true;
|
|
1941
1973
|
}
|
|
@@ -2009,7 +2041,7 @@ function uninstallAtomCode() {
|
|
|
2009
2041
|
if (delta) saveAgentDelta('atomcode', delta);
|
|
2010
2042
|
delete config.mcpServers['huaweicloud-devkit'];
|
|
2011
2043
|
if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
|
|
2012
|
-
|
|
2044
|
+
writeMcpSettingsFile(configPath, config);
|
|
2013
2045
|
console.log(` MCP config cleaned: ${configPath}`);
|
|
2014
2046
|
}
|
|
2015
2047
|
}
|