huaweicloud-devkit 1.1.10-next.3 → 1.1.10-next.5
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/account-type.mjs +10 -1
- package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +1 -1
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +13 -6
- package/plugins/huaweicloud-core/src/auth/service.mjs +13 -1
- package/plugins/huaweicloud-core/src/koocli-install.mjs +57 -0
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +28 -6
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +30 -1
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +4 -4
- package/plugins/huaweicloud-core/src/setup-cli.mjs +29 -5
- package/plugins/huaweicloud-core/src/tools.mjs +34 -2
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.10-next.
|
|
4
|
+
"version": "1.1.10-next.5",
|
|
5
5
|
"kooCliVersion": "7.3.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.10-next.
|
|
20
|
+
"version": "1.1.10-next.5",
|
|
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.10-next.
|
|
23
|
+
"version": "1.1.10-next.5",
|
|
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.10-next.
|
|
20
|
+
"version": "1.1.10-next.5",
|
|
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.10-next.
|
|
3
|
+
"version": "1.1.10-next.5",
|
|
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.10-next.
|
|
3
|
+
"version": "1.1.10-next.5",
|
|
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.10-next.
|
|
5
|
+
"version": "1.1.10-next.5",
|
|
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.",
|
|
@@ -112,7 +112,16 @@ export async function checkAccountType({ region } = {}) {
|
|
|
112
112
|
}
|
|
113
113
|
const identity = parseCallerIdentity(identityResult.stdout);
|
|
114
114
|
if (!identity.ok) {
|
|
115
|
-
|
|
115
|
+
// hcloud ran but stdout was not the expected JSON — usually a credentials
|
|
116
|
+
// problem surfacing as free-text error output, not a parser bug.
|
|
117
|
+
const snippet = String(identityResult.stdout || '')
|
|
118
|
+
.slice(0, 200)
|
|
119
|
+
.replace(/\s+/g, ' ');
|
|
120
|
+
return {
|
|
121
|
+
ok: false,
|
|
122
|
+
error: identity.error + (snippet ? ` (raw: ${snippet})` : ''),
|
|
123
|
+
hint: 'GetCallerIdentity 未返回 JSON,通常是凭据未就绪:请确认已运行 auth init、或平台已注入可用 STS(先用 huaweicloud_auth_status 查看 usableCredentials/credentialPanel)。',
|
|
124
|
+
};
|
|
116
125
|
}
|
|
117
126
|
|
|
118
127
|
const domainsResult = await runHcloud(['IAM', 'KeystoneListAuthDomains'], {
|
|
@@ -31,7 +31,7 @@ function opencodeConfigFile() {
|
|
|
31
31
|
function readJsonSafe(path) {
|
|
32
32
|
if (!existsSync(path)) return null;
|
|
33
33
|
try {
|
|
34
|
-
return JSON.parse(readFileSync(path, 'utf8'));
|
|
34
|
+
return JSON.parse(readFileSync(path, 'utf8').replace(/^\uFEFF/, ''));
|
|
35
35
|
} catch {
|
|
36
36
|
return null;
|
|
37
37
|
}
|
|
@@ -50,11 +50,18 @@ export function obsConfigPath() {
|
|
|
50
50
|
return process.env.HCLOUD_OBS_CONFIG_PATH || join(homedir(), '.obsutilconfig');
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// Read a JSON file, stripping a leading UTF-8 BOM (U+FEFF) if present. CodeArts
|
|
54
|
+
// writes mcp_settings.json with a BOM on Windows; plain JSON.parse would throw.
|
|
55
|
+
export function readJsonFile(path) {
|
|
56
|
+
const text = readFileSync(path, 'utf8');
|
|
57
|
+
return JSON.parse(text.replace(/^\uFEFF/, ''));
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
export function readGlobalCredentials() {
|
|
54
61
|
const path = globalCredentialsPath();
|
|
55
62
|
if (!existsSync(path)) return null;
|
|
56
63
|
try {
|
|
57
|
-
return
|
|
64
|
+
return readJsonFile(path);
|
|
58
65
|
} catch {
|
|
59
66
|
return null;
|
|
60
67
|
}
|
|
@@ -325,7 +332,7 @@ export function readCodeArtsCredentials() {
|
|
|
325
332
|
const path = join(dir, '.codeartsdoer', 'mcp', 'mcp_settings.json');
|
|
326
333
|
try {
|
|
327
334
|
if (!existsSync(path)) continue;
|
|
328
|
-
const config =
|
|
335
|
+
const config = readJsonFile(path);
|
|
329
336
|
const server = pickDevkitMcpServer(config?.mcpServers);
|
|
330
337
|
if (!server?.env) continue;
|
|
331
338
|
|
|
@@ -355,7 +362,7 @@ export function readCodeArtsCredentials() {
|
|
|
355
362
|
for (const path of workPaths) {
|
|
356
363
|
try {
|
|
357
364
|
if (!existsSync(path)) continue;
|
|
358
|
-
const config =
|
|
365
|
+
const config = readJsonFile(path);
|
|
359
366
|
const entry = readWorkEnvironmentEntry(config);
|
|
360
367
|
if (entry) return entry;
|
|
361
368
|
} catch {
|
|
@@ -404,7 +411,7 @@ export function resolveXAuthToken() {
|
|
|
404
411
|
const path = join(dir, '.codeartsdoer', 'mcp', 'mcp_settings.json');
|
|
405
412
|
try {
|
|
406
413
|
if (!existsSync(path)) continue;
|
|
407
|
-
const config =
|
|
414
|
+
const config = readJsonFile(path);
|
|
408
415
|
const server = pickDevkitMcpServer(config?.mcpServers);
|
|
409
416
|
const token = server?.env?.HW_X_AUTH_TOKEN;
|
|
410
417
|
if (present(token)) return token;
|
|
@@ -418,7 +425,7 @@ export function resolveXAuthToken() {
|
|
|
418
425
|
for (const path of workPaths) {
|
|
419
426
|
try {
|
|
420
427
|
if (!existsSync(path)) continue;
|
|
421
|
-
const config =
|
|
428
|
+
const config = readJsonFile(path);
|
|
422
429
|
const server = pickDevkitMcpServer(config?.mcp);
|
|
423
430
|
const token = server?.environment?.HW_X_AUTH_TOKEN;
|
|
424
431
|
if (present(token)) return token;
|
|
@@ -482,7 +489,7 @@ export function readLastSync() {
|
|
|
482
489
|
const path = lastSyncPath();
|
|
483
490
|
if (!existsSync(path)) return null;
|
|
484
491
|
try {
|
|
485
|
-
return
|
|
492
|
+
return readJsonFile(path);
|
|
486
493
|
} catch {
|
|
487
494
|
return null;
|
|
488
495
|
}
|
|
@@ -85,7 +85,8 @@ export function computeOnboarding({ credentials, reconciled } = {}) {
|
|
|
85
85
|
// devkit exchanges it for temporary AK/SK on demand, so no manual setup.
|
|
86
86
|
scenario = 0;
|
|
87
87
|
reason = 'token-auth';
|
|
88
|
-
message =
|
|
88
|
+
message =
|
|
89
|
+
'已使用 X-Auth-Token 认证。管理面与数据面(沙箱部署/exec)都会在需要时用 token 自动换取临时 AK/SK;请确保可访问 IAM 端点。';
|
|
89
90
|
return { needsSetup: false, scenario, reason, message, steps: [], accountHint };
|
|
90
91
|
}
|
|
91
92
|
if (s1Has && !envHasCreds) {
|
|
@@ -189,6 +190,17 @@ export function getAuthStatus(target = 'all') {
|
|
|
189
190
|
};
|
|
190
191
|
return {
|
|
191
192
|
target,
|
|
193
|
+
// usableCredentials: any credential source that makes the MCP/backend path
|
|
194
|
+
// actually work right now (S1, CodeArts settings, platform STS triplet,
|
|
195
|
+
// X-Auth-Token, or runtime creds) — supersedes the misleading
|
|
196
|
+
// credentialsConfigured/onboarding split (#P4).
|
|
197
|
+
usableCredentials: Boolean(
|
|
198
|
+
(credentials?.ak && credentials?.sk) ||
|
|
199
|
+
(s4Creds?.ak && s4Creds?.sk) ||
|
|
200
|
+
envHasRealTriplet() ||
|
|
201
|
+
tokenAuth ||
|
|
202
|
+
hasRuntimeCredentials(),
|
|
203
|
+
),
|
|
192
204
|
credentialsConfigured: Boolean(credentials?.ak && credentials?.sk) || Boolean(tokenAuth),
|
|
193
205
|
credentialsPath: globalCredentialsPath(),
|
|
194
206
|
mcpSettingsConfigured: Boolean(s4Creds?.ak && s4Creds?.sk),
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { getKooCliVersion, kooCliDownloadBase } from './koocli-version.mjs';
|
|
7
|
+
|
|
8
|
+
// Programmatic KooCLI (hcloud) install — downloadable from inside the MCP server
|
|
9
|
+
// process via Node (fetch + tar via child_process), so it does NOT depend on the
|
|
10
|
+
// agent's bash tool (which is unavailable in CodeArts with OPENCODE_CONFIG
|
|
11
|
+
// unset — experience-report #3.5). Returns a structured result for the
|
|
12
|
+
// `huaweicloud_install_hcloud` MCP tool.
|
|
13
|
+
export async function installKooCli(options = {}) {
|
|
14
|
+
const os = process.platform;
|
|
15
|
+
const arch = process.arch;
|
|
16
|
+
|
|
17
|
+
// Windows auto-install needs PowerShell specifics — keep it on the CLI path.
|
|
18
|
+
if (os === 'win32') {
|
|
19
|
+
return { ok: false, error: 'Windows 环境请使用 CLI: npx huaweicloud-devkit install-hcloud' };
|
|
20
|
+
}
|
|
21
|
+
if (os !== 'linux' && os !== 'darwin') {
|
|
22
|
+
return { ok: false, error: `不支持的操作系统: ${os}` };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const installDir = options.binDir || (os === 'linux' ? join(homedir(), '.local', 'bin') : '/usr/local/bin');
|
|
26
|
+
const pkg =
|
|
27
|
+
os === 'linux' ? (arch === 'arm64' ? 'linux-arm64' : 'linux-amd64') : arch === 'arm64' ? 'mac-arm64' : 'mac-amd64';
|
|
28
|
+
const url = `${kooCliDownloadBase()}/huaweicloud-cli-${pkg}.tar.gz`;
|
|
29
|
+
const binPath = join(installDir, 'hcloud');
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
mkdirSync(installDir, { recursive: true });
|
|
33
|
+
|
|
34
|
+
const tmpTar = join(installDir, `huaweicloud-cli-${pkg}.tar.gz`);
|
|
35
|
+
const resp = await fetch(url);
|
|
36
|
+
if (!resp.ok) {
|
|
37
|
+
return { ok: false, error: `下载失败 (HTTP ${resp.status}): ${url}` };
|
|
38
|
+
}
|
|
39
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
40
|
+
writeFileSync(tmpTar, buf);
|
|
41
|
+
|
|
42
|
+
const ex = spawnSync('tar', ['-xzf', tmpTar, '-C', installDir], { stdio: 'pipe', timeout: 60000 });
|
|
43
|
+
rmSync(tmpTar, { force: true });
|
|
44
|
+
if (ex.status !== 0) {
|
|
45
|
+
return { ok: false, error: `解压失败 (tar exit ${ex.status}): ${(ex.stderr || '').toString().slice(0, 200)}` };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!existsSync(binPath)) {
|
|
49
|
+
return { ok: false, error: `hcloud 二进制未生成于 ${binPath}` };
|
|
50
|
+
}
|
|
51
|
+
chmodSync(binPath, 0o755);
|
|
52
|
+
|
|
53
|
+
return { ok: true, binPath, version: getKooCliVersion() };
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return { ok: false, error: error?.message || String(error) };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,20 +1,42 @@
|
|
|
1
|
-
import { getCredentials } from './hwlink-api.mjs';
|
|
2
1
|
import { getProxyDispatcher } from '../proxy/proxy-agent.mjs';
|
|
3
2
|
import { cacheUserHash } from '../telemetry/telemetry.mjs';
|
|
4
3
|
import { readInstalledVersion } from '../update-check.mjs';
|
|
4
|
+
import { resolveCredentialsWithRuntime, resolveXAuthToken } from '../auth/credentials.mjs';
|
|
5
|
+
import { getTokenCredentials } from '../auth/token-exchange.mjs';
|
|
6
|
+
import { makeError } from '../auth/guidance.mjs';
|
|
5
7
|
|
|
6
8
|
function getHdkitBaseUrl() {
|
|
7
9
|
return process.env.HDKITSERVICE_ENDPOINT || 'https://devkit.huaweicloud.com/rest/developer/server/hdkitservice/';
|
|
8
10
|
}
|
|
9
11
|
|
|
12
|
+
// Backend (hdkitservice/voucher) credentials: prefer AK/SK, else fall back to
|
|
13
|
+
// X-Auth-Token → temporary AK/SK exchange, so token-auth users are not blocked.
|
|
14
|
+
async function resolveBackendCredentials() {
|
|
15
|
+
try {
|
|
16
|
+
const creds = resolveCredentialsWithRuntime({ allowMissing: true });
|
|
17
|
+
if (creds && creds.ak && creds.sk) return creds;
|
|
18
|
+
} catch {
|
|
19
|
+
// no AK/SK — fall through to token
|
|
20
|
+
}
|
|
21
|
+
const token = resolveXAuthToken();
|
|
22
|
+
if (!token) return null;
|
|
23
|
+
try {
|
|
24
|
+
return await getTokenCredentials(token);
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
10
30
|
async function hdkitRequest(method, path, body, timeoutMs = 300000) {
|
|
11
|
-
const
|
|
31
|
+
const creds = await resolveBackendCredentials();
|
|
32
|
+
const ak = creds?.ak;
|
|
33
|
+
const sk = creds?.sk;
|
|
34
|
+
const securitytoken = creds?.securityToken || '';
|
|
12
35
|
|
|
13
36
|
if (!ak || !sk) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
);
|
|
37
|
+
const err = makeError('HDKIT_CRED_MISSING');
|
|
38
|
+
err.detail = 'Run "npx huaweicloud-devkit auth init" or set HW_ACCESS_KEY/HW_SECRET_KEY/HW_X_AUTH_TOKEN.';
|
|
39
|
+
throw err;
|
|
18
40
|
}
|
|
19
41
|
const headers = {
|
|
20
42
|
'Content-Type': 'application/json',
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
|
|
3
|
-
import { resolveCredentialsWithRuntime } from '../auth/credentials.mjs';
|
|
3
|
+
import { resolveCredentialsWithRuntime, resolveXAuthToken } from '../auth/credentials.mjs';
|
|
4
|
+
import { getTokenCredentials } from '../auth/token-exchange.mjs';
|
|
5
|
+
import { makeError } from '../auth/guidance.mjs';
|
|
4
6
|
import { getProxyDispatcher } from '../proxy/proxy-agent.mjs';
|
|
5
7
|
|
|
6
8
|
const BASE_URL = process.env.HWLINK_ENDPOINT || 'https://devstation.myhuaweicloud.com';
|
|
@@ -82,6 +84,33 @@ export function getCredentials() {
|
|
|
82
84
|
return { ak: credentials.ak, sk: credentials.sk, securitytoken: credentials.securityToken };
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
// Data-plane (sandbox exec/upload/tunnel) credentials: prefer AK/SK, else fall
|
|
88
|
+
// back to X-Auth-Token → temporary AK/SK, so token-auth users can drive the data
|
|
89
|
+
// plane without a permanent AK/SK (experience-report #3.3/#3.7 dead-loop fix).
|
|
90
|
+
export async function resolveBackendCredentials() {
|
|
91
|
+
try {
|
|
92
|
+
const creds = resolveCredentialsWithRuntime({ allowMissing: true });
|
|
93
|
+
if (creds && creds.ak && creds.sk) return creds;
|
|
94
|
+
} catch {
|
|
95
|
+
// no AK/SK — fall through to token
|
|
96
|
+
}
|
|
97
|
+
const token = resolveXAuthToken();
|
|
98
|
+
if (token) {
|
|
99
|
+
try {
|
|
100
|
+
return await getTokenCredentials(token);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
// Distinguish "token exchange failed" from "no credential at all" (#3.4).
|
|
103
|
+
const err = makeError('HDKIT_CRED_MISSING');
|
|
104
|
+
err.code = error?.code || 'TOKEN_EXCHANGE_FAILED';
|
|
105
|
+
err.messageZh =
|
|
106
|
+
`X-Auth-Token 换取临时 AK/SK 失败(${err.code}):` +
|
|
107
|
+
`${error?.message || '网络异常或 token 无效'},请检查网络连通性与 token 有效性。`;
|
|
108
|
+
throw err;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
throw makeError('HDKIT_CRED_MISSING');
|
|
112
|
+
}
|
|
113
|
+
|
|
85
114
|
async function apiGet(path, query, ak, sk, securitytoken) {
|
|
86
115
|
query = query || {};
|
|
87
116
|
const qs = sortedQs(query);
|
|
@@ -16,7 +16,7 @@ import { join, dirname, basename } from 'node:path';
|
|
|
16
16
|
import { tmpdir } from 'node:os';
|
|
17
17
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
18
18
|
|
|
19
|
-
import { createConnection,
|
|
19
|
+
import { createConnection, resolveBackendCredentials } from './hwlink-api.mjs';
|
|
20
20
|
import { getWebSocketImpl } from '../proxy/proxy-agent.mjs';
|
|
21
21
|
import { trackSandboxConnect, trackSandboxDisconnect } from '../telemetry/telemetry.mjs';
|
|
22
22
|
|
|
@@ -107,7 +107,7 @@ async function getSession(workspaceId, username, timeoutMs) {
|
|
|
107
107
|
const key = `${workspaceId}:${username}`;
|
|
108
108
|
if (sessions.has(key)) return sessions.get(key);
|
|
109
109
|
|
|
110
|
-
const { ak, sk, securitytoken } =
|
|
110
|
+
const { ak, sk, securitytoken } = await resolveBackendCredentials();
|
|
111
111
|
const { wsUrl, source } = await createConnection(workspaceId, ak, sk, securitytoken);
|
|
112
112
|
|
|
113
113
|
const WebSocketImpl = await getWebSocketImpl(wsUrl);
|
|
@@ -126,7 +126,7 @@ async function getSession(workspaceId, username, timeoutMs) {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
async function createTunnelSession(workspaceId, username, timeoutMs = 30000) {
|
|
129
|
-
const { ak, sk, securitytoken } =
|
|
129
|
+
const { ak, sk, securitytoken } = await resolveBackendCredentials();
|
|
130
130
|
const { wsUrl, source } = await createConnection(workspaceId, ak, sk, securitytoken);
|
|
131
131
|
const WebSocketImpl = await getWebSocketImpl(wsUrl);
|
|
132
132
|
|
|
@@ -165,7 +165,7 @@ async function createTunnelSession(workspaceId, username, timeoutMs = 30000) {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
export async function execOneShot(workspaceId, command, username, timeoutMs) {
|
|
168
|
-
const { ak, sk, securitytoken } =
|
|
168
|
+
const { ak, sk, securitytoken } = await resolveBackendCredentials();
|
|
169
169
|
const { wsUrl, source } = await createConnection(workspaceId, ak, sk, securitytoken);
|
|
170
170
|
|
|
171
171
|
const WebSocketImpl = await getWebSocketImpl(wsUrl);
|
|
@@ -25,6 +25,9 @@ import { redactSecrets } from './safety-policy.mjs';
|
|
|
25
25
|
import {
|
|
26
26
|
globalCredentialsPath,
|
|
27
27
|
readGlobalCredentials,
|
|
28
|
+
readJsonFile,
|
|
29
|
+
resolveCredentialsWithRuntime,
|
|
30
|
+
resolveXAuthToken,
|
|
28
31
|
writeGlobalCredentials,
|
|
29
32
|
writeLastSync,
|
|
30
33
|
writeObsConfig,
|
|
@@ -53,8 +56,6 @@ import { queryDistTagsFetch, determineTarget, semverCompare } from './update-che
|
|
|
53
56
|
import { getKooCliVersion, compareVersion, kooCliDownloadBase, KOO_CLI_BASE } from './koocli-version.mjs';
|
|
54
57
|
import { findHcloudBin, hcloudProbeNextStep, probeHcloud } from './hcloud-probe.mjs';
|
|
55
58
|
|
|
56
|
-
const { DatabaseSync } = createRequire(import.meta.url)('node:sqlite');
|
|
57
|
-
|
|
58
59
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
59
60
|
const PLUGIN_ROOT = resolve(__dirname, '..');
|
|
60
61
|
const PACKAGE_ROOT = resolve(PLUGIN_ROOT, '..', '..');
|
|
@@ -244,6 +245,9 @@ function officeaceSqlitePath() {
|
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
function openOfficeaceDb() {
|
|
248
|
+
// Lazy-require node:sqlite (experimental on Node 22.x) so CLI commands that
|
|
249
|
+
// don't touch OfficeAce (version/doctor/install) run without the flag.
|
|
250
|
+
const { DatabaseSync } = createRequire(import.meta.url)('node:sqlite');
|
|
247
251
|
return new DatabaseSync(officeaceSqlitePath());
|
|
248
252
|
}
|
|
249
253
|
|
|
@@ -1584,7 +1588,7 @@ function codeartsStatus() {
|
|
|
1584
1588
|
);
|
|
1585
1589
|
if (existsSync(codeartsMcpSettingsFile())) {
|
|
1586
1590
|
try {
|
|
1587
|
-
const config =
|
|
1591
|
+
const config = readJsonFile(codeartsMcpSettingsFile());
|
|
1588
1592
|
console.log(
|
|
1589
1593
|
` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
|
|
1590
1594
|
);
|
|
@@ -1745,7 +1749,7 @@ function codeartsWorkStatus() {
|
|
|
1745
1749
|
);
|
|
1746
1750
|
if (existsSync(codeartsWorkMcpSettingsFile())) {
|
|
1747
1751
|
try {
|
|
1748
|
-
const config =
|
|
1752
|
+
const config = readJsonFile(codeartsWorkMcpSettingsFile());
|
|
1749
1753
|
console.log(
|
|
1750
1754
|
` MCP config: ${config.mcp?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
|
|
1751
1755
|
);
|
|
@@ -4310,7 +4314,27 @@ async function cmdDoctor() {
|
|
|
4310
4314
|
timeout: 5000,
|
|
4311
4315
|
});
|
|
4312
4316
|
const hasAuth = authCheck.status === 0 && /access.?key/i.test(authCheck.stdout.toString());
|
|
4313
|
-
|
|
4317
|
+
if (hasAuth) {
|
|
4318
|
+
check('hcloud credentials configured', true, '');
|
|
4319
|
+
} else {
|
|
4320
|
+
// In CodeArts, platform-injected STS / X-Auth-Token reach only the MCP
|
|
4321
|
+
// server process, not this bash tool process — so `hcloud configure list`
|
|
4322
|
+
// shows no profile even though the backend API path is usable. Degrade to
|
|
4323
|
+
// a warning instead of a hard fail in that case (#P3 of CodeArts test).
|
|
4324
|
+
let platformCredsActive = false;
|
|
4325
|
+
try {
|
|
4326
|
+
const c = resolveCredentialsWithRuntime({ allowMissing: true });
|
|
4327
|
+
platformCredsActive = Boolean((c && c.ak && c.sk && c.securityToken) || resolveXAuthToken());
|
|
4328
|
+
} catch {}
|
|
4329
|
+
if (platformCredsActive) {
|
|
4330
|
+
console.log(
|
|
4331
|
+
` \x1b[33m[WARN]\x1b[0m hcloud credentials configured — KooCLI 无 profile,但检测到平台凭证(MCP 通道可用);仅 KooCLI 直接调用需 auth init`,
|
|
4332
|
+
);
|
|
4333
|
+
warn++;
|
|
4334
|
+
} else {
|
|
4335
|
+
check('hcloud credentials configured', false, 'Run: npx huaweicloud-devkit auth init');
|
|
4336
|
+
}
|
|
4337
|
+
}
|
|
4314
4338
|
}
|
|
4315
4339
|
|
|
4316
4340
|
// Skills
|
|
@@ -13,6 +13,7 @@ import { checkAccountType } from './account-type.mjs';
|
|
|
13
13
|
import { searchMarketplace } from './search-market.mjs';
|
|
14
14
|
import { getServiceIcon } from './icon-library.mjs';
|
|
15
15
|
import { detectFramework } from './detect-framework.mjs';
|
|
16
|
+
import { installKooCli } from './koocli-install.mjs';
|
|
16
17
|
import {
|
|
17
18
|
execWithSession,
|
|
18
19
|
execOneShot,
|
|
@@ -35,7 +36,7 @@ import {
|
|
|
35
36
|
hdkitCompetitionVoucherClaim,
|
|
36
37
|
hdkitGenerateUserHash,
|
|
37
38
|
} from './sandbox/hdkitservice-api.mjs';
|
|
38
|
-
import {
|
|
39
|
+
import { resolveBackendCredentials } from './sandbox/hwlink-api.mjs';
|
|
39
40
|
import { getAuthStatus, syncAuth } from './auth/service.mjs';
|
|
40
41
|
import { validateIamCredentials } from './auth/credential-validator.mjs';
|
|
41
42
|
import {
|
|
@@ -199,6 +200,15 @@ export const TOOL_DEFINITIONS = [
|
|
|
199
200
|
properties: {},
|
|
200
201
|
},
|
|
201
202
|
},
|
|
203
|
+
{
|
|
204
|
+
name: 'huaweicloud_install_hcloud',
|
|
205
|
+
description:
|
|
206
|
+
'Install KooCLI (hcloud) from inside the MCP server process via Node (no agent bash tool required). Downloads and extracts the pinned hcloud binary to ~/.local/bin/hcloud. Use when huaweicloud_check_cli reports HCLOUD_NOT_FOUND and the agent bash tool is unavailable (e.g. CodeArts with OPENCODE_CONFIG unset).',
|
|
207
|
+
inputSchema: {
|
|
208
|
+
type: 'object',
|
|
209
|
+
properties: {},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
202
212
|
{
|
|
203
213
|
name: 'huaweicloud_plan_cli_command',
|
|
204
214
|
description: 'Classify and plan a Huawei Cloud hcloud command without executing it.',
|
|
@@ -1180,6 +1190,8 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1180
1190
|
switch (name) {
|
|
1181
1191
|
case 'huaweicloud_check_cli':
|
|
1182
1192
|
return runVersionCheck();
|
|
1193
|
+
case 'huaweicloud_install_hcloud':
|
|
1194
|
+
return installKooCli();
|
|
1183
1195
|
case 'huaweicloud_plan_cli_command':
|
|
1184
1196
|
return planHcloudCommand(args.args || [], { allowWrites: args.allowWrites === true });
|
|
1185
1197
|
case 'huaweicloud_run_readonly_command':
|
|
@@ -1572,7 +1584,7 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1572
1584
|
const apiKeyFile = '/tmp/hw_api_key';
|
|
1573
1585
|
if (sandboxWsIdCred) {
|
|
1574
1586
|
try {
|
|
1575
|
-
const { ak, sk, securitytoken } =
|
|
1587
|
+
const { ak, sk, securitytoken } = await resolveBackendCredentials();
|
|
1576
1588
|
const credsScript = [
|
|
1577
1589
|
`export HW_ACCESS_KEY='${ak}'`,
|
|
1578
1590
|
`export HW_SECRET_KEY='${sk}'`,
|
|
@@ -2318,6 +2330,26 @@ function explainError({ service = 'unknown', errorCode = '', message = '', reque
|
|
|
2318
2330
|
}
|
|
2319
2331
|
const patternKey = SERVICE_ALIASES[svc] || resolvedService;
|
|
2320
2332
|
|
|
2333
|
+
// devkit internal credential/environment error codes (experience-report #3.9)
|
|
2334
|
+
// — these come from devkit's own guidance/credential layer, not Huawei Cloud API.
|
|
2335
|
+
const devkitErrorPatterns = {
|
|
2336
|
+
HDKIT_CRED_MISSING:
|
|
2337
|
+
'未配置华为云凭证。运行 npx huaweicloud-devkit auth init 配置 AK/SK,或注入 HW_X_AUTH_TOKEN(devkit 会自动用 token 换取临时 AK/SK)。',
|
|
2338
|
+
HDKIT_CRED_INVALID: '凭证已被华为云拒绝(AK/SK 无效或已过期)。重新生成 AK/SK 后 auth init。',
|
|
2339
|
+
HDKIT_CRED_FORBIDDEN: '凭证有效但无权限列出项目。检查 IAM 授权(可用 huaweicloud_check_account_type 判断账号类型)。',
|
|
2340
|
+
HDKIT_CRED_REGION_MISSING: '缺少 region 配置。运行 hcloud configure set --cli-region=<region>。',
|
|
2341
|
+
HDKIT_KOOCLI_NOT_READY: 'KooCLI(hcloud)未安装或未就绪。运行 npx huaweicloud-devkit install-hcloud。',
|
|
2342
|
+
HDKIT_PROJECT_ID_MISSING: '缺少 project_id。运行 npx huaweicloud-devkit auth init 自动配置。',
|
|
2343
|
+
TOKEN_INVALID: 'X-Auth-Token 无效或已过期。重新注入有效的 HW_X_AUTH_TOKEN。',
|
|
2344
|
+
TOKEN_EXPIRED: 'X-Auth-Token 已过期。重启进程重新注入凭证。',
|
|
2345
|
+
TOKEN_EXCHANGE_FAILED:
|
|
2346
|
+
'X-Auth-Token 换取临时 AK/SK 失败,通常是网络无法访问 IAM 端点。检查网络连通性与 token 有效性。',
|
|
2347
|
+
HDKIT_SUB_ACCOUNT_RESTRICTED: '沙箱使用限制:当前子账号无法使用沙箱。切换主账号认证(auth_init)后重试。',
|
|
2348
|
+
};
|
|
2349
|
+
if (devkitErrorPatterns[errorCode]) {
|
|
2350
|
+
suggestions.push(`${errorCode}: ${devkitErrorPatterns[errorCode]}`);
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2321
2353
|
const hwErrorPatterns = {
|
|
2322
2354
|
OBS: {
|
|
2323
2355
|
InvalidAccessKeyId:
|