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 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.7",
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-next.7",
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-next.7",
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-next.7",
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-next.7",
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-next.7",
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-next.7",
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. After injection, credentials are also written to `/tmp/hw_creds.sh` for shell access:
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 target = parseTarget();
3460
+ const scope = parseTarget();
3409
3461
  console.log(BANNER);
3410
3462
  console.log(`HuaweiCloud DevKit Status\n`);
3411
3463
 
3412
- if (target === 'opencode' || target === 'all') {
3413
- console.log('[OpenCode]');
3414
- opencodeStatus();
3415
- }
3416
- if (target === 'codearts' || target === 'all') {
3417
- console.log('\n[CodeArts]');
3418
- codeartsStatus();
3419
- }
3420
- if (target === 'codearts-work' || target === 'all') {
3421
- console.log('\n[CodeArts Work]');
3422
- codeartsWorkStatus();
3423
- }
3424
- if (target === 'workbuddy' || target === 'all') {
3425
- console.log('\n[WorkBuddy]');
3426
- workbuddyStatus();
3427
- }
3428
- if (target === 'dsh' || target === 'all') {
3429
- console.log('\n[DSH]');
3430
- dshStatus();
3431
- }
3432
- if (target === 'officeace' || target === 'all') {
3433
- console.log('\n[OfficeAce]');
3434
- officeaceStatus();
3435
- }
3436
- if (target === 'hermes' || target === 'all') {
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
- console.log('\nEnvironment:');
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
- console.log('[Linux] Auto-install (pinned to KooCLI <version>):'.replace('<version>', kooCliVersion || ''));
4172
- const pkg = arch === 'arm64' ? 'linux-arm64' : 'linux-amd64';
4173
- console.log(` curl -LO "${baseUrl}/huaweicloud-cli-${pkg}.tar.gz"`);
4174
- console.log(` tar -zxvf huaweicloud-cli-${pkg}.tar.gz`);
4175
- console.log(` mv hcloud ~/.local/bin/`);
4176
- console.log(` hcloud version`);
4177
- console.log(`\n Or one-liner (installs LATEST, may differ from pinned ${kooCliVersion || ''}):`);
4178
- console.log(` curl -sSL ${latestUrl}/hcloud_install.sh -o ./hcloud_install.sh && bash ./hcloud_install.sh -y`);
4179
- console.log(
4180
- ` \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`,
4181
- );
4182
- console.log(`\nFull guide: https://support.huaweicloud.com/qs-hcli/hcli_02_003_02.html`);
4183
- } else if (os === 'darwin') {
4184
- console.log('[macOS] Auto-install (pinned to KooCLI <version>):'.replace('<version>', kooCliVersion || ''));
4185
- const pkg = arch === 'arm64' ? 'mac-arm64' : 'mac-amd64';
4186
- console.log(` curl -LO "${baseUrl}/huaweicloud-cli-${pkg}.tar.gz"`);
4187
- console.log(` tar -zxvf huaweicloud-cli-${pkg}.tar.gz`);
4188
- console.log(` mv hcloud /usr/local/bin/`);
4189
- console.log(` hcloud version`);
4190
- console.log(`\n Or one-liner (installs LATEST, may differ from pinned ${kooCliVersion || ''}):`);
4191
- console.log(` curl -sSL ${latestUrl}/hcloud_install.sh -o ./hcloud_install.sh && bash ./hcloud_install.sh -y`);
4192
- console.log(
4193
- ` \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`,
4194
- );
4195
- console.log(`\nFull guide: https://support.huaweicloud.com/qs-hcli/hcli_02_003_03.html`);
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
- const previousVersion = readInstalledVersion();
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();