huaweicloud-devkit 1.1.8 → 1.1.9-next.1
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/integrations/opencode/package-lock.json +24 -0
- package/integrations/opencode/package.json +10 -0
- package/integrations/opencode/plugins/huaweicloud-devkit.js +194 -0
- package/package.json +2 -2
- 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/skills/huawei-ecs/SKILL.md +5 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +16 -2
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +22 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/windows-ssh.md +118 -0
- package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +3 -3
- package/plugins/huaweicloud-core/skills/huawei-iac/SKILL.md +36 -36
- package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +2 -2
- package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +30 -3
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +35 -6
- package/plugins/huaweicloud-core/skills/huawei-voucher/SKILL.md +1 -0
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +2 -2
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +6 -6
- package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +16 -9
- package/plugins/huaweicloud-core/src/auth/credential-validator.mjs +37 -2
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +14 -27
- package/plugins/huaweicloud-core/src/auth/guidance.mjs +112 -0
- package/plugins/huaweicloud-core/src/auth/reconcile.mjs +36 -2
- package/plugins/huaweicloud-core/src/auth/service.mjs +54 -46
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +32 -4
- package/plugins/huaweicloud-core/src/hcloud-probe.mjs +26 -6
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +120 -4
- package/plugins/huaweicloud-core/src/setup-cli.mjs +111 -8
- package/plugins/huaweicloud-core/src/tools.mjs +23 -3
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
import { dirname, join } from 'node:path';
|
|
12
12
|
import { homedir } from 'node:os';
|
|
13
13
|
|
|
14
|
+
import { buildOnboardingSteps, makeError } from './guidance.mjs';
|
|
15
|
+
|
|
14
16
|
// Verify a credential file ended up with 0600. On Windows-mounted drives inside WSL
|
|
15
17
|
// (drvfs/9p) chmod is silently ignored, so the file can be world-readable (0777).
|
|
16
18
|
// Native Windows has no POSIX modes (statSync always reports 0666), so skip the check there.
|
|
@@ -203,7 +205,8 @@ export function writeObsConfig(credentials = {}) {
|
|
|
203
205
|
const sk = String(credentials.sk || '');
|
|
204
206
|
const securityToken = String(credentials.securityToken || '');
|
|
205
207
|
if (!region || !ak || !sk) {
|
|
206
|
-
|
|
208
|
+
// #900: unified error shape from the shared ERROR_TABLE.
|
|
209
|
+
throw makeError('HDKIT_CRED_REGION_MISSING');
|
|
207
210
|
}
|
|
208
211
|
const path = obsConfigPath();
|
|
209
212
|
const endpoint = credentials.endpoint || `https://obs.${region}.myhuaweicloud.com`;
|
|
@@ -263,43 +266,25 @@ export function resolveCredentials(options = {}) {
|
|
|
263
266
|
|
|
264
267
|
if (!ak || !sk) {
|
|
265
268
|
if (options.allowMissing) return null;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
)
|
|
269
|
-
err.code = 'HDKIT_CRED_MISSING';
|
|
270
|
-
// Lightweight onboarding hint. HDKIT_CRED_MISSING fires when no credential
|
|
271
|
-
// resolved: with env empty/masked and S1 absent, the scenario is 3 by
|
|
272
|
-
// default. Scenario 4 (import a config-provided account) is only reachable
|
|
273
|
-
// in the narrow case where CodeArts S4 offers real creds that the resolver
|
|
274
|
-
// still couldn't adopt (e.g. CODEARTS_PROJECT_DIR set but the marker dir is
|
|
275
|
-
// absent). Full guidance comes from getAuthStatus(); keep this branch cheap.
|
|
269
|
+
// #900: unified guidance — one steps builder shared with computeOnboarding,
|
|
270
|
+
// and the error shape from the single ERROR_TABLE. Zero IO beyond the
|
|
271
|
+
// readCodeArtsCredentials() probe we already did above for scenario routing.
|
|
276
272
|
const codeartsCreds = readCodeArtsCredentials();
|
|
277
273
|
const injectedAvailable = present(codeartsCreds?.ak) && present(codeartsCreds?.sk);
|
|
274
|
+
const codearts = isCodeArtsContext();
|
|
275
|
+
const err = makeError('HDKIT_CRED_MISSING');
|
|
278
276
|
err.onboarding = injectedAvailable
|
|
279
277
|
? {
|
|
280
278
|
scenario: 4,
|
|
281
279
|
reason: 'import-injected',
|
|
282
280
|
message: '检测到配置中已有有效账号,可导入为正式凭证。',
|
|
283
|
-
steps:
|
|
284
|
-
? [{ action: 'auth_switch', args: { mode: 'mcp-config', action: 'persist' }, label: '导入配置中的账号' }]
|
|
285
|
-
: [
|
|
286
|
-
{
|
|
287
|
-
action: 'auth_switch',
|
|
288
|
-
args: { mode: 'import', action: 'persist' },
|
|
289
|
-
label: '通过 creds-import.json 导入',
|
|
290
|
-
},
|
|
291
|
-
],
|
|
281
|
+
steps: buildOnboardingSteps(4, { codearts }),
|
|
292
282
|
}
|
|
293
283
|
: {
|
|
294
284
|
scenario: 3,
|
|
295
285
|
reason: 's1-missing',
|
|
296
286
|
message: '未配置华为云凭证,需要完成登录后才能使用云能力。',
|
|
297
|
-
steps:
|
|
298
|
-
? [
|
|
299
|
-
{ action: 'write-import', target: join(homedir(), '.config', 'huaweicloud', 'creds-import.json') },
|
|
300
|
-
{ action: 'auth_switch', args: { mode: 'import', action: 'persist' } },
|
|
301
|
-
]
|
|
302
|
-
: [{ action: 'auth-init', args: {} }],
|
|
287
|
+
steps: buildOnboardingSteps(3, { codearts }),
|
|
303
288
|
};
|
|
304
289
|
throw err;
|
|
305
290
|
}
|
|
@@ -320,7 +305,9 @@ export function getParentCwd() {
|
|
|
320
305
|
}
|
|
321
306
|
}
|
|
322
307
|
|
|
323
|
-
|
|
308
|
+
// Exported for tests: the failure path and the tests must agree on which
|
|
309
|
+
// onboarding variant (codearts vs plain) the current environment selects.
|
|
310
|
+
export function isCodeArtsContext() {
|
|
324
311
|
return (
|
|
325
312
|
existsSync(join(process.cwd(), '.codeartsdoer')) ||
|
|
326
313
|
existsSync(join(homedir(), '.codeartsdoer')) ||
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// plugins/huaweicloud-core/src/auth/guidance.mjs
|
|
2
|
+
// Single Source of Truth for onboarding guidance steps and the error surface.
|
|
3
|
+
// Leaf module: imports NOTHING from auth/ siblings. Zero IO — pure builders.
|
|
4
|
+
// Callers: credentials.mjs (failure path), service.mjs (computeOnboarding),
|
|
5
|
+
// credential-validator / reconcile / hcloud-probe (error shapes).
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
|
|
9
|
+
export function buildOnboardingSteps(scenario, { codearts = false } = {}) {
|
|
10
|
+
switch (scenario) {
|
|
11
|
+
case 3: {
|
|
12
|
+
const base = [{ order: 1, action: 'obtain-aksk', target: 'console', label: '获取 AK/SK(华为云控制台)' }];
|
|
13
|
+
if (codearts) {
|
|
14
|
+
return [
|
|
15
|
+
...base,
|
|
16
|
+
{
|
|
17
|
+
order: 2,
|
|
18
|
+
action: 'write-import',
|
|
19
|
+
target: join(homedir(), '.config', 'huaweicloud', 'creds-import.json'),
|
|
20
|
+
label: '把 AK/SK 写入 creds-import.json',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
order: 3,
|
|
24
|
+
action: 'auth_switch',
|
|
25
|
+
// QA #905 Required: scenario-3 codearts flow writes creds-import.json
|
|
26
|
+
// in step 2, so the import must READ that file (mode:'import').
|
|
27
|
+
// mode:'mcp-config' reads the CodeArts injected environment, which
|
|
28
|
+
// is empty/placeholder in scenario 3 — the channel would not close.
|
|
29
|
+
args: { mode: 'import', action: 'persist' },
|
|
30
|
+
label: '执行导入完成配置',
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
return [...base, { order: 2, action: 'auth-init', args: {}, label: '运行 npx huaweicloud-devkit auth init' }];
|
|
35
|
+
}
|
|
36
|
+
case 4:
|
|
37
|
+
return [
|
|
38
|
+
{
|
|
39
|
+
order: 1,
|
|
40
|
+
action: 'auth_switch',
|
|
41
|
+
args: { mode: codearts ? 'mcp-config' : 'import', action: 'persist' },
|
|
42
|
+
label: '导入该账号',
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
default:
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// #900 error contract table. messageZh: Chinese, human-readable, no English
|
|
51
|
+
// sentences, no internal codenames. action: directly executable. nextStep is
|
|
52
|
+
// kept as a derived compatibility field for existing consumers.
|
|
53
|
+
export const ERROR_TABLE = {
|
|
54
|
+
HDKIT_CRED_MISSING: {
|
|
55
|
+
messageZh: '未配置华为云凭证,需要先完成登录。',
|
|
56
|
+
action: 'npx huaweicloud-devkit auth init',
|
|
57
|
+
},
|
|
58
|
+
HDKIT_CRED_INVALID: {
|
|
59
|
+
messageZh: '凭证已被华为云拒绝(AK/SK 无效或已过期),请重新生成。',
|
|
60
|
+
action: 'npx huaweicloud-devkit auth status',
|
|
61
|
+
},
|
|
62
|
+
HDKIT_CRED_FORBIDDEN: {
|
|
63
|
+
messageZh: '凭证有效但无权限列出项目,请检查账号授权。',
|
|
64
|
+
action: 'hcloud IAM KeystoneListProjects --cli-region=<region>',
|
|
65
|
+
},
|
|
66
|
+
HDKIT_CRED_REGION_MISSING: {
|
|
67
|
+
messageZh: '缺少 region(区域),登录配置不完整。',
|
|
68
|
+
action: 'hcloud configure set --cli-region=<region>',
|
|
69
|
+
},
|
|
70
|
+
HDKIT_KOOCLI_NOT_READY: {
|
|
71
|
+
messageZh: 'KooCLI(hcloud)未安装或未就绪。',
|
|
72
|
+
action: 'npx huaweicloud-devkit install-hcloud',
|
|
73
|
+
},
|
|
74
|
+
HDKIT_PROJECT_ID_MISSING: {
|
|
75
|
+
messageZh: 'KooCLI 配置缺少 project_id,导致同步失败。',
|
|
76
|
+
action: 'hcloud configure set --cli-project-id=<id>',
|
|
77
|
+
},
|
|
78
|
+
// Runtime credentials (in-process, set via auth_init/auth_switch) suppress
|
|
79
|
+
// auto-sync (rule R10). No `auth sync --force` flag exists; the real unblock
|
|
80
|
+
// is clearing the runtime store first, then re-running the sync.
|
|
81
|
+
HDKIT_SYNC_SKIPPED_RUNTIME: {
|
|
82
|
+
messageZh: '运行时凭证生效,自动同步已跳过。',
|
|
83
|
+
action: 'huaweicloud_auth_switch action=clear',
|
|
84
|
+
},
|
|
85
|
+
// IAM returned something other than 200/401/403 (or the request itself
|
|
86
|
+
// failed). The canonical debug hint lives in the cli-and-auth skill.
|
|
87
|
+
HDKIT_CRED_UNEXPECTED: {
|
|
88
|
+
messageZh: '凭证校验遇到异常响应,请开启调试排查。',
|
|
89
|
+
action: 'hcloud <Service> <Op> --cli-debug=true',
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export function errorFromTable(code, detail) {
|
|
94
|
+
const e = ERROR_TABLE[code];
|
|
95
|
+
if (!e) return { code, messageZh: detail || String(code), action: '', nextStep: detail || '' };
|
|
96
|
+
return {
|
|
97
|
+
code,
|
|
98
|
+
messageZh: detail ? `${e.messageZh}(${detail})` : e.messageZh,
|
|
99
|
+
action: e.action,
|
|
100
|
+
nextStep: e.action,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function makeError(code, detail) {
|
|
105
|
+
const shape = errorFromTable(code, detail);
|
|
106
|
+
const err = new Error(shape.messageZh);
|
|
107
|
+
err.code = shape.code;
|
|
108
|
+
err.action = shape.action;
|
|
109
|
+
err.nextStep = shape.nextStep;
|
|
110
|
+
err.messageZh = shape.messageZh;
|
|
111
|
+
return err;
|
|
112
|
+
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
readLastSync,
|
|
13
13
|
resolveCredentialsWithRuntime,
|
|
14
14
|
} from './credentials.mjs';
|
|
15
|
+
import { errorFromTable } from './guidance.mjs';
|
|
15
16
|
import { resolveHcloudCommand } from '../hcloud-probe.mjs';
|
|
16
17
|
import { redactSecrets } from '../safety-policy.mjs';
|
|
17
18
|
|
|
@@ -43,7 +44,12 @@ export function isManualModified(path) {
|
|
|
43
44
|
|
|
44
45
|
export function readKooCliProfiles() {
|
|
45
46
|
const configPath = kooCliConfigPath();
|
|
46
|
-
if (!existsSync(configPath))
|
|
47
|
+
if (!existsSync(configPath)) {
|
|
48
|
+
// #900: bare string replaced by the unified ERROR_TABLE shape; the old
|
|
49
|
+
// English text survives only as the distinguishing detail argument.
|
|
50
|
+
const e = errorFromTable('HDKIT_KOOCLI_NOT_READY', 'KooCLI config not found');
|
|
51
|
+
return { error: e.messageZh, code: e.code, messageZh: e.messageZh, action: e.action, nextStep: e.nextStep };
|
|
52
|
+
}
|
|
47
53
|
try {
|
|
48
54
|
const raw = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
49
55
|
const current = String(raw.current || 'default');
|
|
@@ -72,7 +78,9 @@ export function readKooCliProfiles() {
|
|
|
72
78
|
),
|
|
73
79
|
};
|
|
74
80
|
} catch {
|
|
75
|
-
|
|
81
|
+
// #900: bare string replaced by the unified ERROR_TABLE shape.
|
|
82
|
+
const e = errorFromTable('HDKIT_KOOCLI_NOT_READY', 'KooCLI config parse failed');
|
|
83
|
+
return { error: e.messageZh, code: e.code, messageZh: e.messageZh, action: e.action, nextStep: e.nextStep };
|
|
76
84
|
}
|
|
77
85
|
}
|
|
78
86
|
|
|
@@ -221,3 +229,29 @@ export function runHcloudConfigure(profile, ak, sk, region) {
|
|
|
221
229
|
),
|
|
222
230
|
};
|
|
223
231
|
}
|
|
232
|
+
|
|
233
|
+
// Delete a KooCLI profile (best-effort). Used by the auth persist path (#920):
|
|
234
|
+
// `configure set` REFUSES an empty --cli-security-token, so a stale/expired
|
|
235
|
+
// token already in the profile can never be overwritten — it poisons every
|
|
236
|
+
// hcloud call with APIGW.0301. The documented manual bypass is
|
|
237
|
+
// `hcloud configure delete --cli-profile=<profile>` followed by a re-configure;
|
|
238
|
+
// this helper makes that path a runtime step instead of user knowledge.
|
|
239
|
+
// A missing profile reports a non-zero status, which callers treat as
|
|
240
|
+
// "nothing to clean" — never as an error.
|
|
241
|
+
export function runHcloudProfileDelete(profile) {
|
|
242
|
+
const { executable, argsPrefix } = resolveHcloudCommand();
|
|
243
|
+
const r = spawnSync(executable, [...argsPrefix, 'configure', 'delete', `--cli-profile=${profile}`], {
|
|
244
|
+
shell: false,
|
|
245
|
+
windowsHide: true,
|
|
246
|
+
stdio: 'pipe',
|
|
247
|
+
timeout: 30000,
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
ok: r.status === 0,
|
|
251
|
+
error: redactSecrets(
|
|
252
|
+
String(r.stderr || '')
|
|
253
|
+
.trim()
|
|
254
|
+
.slice(0, 240),
|
|
255
|
+
),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
2
|
|
|
5
3
|
import { getAgentRegistrationStatuses } from './agent-registration.mjs';
|
|
6
4
|
import { resolveAndApplyProjectId } from './project-id.mjs';
|
|
7
5
|
import {
|
|
8
6
|
globalCredentialsPath,
|
|
7
|
+
isCodeArtsContext,
|
|
9
8
|
isPlaceholder,
|
|
10
9
|
obsConfigPath,
|
|
11
10
|
parseStsExpiry,
|
|
@@ -15,6 +14,7 @@ import {
|
|
|
15
14
|
writeLastSync,
|
|
16
15
|
writeObsConfig,
|
|
17
16
|
} from './credentials.mjs';
|
|
17
|
+
import { buildOnboardingSteps, errorFromTable } from './guidance.mjs';
|
|
18
18
|
import {
|
|
19
19
|
fingerprint,
|
|
20
20
|
exportStateForStatus,
|
|
@@ -22,15 +22,7 @@ import {
|
|
|
22
22
|
resolveManagedProfile,
|
|
23
23
|
runHcloudConfigure,
|
|
24
24
|
} from './reconcile.mjs';
|
|
25
|
-
import { hcloudProbeNextStep, probeHcloud } from '../hcloud-probe.mjs';
|
|
26
|
-
|
|
27
|
-
function isCodeArtsHome() {
|
|
28
|
-
return (
|
|
29
|
-
existsSync(join(process.cwd(), '.codeartsdoer')) ||
|
|
30
|
-
existsSync(join(homedir(), '.codeartsdoer')) ||
|
|
31
|
-
existsSync(join(homedir(), '.codeartswork'))
|
|
32
|
-
);
|
|
33
|
-
}
|
|
25
|
+
import { hcloudProbeNextStep, hcloudProbeRaw, probeHcloud } from '../hcloud-probe.mjs';
|
|
34
26
|
|
|
35
27
|
function envHasRealTriplet() {
|
|
36
28
|
return (
|
|
@@ -57,7 +49,9 @@ export function computeOnboarding({ credentials, reconciled } = {}) {
|
|
|
57
49
|
const envRealAk = !isPlaceholder(process.env.HW_ACCESS_KEY) && Boolean(process.env.HW_ACCESS_KEY);
|
|
58
50
|
const envRealSk = !isPlaceholder(process.env.HW_SECRET_KEY) && Boolean(process.env.HW_SECRET_KEY);
|
|
59
51
|
const envHasCreds = envRealAk && envRealSk;
|
|
60
|
-
|
|
52
|
+
// Same context detector the resolveCredentials failure path uses (#900): the
|
|
53
|
+
// codearts flag must select the identical builder variant on both paths.
|
|
54
|
+
const codeArts = isCodeArtsContext();
|
|
61
55
|
const accountHint = s1Has ? fingerprint(creds.ak, creds.sk) : null;
|
|
62
56
|
|
|
63
57
|
let scenario;
|
|
@@ -133,32 +127,12 @@ export function computeOnboarding({ credentials, reconciled } = {}) {
|
|
|
133
127
|
scenario = 4;
|
|
134
128
|
reason = 'import-injected';
|
|
135
129
|
message = '检测到配置中已有有效账号,可导入为正式凭证。';
|
|
136
|
-
steps =
|
|
137
|
-
{
|
|
138
|
-
order: 1,
|
|
139
|
-
action: 'switch-persist',
|
|
140
|
-
args: { mode: codeArts ? 'mcp-config' : 'import', action: 'persist' },
|
|
141
|
-
label: '导入该账号',
|
|
142
|
-
},
|
|
143
|
-
];
|
|
130
|
+
steps = buildOnboardingSteps(4, { codearts: codeArts });
|
|
144
131
|
} else {
|
|
145
132
|
scenario = 3;
|
|
146
133
|
reason = 's1-missing';
|
|
147
134
|
message = '未配置华为云凭证,需要完成登录后才能使用云能力。';
|
|
148
|
-
steps =
|
|
149
|
-
{ order: 1, action: 'obtain-aksk', target: 'console', label: '获取 AK/SK(华为云控制台)' },
|
|
150
|
-
...(codeArts
|
|
151
|
-
? [
|
|
152
|
-
{
|
|
153
|
-
order: 2,
|
|
154
|
-
action: 'write-import',
|
|
155
|
-
target: join(homedir(), '.config', 'huaweicloud', 'creds-import.json'),
|
|
156
|
-
label: '把 AK/SK 写入 creds-import.json',
|
|
157
|
-
},
|
|
158
|
-
{ order: 3, action: 'auth_switch', args: { mode: 'import', action: 'persist' }, label: '执行导入完成配置' },
|
|
159
|
-
]
|
|
160
|
-
: [{ order: 2, action: 'auth-init', args: {}, label: '运行 npx huaweicloud-devkit auth init' }]),
|
|
161
|
-
];
|
|
135
|
+
steps = buildOnboardingSteps(3, { codearts: codeArts });
|
|
162
136
|
}
|
|
163
137
|
|
|
164
138
|
return { needsSetup: true, scenario, reason, message, steps, accountHint };
|
|
@@ -215,6 +189,7 @@ export function getAuthStatus(target = 'all') {
|
|
|
215
189
|
kooCliInstalled: hcloud.installed,
|
|
216
190
|
kooCliStatus: hcloud.status,
|
|
217
191
|
kooCliNextStep: hcloudProbeNextStep(hcloud),
|
|
192
|
+
kooCliRaw: hcloudProbeRaw(hcloud),
|
|
218
193
|
reconciled,
|
|
219
194
|
onboarding,
|
|
220
195
|
agents: getAgentRegistrationStatuses(target).agents,
|
|
@@ -224,17 +199,28 @@ export function getAuthStatus(target = 'all') {
|
|
|
224
199
|
export function syncAuth(target = 'all') {
|
|
225
200
|
const credentials = readGlobalCredentials();
|
|
226
201
|
if (!credentials?.ak || !credentials?.sk) {
|
|
202
|
+
// #900: unified error shape from the shared ERROR_TABLE.
|
|
203
|
+
const e = errorFromTable('HDKIT_CRED_MISSING');
|
|
227
204
|
return {
|
|
228
205
|
ok: false,
|
|
229
|
-
error:
|
|
230
|
-
|
|
206
|
+
error: e.messageZh,
|
|
207
|
+
code: e.code,
|
|
208
|
+
messageZh: e.messageZh,
|
|
209
|
+
action: e.action,
|
|
210
|
+
nextStep: e.nextStep,
|
|
231
211
|
};
|
|
232
212
|
}
|
|
233
213
|
if (hasRuntimeCredentials()) {
|
|
214
|
+
// R10: runtime credentials suppress auto-sync. The codename stays in code
|
|
215
|
+
// comments only, never in user-facing strings (#900).
|
|
216
|
+
const e = errorFromTable('HDKIT_SYNC_SKIPPED_RUNTIME');
|
|
234
217
|
return {
|
|
235
218
|
ok: false,
|
|
236
|
-
error:
|
|
237
|
-
|
|
219
|
+
error: e.messageZh,
|
|
220
|
+
code: e.code,
|
|
221
|
+
messageZh: e.messageZh,
|
|
222
|
+
action: e.action,
|
|
223
|
+
nextStep: e.nextStep,
|
|
238
224
|
};
|
|
239
225
|
}
|
|
240
226
|
|
|
@@ -264,25 +250,47 @@ export function syncAuth(target = 'all') {
|
|
|
264
250
|
|
|
265
251
|
const hcloud = probeHcloud();
|
|
266
252
|
if (!hcloud.ok) {
|
|
253
|
+
// #900: three probe states share the table code; the detail distinguishes.
|
|
254
|
+
const detail =
|
|
255
|
+
hcloud.status === 'sandbox_home_failure'
|
|
256
|
+
? '无法解析用户主目录'
|
|
257
|
+
: hcloud.status === 'privacy_pending'
|
|
258
|
+
? '隐私协议待同意'
|
|
259
|
+
: '';
|
|
260
|
+
const e = errorFromTable('HDKIT_KOOCLI_NOT_READY', detail);
|
|
267
261
|
return {
|
|
268
262
|
ok: false,
|
|
269
|
-
error:
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
263
|
+
error: e.messageZh,
|
|
264
|
+
code: e.code,
|
|
265
|
+
messageZh: e.messageZh,
|
|
266
|
+
action: e.action,
|
|
267
|
+
// #900 issue comment: nextStep is the executable table action; the
|
|
268
|
+
// verbose English guidance survives only in the `raw` side-channel.
|
|
275
269
|
nextStep: hcloudProbeNextStep(hcloud),
|
|
270
|
+
raw: hcloudProbeRaw(hcloud),
|
|
276
271
|
obs: { configured: true, path: obs.path, endpoint: obs.endpoint },
|
|
277
272
|
};
|
|
278
273
|
}
|
|
279
274
|
|
|
280
275
|
const { ok, error } = runHcloudConfigure(profile, credentials.ak, credentials.sk, credentials.region);
|
|
281
276
|
if (!ok) {
|
|
277
|
+
// #900: `hcloud configure set` failures are usually a missing/invalid
|
|
278
|
+
// project_id for the target region; anything else keeps the raw (already
|
|
279
|
+
// redacted) stderr as the detail argument.
|
|
280
|
+
const detail = String(error || '').trim();
|
|
281
|
+
const projectIdCause = /project[_\s-]?id/i.test(detail);
|
|
282
|
+
const e = projectIdCause
|
|
283
|
+
? errorFromTable('HDKIT_PROJECT_ID_MISSING', detail)
|
|
284
|
+
: errorFromTable('HDKIT_KOOCLI_NOT_READY', detail || 'KooCLI 配置同步失败');
|
|
282
285
|
return {
|
|
283
286
|
ok: false,
|
|
284
|
-
error:
|
|
285
|
-
|
|
287
|
+
error: e.messageZh,
|
|
288
|
+
code: e.code,
|
|
289
|
+
messageZh: e.messageZh,
|
|
290
|
+
action: e.action,
|
|
291
|
+
nextStep: projectIdCause
|
|
292
|
+
? e.nextStep
|
|
293
|
+
: 'Run "npx huaweicloud-devkit auth init" in a real terminal to refresh the KooCLI config.',
|
|
286
294
|
obs: { configured: true, path: obs.path, endpoint: obs.endpoint },
|
|
287
295
|
};
|
|
288
296
|
}
|
|
@@ -659,11 +659,39 @@ function runHcloudOnce(plan, options) {
|
|
|
659
659
|
});
|
|
660
660
|
return;
|
|
661
661
|
}
|
|
662
|
+
// #921: the ok verdict converges on the exit code plus structured error
|
|
663
|
+
// parsing (errorCode-first, handled above). Two false-positive classes
|
|
664
|
+
// are closed here:
|
|
665
|
+
// 1. exit code 0 but the output carries a KooCLI parameter error (e.g.
|
|
666
|
+
// `--delete_ip` instead of `--delete_publicip`) — the command never
|
|
667
|
+
// executed; report ok:false with a PARAM_ERROR errorCode.
|
|
668
|
+
// 2. non-zero exit code with "successfully" in the output — the old
|
|
669
|
+
// broad regex flagged ANY failure mentioning "successfully". It is
|
|
670
|
+
// narrowed to OBS obsutil-style commands (which are known to return
|
|
671
|
+
// non-zero exit codes on success, #57) AND require the output to
|
|
672
|
+
// carry no error indicators.
|
|
673
|
+
const combined = `${stdout}${stderr}`;
|
|
674
|
+
const paramError = combined.match(
|
|
675
|
+
/\[?(?:ERROR|USE_ERROR)\]?[: ]*(?:unknown parameter|unknown option|incorrect (?:option|switch|parameter)|invalid (?:parameter|argument)|参数不存在|参数错误|无效的参数)[^\n]*/i,
|
|
676
|
+
);
|
|
677
|
+
if (paramError) {
|
|
678
|
+
finish({
|
|
679
|
+
ok: false,
|
|
680
|
+
exitCode: code,
|
|
681
|
+
signal,
|
|
682
|
+
errorCode: 'PARAM_ERROR',
|
|
683
|
+
errorMessage: paramError[0].trim(),
|
|
684
|
+
stdout: redactOutput(stdout),
|
|
685
|
+
stderr: redactOutput(stderr),
|
|
686
|
+
plan,
|
|
687
|
+
});
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
const isObsCommand = /^obs\b/i.test(String(plan.rawArgs?.[0] || ''));
|
|
691
|
+
const successText = /successfully/i.test(combined);
|
|
692
|
+
const errorIndicators = /error|failed|失败|invalid|not found|denied|拒绝/i.test(combined);
|
|
662
693
|
finish({
|
|
663
|
-
ok:
|
|
664
|
-
code === 0 ||
|
|
665
|
-
(code !== 0 &&
|
|
666
|
-
/successfully|succ?ess.*\[200\]|create bucket successfully|upload successfully/i.test(stdout + stderr)),
|
|
694
|
+
ok: code === 0 || (isObsCommand && successText && !errorIndicators),
|
|
667
695
|
exitCode: code,
|
|
668
696
|
signal,
|
|
669
697
|
stdout: redactOutput(stdout),
|
|
@@ -3,6 +3,7 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
|
|
6
|
+
import { errorFromTable } from './auth/guidance.mjs';
|
|
6
7
|
import { compareVersion, getKooCliVersion, parseHcloudVersion } from './koocli-version.mjs';
|
|
7
8
|
import { kooCliInstallLogPath } from './preflight.mjs';
|
|
8
9
|
import { redactSecrets } from './safety-policy.mjs';
|
|
@@ -140,19 +141,24 @@ export function probeHcloud(options = {}) {
|
|
|
140
141
|
};
|
|
141
142
|
}
|
|
142
143
|
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
// #900 (issue comment Required 1): every not-ready probe state routes through
|
|
145
|
+
// the shared ERROR_TABLE.HDKIT_KOOCLI_NOT_READY so nextStep is always a
|
|
146
|
+
// directly executable command (d11-41-nextstep-actionable). The verbose
|
|
147
|
+
// English guidance is preserved verbatim in hcloudProbeRaw() — a docs/debug
|
|
148
|
+
// side-channel — and must never leak back into the top-level nextStep.
|
|
149
|
+
export function hcloudProbeRaw(probe) {
|
|
150
|
+
if (probe?.status === 'ok')
|
|
145
151
|
return 'Use huaweicloud_show_profile_redacted to inspect the active KooCLI profile safely.';
|
|
146
|
-
if (probe
|
|
152
|
+
if (probe?.status === 'version_mismatch') {
|
|
147
153
|
return `KooCLI version mismatch: installed ${probe.installedVersion}, this plugin is paired with ${probe.requiredVersion}. Reinstall the pinned version (see huaweicloud-cli-and-auth skill) and restart the agent.`;
|
|
148
154
|
}
|
|
149
|
-
if (probe
|
|
155
|
+
if (probe?.status === 'sandbox_home_failure') {
|
|
150
156
|
return 'KooCLI was found, but this agent sandbox cannot resolve the Windows user home directory. Set HCLOUD_BIN to the full hcloud path, restart Codex, or verify from a normal terminal.';
|
|
151
157
|
}
|
|
152
|
-
if (probe
|
|
158
|
+
if (probe?.status === 'privacy_pending') {
|
|
153
159
|
return 'KooCLI requires accepting its one-time privacy agreement. Run hcloud version in a real terminal and accept the prompt, then restart the agent.';
|
|
154
160
|
}
|
|
155
|
-
if (probe
|
|
161
|
+
if (probe?.status === 'not_found') {
|
|
156
162
|
return (
|
|
157
163
|
'KooCLI is not installed yet. Background auto-install may be running — please retry shortly instead of assuming failure. To install now: npx huaweicloud-devkit install-hcloud. DevKit picks up the binary from its fixed install dirs (~/.local/bin on Linux, /usr/local/bin on macOS, ~/hcloud on Windows) or HCLOUD_BIN on the next call — no agent restart needed. Install progress: ' +
|
|
158
164
|
kooCliInstallLogPath()
|
|
@@ -160,3 +166,17 @@ export function hcloudProbeNextStep(probe) {
|
|
|
160
166
|
}
|
|
161
167
|
return 'Install Huawei Cloud KooCLI: npx huaweicloud-devkit install-hcloud. Configure credentials outside the agent conversation.';
|
|
162
168
|
}
|
|
169
|
+
|
|
170
|
+
export function hcloudProbeNextStep(probe) {
|
|
171
|
+
if (probe?.status === 'ok') return 'huaweicloud_show_profile_redacted';
|
|
172
|
+
const detail =
|
|
173
|
+
probe?.status === 'version_mismatch'
|
|
174
|
+
? `版本不匹配(已装 ${probe?.installedVersion ?? '未知'},插件配套 ${probe?.requiredVersion ?? '未知'})`
|
|
175
|
+
: probe?.status === 'sandbox_home_failure'
|
|
176
|
+
? '无法解析用户主目录'
|
|
177
|
+
: probe?.status === 'privacy_pending'
|
|
178
|
+
? '隐私协议待同意'
|
|
179
|
+
: '';
|
|
180
|
+
const e = errorFromTable('HDKIT_KOOCLI_NOT_READY', detail);
|
|
181
|
+
return e.nextStep;
|
|
182
|
+
}
|