@lyra-ai/toolkit 0.2.5 → 0.2.7

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/src/skillhub.mjs CHANGED
@@ -3339,21 +3339,11 @@ export function unzipSync(data, opts) {
3339
3339
 
3340
3340
 
3341
3341
  // ==================================================================
3342
- // === Tools: self-implemented replacements (semverGt + readline) ===
3342
+ // === Tools: self-implemented replacements (readline prompts) ===
3343
3343
  // ==================================================================
3344
3344
 
3345
3345
  import readline from 'node:readline';
3346
3346
 
3347
- function semverGt(a, b) {
3348
- const pa = String(a).replace(/^v/, '').split('.').map(Number);
3349
- const pb = String(b).replace(/^v/, '').split('.').map(Number);
3350
- for (let i = 0; i < 3; i++) {
3351
- if ((pa[i] ?? 0) > (pb[i] ?? 0)) return true;
3352
- if ((pa[i] ?? 0) < (pb[i] ?? 0)) return false;
3353
- }
3354
- return false;
3355
- }
3356
-
3357
3347
  async function promptSelect(message, choices) {
3358
3348
  const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
3359
3349
  process.stderr.write(`${message}\n`);
@@ -3395,7 +3385,6 @@ async function promptMultiselect(message, choices) {
3395
3385
  // === Version Info ===
3396
3386
  // ==================================================================
3397
3387
 
3398
- const PKG_NAME = "@astron-team/skillhub";
3399
3388
  const PKG_VERSION = "0.1.8";
3400
3389
 
3401
3390
  // ==================================================================
@@ -3417,7 +3406,6 @@ class CliError extends Error {
3417
3406
 
3418
3407
  const DEFAULT_REGISTRY = '';
3419
3408
  const CLI_VERSION = PKG_VERSION;
3420
- const CLI_PACKAGE_NAME = PKG_NAME;
3421
3409
  const EXIT = {
3422
3410
  generic: 1,
3423
3411
  auth: 2,
@@ -3614,79 +3602,6 @@ async function readBoundedResponseBody(response, maxBytes = MAX_PACKAGE_BYTES) {
3614
3602
  return result.buffer;
3615
3603
  }
3616
3604
 
3617
- // ==================================================================
3618
- // === Source: platform/updater ===
3619
- // ==================================================================
3620
-
3621
- import { spawn } from 'node:child_process';
3622
-
3623
- async function runUpdateCommand(command) {
3624
- try {
3625
- const [program, ...args] = command;
3626
- if (!program) {
3627
- return { success: false, output: 'empty update command' };
3628
- }
3629
-
3630
- const spawnOptions = {
3631
- stdio: ['ignore', 'pipe', 'pipe'],
3632
- ...(process.platform === 'win32' && { shell: true })
3633
- };
3634
-
3635
- return await new Promise((resolve) => {
3636
- const proc = spawn(program, args, spawnOptions);
3637
- const chunks = [];
3638
-
3639
- proc.stdout?.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
3640
- proc.stderr?.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
3641
- proc.on('error', (error) => resolve({ success: false, output: error.message }));
3642
- proc.on('close', (code) => resolve({
3643
- success: code === 0,
3644
- output: Buffer.concat(chunks).toString('utf-8')
3645
- }));
3646
- });
3647
- } catch (error) {
3648
- return {
3649
- success: false,
3650
- output: error instanceof Error ? error.message : String(error)
3651
- };
3652
- }
3653
- }
3654
-
3655
- // ==================================================================
3656
- // === Source: platform/package-manager ===
3657
- // ==================================================================
3658
-
3659
- function detectInstallMode(argv = process.argv, env = process.env) {
3660
- const execPath = argv[1] ?? '';
3661
-
3662
- if (execPath.includes('_npx/') || execPath.includes('_npx\\')) {
3663
- return 'npx';
3664
- }
3665
-
3666
- const npmExecPath = env.npm_execpath ?? '';
3667
- if (npmExecPath.includes('npx')) {
3668
- return 'npx';
3669
- }
3670
-
3671
- const bunInstall = env.BUN_INSTALL ?? '';
3672
- if (bunInstall && execPath.startsWith(bunInstall)) {
3673
- return 'bun-global';
3674
- }
3675
- if (execPath.includes('/.bun/') || execPath.includes('\\.bun\\')) {
3676
- return 'bun-global';
3677
- }
3678
-
3679
- const npmGlobalPrefix = env.npm_config_prefix ?? '';
3680
- if (npmGlobalPrefix && execPath.startsWith(npmGlobalPrefix)) {
3681
- return 'npm-global';
3682
- }
3683
- if (execPath.includes('/lib/node_modules/') || execPath.includes('/node_modules/.bin/')) {
3684
- return 'npm-global';
3685
- }
3686
-
3687
- return 'unknown';
3688
- }
3689
-
3690
3605
  // ==================================================================
3691
3606
  // === Source: platform/archive ===
3692
3607
  // ==================================================================
@@ -4192,97 +4107,6 @@ class SkillHubClient {
4192
4107
  }
4193
4108
  }
4194
4109
 
4195
- // ==================================================================
4196
- // === Source: clients/npm-registry-client ===
4197
- // ==================================================================
4198
-
4199
- const DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org';
4200
-
4201
- function readEnv(env, name) {
4202
- const exactValue = env[name]?.trim();
4203
- if (exactValue) {
4204
- return exactValue;
4205
- }
4206
-
4207
- const lowerName = name.toLowerCase();
4208
- for (const [key, value] of Object.entries(env)) {
4209
- const normalizedValue = value?.trim();
4210
- if (key.toLowerCase() === lowerName && normalizedValue) {
4211
- return normalizedValue;
4212
- }
4213
- }
4214
- return undefined;
4215
- }
4216
-
4217
- function resolveNpmRegistry(env) {
4218
- return readEnv(env, 'SKILLHUB_NPM_REGISTRY')
4219
- ?? readEnv(env, 'npm_config_registry')
4220
- ?? readEnv(env, 'NPM_CONFIG_REGISTRY')
4221
- ?? DEFAULT_NPM_REGISTRY;
4222
- }
4223
-
4224
- function buildLatestUrl(registry, packageName) {
4225
- try {
4226
- const base = registry.endsWith('/') ? registry : `${registry}/`;
4227
- return new URL(`${encodeURIComponent(packageName)}/latest`, base).toString();
4228
- } catch {
4229
- throw new CliError('invalid npm registry URL', EXIT.usage, {
4230
- registry,
4231
- next: 'check npm registry configuration and retry'
4232
- });
4233
- }
4234
- }
4235
-
4236
- class NpmRegistryClient {
4237
- constructor(fetchImpl = fetch, timeoutMs = 10000, env = process.env) {
4238
- this.fetchImpl = fetchImpl;
4239
- this.timeoutMs = timeoutMs;
4240
- this.env = env;
4241
- }
4242
-
4243
- async latestVersion(packageName = CLI_PACKAGE_NAME) {
4244
- const registry = resolveNpmRegistry(this.env);
4245
- const url = buildLatestUrl(registry, packageName);
4246
- const controller = new AbortController();
4247
- const timer = setTimeout(() => controller.abort(), this.timeoutMs);
4248
- try {
4249
- let response;
4250
- try {
4251
- response = await this.fetchImpl(url, {
4252
- signal: controller.signal
4253
- });
4254
- } catch (error) {
4255
- const isTimeout = error instanceof Error && error.name === 'AbortError';
4256
- throw new CliError('npm registry unreachable', EXIT.network, {
4257
- registry,
4258
- cause: error instanceof Error ? error.message : String(error),
4259
- next: isTimeout
4260
- ? 'check npm registry connectivity or proxy settings and retry'
4261
- : 'check npm registry/proxy configuration and retry'
4262
- });
4263
- }
4264
- if (!response.ok) {
4265
- throw new CliError(`npm registry returned ${response.status}`, EXIT.network, { registry });
4266
- }
4267
- let body;
4268
- try {
4269
- body = await response.json();
4270
- } catch (error) {
4271
- throw new CliError('npm registry response invalid', EXIT.network, {
4272
- registry,
4273
- cause: error instanceof Error ? error.message : String(error)
4274
- });
4275
- }
4276
- if (typeof body !== 'object' || body === null || !('version' in body) || typeof body.version !== 'string') {
4277
- throw new CliError('npm registry response missing version', EXIT.network, { registry });
4278
- }
4279
- return body.version;
4280
- } finally {
4281
- clearTimeout(timer);
4282
- }
4283
- }
4284
- }
4285
-
4286
4110
  // ==================================================================
4287
4111
  // === Source: services/registry-service ===
4288
4112
  // ==================================================================
@@ -4340,7 +4164,7 @@ async function deleteToolkitToken() {
4340
4164
  function requireRegistry(registry) {
4341
4165
  if (!registry) {
4342
4166
  throw new CliError(
4343
- '未配置 registry。运行: toolkit install claudecode --registry <url>',
4167
+ '未配置 registry。运行: toolkit install --registry <url>',
4344
4168
  EXIT.config
4345
4169
  );
4346
4170
  }
@@ -4529,84 +4353,6 @@ async function removeLocalSkill(options) {
4529
4353
  return { removed };
4530
4354
  }
4531
4355
 
4532
- // ==================================================================
4533
- // === Source: services/update-service ===
4534
- // ==================================================================
4535
-
4536
- class UpdateService {
4537
- constructor(deps) {
4538
- this.deps = deps;
4539
- }
4540
-
4541
- async update(options) {
4542
- const current = this.deps.currentVersion;
4543
- const latest = await this.deps.latestVersion();
4544
-
4545
- if (!semverGt(latest, current)) {
4546
- return {
4547
- updated: false,
4548
- available: false,
4549
- currentVersion: current,
4550
- latestVersion: latest
4551
- };
4552
- }
4553
-
4554
- if (options.checkOnly) {
4555
- return {
4556
- updated: false,
4557
- available: true,
4558
- currentVersion: current,
4559
- latestVersion: latest
4560
- };
4561
- }
4562
-
4563
- const mode = this.deps.detectInstallMode();
4564
-
4565
- switch (mode) {
4566
- case 'npx':
4567
- return {
4568
- updated: false,
4569
- available: true,
4570
- currentVersion: current,
4571
- latestVersion: latest,
4572
- next: `Run: npx ${CLI_PACKAGE_NAME}@latest <command> or install globally: npm install -g ${CLI_PACKAGE_NAME}`
4573
- };
4574
-
4575
- case 'npm-global': {
4576
- const result = await this.deps.run(['npm', 'install', '-g', `${CLI_PACKAGE_NAME}@latest`]);
4577
- return {
4578
- updated: result.success,
4579
- available: true,
4580
- currentVersion: current,
4581
- latestVersion: latest,
4582
- error: result.success ? undefined : result.output
4583
- };
4584
- }
4585
-
4586
- case 'bun-global': {
4587
- const result = await this.deps.run(['bun', 'add', '-g', `${CLI_PACKAGE_NAME}@latest`]);
4588
- return {
4589
- updated: result.success,
4590
- available: true,
4591
- currentVersion: current,
4592
- latestVersion: latest,
4593
- error: result.success ? undefined : result.output
4594
- };
4595
- }
4596
-
4597
- case 'unknown':
4598
- default:
4599
- return {
4600
- updated: false,
4601
- available: true,
4602
- currentVersion: current,
4603
- latestVersion: latest,
4604
- next: `Update manually: npm install -g ${CLI_PACKAGE_NAME}@latest or bun add -g ${CLI_PACKAGE_NAME}@latest`
4605
- };
4606
- }
4607
- }
4608
- }
4609
-
4610
4356
  // ==================================================================
4611
4357
  // === Source: services/doctor-service ===
4612
4358
  // ==================================================================
@@ -5044,11 +4790,6 @@ const commands = {
5044
4790
  summary: 'Publish a local skill package',
5045
4791
  usage: 'skillhub publish <path> [--namespace <slug>] [--visibility <public|namespace-only|private>] [--registry <url>] [--json]',
5046
4792
  examples: ['skillhub publish ./my-skill', 'skillhub publish ./my-skill --namespace myspace']
5047
- },
5048
- update: {
5049
- summary: 'Check or update CLI itself',
5050
- usage: 'skillhub update [--check] [--json]',
5051
- examples: ['skillhub update --check', 'skillhub update']
5052
4793
  }
5053
4794
  };
5054
4795
 
@@ -5483,58 +5224,6 @@ function toServerVisibility(visibility) {
5483
5224
  return visibility.toUpperCase().replace(/-/g, '_');
5484
5225
  }
5485
5226
 
5486
- // ==================================================================
5487
- // === Source: commands/update ===
5488
- // ==================================================================
5489
-
5490
- async function updateCommand(options) {
5491
- const json = Boolean(options.json);
5492
- const checkOnly = Boolean(options.check);
5493
-
5494
- const service = new UpdateService({
5495
- currentVersion: CLI_VERSION,
5496
- latestVersion: () => new NpmRegistryClient().latestVersion(),
5497
- detectInstallMode: () => detectInstallMode(),
5498
- run: runUpdateCommand
5499
- });
5500
-
5501
- const result = await service.update({ checkOnly });
5502
-
5503
- if (!result.available) {
5504
- return printResult(
5505
- json
5506
- ? { ok: true, upToDate: true, version: result.currentVersion }
5507
- : `Already up to date (${result.currentVersion})`,
5508
- json
5509
- );
5510
- }
5511
-
5512
- if (result.updated) {
5513
- return printResult(
5514
- json
5515
- ? { ok: true, updated: true, from: result.currentVersion, to: result.latestVersion }
5516
- : `Updated skillhub ${result.currentVersion} -> ${result.latestVersion}`,
5517
- json
5518
- );
5519
- }
5520
-
5521
- if (result.error) {
5522
- throw new CliError(result.error, EXIT.generic, { from: result.currentVersion, to: result.latestVersion });
5523
- }
5524
-
5525
- const lines = [`Update available: ${result.currentVersion} -> ${result.latestVersion}`];
5526
- if (result.next) {
5527
- lines.push(result.next);
5528
- }
5529
-
5530
- return printResult(
5531
- json
5532
- ? { ok: true, available: true, from: result.currentVersion, to: result.latestVersion, next: result.next }
5533
- : lines.join('\n'),
5534
- json
5535
- );
5536
- }
5537
-
5538
5227
  // ==================================================================
5539
5228
  // === Source: index (CLI entry point) ===
5540
5229
  // ==================================================================
@@ -5685,10 +5374,6 @@ cli.command('help [command]', 'Show help')
5685
5374
  cli.command('version', 'Show CLI version')
5686
5375
  .option('--json', 'Output JSON');
5687
5376
 
5688
- cli.command('update', 'Update CLI to latest version')
5689
- .option('--check', 'Check for updates without installing')
5690
- .option('--json', 'Output JSON');
5691
-
5692
5377
  cli.command('login', 'Save registry and token')
5693
5378
  .option('--registry <url>', 'Registry URL')
5694
5379
  .option('--token <token>', 'API token')
@@ -5756,8 +5441,6 @@ async function dispatchCommand(commandName, args, options) {
5756
5441
  return helpCommand([...args, ...(options.json ? ['--json'] : [])]);
5757
5442
  case 'version':
5758
5443
  return versionCommand(options.json ? ['--json'] : []);
5759
- case 'update':
5760
- return updateCommand(options);
5761
5444
  case 'login':
5762
5445
  return loginCommand(options);
5763
5446
  case 'logout':
package/src/status.mjs CHANGED
@@ -1,66 +1,133 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * status.mjs — `toolkit status`
4
+ *
5
+ * 报告 backlog 模型下的安装状态:
6
+ * - registry / uid(来自 config.json)
7
+ * - installed_at / last_sweep(来自 backlog.json)
8
+ * - 每 project 剩余 backlog 文件数(mtime ≤ installed_at 且
9
+ * compareKey(file, cursor) < 0 = 待排干)
10
+ *
11
+ * `collectStatus()` 返回字符串,`status()` 为 CLI 入口,调用 console.log。
4
12
  */
5
13
  import fs from 'node:fs';
6
14
  import path from 'node:path';
7
15
  import os from 'node:os';
8
- import { load as loadConfig, UPLOADED_PATH } from './shared/config.mjs';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { load as loadConfig } from './shared/config.mjs';
18
+ import {
19
+ loadBacklog,
20
+ compareKey,
21
+ topCursor,
22
+ } from './shared/backlog.mjs';
9
23
 
10
24
  const HOME = os.homedir();
11
- const PLUGIN_DIR = path.join(HOME, '.claude', 'plugins', 'toolkit');
25
+ const PLUGIN_DIR = path.join(HOME, '.claude', 'skills', 'toolkit');
12
26
 
13
- export function status() {
14
- const pluginExists = fs.existsSync(path.join(PLUGIN_DIR, '.claude-plugin', 'plugin.json'));
27
+ /**
28
+ * 收集状态文本。
29
+ * @returns {string}
30
+ */
31
+ export function collectStatus() {
15
32
  const config = loadConfig();
16
33
  const configExists = !!config.registry || !!config.uid;
17
34
 
18
- console.log('Toolkit Status');
19
- console.log('════════════════════════════════════════');
35
+ const pluginExists = fs.existsSync(
36
+ path.join(PLUGIN_DIR, '.claude-plugin', 'plugin.json'),
37
+ );
38
+
39
+ const lines = [];
40
+ lines.push('Toolkit Status');
41
+ lines.push('════════════════════════════════════════');
20
42
 
21
43
  if (!pluginExists && !configExists) {
22
- console.log(' ❌ 未安装。运行: toolkit install claudecode --registry <url>');
23
- return;
44
+ lines.push(' ❌ 未安装。运行: toolkit install --registry <url>');
45
+ return lines.join('\n');
24
46
  }
25
47
 
26
48
  // 插件状态
27
- console.log(` 插件: ${pluginExists ? '✅ 已安装' : '❌ 缺失'}`);
49
+ lines.push(` 插件: ${pluginExists ? '✅ 已安装' : '❌ 缺失'}`);
28
50
  if (pluginExists) {
29
- const pj = JSON.parse(fs.readFileSync(path.join(PLUGIN_DIR, '.claude-plugin', 'plugin.json'), 'utf-8'));
30
- console.log(` 版本: ${pj.version}`);
51
+ try {
52
+ const pj = JSON.parse(
53
+ fs.readFileSync(
54
+ path.join(PLUGIN_DIR, '.claude-plugin', 'plugin.json'),
55
+ 'utf-8',
56
+ ),
57
+ );
58
+ lines.push(` 版本: ${pj.version || '(未知)'}`);
59
+ } catch {
60
+ lines.push(' 版本: (读取失败)');
61
+ }
31
62
  }
32
63
 
33
- // 配置(走 shared/config.mjs)
64
+ // 配置
34
65
  if (configExists) {
35
- console.log(` 平台地址: ${config.registry}${config.contexts?.metrics || '/ai-metrics'}/`);
36
- console.log(` 身份 UID: ${config.uid?.slice(0, 12)}...`);
66
+ lines.push(
67
+ ` 平台地址: ${config.registry}${config.contexts?.metrics || '/ai-metrics'}/`,
68
+ );
69
+ lines.push(` 身份 UID: ${(config.uid || '').slice(0, 12)}...`);
37
70
  }
38
71
 
39
- // 上传状态
40
- try {
41
- const up = JSON.parse(fs.readFileSync(UPLOADED_PATH, 'utf-8'));
42
- console.log(` 已同步: ${up.uploaded_sids?.length || 0} 个会话`);
43
- console.log(` 上次同步: ${up.last_flush || '从未'}`);
44
- } catch {
45
- console.log(' 同步状态: 未初始化');
46
- }
72
+ // backlog 状态(只读:status 不应有写副作用,installed_at 边界由 install 写入)
73
+ const st = loadBacklog();
74
+ lines.push(` installed_at: ${st.installed_at || '(无)'}`);
75
+ lines.push(` last_sweep: ${st.last_sweep || '(从未)'}`);
47
76
 
48
- // 待传会话计数
49
- const projectsDir = path.join(HOME, '.claude', 'projects');
50
- let pending = 0;
51
- if (fs.existsSync(projectsDir) && configExists) {
52
- const up = JSON.parse(fs.readFileSync(UPLOADED_PATH, 'utf-8'));
53
- const uploadedSet = new Set(up.uploaded_sids || []);
54
- for (const projDir of fs.readdirSync(projectsDir)) {
55
- const pp = path.join(projectsDir, projDir);
77
+ // 每 project 剩余 backlog 文件计数
78
+ const base =
79
+ process.env.CLAUDE_PROJECTS_DIR ||
80
+ path.join(HOME, '.claude', 'projects');
81
+ const installedMs = st.installed_at
82
+ ? new Date(st.installed_at).getTime()
83
+ : null;
84
+
85
+ if (installedMs != null && fs.existsSync(base)) {
86
+ lines.push(' backlog 剩余 (按 project):');
87
+ let total = 0;
88
+ for (const proj of fs.readdirSync(base)) {
89
+ const pp = path.join(base, proj);
90
+ let stat;
56
91
  try {
57
- if (!fs.statSync(pp).isDirectory()) continue;
58
- for (const f of fs.readdirSync(pp)) {
59
- if (f.endsWith('.jsonl') && !uploadedSet.has(f.slice(0, -6))) pending++;
92
+ stat = fs.statSync(pp);
93
+ } catch {
94
+ continue;
95
+ }
96
+ if (!stat.isDirectory()) continue;
97
+
98
+ const cursor = st.cursors[proj] || topCursor(installedMs);
99
+ let remain = 0;
100
+ for (const f of fs.readdirSync(pp)) {
101
+ if (!f.endsWith('.jsonl')) continue;
102
+ let s;
103
+ try {
104
+ s = fs.statSync(path.join(pp, f));
105
+ } catch {
106
+ continue;
60
107
  }
61
- } catch {}
108
+ if (s.mtimeMs > installedMs) continue; // current 负责
109
+ if (compareKey({ mtime: s.mtimeMs, name: f }, cursor) < 0) remain++;
110
+ }
111
+ total += remain;
112
+ lines.push(` ${proj}: ${remain}`);
62
113
  }
114
+ lines.push(` 合计待排干: ${total}`);
63
115
  }
64
- console.log(` 待同步: ${pending} 个会话`);
65
- console.log('════════════════════════════════════════');
116
+
117
+ lines.push('════════════════════════════════════════');
118
+ return lines.join('\n');
119
+ }
120
+
121
+ /**
122
+ * CLI 入口。
123
+ */
124
+ export function status() {
125
+ console.log(collectStatus());
126
+ }
127
+
128
+ // self-invocation guard:被 import 时不触发 main()
129
+ const invoked =
130
+ process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
131
+ if (invoked) {
132
+ status();
66
133
  }
package/src/uninstall.mjs CHANGED
@@ -1,16 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * uninstall.mjs — `toolkit uninstall`
4
- * 删插件目录 + 移除 settings.json enabledPlugins 条目 + 可选保留 ~/.toolkit/ 数据。
3
+ * uninstall.mjs — `toolkit uninstall [--purge]`
4
+ *
5
+ * 删除 @skills-dir 插件目录(~/.claude/skills/toolkit)+ 旧版裸目录残留
6
+ * + settings.json 里失效的 toolkit@local 条目。可选 --purge 清除 ~/.toolkit/ 数据。
7
+ *
8
+ * 注:@skills-dir plugin 没有「卸载步骤」(按官方文档,删文件夹即停用)。
5
9
  */
6
10
  import fs from 'node:fs';
7
11
  import path from 'node:path';
8
12
  import os from 'node:os';
9
13
 
10
14
  const HOME = os.homedir();
11
- const PLUGIN_DIR = path.join(HOME, '.claude', 'plugins', 'toolkit');
15
+ const PLUGIN_DIR = path.join(HOME, '.claude', 'skills', 'toolkit');
16
+ const OLD_PLUGIN_DIR = path.join(HOME, '.claude', 'plugins', 'toolkit');
12
17
  const SETTINGS_PATH = path.join(HOME, '.claude', 'settings.json');
13
- const PLUGIN_KEY = 'toolkit@local';
18
+ const OLD_PLUGIN_KEY = 'toolkit@local';
19
+
20
+ // 数据目录与 shared/config.mjs 的 TOOLKIT_DIR 同逻辑,但在调用时求值,
21
+ // 以便测试用 TOOLKIT_HOME / HOME 隔离(避免模块加载时绑定的陈旧值)。
22
+ function dataDir() {
23
+ return process.env.TOOLKIT_HOME || path.join(os.homedir(), '.toolkit');
24
+ }
14
25
 
15
26
  function rmrf(dir) {
16
27
  if (!fs.existsSync(dir)) return;
@@ -25,24 +36,26 @@ function rmrf(dir) {
25
36
  }
26
37
 
27
38
  export function uninstall(opts = {}) {
28
- // 1. 删插件目录
39
+ // 1. 删 @skills-dir 插件目录 + 旧版裸目录残留
29
40
  rmrf(PLUGIN_DIR);
30
- console.log('✅ 插件目录已删除');
41
+ rmrf(OLD_PLUGIN_DIR);
42
+ console.log(`✅ 插件目录已删除(${PLUGIN_DIR})`);
31
43
 
32
- // 2. 移除 settings.json enabledPlugins
44
+ // 2. 移除 settings.json 里失效的 toolkit@local(若有)
33
45
  try {
34
46
  const settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf-8'));
35
- if (settings.enabledPlugins?.[PLUGIN_KEY] !== undefined) {
36
- delete settings.enabledPlugins[PLUGIN_KEY];
47
+ if (settings.enabledPlugins?.[OLD_PLUGIN_KEY] !== undefined) {
48
+ delete settings.enabledPlugins[OLD_PLUGIN_KEY];
37
49
  fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
38
- console.log('✅ settings.json enabledPlugins 已移除');
50
+ console.log('✅ settings.json 失效条目已移除');
39
51
  }
40
52
  } catch {}
41
53
 
42
- // 3. 数据(~/.toolkit/)
54
+ // 3. 数据(~/.toolkit/,走 TOOLKIT_HOME 或 ~/.toolkit,与 install 一致)
43
55
  if (opts.purge) {
44
- rmrf(path.join(HOME, '.toolkit'));
45
- console.log('✅ ~/.toolkit/ 数据已清除');
56
+ const dir = dataDir();
57
+ rmrf(dir);
58
+ console.log(`✅ ${dir} 数据已清除`);
46
59
  } else {
47
60
  console.log('ℹ️ ~/.toolkit/ 数据保留(含 UID + 上传记录)。');
48
61
  console.log(' 彻底清除: toolkit uninstall --purge');