huaweicloud-devkit 1.1.2-next.3 → 1.1.2-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/README.md +22 -0
- package/README.zh-CN.md +22 -0
- 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/hooks/hooks.json +2 -2
- package/plugins/huaweicloud-core/hooks/huaweicloud-safety.mjs +56 -0
- package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +1 -1
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +6 -2
- package/plugins/huaweicloud-core/src/auth/reconcile.mjs +30 -6
- package/plugins/huaweicloud-core/src/auth/service.mjs +16 -22
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +5 -10
- package/plugins/huaweicloud-core/src/hcloud-probe.mjs +154 -0
- package/plugins/huaweicloud-core/src/safety-policy.mjs +17 -5
- package/plugins/huaweicloud-core/src/setup-cli.mjs +163 -128
- package/plugins/huaweicloud-core/src/tools.mjs +14 -30
package/README.md
CHANGED
|
@@ -57,14 +57,36 @@ npx --yes huaweicloud-devkit install --target codex
|
|
|
57
57
|
**Restart the Codex session** after installation.
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
+
codex plugin list # verify huaweicloud-devkit@huaweicloud-devkit is installed and enabled
|
|
60
61
|
npx --yes huaweicloud-devkit doctor --target codex
|
|
61
62
|
npx --yes huaweicloud-devkit status --target codex
|
|
62
63
|
npx --yes huaweicloud-devkit update --target codex
|
|
63
64
|
npx --yes huaweicloud-devkit uninstall --target codex
|
|
64
65
|
```
|
|
65
66
|
|
|
67
|
+
Then mention `@huaweicloud-devkit` in Codex or describe your Huawei Cloud task directly.
|
|
68
|
+
|
|
66
69
|
> **Requires Codex CLI** — the `codex` command must be in PATH. If Codex is installed via WindowsApps (Microsoft Store), use `--target codex-desktop` instead. Run `codex --version` to verify CLI availability.
|
|
67
70
|
|
|
71
|
+
### Codex Desktop
|
|
72
|
+
|
|
73
|
+
Use this target when the Codex CLI is unavailable or when Codex is installed through WindowsApps on Windows.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx --yes huaweicloud-devkit install --target codex-desktop
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Restart the Codex Desktop session** after installation.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npx --yes huaweicloud-devkit doctor --target codex-desktop
|
|
83
|
+
npx --yes huaweicloud-devkit status --target codex-desktop
|
|
84
|
+
npx --yes huaweicloud-devkit update --target codex-desktop
|
|
85
|
+
npx --yes huaweicloud-devkit uninstall --target codex-desktop
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Then mention `@huaweicloud-devkit` in a new Codex Desktop task or describe your Huawei Cloud task directly.
|
|
89
|
+
|
|
68
90
|
### CodeArts Agent
|
|
69
91
|
|
|
70
92
|
```bash
|
package/README.zh-CN.md
CHANGED
|
@@ -57,14 +57,36 @@ npx --yes huaweicloud-devkit install --target codex
|
|
|
57
57
|
安装后**重启 Codex 会话**。
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
+
codex plugin list # 验证 huaweicloud-devkit@huaweicloud-devkit 已安装并启用
|
|
60
61
|
npx --yes huaweicloud-devkit doctor --target codex
|
|
61
62
|
npx --yes huaweicloud-devkit status --target codex
|
|
62
63
|
npx --yes huaweicloud-devkit update --target codex
|
|
63
64
|
npx --yes huaweicloud-devkit uninstall --target codex
|
|
64
65
|
```
|
|
65
66
|
|
|
67
|
+
随后在 Codex 中提及 `@huaweicloud-devkit`,或直接描述华为云任务。
|
|
68
|
+
|
|
66
69
|
> **需要 Codex CLI** — `codex` 命令必须在 PATH 中。若 Codex 通过 WindowsApps(Microsoft Store)安装,请使用 `--target codex-desktop` 替代。运行 `codex --version` 验证 CLI 可用性。
|
|
67
70
|
|
|
71
|
+
### Codex Desktop
|
|
72
|
+
|
|
73
|
+
当 Codex CLI 不可用,或 Windows 上通过 WindowsApps 安装 Codex 时,使用此目标。
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx --yes huaweicloud-devkit install --target codex-desktop
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
安装后**重启 Codex Desktop 会话**。
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npx --yes huaweicloud-devkit doctor --target codex-desktop
|
|
83
|
+
npx --yes huaweicloud-devkit status --target codex-desktop
|
|
84
|
+
npx --yes huaweicloud-devkit update --target codex-desktop
|
|
85
|
+
npx --yes huaweicloud-devkit uninstall --target codex-desktop
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
随后在新的 Codex Desktop 任务中提及 `@huaweicloud-devkit`,或直接描述华为云任务。
|
|
89
|
+
|
|
68
90
|
### CodeArts Agent(码道)
|
|
69
91
|
|
|
70
92
|
```bash
|
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.2-next.
|
|
4
|
+
"version": "1.1.2-next.4",
|
|
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.2-next.
|
|
20
|
+
"version": "1.1.2-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.2-next.
|
|
23
|
+
"version": "1.1.2-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.2-next.
|
|
20
|
+
"version": "1.1.2-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.2-next.
|
|
3
|
+
"version": "1.1.2-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.2-next.
|
|
3
|
+
"version": "1.1.2-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",
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "
|
|
9
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/huaweicloud-safety.mjs\"",
|
|
10
10
|
"timeout": 5
|
|
11
11
|
}
|
|
12
12
|
]
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"hooks": [
|
|
17
17
|
{
|
|
18
18
|
"type": "command",
|
|
19
|
-
"command": "
|
|
19
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/huaweicloud-safety.mjs\"",
|
|
20
20
|
"timeout": 5
|
|
21
21
|
}
|
|
22
22
|
]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
import { classifyTextCommand } from '../src/safety-policy.mjs';
|
|
4
|
+
|
|
5
|
+
const DENY_PREFIX = 'Huawei Cloud safety hook blocked this action: ';
|
|
6
|
+
|
|
7
|
+
function commandText(toolInput) {
|
|
8
|
+
if (typeof toolInput === 'string') return toolInput;
|
|
9
|
+
if (toolInput && typeof toolInput === 'object') {
|
|
10
|
+
const values = [];
|
|
11
|
+
for (const key of ['command', 'cmd', 'script', 'args', 'arguments']) {
|
|
12
|
+
const value = toolInput[key];
|
|
13
|
+
if (Array.isArray(value)) values.push(value.map(String).join(' '));
|
|
14
|
+
else if (value !== undefined && value !== null) values.push(String(value));
|
|
15
|
+
}
|
|
16
|
+
if (values.length > 0) return values.join('\n');
|
|
17
|
+
return JSON.stringify(toolInput);
|
|
18
|
+
}
|
|
19
|
+
return JSON.stringify(toolInput);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function deny(reason) {
|
|
23
|
+
process.stdout.write(
|
|
24
|
+
JSON.stringify({
|
|
25
|
+
hookSpecificOutput: {
|
|
26
|
+
hookEventName: 'PreToolUse',
|
|
27
|
+
permissionDecision: 'deny',
|
|
28
|
+
permissionDecisionReason: DENY_PREFIX + reason,
|
|
29
|
+
},
|
|
30
|
+
}) + '\n',
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readStdin() {
|
|
35
|
+
try {
|
|
36
|
+
return readFileSync(0, 'utf8');
|
|
37
|
+
} catch {
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function main() {
|
|
43
|
+
const input = readStdin();
|
|
44
|
+
let data;
|
|
45
|
+
try {
|
|
46
|
+
data = JSON.parse(input);
|
|
47
|
+
} catch {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const text = commandText(data.tool_input ?? {});
|
|
52
|
+
const result = classifyTextCommand(text);
|
|
53
|
+
if (result.decision === 'deny') deny(result.reason);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
main();
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
3
|
"id": "huaweicloud-devkit",
|
|
4
4
|
"displayName": "HuaweiCloud DevKit",
|
|
5
|
-
"version": "1.1.2-next.
|
|
5
|
+
"version": "1.1.2-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.",
|
|
@@ -27,7 +27,7 @@ Use this skill before any Huawei Cloud action that may expose secrets, change re
|
|
|
27
27
|
|
|
28
28
|
## Enforcement Layers
|
|
29
29
|
|
|
30
|
-
- Hooks: `hooks/huaweicloud-safety.
|
|
30
|
+
- Hooks: `hooks/huaweicloud-safety.mjs` blocks risky Codex native hook calls without requiring Python; `hooks/huaweicloud-safety.py` remains for Hermes/Python hook compatibility.
|
|
31
31
|
- MCP wrapper: the Node MCP server applies the same safety policy for Codex/OpenCode paths that do not enforce hooks.
|
|
32
32
|
- Skills: this document teaches agents the rule before they act.
|
|
33
33
|
|
|
@@ -272,10 +272,14 @@ export function readLastSync() {
|
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
export function writeLastSync() {
|
|
275
|
+
export function writeLastSync(metadata = {}) {
|
|
276
276
|
const path = lastSyncPath();
|
|
277
277
|
mkdirSync(dirname(path), { recursive: true });
|
|
278
|
-
const payload = {
|
|
278
|
+
const payload = {
|
|
279
|
+
ts: Date.now(),
|
|
280
|
+
...(metadata.kooCliProfile ? { kooCliProfile: String(metadata.kooCliProfile) } : {}),
|
|
281
|
+
...(metadata.s1Fingerprint ? { s1Fingerprint: String(metadata.s1Fingerprint) } : {}),
|
|
282
|
+
};
|
|
279
283
|
writeFileSync(path, JSON.stringify(payload), { encoding: 'utf8', mode: 0o600 });
|
|
280
284
|
ensurePrivateMode(path);
|
|
281
285
|
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
readLastSync,
|
|
12
12
|
resolveCredentialsWithRuntime,
|
|
13
13
|
} from './credentials.mjs';
|
|
14
|
+
import { resolveHcloudCommand } from '../hcloud-probe.mjs';
|
|
14
15
|
import { redactSecrets } from '../safety-policy.mjs';
|
|
15
16
|
|
|
16
17
|
export { hasRuntimeCredentials };
|
|
@@ -47,6 +48,7 @@ export function readKooCliProfiles() {
|
|
|
47
48
|
return {
|
|
48
49
|
current,
|
|
49
50
|
mtimeMs,
|
|
51
|
+
configPath,
|
|
50
52
|
profiles: profiles.map((p) => ({
|
|
51
53
|
name: String(p.name || ''),
|
|
52
54
|
fingerprint: fingerprint(p.accessKeyId, p.secretAccessKey),
|
|
@@ -70,15 +72,32 @@ export function currentFingerprintFromHcloud(res) {
|
|
|
70
72
|
return cur ? cur.fingerprint : null;
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
function currentProfileFromHcloud(res) {
|
|
76
|
+
if (res.error) return null;
|
|
77
|
+
return res.profiles.find((p) => p.name === res.current) || null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function s2CurrentMatchesLastDevkitSync(kooCli, s1, s1Fingerprint) {
|
|
81
|
+
const current = currentProfileFromHcloud(kooCli);
|
|
82
|
+
const lastSync = readLastSync();
|
|
83
|
+
if (!current || !lastSync?.ts || !lastSync.kooCliProfile || !lastSync.s1Fingerprint) return false;
|
|
84
|
+
if (!s1?.ak || current.accessKeyId !== s1.ak) return false;
|
|
85
|
+
if (lastSync.kooCliProfile !== kooCli.current) return false;
|
|
86
|
+
if (lastSync.s1Fingerprint !== s1Fingerprint) return false;
|
|
87
|
+
return Number(kooCli.mtimeMs || 0) <= Number(lastSync.ts) + 1000;
|
|
88
|
+
}
|
|
89
|
+
|
|
73
90
|
export function scanState() {
|
|
74
91
|
const s1 = readGlobalCredentials() || {};
|
|
75
92
|
const envAk = process.env.HW_ACCESS_KEY || '';
|
|
76
93
|
const envSk = process.env.HW_SECRET_KEY || '';
|
|
77
94
|
const kooCli = readKooCliProfiles();
|
|
78
|
-
const currentFp = currentFingerprintFromHcloud(kooCli);
|
|
79
95
|
const inconsistencies = [];
|
|
80
96
|
|
|
81
97
|
const s1Fingerprint = fingerprint(s1.ak, s1.sk);
|
|
98
|
+
const currentFp = currentFingerprintFromHcloud(kooCli);
|
|
99
|
+
const s2SyncedByDevkit = s1Fingerprint ? s2CurrentMatchesLastDevkitSync(kooCli, s1, s1Fingerprint) : false;
|
|
100
|
+
const effectiveCurrentFp = s2SyncedByDevkit ? s1Fingerprint : currentFp;
|
|
82
101
|
const envFingerprint = fingerprint(envAk, envSk);
|
|
83
102
|
const s3 = existsSync(obsConfigPath()) ? parseS3ObsConfig(obsConfigPath()) : null;
|
|
84
103
|
const s3Fingerprint = s3 ? fingerprint(s3.ak, s3.sk) : null;
|
|
@@ -93,12 +112,12 @@ export function scanState() {
|
|
|
93
112
|
// nothing resolvable → runtime store inactive
|
|
94
113
|
}
|
|
95
114
|
|
|
96
|
-
if (s1Fingerprint && currentFp && s1Fingerprint !==
|
|
115
|
+
if (s1Fingerprint && currentFp && s1Fingerprint !== effectiveCurrentFp) {
|
|
97
116
|
inconsistencies.push({
|
|
98
117
|
store: 'S2-current',
|
|
99
118
|
source: 'KooCLI current profile',
|
|
100
119
|
fingerprint: currentFp,
|
|
101
|
-
manualModified: isManualModified(join(baseHome(), '.hcloud', 'config.json')),
|
|
120
|
+
manualModified: isManualModified(kooCli.configPath || join(baseHome(), '.hcloud', 'config.json')),
|
|
102
121
|
});
|
|
103
122
|
}
|
|
104
123
|
if (s1Fingerprint && s3Fingerprint && s1Fingerprint !== s3Fingerprint) {
|
|
@@ -114,7 +133,7 @@ export function scanState() {
|
|
|
114
133
|
stores: {
|
|
115
134
|
s1Fingerprint,
|
|
116
135
|
envFingerprint,
|
|
117
|
-
currentFingerprint:
|
|
136
|
+
currentFingerprint: effectiveCurrentFp,
|
|
118
137
|
s3Fingerprint,
|
|
119
138
|
runtimeFingerprint,
|
|
120
139
|
},
|
|
@@ -153,7 +172,7 @@ export function exportStateForStatus(_jailed) {
|
|
|
153
172
|
}
|
|
154
173
|
|
|
155
174
|
export function runHcloudConfigure(profile, ak, sk, region) {
|
|
156
|
-
const
|
|
175
|
+
const { executable, argsPrefix } = resolveHcloudCommand();
|
|
157
176
|
const args = [
|
|
158
177
|
'configure',
|
|
159
178
|
'set',
|
|
@@ -162,7 +181,12 @@ export function runHcloudConfigure(profile, ak, sk, region) {
|
|
|
162
181
|
`--cli-secret-key=${sk}`,
|
|
163
182
|
`--cli-region=${region || ''}`,
|
|
164
183
|
];
|
|
165
|
-
const r = spawnSync(
|
|
184
|
+
const r = spawnSync(executable, [...argsPrefix, ...args], {
|
|
185
|
+
shell: false,
|
|
186
|
+
windowsHide: true,
|
|
187
|
+
stdio: 'pipe',
|
|
188
|
+
timeout: 30000,
|
|
189
|
+
});
|
|
166
190
|
return {
|
|
167
191
|
ok: r.status === 0,
|
|
168
192
|
error: redactSecrets(
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import { spawnSync } from 'node:child_process';
|
|
3
2
|
|
|
4
3
|
import { getAgentRegistrationStatuses } from './agent-registration.mjs';
|
|
5
4
|
import {
|
|
@@ -10,38 +9,27 @@ import {
|
|
|
10
9
|
writeObsConfig,
|
|
11
10
|
} from './credentials.mjs';
|
|
12
11
|
import {
|
|
12
|
+
fingerprint,
|
|
13
13
|
exportStateForStatus,
|
|
14
14
|
hasRuntimeCredentials,
|
|
15
15
|
resolveManagedProfile,
|
|
16
16
|
runHcloudConfigure,
|
|
17
17
|
} from './reconcile.mjs';
|
|
18
|
-
|
|
19
|
-
function hcloudInstalled() {
|
|
20
|
-
const bin = process.env.HCLOUD_BIN || 'hcloud';
|
|
21
|
-
try {
|
|
22
|
-
const r = spawnSync(`"${bin}" version`, [], {
|
|
23
|
-
shell: true,
|
|
24
|
-
windowsHide: true,
|
|
25
|
-
stdio: 'pipe',
|
|
26
|
-
timeout: 5000,
|
|
27
|
-
});
|
|
28
|
-
const out = `${r.stdout || ''}${r.stderr || ''}`;
|
|
29
|
-
return r.status === 0 && /KooCLI|Current.*version|当前KooCLI/i.test(out);
|
|
30
|
-
} catch {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
18
|
+
import { hcloudProbeNextStep, probeHcloud } from '../hcloud-probe.mjs';
|
|
34
19
|
|
|
35
20
|
export function getAuthStatus(target = 'all') {
|
|
36
21
|
const credentials = readGlobalCredentials();
|
|
37
22
|
const reconciled = { ...exportStateForStatus(), runtimeActive: hasRuntimeCredentials() };
|
|
23
|
+
const hcloud = probeHcloud();
|
|
38
24
|
return {
|
|
39
25
|
target,
|
|
40
26
|
credentialsConfigured: Boolean(credentials?.ak && credentials?.sk),
|
|
41
27
|
credentialsPath: globalCredentialsPath(),
|
|
42
28
|
obsConfigured: existsSync(obsConfigPath()),
|
|
43
29
|
obsConfigPath: obsConfigPath(),
|
|
44
|
-
kooCliInstalled:
|
|
30
|
+
kooCliInstalled: hcloud.installed,
|
|
31
|
+
kooCliStatus: hcloud.status,
|
|
32
|
+
kooCliNextStep: hcloudProbeNextStep(hcloud),
|
|
45
33
|
reconciled,
|
|
46
34
|
agents: getAgentRegistrationStatuses(target).agents,
|
|
47
35
|
};
|
|
@@ -85,11 +73,17 @@ export function syncAuth(target = 'all') {
|
|
|
85
73
|
};
|
|
86
74
|
}
|
|
87
75
|
|
|
88
|
-
|
|
76
|
+
const hcloud = probeHcloud();
|
|
77
|
+
if (!hcloud.ok) {
|
|
89
78
|
return {
|
|
90
79
|
ok: false,
|
|
91
|
-
error:
|
|
92
|
-
|
|
80
|
+
error:
|
|
81
|
+
hcloud.status === 'sandbox_home_failure'
|
|
82
|
+
? 'KooCLI detected but cannot resolve the user home directory in this agent sandbox.'
|
|
83
|
+
: hcloud.status === 'privacy_pending'
|
|
84
|
+
? 'KooCLI privacy agreement is pending.'
|
|
85
|
+
: 'KooCLI not ready.',
|
|
86
|
+
nextStep: hcloudProbeNextStep(hcloud),
|
|
93
87
|
obs: { configured: true, path: obs.path, endpoint: obs.endpoint },
|
|
94
88
|
};
|
|
95
89
|
}
|
|
@@ -104,7 +98,7 @@ export function syncAuth(target = 'all') {
|
|
|
104
98
|
};
|
|
105
99
|
}
|
|
106
100
|
|
|
107
|
-
writeLastSync();
|
|
101
|
+
writeLastSync({ kooCliProfile: profile, s1Fingerprint: fingerprint(credentials.ak, credentials.sk) });
|
|
108
102
|
|
|
109
103
|
return {
|
|
110
104
|
ok: true,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { spawn, spawnSync } from 'node:child_process';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import {
|
|
4
|
-
import { homedir } from 'node:os';
|
|
3
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
5
4
|
import { join } from 'node:path';
|
|
6
5
|
|
|
7
6
|
import { classifyHcloudArgs, redactSecrets, assertAllowed } from './safety-policy.mjs';
|
|
8
7
|
import { getProxySettings } from './proxy/proxy-config.mjs';
|
|
8
|
+
import { findHcloudBin, resolveHcloudCommand } from './hcloud-probe.mjs';
|
|
9
9
|
|
|
10
10
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
11
11
|
const DEFAULT_FORCE_KILL_AFTER_MS = 2_000;
|
|
@@ -67,10 +67,10 @@ function preflightSecurityGroupCheck(normalizedArgs) {
|
|
|
67
67
|
const findings = [];
|
|
68
68
|
for (const sgId of sgIds) {
|
|
69
69
|
try {
|
|
70
|
-
const
|
|
70
|
+
const { executable, argsPrefix } = resolveHcloudCommand();
|
|
71
71
|
const spawnArgs = ['VPC', 'ListSecurityGroupRules', `--security_group_id.1=${sgId}`];
|
|
72
72
|
if (regionArg) spawnArgs.push(regionArg);
|
|
73
|
-
const r = spawnSync(
|
|
73
|
+
const r = spawnSync(executable, [...argsPrefix, ...spawnArgs], {
|
|
74
74
|
shell: false,
|
|
75
75
|
windowsHide: true,
|
|
76
76
|
stdio: 'pipe',
|
|
@@ -216,12 +216,7 @@ async function runtimeCurrentMismatchWarning() {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
function discoverHcloudPath() {
|
|
219
|
-
|
|
220
|
-
const candidates =
|
|
221
|
-
process.platform === 'win32'
|
|
222
|
-
? [join(homedir(), 'hcloud', 'hcloud.exe')]
|
|
223
|
-
: [join(homedir(), '.local', 'bin', 'hcloud'), join(homedir(), 'hcloud', 'hcloud')];
|
|
224
|
-
return candidates.find((c) => existsSync(c)) || null;
|
|
219
|
+
return findHcloudBin();
|
|
225
220
|
}
|
|
226
221
|
|
|
227
222
|
function runHcloudOnce(plan, options) {
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { compareVersion, getKooCliVersion, parseHcloudVersion } from './koocli-version.mjs';
|
|
7
|
+
import { redactSecrets } from './safety-policy.mjs';
|
|
8
|
+
|
|
9
|
+
const VERSION_RE = /KooCLI|Current.*version|当前KooCLI/i;
|
|
10
|
+
|
|
11
|
+
export function findHcloudBin() {
|
|
12
|
+
if (process.env.HCLOUD_BIN && existsSync(process.env.HCLOUD_BIN)) return process.env.HCLOUD_BIN;
|
|
13
|
+
const candidates =
|
|
14
|
+
process.platform === 'win32'
|
|
15
|
+
? [join(homedir(), 'hcloud', 'hcloud.exe')]
|
|
16
|
+
: [join(homedir(), '.local', 'bin', 'hcloud'), join(homedir(), 'hcloud', 'hcloud')];
|
|
17
|
+
const found = candidates.find((candidate) => existsSync(candidate));
|
|
18
|
+
if (found) return found;
|
|
19
|
+
|
|
20
|
+
const locator =
|
|
21
|
+
process.platform === 'win32'
|
|
22
|
+
? spawnSync('where.exe', ['hcloud'], { windowsHide: true, stdio: 'pipe', encoding: 'utf8', timeout: 3000 })
|
|
23
|
+
: spawnSync('sh', ['-c', 'command -v hcloud'], {
|
|
24
|
+
windowsHide: true,
|
|
25
|
+
stdio: 'pipe',
|
|
26
|
+
encoding: 'utf8',
|
|
27
|
+
timeout: 3000,
|
|
28
|
+
});
|
|
29
|
+
if (locator.status === 0) {
|
|
30
|
+
const first = String(locator.stdout || '')
|
|
31
|
+
.split(/\r?\n/)
|
|
32
|
+
.map((line) => line.trim())
|
|
33
|
+
.find(Boolean);
|
|
34
|
+
if (first) return first;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function envHcloudArgs() {
|
|
40
|
+
if (!process.env.HCLOUD_BIN_ARGS_JSON) return [];
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(process.env.HCLOUD_BIN_ARGS_JSON);
|
|
43
|
+
if (Array.isArray(parsed)) return parsed.map((item) => String(item));
|
|
44
|
+
} catch {}
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resolveHcloudCommand(options = {}) {
|
|
49
|
+
return {
|
|
50
|
+
executable: options.executable || findHcloudBin() || 'hcloud',
|
|
51
|
+
argsPrefix: Array.isArray(options.executableArgs) ? options.executableArgs : envHcloudArgs(),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function classifyHcloudProbe(result, requiredVersion = getKooCliVersion()) {
|
|
56
|
+
const stdout = String(result.stdout || '');
|
|
57
|
+
const stderr = String(result.stderr || '');
|
|
58
|
+
const output = `${stdout}${stderr}`;
|
|
59
|
+
const installedVersion = parseHcloudVersion(output);
|
|
60
|
+
|
|
61
|
+
if (result.error?.code === 'ENOENT') {
|
|
62
|
+
return {
|
|
63
|
+
status: 'not_found',
|
|
64
|
+
installed: false,
|
|
65
|
+
ok: false,
|
|
66
|
+
errorCode: 'HCLOUD_NOT_FOUND',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (result.status === 0 && (VERSION_RE.test(output) || installedVersion)) {
|
|
71
|
+
const versionMismatch = !!(
|
|
72
|
+
requiredVersion &&
|
|
73
|
+
installedVersion &&
|
|
74
|
+
compareVersion(installedVersion, requiredVersion) !== 0
|
|
75
|
+
);
|
|
76
|
+
return {
|
|
77
|
+
status: versionMismatch ? 'version_mismatch' : 'ok',
|
|
78
|
+
installed: true,
|
|
79
|
+
ok: true,
|
|
80
|
+
installedVersion,
|
|
81
|
+
requiredVersion: requiredVersion || undefined,
|
|
82
|
+
versionMismatch,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (/获取当前用户家目录失败|home\s+directory|user\s+home|home\s+dir|homedir/i.test(output)) {
|
|
87
|
+
return {
|
|
88
|
+
status: 'sandbox_home_failure',
|
|
89
|
+
installed: true,
|
|
90
|
+
ok: false,
|
|
91
|
+
errorCode: 'HCLOUD_SANDBOX_HOME_FAILURE',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
/同意并继续使用|不同意并退出|隐私协议|隐私声明|privacy agreement|privacy statement|invalid character|无效字符/i.test(
|
|
97
|
+
output,
|
|
98
|
+
)
|
|
99
|
+
) {
|
|
100
|
+
return {
|
|
101
|
+
status: 'privacy_pending',
|
|
102
|
+
installed: true,
|
|
103
|
+
ok: false,
|
|
104
|
+
errorCode: 'HCLOUD_PRIVACY_PENDING',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
status: 'unavailable',
|
|
110
|
+
installed: false,
|
|
111
|
+
ok: false,
|
|
112
|
+
errorCode: 'HCLOUD_UNAVAILABLE',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function probeHcloud(options = {}) {
|
|
117
|
+
const { executable, argsPrefix } = resolveHcloudCommand(options);
|
|
118
|
+
const r = spawnSync(executable, [...argsPrefix, 'version'], {
|
|
119
|
+
shell: false,
|
|
120
|
+
windowsHide: true,
|
|
121
|
+
stdio: 'pipe',
|
|
122
|
+
encoding: 'utf8',
|
|
123
|
+
timeout: options.timeoutMs ?? 5000,
|
|
124
|
+
});
|
|
125
|
+
const classified = classifyHcloudProbe(r, options.requiredVersion);
|
|
126
|
+
const stdout = redactSecrets(String(r.stdout || '').trim());
|
|
127
|
+
const stderr = redactSecrets(String(r.stderr || r.error?.message || '').trim());
|
|
128
|
+
return {
|
|
129
|
+
executable,
|
|
130
|
+
exitCode: r.status,
|
|
131
|
+
stdout,
|
|
132
|
+
stderr,
|
|
133
|
+
output: classified.ok ? stdout : stderr || stdout,
|
|
134
|
+
...classified,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function hcloudProbeNextStep(probe) {
|
|
139
|
+
if (probe.status === 'ok')
|
|
140
|
+
return 'Use huaweicloud_show_profile_redacted to inspect the active KooCLI profile safely.';
|
|
141
|
+
if (probe.status === 'version_mismatch') {
|
|
142
|
+
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.`;
|
|
143
|
+
}
|
|
144
|
+
if (probe.status === 'sandbox_home_failure') {
|
|
145
|
+
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.';
|
|
146
|
+
}
|
|
147
|
+
if (probe.status === 'privacy_pending') {
|
|
148
|
+
return 'KooCLI requires accepting its one-time privacy agreement. Run hcloud version in a real terminal and accept the prompt, then restart the agent.';
|
|
149
|
+
}
|
|
150
|
+
if (probe.status === 'not_found') {
|
|
151
|
+
return 'hcloud executable not found. Set HCLOUD_BIN to the full hcloud path, or install KooCLI: npx huaweicloud-devkit install-hcloud. Then restart the agent.';
|
|
152
|
+
}
|
|
153
|
+
return 'Install Huawei Cloud KooCLI: npx huaweicloud-devkit install-hcloud. Configure credentials outside the agent conversation.';
|
|
154
|
+
}
|
|
@@ -109,6 +109,14 @@ function applyCommandRiskRules(base, normalizedArgs, options = {}) {
|
|
|
109
109
|
return mergeRiskDecision(base, risk);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
function applyRawCommandRiskRules(base, command, options = {}) {
|
|
113
|
+
if (base.decision === 'deny' || options.skipRiskRules === true) {
|
|
114
|
+
return base;
|
|
115
|
+
}
|
|
116
|
+
const risk = evaluateCommandRisk(command);
|
|
117
|
+
return mergeRiskDecision(base, risk);
|
|
118
|
+
}
|
|
119
|
+
|
|
112
120
|
export function classifyHcloudArgs(args, options = {}) {
|
|
113
121
|
const policy = options.policy || DEFAULT_POLICY;
|
|
114
122
|
const { service, operation, args: normalizedArgs } = commandOperation(args);
|
|
@@ -340,11 +348,15 @@ export function classifyTextCommand(command, options = {}) {
|
|
|
340
348
|
};
|
|
341
349
|
}
|
|
342
350
|
|
|
343
|
-
return
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
351
|
+
return applyRawCommandRiskRules(
|
|
352
|
+
{
|
|
353
|
+
decision: 'allow',
|
|
354
|
+
risk: 'not_huaweicloud',
|
|
355
|
+
reason: 'No Huawei Cloud safety rule matched.',
|
|
356
|
+
},
|
|
357
|
+
text,
|
|
358
|
+
options,
|
|
359
|
+
);
|
|
348
360
|
}
|
|
349
361
|
|
|
350
362
|
export function assertAllowed(result) {
|
|
@@ -18,7 +18,7 @@ import { createRequire } from 'node:module';
|
|
|
18
18
|
|
|
19
19
|
import { getAuthStatus, syncAuth } from './auth/service.mjs';
|
|
20
20
|
import { SUPPORTED_AGENT_TARGETS } from './auth/agent-registration.mjs';
|
|
21
|
-
import { resolveManagedProfile } from './auth/reconcile.mjs';
|
|
21
|
+
import { fingerprint, resolveManagedProfile } from './auth/reconcile.mjs';
|
|
22
22
|
import { redactSecrets } from './safety-policy.mjs';
|
|
23
23
|
import {
|
|
24
24
|
globalCredentialsPath,
|
|
@@ -36,13 +36,8 @@ import {
|
|
|
36
36
|
} from './proxy/proxy-config.mjs';
|
|
37
37
|
import { removeKooCli, removeObsConfig } from './sandbox/uninstall-cleanup.mjs';
|
|
38
38
|
import { queryDistTagsSync, determineTarget, semverCompare } from './update-check.mjs';
|
|
39
|
-
import {
|
|
40
|
-
|
|
41
|
-
parseHcloudVersion,
|
|
42
|
-
compareVersion,
|
|
43
|
-
kooCliDownloadBase,
|
|
44
|
-
KOO_CLI_BASE,
|
|
45
|
-
} from './koocli-version.mjs';
|
|
39
|
+
import { getKooCliVersion, compareVersion, kooCliDownloadBase, KOO_CLI_BASE } from './koocli-version.mjs';
|
|
40
|
+
import { findHcloudBin, hcloudProbeNextStep, probeHcloud } from './hcloud-probe.mjs';
|
|
46
41
|
|
|
47
42
|
const { DatabaseSync } = createRequire(import.meta.url)('node:sqlite');
|
|
48
43
|
|
|
@@ -420,23 +415,6 @@ function detectCodeartsSandbox() {
|
|
|
420
415
|
}
|
|
421
416
|
}
|
|
422
417
|
|
|
423
|
-
// Locate an existing hcloud executable (HCLOUD_BIN, ~/hcloud on Windows, ~/.local/bin elsewhere).
|
|
424
|
-
function findHcloudBin() {
|
|
425
|
-
if (process.env.HCLOUD_BIN && existsSync(process.env.HCLOUD_BIN)) return process.env.HCLOUD_BIN;
|
|
426
|
-
const isWin = platform() === 'win32';
|
|
427
|
-
const candidates = isWin
|
|
428
|
-
? [join(homedir(), 'hcloud', 'hcloud.exe')]
|
|
429
|
-
: [
|
|
430
|
-
join(homedir(), '.local', 'bin', 'hcloud'),
|
|
431
|
-
join(homedir(), 'hcloud', 'hcloud'),
|
|
432
|
-
join(homedir(), 'hcloud', 'hcloud.exe'),
|
|
433
|
-
];
|
|
434
|
-
for (const c of candidates) {
|
|
435
|
-
if (existsSync(c)) return c;
|
|
436
|
-
}
|
|
437
|
-
return null;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
418
|
function printSandboxWarning(reason) {
|
|
441
419
|
console.log(`\n\x1b[1m\x1b[31m⚠ 检测到码道沙箱模式 (bash_mode: sandbox)\x1b[0m`);
|
|
442
420
|
console.log(`\x1b[31m ${reason}\x1b[0m`);
|
|
@@ -630,27 +608,25 @@ function hasCodexCLI() {
|
|
|
630
608
|
return false;
|
|
631
609
|
}
|
|
632
610
|
|
|
633
|
-
function
|
|
634
|
-
const
|
|
635
|
-
if (!existsSync(bin)) return false;
|
|
636
|
-
try {
|
|
637
|
-
if (statSync(bin).size < 1024) return false;
|
|
638
|
-
} catch {
|
|
639
|
-
return false;
|
|
640
|
-
}
|
|
611
|
+
function getMarketplaceName() {
|
|
612
|
+
const marketplacePath = join(PACKAGE_ROOT, '.agents', 'plugins', 'marketplace.json');
|
|
641
613
|
try {
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
return false;
|
|
647
|
-
}
|
|
614
|
+
const manifest = JSON.parse(readFileSync(marketplacePath, 'utf8'));
|
|
615
|
+
if (manifest.name) return manifest.name;
|
|
616
|
+
} catch {}
|
|
617
|
+
return 'huaweicloud-devkit';
|
|
648
618
|
}
|
|
649
619
|
|
|
650
|
-
function
|
|
620
|
+
function getCodexPluginName() {
|
|
651
621
|
const marketplacePath = join(PACKAGE_ROOT, '.agents', 'plugins', 'marketplace.json');
|
|
652
622
|
try {
|
|
653
|
-
const
|
|
623
|
+
const marketplace = JSON.parse(readFileSync(marketplacePath, 'utf8'));
|
|
624
|
+
const pluginName = marketplace.plugins?.[0]?.name;
|
|
625
|
+
if (pluginName) return pluginName;
|
|
626
|
+
} catch {}
|
|
627
|
+
const codexManifestPath = join(PLUGIN_ROOT, '.codex-plugin', 'plugin.json');
|
|
628
|
+
try {
|
|
629
|
+
const manifest = JSON.parse(readFileSync(codexManifestPath, 'utf8'));
|
|
654
630
|
if (manifest.name) return manifest.name;
|
|
655
631
|
} catch {}
|
|
656
632
|
return 'huaweicloud-devkit';
|
|
@@ -658,7 +634,7 @@ function getMarketplaceName() {
|
|
|
658
634
|
|
|
659
635
|
function installCodex() {
|
|
660
636
|
const marketplaceRoot = PACKAGE_ROOT;
|
|
661
|
-
const pluginName =
|
|
637
|
+
const pluginName = getCodexPluginName();
|
|
662
638
|
const marketplaceName = getMarketplaceName();
|
|
663
639
|
|
|
664
640
|
console.log(` Registering Codex marketplace: ${marketplaceRoot}`);
|
|
@@ -674,6 +650,10 @@ function installCodex() {
|
|
|
674
650
|
console.log(` \x1b[33mUse: npx huaweicloud-devkit install --target codex-desktop\x1b[0m`);
|
|
675
651
|
return false;
|
|
676
652
|
}
|
|
653
|
+
if (r1.status !== 0) {
|
|
654
|
+
console.log(` \x1b[31mCodex marketplace registration failed.\x1b[0m`);
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
677
657
|
|
|
678
658
|
console.log(` Installing plugin: ${pluginName}@${marketplaceName}`);
|
|
679
659
|
const r2 = spawnSync(`codex plugin add "${pluginName}@${marketplaceName}"`, [], {
|
|
@@ -688,20 +668,26 @@ function installCodex() {
|
|
|
688
668
|
console.log(` \x1b[33mUse: npx huaweicloud-devkit install --target codex-desktop\x1b[0m`);
|
|
689
669
|
return false;
|
|
690
670
|
}
|
|
671
|
+
if (r2.status !== 0) {
|
|
672
|
+
console.log(` \x1b[31mCodex plugin installation failed.\x1b[0m`);
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
691
675
|
|
|
692
676
|
return true;
|
|
693
677
|
}
|
|
694
678
|
|
|
695
679
|
function uninstallCodex() {
|
|
696
|
-
const pluginName =
|
|
680
|
+
const pluginName = getCodexPluginName();
|
|
697
681
|
const marketplaceName = getMarketplaceName();
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
682
|
+
for (const name of new Set([pluginName, 'huaweicloud-core'])) {
|
|
683
|
+
console.log(` Removing Codex plugin: ${name}@${marketplaceName}`);
|
|
684
|
+
const r = spawnSync(`codex plugin remove "${name}@${marketplaceName}"`, [], {
|
|
685
|
+
shell: true,
|
|
686
|
+
windowsHide: true,
|
|
687
|
+
stdio: 'pipe',
|
|
688
|
+
});
|
|
689
|
+
console.log(` ${r.stdout ? r.stdout.toString().trim() : r.stderr.toString().trim()}`);
|
|
690
|
+
}
|
|
705
691
|
|
|
706
692
|
for (const name of new Set([marketplaceName, 'HuaweiCloud-Devkit'])) {
|
|
707
693
|
console.log(` Removing Codex marketplace: ${name}`);
|
|
@@ -717,7 +703,7 @@ function uninstallCodex() {
|
|
|
717
703
|
function codexStatus() {
|
|
718
704
|
const r = spawnSync('codex plugin list', [], { shell: true, windowsHide: true, stdio: 'pipe' });
|
|
719
705
|
const out = r.stdout ? r.stdout.toString() : '';
|
|
720
|
-
return out.includes('huaweicloud-core');
|
|
706
|
+
return out.includes(getCodexPluginName()) || out.includes('huaweicloud-core');
|
|
721
707
|
}
|
|
722
708
|
|
|
723
709
|
async function installOpenCode() {
|
|
@@ -3074,75 +3060,116 @@ function checkForUpdate() {
|
|
|
3074
3060
|
}
|
|
3075
3061
|
}
|
|
3076
3062
|
|
|
3063
|
+
function installMarkerDirForTarget(target) {
|
|
3064
|
+
if (target === 'codex') return null;
|
|
3065
|
+
if (target === 'dsh') return dshPluginsDir();
|
|
3066
|
+
if (target === 'codearts') return codeartsPluginsDir();
|
|
3067
|
+
if (target === 'codearts-work') return codeartsWorkPluginsDir();
|
|
3068
|
+
if (target === 'workbuddy') return workbuddyPluginsDir();
|
|
3069
|
+
if (target === 'officeace') return officeacePluginsDir();
|
|
3070
|
+
if (target === 'hermes') return hermesPluginsDir();
|
|
3071
|
+
if (target === 'openclaw') return openclawPluginsDir();
|
|
3072
|
+
if (target === 'atomcode') return atomcodePluginsDir();
|
|
3073
|
+
if (target === 'codex-desktop') return codexDesktopPluginsDir();
|
|
3074
|
+
if (target === 'opencode') return opencodePluginsDir();
|
|
3075
|
+
return null;
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
function writeInstallMarker(target) {
|
|
3079
|
+
const markerDir = installMarkerDirForTarget(target);
|
|
3080
|
+
if (!markerDir) return;
|
|
3081
|
+
mkdirSync(markerDir, { recursive: true });
|
|
3082
|
+
writeFileSync(join(markerDir, '.installed'), new Date().toISOString());
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3077
3085
|
async function cmdInstall() {
|
|
3078
3086
|
const target = parseTarget();
|
|
3079
3087
|
console.log(BANNER);
|
|
3080
3088
|
console.log(`Installing HuaweiCloud DevKit${target !== 'opencode' ? ` for ${target}` : ''}...\n`);
|
|
3081
3089
|
checkNode();
|
|
3082
3090
|
checkForUpdate();
|
|
3091
|
+
const installFailures = [];
|
|
3092
|
+
|
|
3093
|
+
async function runInstallStep(stepTarget, title, fn) {
|
|
3094
|
+
console.log(title);
|
|
3095
|
+
try {
|
|
3096
|
+
const result = await fn();
|
|
3097
|
+
if (result === false) {
|
|
3098
|
+
installFailures.push(stepTarget);
|
|
3099
|
+
return false;
|
|
3100
|
+
}
|
|
3101
|
+
writeInstallMarker(stepTarget);
|
|
3102
|
+
return true;
|
|
3103
|
+
} catch (error) {
|
|
3104
|
+
installFailures.push(stepTarget);
|
|
3105
|
+
console.log(` \x1b[31m${stepTarget} install failed: ${error.message}\x1b[0m`);
|
|
3106
|
+
return false;
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3083
3109
|
|
|
3084
3110
|
if (target === 'opencode' || target === 'all') {
|
|
3085
|
-
|
|
3086
|
-
await installOpenCode();
|
|
3111
|
+
await runInstallStep('opencode', '[OpenCode]', installOpenCode);
|
|
3087
3112
|
}
|
|
3088
3113
|
if (target === 'codex-desktop' || target === 'all') {
|
|
3089
|
-
|
|
3090
|
-
await installCodexDesktop();
|
|
3114
|
+
await runInstallStep('codex-desktop', '\n[Codex Desktop]', installCodexDesktop);
|
|
3091
3115
|
}
|
|
3092
3116
|
if (target === 'codearts' || target === 'all') {
|
|
3093
|
-
|
|
3094
|
-
await installCodeArts();
|
|
3117
|
+
await runInstallStep('codearts', '\n[CodeArts]', installCodeArts);
|
|
3095
3118
|
}
|
|
3096
3119
|
if (target === 'codearts-work' || target === 'all') {
|
|
3097
|
-
|
|
3098
|
-
await installCodeArtsWork();
|
|
3120
|
+
await runInstallStep('codearts-work', '\n[CodeArts Work]', installCodeArtsWork);
|
|
3099
3121
|
}
|
|
3100
3122
|
if (target === 'workbuddy' || target === 'all') {
|
|
3101
|
-
|
|
3102
|
-
await installWorkBuddy();
|
|
3123
|
+
await runInstallStep('workbuddy', '\n[WorkBuddy]', installWorkBuddy);
|
|
3103
3124
|
}
|
|
3104
3125
|
if (target === 'dsh' || target === 'all') {
|
|
3105
|
-
|
|
3106
|
-
await installDsh();
|
|
3126
|
+
await runInstallStep('dsh', '\n[DSH]', installDsh);
|
|
3107
3127
|
}
|
|
3108
3128
|
if (target === 'officeace' || target === 'all') {
|
|
3109
|
-
|
|
3110
|
-
await installOfficeAce();
|
|
3129
|
+
await runInstallStep('officeace', '\n[OfficeAce]', installOfficeAce);
|
|
3111
3130
|
}
|
|
3112
3131
|
if (target === 'hermes' || target === 'all') {
|
|
3113
|
-
|
|
3114
|
-
await installHermes();
|
|
3132
|
+
await runInstallStep('hermes', '\n[Hermes Agent]', installHermes);
|
|
3115
3133
|
}
|
|
3116
3134
|
if (target === 'openclaw' || target === 'all') {
|
|
3117
|
-
|
|
3118
|
-
await installOpenClaw();
|
|
3135
|
+
await runInstallStep('openclaw', '\n[OpenClaw]', installOpenClaw);
|
|
3119
3136
|
}
|
|
3120
3137
|
if (target === 'atomcode' || target === 'all') {
|
|
3121
|
-
|
|
3122
|
-
await installAtomCode();
|
|
3138
|
+
await runInstallStep('atomcode', '\n[AtomCode]', installAtomCode);
|
|
3123
3139
|
}
|
|
3124
3140
|
if (target === 'codex' || target === 'all') {
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3141
|
+
await runInstallStep('codex', '\n[Codex]', () => {
|
|
3142
|
+
if (!hasCodexCLI()) {
|
|
3143
|
+
if (target === 'codex') {
|
|
3144
|
+
console.log(` \x1b[31mCodex CLI not found.\x1b[0m`);
|
|
3145
|
+
if (process.platform === 'win32') {
|
|
3146
|
+
console.log(` \x1b[33mTip: Codex Desktop on Windows installs codex.exe under WindowsApps,\x1b[0m`);
|
|
3147
|
+
console.log(` \x1b[33m which may fail with "Access is denied". Try instead:\x1b[0m`);
|
|
3148
|
+
console.log(` \x1b[33m npx huaweicloud-devkit install --target codex-desktop\x1b[0m`);
|
|
3149
|
+
}
|
|
3150
|
+
console.log(` \x1b[31mOr install Codex CLI: https://github.com/openai/codex-cli\x1b[0m`);
|
|
3151
|
+
return false;
|
|
3152
|
+
}
|
|
3153
|
+
console.log(` \x1b[33mCodex CLI not found. Skipping Codex.\x1b[0m`);
|
|
3129
3154
|
if (process.platform === 'win32') {
|
|
3130
|
-
console.log(
|
|
3131
|
-
|
|
3132
|
-
console.log(
|
|
3155
|
+
console.log(' \x1b[33mTip: try --target codex-desktop for Codex Desktop on Windows\x1b[0m');
|
|
3156
|
+
} else {
|
|
3157
|
+
console.log(' Install Codex CLI to enable: npx huaweicloud-devkit install --target codex');
|
|
3133
3158
|
}
|
|
3134
|
-
|
|
3135
|
-
process.exit(1);
|
|
3136
|
-
}
|
|
3137
|
-
console.log(` \x1b[33mCodex CLI not found. Skipping Codex.\x1b[0m`);
|
|
3138
|
-
if (process.platform === 'win32') {
|
|
3139
|
-
console.log(' \x1b[33mTip: try --target codex-desktop for Codex Desktop on Windows\x1b[0m');
|
|
3140
|
-
} else {
|
|
3141
|
-
console.log(' Install Codex CLI to enable: npx huaweicloud-devkit install --target codex');
|
|
3159
|
+
return true;
|
|
3142
3160
|
}
|
|
3143
|
-
|
|
3144
|
-
|
|
3161
|
+
return installCodex();
|
|
3162
|
+
});
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
if (installFailures.length > 0) {
|
|
3166
|
+
console.log(`\n\x1b[31mInstallation failed for: ${installFailures.join(', ')}\x1b[0m`);
|
|
3167
|
+
if (target === 'all') {
|
|
3168
|
+
console.log(
|
|
3169
|
+
'\x1b[33mSome targets may have been installed before the failure. Re-run status/doctor per target.\x1b[0m',
|
|
3170
|
+
);
|
|
3145
3171
|
}
|
|
3172
|
+
process.exit(1);
|
|
3146
3173
|
}
|
|
3147
3174
|
|
|
3148
3175
|
console.log(`\n\x1b[32mInstallation complete!\x1b[0m`);
|
|
@@ -3197,10 +3224,15 @@ async function cmdInstall() {
|
|
|
3197
3224
|
console.log(`\x1b[1m\x1b[33m╚══════════════════════════════════════════════════════╝\x1b[0m`);
|
|
3198
3225
|
}
|
|
3199
3226
|
|
|
3200
|
-
const
|
|
3201
|
-
if (!
|
|
3202
|
-
|
|
3203
|
-
|
|
3227
|
+
const hcloudProbe = probeHcloud();
|
|
3228
|
+
if (!hcloudProbe.ok) {
|
|
3229
|
+
if (hcloudProbe.status === 'sandbox_home_failure') {
|
|
3230
|
+
console.log(`\n\x1b[33mKooCLI (hcloud) detected, but cannot run in this sandbox.`);
|
|
3231
|
+
console.log(` ${hcloudProbeNextStep(hcloudProbe)}\x1b[0m`);
|
|
3232
|
+
} else {
|
|
3233
|
+
console.log(`\n\x1b[33mKooCLI (hcloud) is not ready.`);
|
|
3234
|
+
console.log(` ${hcloudProbeNextStep(hcloudProbe)}\x1b[0m`);
|
|
3235
|
+
}
|
|
3204
3236
|
} else {
|
|
3205
3237
|
console.log(`\nKooCLI (hcloud) detected.`);
|
|
3206
3238
|
}
|
|
@@ -3219,27 +3251,6 @@ async function cmdInstall() {
|
|
|
3219
3251
|
}
|
|
3220
3252
|
console.log(` 4. 运行自检:npx huaweicloud-devkit doctor`);
|
|
3221
3253
|
|
|
3222
|
-
// Write install marker for doctor to detect
|
|
3223
|
-
const markerDir =
|
|
3224
|
-
target === 'dsh'
|
|
3225
|
-
? dshPluginsDir()
|
|
3226
|
-
: target === 'codearts'
|
|
3227
|
-
? codeartsPluginsDir()
|
|
3228
|
-
: target === 'codearts-work'
|
|
3229
|
-
? codeartsWorkPluginsDir()
|
|
3230
|
-
: target === 'workbuddy'
|
|
3231
|
-
? workbuddyPluginsDir()
|
|
3232
|
-
: target === 'officeace'
|
|
3233
|
-
? officeacePluginsDir()
|
|
3234
|
-
: target === 'openclaw'
|
|
3235
|
-
? codexDesktopPluginsDir()
|
|
3236
|
-
: target === 'atomcode'
|
|
3237
|
-
? atomcodePluginsDir()
|
|
3238
|
-
: target === 'codex-desktop'
|
|
3239
|
-
? codexDesktopPluginsDir()
|
|
3240
|
-
: opencodePluginsDir();
|
|
3241
|
-
mkdirSync(markerDir, { recursive: true });
|
|
3242
|
-
writeFileSync(join(markerDir, '.installed'), new Date().toISOString());
|
|
3243
3254
|
if (target === 'opencode' || target === 'all') {
|
|
3244
3255
|
console.log('Or describe your Huawei Cloud task in OpenCode');
|
|
3245
3256
|
}
|
|
@@ -3250,7 +3261,7 @@ async function cmdInstall() {
|
|
|
3250
3261
|
console.log('Or describe your Huawei Cloud task in CodeArts Work');
|
|
3251
3262
|
}
|
|
3252
3263
|
if (target === 'codex' || target === 'all') {
|
|
3253
|
-
console.log('Or mention @huaweicloud-
|
|
3264
|
+
console.log('Or mention @huaweicloud-devkit in Codex');
|
|
3254
3265
|
}
|
|
3255
3266
|
if (target === 'workbuddy' || target === 'all') {
|
|
3256
3267
|
console.log('Or describe your Huawei Cloud task in WorkBuddy');
|
|
@@ -3553,6 +3564,21 @@ async function cmdDoctor() {
|
|
|
3553
3564
|
existsSync(join(atomcodePluginDir, 'safety', 'policy.json'));
|
|
3554
3565
|
check('Safety policy installed', safetyOk, 'Run: npx huaweicloud-devkit install');
|
|
3555
3566
|
|
|
3567
|
+
const hookConfigPath = join(PLUGIN_ROOT, 'hooks', 'hooks.json');
|
|
3568
|
+
const hookNodePath = join(PLUGIN_ROOT, 'hooks', 'huaweicloud-safety.mjs');
|
|
3569
|
+
let hookConfigUsesNode = false;
|
|
3570
|
+
let hookConfigUsesPython = false;
|
|
3571
|
+
try {
|
|
3572
|
+
const hookConfigText = readFileSync(hookConfigPath, 'utf8');
|
|
3573
|
+
hookConfigUsesNode = hookConfigText.includes('huaweicloud-safety.mjs') && /\bnode\b/.test(hookConfigText);
|
|
3574
|
+
hookConfigUsesPython = /\bpython3?\b/.test(hookConfigText);
|
|
3575
|
+
} catch {}
|
|
3576
|
+
check(
|
|
3577
|
+
'Safety hook runtime (Node)',
|
|
3578
|
+
existsSync(hookNodePath) && hookConfigUsesNode && !hookConfigUsesPython,
|
|
3579
|
+
'hooks/hooks.json should invoke node hooks/huaweicloud-safety.mjs, not python3',
|
|
3580
|
+
);
|
|
3581
|
+
|
|
3556
3582
|
// MCP config — check OpenCode, Codex Desktop, CodeArts, WorkBuddy, and DSH
|
|
3557
3583
|
let mcpConfigured = false;
|
|
3558
3584
|
let mcpCfgTarget = '';
|
|
@@ -3664,20 +3690,23 @@ async function cmdDoctor() {
|
|
|
3664
3690
|
}
|
|
3665
3691
|
|
|
3666
3692
|
// hcloud CLI
|
|
3667
|
-
const
|
|
3668
|
-
const
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3693
|
+
const hcloudProbe = probeHcloud();
|
|
3694
|
+
const hcloudBin = hcloudProbe.executable;
|
|
3695
|
+
const hcloudOk = hcloudProbe.ok;
|
|
3696
|
+
check('hcloud CLI installed', hcloudProbe.installed, hcloudProbeNextStep(hcloudProbe));
|
|
3697
|
+
if (hcloudProbe.status === 'sandbox_home_failure') {
|
|
3698
|
+
warn++;
|
|
3699
|
+
console.log(` \x1b[33m[WARN]\x1b[0m hcloud cannot resolve the Windows user home in this agent sandbox.`);
|
|
3700
|
+
console.log(` ${hcloudProbeNextStep(hcloudProbe)}`);
|
|
3701
|
+
} else if (hcloudProbe.status === 'privacy_pending') {
|
|
3702
|
+
warn++;
|
|
3703
|
+
console.log(` \x1b[33m[WARN]\x1b[0m KooCLI privacy agreement is pending.`);
|
|
3704
|
+
console.log(` ${hcloudProbeNextStep(hcloudProbe)}`);
|
|
3705
|
+
}
|
|
3677
3706
|
|
|
3678
3707
|
const kooCliTarget = getKooCliVersion();
|
|
3679
3708
|
if (hcloudOk && kooCliTarget) {
|
|
3680
|
-
const installed =
|
|
3709
|
+
const installed = hcloudProbe.installedVersion;
|
|
3681
3710
|
if (installed && compareVersion(installed, kooCliTarget) !== 0) {
|
|
3682
3711
|
warn++;
|
|
3683
3712
|
console.log(
|
|
@@ -3701,7 +3730,7 @@ async function cmdDoctor() {
|
|
|
3701
3730
|
}
|
|
3702
3731
|
|
|
3703
3732
|
if (hcloudOk) {
|
|
3704
|
-
const ver =
|
|
3733
|
+
const ver = hcloudProbe.installedVersion || 'unknown';
|
|
3705
3734
|
console.log(` Version: ${ver}`);
|
|
3706
3735
|
|
|
3707
3736
|
// Check auth
|
|
@@ -4373,12 +4402,18 @@ async function cmdAuthReconcile() {
|
|
|
4373
4402
|
console.log(`OBS sync failed: ${error.message}`);
|
|
4374
4403
|
}
|
|
4375
4404
|
const profile = resolveManagedProfile();
|
|
4405
|
+
let hcloudSynced = false;
|
|
4376
4406
|
if (profile) {
|
|
4377
4407
|
const r = runHcloudConfigure(profile, credentials.ak, credentials.sk, credentials.region);
|
|
4408
|
+
hcloudSynced = r.ok;
|
|
4378
4409
|
console.log(` KooCLI ${r.ok ? 'synced' : 'sync failed'}: profile=${profile} ${r.error || ''}`);
|
|
4379
4410
|
}
|
|
4380
|
-
|
|
4381
|
-
|
|
4411
|
+
if (hcloudSynced) {
|
|
4412
|
+
writeLastSync({ kooCliProfile: profile, s1Fingerprint: fingerprint(credentials.ak, credentials.sk) });
|
|
4413
|
+
console.log('Done. .last_sync refreshed.');
|
|
4414
|
+
} else {
|
|
4415
|
+
console.log('Done. .last_sync was not refreshed because KooCLI sync did not complete.');
|
|
4416
|
+
}
|
|
4382
4417
|
}
|
|
4383
4418
|
|
|
4384
4419
|
async function cmdAuthSync() {
|
|
@@ -57,7 +57,7 @@ import {
|
|
|
57
57
|
resolveSkipFilePath,
|
|
58
58
|
upgradePackage,
|
|
59
59
|
} from './update-check.mjs';
|
|
60
|
-
import {
|
|
60
|
+
import { hcloudProbeNextStep, probeHcloud } from './hcloud-probe.mjs';
|
|
61
61
|
|
|
62
62
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
63
63
|
const SKILLS_ROOT_DEV = join(__dirname, '..', 'skills');
|
|
@@ -984,7 +984,9 @@ function persistCredentials(ak, sk, securityToken, region) {
|
|
|
984
984
|
} else {
|
|
985
985
|
hcloud = runHcloudConfigure(profile, ak, sk, region);
|
|
986
986
|
}
|
|
987
|
-
|
|
987
|
+
if (hcloud.ok) {
|
|
988
|
+
writeLastSync({ kooCliProfile: profile, s1Fingerprint: fingerprint(ak, sk) });
|
|
989
|
+
}
|
|
988
990
|
return {
|
|
989
991
|
status: 'ok',
|
|
990
992
|
scope: 'persist',
|
|
@@ -1382,35 +1384,17 @@ function hookResult(result) {
|
|
|
1382
1384
|
}
|
|
1383
1385
|
|
|
1384
1386
|
export async function runVersionCheck(options = {}) {
|
|
1385
|
-
const result =
|
|
1386
|
-
...options,
|
|
1387
|
-
maxRetries: options.maxRetries ?? 0,
|
|
1388
|
-
});
|
|
1389
|
-
const errorText = result.error || result.stderr || '';
|
|
1390
|
-
const isSpawnError = /ENOENT|SPAWN_ERROR/i.test(errorText) || result.code === 'SPAWN_ERROR';
|
|
1391
|
-
const requiredVersion = getKooCliVersion();
|
|
1392
|
-
const installedVersion = parseHcloudVersion(result.stdout || '');
|
|
1393
|
-
const versionMismatch = !!(
|
|
1394
|
-
result.ok &&
|
|
1395
|
-
requiredVersion &&
|
|
1396
|
-
installedVersion &&
|
|
1397
|
-
compareVersion(installedVersion, requiredVersion) !== 0
|
|
1398
|
-
);
|
|
1387
|
+
const result = probeHcloud(options);
|
|
1399
1388
|
return {
|
|
1400
|
-
installed: result.
|
|
1401
|
-
authenticated: result.ok && !/配置文件中不存在配置项|USE_ERROR.*配置/i.test(result.
|
|
1402
|
-
errorCode:
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
? `KooCLI version mismatch: installed ${installedVersion}, this plugin is paired with ${requiredVersion}. Reinstall the pinned version (see huaweicloud-cli-and-auth skill) and restart the agent.`
|
|
1410
|
-
: 'Use huaweicloud_show_profile_redacted to inspect the active KooCLI profile safely.'
|
|
1411
|
-
: isSpawnError
|
|
1412
|
-
? 'hcloud executable not found. Set HCLOUD_BIN to the full hcloud path, or install KooCLI: npx huaweicloud-devkit install-hcloud. Then restart the agent.'
|
|
1413
|
-
: 'Install Huawei Cloud KooCLI: npx huaweicloud-devkit install-hcloud. Configure credentials outside the agent conversation.',
|
|
1389
|
+
installed: result.installed,
|
|
1390
|
+
authenticated: result.ok && !/配置文件中不存在配置项|USE_ERROR.*配置/i.test(result.output || ''),
|
|
1391
|
+
errorCode: result.errorCode,
|
|
1392
|
+
status: result.status,
|
|
1393
|
+
output: result.output,
|
|
1394
|
+
kooCliVersion: result.requiredVersion || undefined,
|
|
1395
|
+
installedVersion: result.installedVersion || undefined,
|
|
1396
|
+
versionMismatch: Boolean(result.versionMismatch),
|
|
1397
|
+
nextStep: hcloudProbeNextStep(result),
|
|
1414
1398
|
authHint:
|
|
1415
1399
|
'If hcloud is installed but commands fail with "配置文件中不存在配置项", run `npx huaweicloud-devkit auth init` outside agent chat to configure credentials.',
|
|
1416
1400
|
};
|