huaweicloud-devkit 1.1.2-next.7 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +7 -1
- package/plugins/huaweicloud-core/src/setup-cli.mjs +199 -87
- package/plugins/huaweicloud-core/src/update-check.mjs +3 -1
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
|
|
4
|
+
"version": "1.1.2",
|
|
5
5
|
"kooCliVersion": "7.2.12",
|
|
6
6
|
"description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
|
|
7
7
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"mcpServers": "./.mcp.json",
|
|
18
18
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
19
19
|
"skills": "./skills/",
|
|
20
|
-
"version": "1.1.2
|
|
20
|
+
"version": "1.1.2",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"mcpServers": "./.mcp.json",
|
|
21
21
|
"description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
|
|
22
22
|
"skills": "./skills/",
|
|
23
|
-
"version": "1.1.2
|
|
23
|
+
"version": "1.1.2",
|
|
24
24
|
"author": {
|
|
25
25
|
"name": "HuaweiCloud Mate",
|
|
26
26
|
"url": "https://github.com/huaweicloud"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"mcpServers": "./.mcp.json",
|
|
18
18
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
19
19
|
"skills": "./skills/",
|
|
20
|
-
"version": "1.1.2
|
|
20
|
+
"version": "1.1.2",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.2
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.2
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
3
|
"id": "huaweicloud-devkit",
|
|
4
4
|
"displayName": "HuaweiCloud DevKit",
|
|
5
|
-
"version": "1.1.2
|
|
5
|
+
"version": "1.1.2",
|
|
6
6
|
"family": "bundle-plugin",
|
|
7
7
|
"bundleFormat": "codex",
|
|
8
8
|
"description": "Guide coding agents to use Huawei Cloud safely — KooCLI, APIs, SDKs, 28 MCP tools, skills, and safety guardrails.",
|
|
@@ -112,7 +112,13 @@ Setup is a **plugin-side preflight** — the developer should be asked a questio
|
|
|
112
112
|
sudo nginx -s reload 2>/dev/null || true
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
7. **Inject credentials** (optional): `huaweicloud_sandbox_credentials` — enables cloud API access from sandbox.
|
|
115
|
+
7. **Inject credentials** (optional): `huaweicloud_sandbox_credentials` — enables cloud API access from sandbox. Pass `session_id` **or** `dev_stage_id` (at least one; both come from `sandbox_connect`, and `dev_stage_id` may be omitted to reuse the most recent connect). The tool validates the AK/SK against IAM first — an invalid SK is rejected here instead of failing later with `APIGW.0301` during exec. Example:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{ "dev_stage_id": "<dev_stage_id from connect>" }
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
After injection, credentials are also written to `/tmp/hw_creds.sh` for shell access (includes `HW_PROJECT_ID` when resolvable):
|
|
116
122
|
|
|
117
123
|
```bash
|
|
118
124
|
# Source credentials in any sandbox shell before using hcloud/devbridge:
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from 'node:fs';
|
|
11
11
|
import { join, dirname, resolve } from 'node:path';
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
|
-
import { homedir, platform } from 'node:os';
|
|
13
|
+
import { homedir, platform, tmpdir } from 'node:os';
|
|
14
14
|
import { createInterface } from 'node:readline';
|
|
15
15
|
import { spawnSync } from 'node:child_process';
|
|
16
16
|
import { randomUUID } from 'node:crypto';
|
|
@@ -3120,6 +3120,10 @@ async function cmdInstall() {
|
|
|
3120
3120
|
if (target === 'codearts-work' || target === 'all') {
|
|
3121
3121
|
await runInstallStep('codearts-work', '\n[CodeArts Work]', installCodeArtsWork);
|
|
3122
3122
|
}
|
|
3123
|
+
if (target === 'codearts-work' || target === 'all') {
|
|
3124
|
+
console.log('\n[CodeArts Work]');
|
|
3125
|
+
await installCodeArtsWork();
|
|
3126
|
+
}
|
|
3123
3127
|
if (target === 'workbuddy' || target === 'all') {
|
|
3124
3128
|
await runInstallStep('workbuddy', '\n[WorkBuddy]', installWorkBuddy);
|
|
3125
3129
|
}
|
|
@@ -3138,6 +3142,26 @@ async function cmdInstall() {
|
|
|
3138
3142
|
if (target === 'atomcode' || target === 'all') {
|
|
3139
3143
|
await runInstallStep('atomcode', '\n[AtomCode]', installAtomCode);
|
|
3140
3144
|
}
|
|
3145
|
+
if (target === 'dsh' || target === 'all') {
|
|
3146
|
+
console.log('\n[DSH]');
|
|
3147
|
+
await installDsh();
|
|
3148
|
+
}
|
|
3149
|
+
if (target === 'officeace' || target === 'all') {
|
|
3150
|
+
console.log('\n[OfficeAce]');
|
|
3151
|
+
await installOfficeAce();
|
|
3152
|
+
}
|
|
3153
|
+
if (target === 'hermes' || target === 'all') {
|
|
3154
|
+
console.log('\n[Hermes Agent]');
|
|
3155
|
+
await installHermes();
|
|
3156
|
+
}
|
|
3157
|
+
if (target === 'openclaw' || target === 'all') {
|
|
3158
|
+
console.log('\n[OpenClaw]');
|
|
3159
|
+
await installOpenClaw();
|
|
3160
|
+
}
|
|
3161
|
+
if (target === 'atomcode' || target === 'all') {
|
|
3162
|
+
console.log('\n[AtomCode]');
|
|
3163
|
+
await installAtomCode();
|
|
3164
|
+
}
|
|
3141
3165
|
if (target === 'codex' || target === 'all') {
|
|
3142
3166
|
await runInstallStep('codex', '\n[Codex]', () => {
|
|
3143
3167
|
if (!hasCodexCLI()) {
|
|
@@ -3404,72 +3428,89 @@ async function promptGlobalCleanup() {
|
|
|
3404
3428
|
}
|
|
3405
3429
|
}
|
|
3406
3430
|
|
|
3431
|
+
function openclawStatus() {
|
|
3432
|
+
const cdPluginDir = openclawPluginsDir();
|
|
3433
|
+
const cdSkillsDir = openclawSkillsDir();
|
|
3434
|
+
console.log(
|
|
3435
|
+
` MCP Server: ${existsSync(join(cdPluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
3436
|
+
);
|
|
3437
|
+
console.log(
|
|
3438
|
+
` Safety Policy: ${existsSync(join(cdPluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
3439
|
+
);
|
|
3440
|
+
let cdSkillCount = 0;
|
|
3441
|
+
if (existsSync(cdSkillsDir)) {
|
|
3442
|
+
cdSkillCount = readdirSync(cdSkillsDir, { withFileTypes: true }).filter(
|
|
3443
|
+
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
3444
|
+
).length;
|
|
3445
|
+
}
|
|
3446
|
+
console.log(
|
|
3447
|
+
` Skills: ${cdSkillCount > 0 ? `\x1b[32m${cdSkillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
3448
|
+
);
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3451
|
+
function codexSectionStatus() {
|
|
3452
|
+
if (!hasCodexCLI()) {
|
|
3453
|
+
console.log(' \x1b[33mCodex CLI not found.\x1b[0m');
|
|
3454
|
+
} else {
|
|
3455
|
+
console.log(` Plugin: ${codexStatus() ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3407
3459
|
async function cmdStatus() {
|
|
3408
|
-
const
|
|
3460
|
+
const scope = parseTarget();
|
|
3409
3461
|
console.log(BANNER);
|
|
3410
3462
|
console.log(`HuaweiCloud DevKit Status\n`);
|
|
3411
3463
|
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
console.log('\n[Hermes Agent]');
|
|
3438
|
-
hermesStatus();
|
|
3439
|
-
}
|
|
3440
|
-
if (target === 'openclaw' || target === 'all') {
|
|
3441
|
-
console.log('\n[OpenClaw]');
|
|
3442
|
-
const cdPluginDir = openclawPluginsDir();
|
|
3443
|
-
const cdSkillsDir = openclawSkillsDir();
|
|
3444
|
-
console.log(
|
|
3445
|
-
` MCP Server: ${existsSync(join(cdPluginDir, 'src', 'mcp-server.mjs')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
3446
|
-
);
|
|
3447
|
-
console.log(
|
|
3448
|
-
` Safety Policy: ${existsSync(join(cdPluginDir, 'safety', 'policy.json')) ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`,
|
|
3449
|
-
);
|
|
3450
|
-
let cdSkillCount = 0;
|
|
3451
|
-
if (existsSync(cdSkillsDir)) {
|
|
3452
|
-
cdSkillCount = readdirSync(cdSkillsDir, { withFileTypes: true }).filter(
|
|
3453
|
-
(d) => d.isDirectory() && d.name.startsWith('huawei'),
|
|
3454
|
-
).length;
|
|
3455
|
-
}
|
|
3456
|
-
console.log(
|
|
3457
|
-
` Skills: ${cdSkillCount > 0 ? `\x1b[32m${cdSkillCount} installed\x1b[0m` : '\x1b[31mNot installed\x1b[0m'}`,
|
|
3458
|
-
);
|
|
3459
|
-
}
|
|
3460
|
-
if (target === 'atomcode' || target === 'all') {
|
|
3461
|
-
console.log('\n[AtomCode]');
|
|
3462
|
-
atomcodeStatus();
|
|
3463
|
-
}
|
|
3464
|
-
if (target === 'codex' || target === 'all') {
|
|
3465
|
-
console.log('\n[Codex]');
|
|
3466
|
-
if (!hasCodexCLI()) {
|
|
3467
|
-
console.log(' \x1b[33mCodex CLI not found.\x1b[0m');
|
|
3468
|
-
} else {
|
|
3469
|
-
console.log(` Plugin: ${codexStatus() ? '\x1b[32mInstalled\x1b[0m' : '\x1b[31mNot installed\x1b[0m'}`);
|
|
3464
|
+
const sections = [
|
|
3465
|
+
{ name: 'OpenCode', run: opencodeStatus, on: (target) => target === 'opencode' || target === 'all' },
|
|
3466
|
+
{ name: 'CodeArts', run: codeartsStatus, on: (target) => target === 'codearts' || target === 'all' },
|
|
3467
|
+
{ name: 'CodeArts Work', run: codeartsWorkStatus, on: (target) => target === 'codearts-work' || target === 'all' },
|
|
3468
|
+
{ name: 'WorkBuddy', run: workbuddyStatus, on: (target) => target === 'workbuddy' || target === 'all' },
|
|
3469
|
+
{ name: 'DSH', run: dshStatus, on: (target) => target === 'dsh' || target === 'all' },
|
|
3470
|
+
{ name: 'OfficeAce', run: officeaceStatus, on: (target) => target === 'officeace' || target === 'all' },
|
|
3471
|
+
{ name: 'Hermes Agent', run: hermesStatus, on: (target) => target === 'hermes' || target === 'all' },
|
|
3472
|
+
{ name: 'OpenClaw', run: openclawStatus, on: (target) => target === 'openclaw' || target === 'all' },
|
|
3473
|
+
{ name: 'AtomCode', run: atomcodeStatus, on: (target) => target === 'atomcode' || target === 'all' },
|
|
3474
|
+
{ name: 'Codex', run: codexSectionStatus, on: (target) => target === 'codex' || target === 'all' },
|
|
3475
|
+
];
|
|
3476
|
+
|
|
3477
|
+
// Capture each section's output so results can be grouped by install state
|
|
3478
|
+
// (installed first) instead of the fixed agent order.
|
|
3479
|
+
const originalLog = console.log;
|
|
3480
|
+
const results = [];
|
|
3481
|
+
for (const section of sections) {
|
|
3482
|
+
if (!section.on(scope)) continue;
|
|
3483
|
+
const lines = [];
|
|
3484
|
+
console.log = (...args) => lines.push(args.map((a) => String(a)).join(' '));
|
|
3485
|
+
try {
|
|
3486
|
+
section.run();
|
|
3487
|
+
} finally {
|
|
3488
|
+
console.log = originalLog;
|
|
3470
3489
|
}
|
|
3490
|
+
const plain = lines.join('\n');
|
|
3491
|
+
const hasInstalled = /Installed/.test(plain);
|
|
3492
|
+
const hasMissing = /Not installed|not found/i.test(plain);
|
|
3493
|
+
const state = hasInstalled && hasMissing ? 'partial' : hasInstalled ? 'installed' : hasMissing ? 'not' : 'unknown';
|
|
3494
|
+
results.push({ name: section.name, lines, state });
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
const stateOrder = { installed: 0, partial: 1, unknown: 2, not: 3 };
|
|
3498
|
+
results.sort((a, b) => stateOrder[a.state] - stateOrder[b.state]);
|
|
3499
|
+
|
|
3500
|
+
const namesIn = (state) => results.filter((r) => r.state === state).map((r) => r.name);
|
|
3501
|
+
const summaryParts = [];
|
|
3502
|
+
if (namesIn('installed').length) summaryParts.push(`已安装: ${namesIn('installed').join(', ')}`);
|
|
3503
|
+
if (namesIn('partial').length) summaryParts.push(`部分安装: ${namesIn('partial').join(', ')}`);
|
|
3504
|
+
if (namesIn('not').length) summaryParts.push(`未安装: ${namesIn('not').join(', ')}`);
|
|
3505
|
+
if (summaryParts.length) console.log(` ${summaryParts.join(' | ')}\n`);
|
|
3506
|
+
|
|
3507
|
+
for (const result of results) {
|
|
3508
|
+
console.log(`[${result.name}]`);
|
|
3509
|
+
for (const line of result.lines) console.log(line);
|
|
3510
|
+
console.log('');
|
|
3471
3511
|
}
|
|
3472
|
-
|
|
3512
|
+
|
|
3513
|
+
console.log('Environment:');
|
|
3473
3514
|
console.log(` Node.js: ${process.version}`);
|
|
3474
3515
|
console.log(` Platform: ${platform()}`);
|
|
3475
3516
|
}
|
|
@@ -3812,6 +3853,7 @@ async function cmdDoctor() {
|
|
|
3812
3853
|
{ path: join(codeartsPluginsDir(), '.installed'), name: 'CodeArts' },
|
|
3813
3854
|
{ path: join(codeartsWorkPluginsDir(), '.installed'), name: 'CodeArts Work' },
|
|
3814
3855
|
{ path: join(dshPluginsDir(), '.installed'), name: 'DSH' },
|
|
3856
|
+
{ path: join(hermesPluginsDir(), '.installed'), name: 'Hermes Agent' },
|
|
3815
3857
|
{ path: join(officeacePluginsDir(), '.installed'), name: 'OfficeAce' },
|
|
3816
3858
|
{ path: join(atomcodePluginsDir(), '.installed'), name: 'AtomCode' },
|
|
3817
3859
|
];
|
|
@@ -4167,32 +4209,102 @@ async function cmdInstallHcloud() {
|
|
|
4167
4209
|
printSandboxWarning('沙箱模式拦截了 KooCLI 自动安装(无法创建/写入安装目录)。');
|
|
4168
4210
|
}
|
|
4169
4211
|
}
|
|
4170
|
-
} else if (os === 'linux') {
|
|
4171
|
-
|
|
4172
|
-
const pkg =
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
);
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4212
|
+
} else if (os === 'linux' || os === 'darwin') {
|
|
4213
|
+
const label = os === 'linux' ? 'Linux' : 'macOS';
|
|
4214
|
+
const pkg =
|
|
4215
|
+
os === 'linux'
|
|
4216
|
+
? arch === 'arm64'
|
|
4217
|
+
? 'linux-arm64'
|
|
4218
|
+
: 'linux-amd64'
|
|
4219
|
+
: arch === 'arm64'
|
|
4220
|
+
? 'mac-arm64'
|
|
4221
|
+
: 'mac-amd64';
|
|
4222
|
+
const url = `${baseUrl}/huaweicloud-cli-${pkg}.tar.gz`;
|
|
4223
|
+
const binDir = os === 'linux' ? join(homedir(), '.local', 'bin') : '/usr/local/bin';
|
|
4224
|
+
const binPath = join(binDir, 'hcloud');
|
|
4225
|
+
|
|
4226
|
+
const printManual = () => {
|
|
4227
|
+
console.log(`\n[${label}] Manual install (pinned to KooCLI ${kooCliVersion || ''}):`);
|
|
4228
|
+
console.log(` curl -LO "${url}"`);
|
|
4229
|
+
console.log(` tar -zxvf huaweicloud-cli-${pkg}.tar.gz`);
|
|
4230
|
+
console.log(` mv hcloud ${binDir}/`);
|
|
4231
|
+
console.log(` hcloud version`);
|
|
4232
|
+
console.log(`\n Or one-liner (installs LATEST, may differ from pinned ${kooCliVersion || ''}):`);
|
|
4233
|
+
console.log(` curl -sSL ${latestUrl}/hcloud_install.sh -o ./hcloud_install.sh && bash ./hcloud_install.sh -y`);
|
|
4234
|
+
console.log(
|
|
4235
|
+
` \x1b[33m⚠️ The one-liner installs the latest KooCLI. If \`hcloud version\` != ${kooCliVersion}, doctor/check_cli will warn — use the pinned download above instead.\x1b[0m`,
|
|
4236
|
+
);
|
|
4237
|
+
console.log(
|
|
4238
|
+
`\nFull guide: https://support.huaweicloud.com/qs-hcli/hcli_02_003_0${os === 'linux' ? '2' : '3'}.html`,
|
|
4239
|
+
);
|
|
4240
|
+
};
|
|
4241
|
+
|
|
4242
|
+
if (detectCodeartsSandbox() === 'sandbox') {
|
|
4243
|
+
printSandboxWarning('沙箱模式拦截了 KooCLI 自动安装(无法创建/写入安装目录)。');
|
|
4244
|
+
printManual();
|
|
4245
|
+
} else {
|
|
4246
|
+
console.log(`[${label}] Auto-installing to ${binDir}...`);
|
|
4247
|
+
try {
|
|
4248
|
+
const tmpTar = join(tmpdir(), `huaweicloud-cli-${pkg}.tar.gz`);
|
|
4249
|
+
console.log(` Downloading ${url}...`);
|
|
4250
|
+
const dl = spawnSync('curl', ['-fL', url, '-o', tmpTar], { stdio: 'inherit', timeout: 180000 });
|
|
4251
|
+
if (dl.status !== 0) throw new Error(`下载失败 (curl exit ${dl.status})`);
|
|
4252
|
+
|
|
4253
|
+
console.log(' Extracting...');
|
|
4254
|
+
const ex = spawnSync('tar', ['-xzf', tmpTar, '-C', tmpdir()], { stdio: 'inherit', timeout: 60000 });
|
|
4255
|
+
if (ex.status !== 0) throw new Error(`解压失败 (tar exit ${ex.status})`);
|
|
4256
|
+
rmSync(tmpTar, { force: true });
|
|
4257
|
+
const extracted = join(tmpdir(), 'hcloud');
|
|
4258
|
+
if (!existsSync(extracted)) throw new Error('hcloud 二进制未生成,可能已被安全软件拦截');
|
|
4259
|
+
|
|
4260
|
+
try {
|
|
4261
|
+
mkdirSync(binDir, { recursive: true });
|
|
4262
|
+
} catch {}
|
|
4263
|
+
console.log(' Installing...');
|
|
4264
|
+
let mv = spawnSync('mv', [extracted, binPath], { stdio: 'inherit', timeout: 30000 });
|
|
4265
|
+
if (mv.status !== 0) {
|
|
4266
|
+
console.log(' 目标目录需要权限,尝试 sudo mv...');
|
|
4267
|
+
mv = spawnSync('sudo', ['mv', extracted, binPath], { stdio: 'inherit', timeout: 30000 });
|
|
4268
|
+
if (mv.status !== 0) throw new Error(`安装失败 (mv exit ${mv.status})`);
|
|
4269
|
+
}
|
|
4270
|
+
|
|
4271
|
+
console.log(`\n\x1b[32mInstall complete.\x1b[0m`);
|
|
4272
|
+
console.log(` Verify: ${binPath} version`);
|
|
4273
|
+
|
|
4274
|
+
// Ask user before accepting the privacy agreement — never auto-accept.
|
|
4275
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
4276
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
4277
|
+
const agree = await new Promise((resolve) => {
|
|
4278
|
+
rl.question('\n KooCLI requires accepting its privacy agreement. Do you accept? (y/N) ', (answer) => {
|
|
4279
|
+
rl.close();
|
|
4280
|
+
resolve(/^\s*y\s*$/i.test(answer));
|
|
4281
|
+
});
|
|
4282
|
+
});
|
|
4283
|
+
if (agree) {
|
|
4284
|
+
const r = spawnSync(binPath, ['version'], {
|
|
4285
|
+
input: 'y\n',
|
|
4286
|
+
encoding: 'utf8',
|
|
4287
|
+
timeout: 15000,
|
|
4288
|
+
});
|
|
4289
|
+
console.log(
|
|
4290
|
+
r.status === 0
|
|
4291
|
+
? ' \x1b[32mPrivacy agreement accepted. KooCLI ready.\x1b[0m'
|
|
4292
|
+
: ' \x1b[33m无法写入配置目录。请在终端运行: hcloud version 并按提示操作\x1b[0m',
|
|
4293
|
+
);
|
|
4294
|
+
} else {
|
|
4295
|
+
console.log(' \x1b[33m请手动接受隐私协议:在终端运行 hcloud version 并按提示操作\x1b[0m');
|
|
4296
|
+
}
|
|
4297
|
+
} else {
|
|
4298
|
+
console.log(' \x1b[33m非交互会话:请稍后在终端运行 hcloud version 并按提示接受隐私协议\x1b[0m');
|
|
4299
|
+
}
|
|
4300
|
+
} catch (error) {
|
|
4301
|
+
console.log(`\n\x1b[33mAuto-install failed: ${error.message}\x1b[0m`);
|
|
4302
|
+
printManual();
|
|
4303
|
+
if (detectCodeartsSandbox() === 'sandbox') {
|
|
4304
|
+
printSandboxWarning('沙箱模式拦截了 KooCLI 自动安装(无法创建/写入安装目录)。');
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4196
4308
|
}
|
|
4197
4309
|
|
|
4198
4310
|
console.log('\nAfter install, set HCLOUD_BIN if hcloud is not on PATH.');
|
|
@@ -323,7 +323,9 @@ export async function upgradePackage({ target = 'all', version = 'latest' } = {}
|
|
|
323
323
|
return { success: false, error: 'version 参数仅支持 latest。目标版本由插件自动判定。' };
|
|
324
324
|
}
|
|
325
325
|
const { doQuery = queryDistTags, spawnFn = defaultSpawn } = options;
|
|
326
|
-
|
|
326
|
+
// Tests inject currentVersion explicitly - the repo package.json version changes
|
|
327
|
+
// between prerelease and stable lines, which must not flip the upgrade-tag logic.
|
|
328
|
+
const previousVersion = options.currentVersion || readInstalledVersion();
|
|
327
329
|
let distTags;
|
|
328
330
|
try {
|
|
329
331
|
distTags = await doQuery();
|