huaweicloud-devkit 1.1.10-next.3 → 1.1.10-next.4

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 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.3",
4
+ "version": "1.1.10-next.4",
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.3",
20
+ "version": "1.1.10-next.4",
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.3",
23
+ "version": "1.1.10-next.4",
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.3",
20
+ "version": "1.1.10-next.4",
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",
3
+ "version": "1.1.10-next.4",
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",
3
+ "version": "1.1.10-next.4",
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.3",
5
+ "version": "1.1.10-next.4",
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
- return { ok: false, error: identity.error };
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 JSON.parse(readFileSync(path, 'utf8'));
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 = JSON.parse(readFileSync(path, 'utf8'));
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 = JSON.parse(readFileSync(path, 'utf8'));
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 = JSON.parse(readFileSync(path, 'utf8'));
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 = JSON.parse(readFileSync(path, 'utf8'));
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 JSON.parse(readFileSync(path, 'utf8'));
492
+ return readJsonFile(path);
486
493
  } catch {
487
494
  return null;
488
495
  }
@@ -189,6 +189,17 @@ export function getAuthStatus(target = 'all') {
189
189
  };
190
190
  return {
191
191
  target,
192
+ // usableCredentials: any credential source that makes the MCP/backend path
193
+ // actually work right now (S1, CodeArts settings, platform STS triplet,
194
+ // X-Auth-Token, or runtime creds) — supersedes the misleading
195
+ // credentialsConfigured/onboarding split (#P4).
196
+ usableCredentials: Boolean(
197
+ (credentials?.ak && credentials?.sk) ||
198
+ (s4Creds?.ak && s4Creds?.sk) ||
199
+ envHasRealTriplet() ||
200
+ tokenAuth ||
201
+ hasRuntimeCredentials(),
202
+ ),
192
203
  credentialsConfigured: Boolean(credentials?.ak && credentials?.sk) || Boolean(tokenAuth),
193
204
  credentialsPath: globalCredentialsPath(),
194
205
  mcpSettingsConfigured: Boolean(s4Creds?.ak && s4Creds?.sk),
@@ -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 { ak, sk, securitytoken } = getCredentials();
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
- throw new Error(
15
- 'Huawei Cloud credentials are not configured. ' +
16
- 'Run "npx huaweicloud-devkit auth init" or set HW_ACCESS_KEY/HW_SECRET_KEY.',
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',
@@ -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 = JSON.parse(readFileSync(codeartsMcpSettingsFile(), 'utf8'));
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 = JSON.parse(readFileSync(codeartsWorkMcpSettingsFile(), 'utf8'));
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
- check('hcloud credentials configured', hasAuth, 'Run: npx huaweicloud-devkit auth init');
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