itismyskillmarket 1.3.1 → 1.3.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/CHANGELOG.md CHANGED
@@ -1,3 +1,158 @@
1
+ # SkillMarket v1.3.2 更新日志
2
+
3
+ **日期**: 2026-04-30
4
+ **版本**: 1.3.2
5
+
6
+ ---
7
+
8
+ ## 🎉 新功能:增强卸载命令
9
+
10
+ ### 1. 卸载所有 Skills (`--all`)
11
+
12
+ 现在可以一键卸载所有已安装的 skills:
13
+
14
+ ```bash
15
+ # 卸载所有 skills(需要确认)
16
+ skm uninstall --all
17
+
18
+ # 强制卸载所有,跳过确认
19
+ skm uninstall --all --yes
20
+ ```
21
+
22
+ **确认提示示例**:
23
+ ```
24
+ Found 5 installed skill(s):
25
+ - brainstorming@1.2.0
26
+ - test-skill-1@1.1.0
27
+ - test-skill-2@1.0.0
28
+ - weather-time@1.0.0
29
+ - chinese-almanac@1.0.0
30
+
31
+ ⚠️ Are you sure you want to uninstall ALL 5 skill(s)? This action cannot be undone. (y/N): _
32
+ ```
33
+
34
+ ### 2. 预览模式 (`--dry-run`)
35
+
36
+ 新增 `--dry-run` 标志,可以预览将要删除的内容,而不实际执行删除:
37
+
38
+ ```bash
39
+ # 预览卸载单个 skill
40
+ skm uninstall brainstorming --dry-run
41
+
42
+ # 预览卸载所有 skills
43
+ skm uninstall --all --dry-run
44
+ ```
45
+
46
+ **预览输出示例**:
47
+ ```
48
+ 📋 Uninstall Preview for "brainstorming":
49
+
50
+ Version: 1.2.0
51
+ Installed: 2026-04-15T10:30:00Z
52
+ Platforms (from registry): OpenCode, Claude Code, VSCode
53
+
54
+ Local files to remove:
55
+ - ~/.skillmarket/skills/brainstorming
56
+
57
+ Platform links to remove:
58
+ - ~/.skillmarket/platform-links/opencode/skills/brainstorming
59
+ - ~/.skillmarket/platform-links/claude/skills/brainstorming
60
+ - ~/.skillmarket/platform-links/vscode/skills/brainstorming
61
+
62
+ ⚠️ This was a dry-run. No files were actually deleted.
63
+ ```
64
+
65
+ ### 3. 跳过确认 (`-y, --yes`)
66
+
67
+ 新增 `-y` 或 `--yes` 选项,跳过所有确认提示:
68
+
69
+ ```bash
70
+ # 强制卸载,不提示确认
71
+ skm uninstall brainstorming --yes
72
+
73
+ # 强制卸载所有,不提示确认
74
+ skm uninstall --all --yes
75
+ ```
76
+
77
+ ### 4. 改进错误处理
78
+
79
+ - 当平台卸载失败时,会询问用户是否继续清理本地文件
80
+ - 避免误删本地文件导致无法恢复
81
+
82
+ **错误处理示例**:
83
+ ```
84
+ Uninstalling from 3 platform(s)...
85
+
86
+ OpenCode ✅ Uninstalled
87
+ Claude Code ❌ Failed: EPERM: operation not permitted
88
+ VSCode ✅ Uninstalled
89
+
90
+ ⚠️ 1 platform(s) failed to uninstall. Continue with local cleanup? (y/N): _
91
+ ```
92
+
93
+ ### 5. 更新帮助文档
94
+
95
+ `skm --help` 现在包含卸载命令的完整说明:
96
+
97
+ ```bash
98
+ skm uninstall --help
99
+ ```
100
+
101
+ ---
102
+
103
+ ## 🔧 技术实现
104
+
105
+ ### 新增函数
106
+
107
+ | 函数名 | 功能 |
108
+ |--------|------|
109
+ | `uninstallAll()` | 卸载所有已安装的 skills |
110
+ | `askConfirmation()` | 请求用户确认(内部工具函数) |
111
+ | `getUninstallPreview()` | 收集卸载预览信息(内部工具函数) |
112
+
113
+ ### 更新接口
114
+
115
+ **UninstallOptions** 新增字段:
116
+ ```typescript
117
+ export interface UninstallOptions {
118
+ platforms?: string[]; // 目标平台列表
119
+ all?: boolean; // 卸载所有 skills
120
+ dryRun?: boolean; // 预览模式
121
+ yes?: boolean; // 跳过确认
122
+ }
123
+ ```
124
+
125
+ ### CLI 参数更新
126
+
127
+ | 参数 | 说明 |
128
+ |------|------|
129
+ | `-a, --all` | 卸载所有已安装的 skills |
130
+ | `-d, --dry-run` | 预览模式,不实际删除 |
131
+ | `-y, --yes` | 跳过确认提示 |
132
+
133
+ ---
134
+
135
+ ## 📦 完整版本历史
136
+
137
+ | 版本 | 日期 | 描述 |
138
+ |------|------|------|
139
+ | 1.3.2 | 2026-04-30 | 增强卸载命令:--all, --dry-run, --yes |
140
+ | 1.3.1 | 2026-04-29 | Bug 修复,workflow 改进 |
141
+ | 1.3.0 | 2026-04-23 | 独立搜索命令,改进分页逻辑 |
142
+ | 1.2.6 | 2026-04-22 | 添加搜索功能(--search) |
143
+ | 1.2.5 | 2026-04-16 | 文档更新 |
144
+ | 1.2.4 | 2026-04-16 | 修复版本号硬编码问题 |
145
+ | 1.2.3 | 2026-04-15 | 跨平台 Skill 安装支持 |
146
+
147
+ ---
148
+
149
+ ## 贡献者
150
+
151
+ - wxc2004 (wanxuchen)
152
+ - Sisyphus Agent
153
+
154
+ ---
155
+
1
156
  # SkillMarket v1.2.6 更新日志
2
157
 
3
158
  **日期**: 2026-04-22
package/dist/index.js CHANGED
@@ -787,15 +787,80 @@ async function updateSkill(skillId) {
787
787
  // src/commands/uninstall.ts
788
788
  import fs9 from "fs-extra";
789
789
  import path8 from "path";
790
+ import readline from "readline";
791
+ async function askConfirmation(message) {
792
+ const rl = readline.createInterface({
793
+ input: process.stdin,
794
+ output: process.stdout
795
+ });
796
+ return new Promise((resolve2) => {
797
+ rl.question(`${message} (y/N): `, (answer) => {
798
+ rl.close();
799
+ resolve2(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
800
+ });
801
+ });
802
+ }
803
+ async function getUninstallPreview(skillId, options) {
804
+ const registry = await loadRegistry();
805
+ const skillInfo = registry.skills[skillId];
806
+ let platformNames = [];
807
+ if (options?.platforms && options.platforms.length > 0) {
808
+ platformNames = options.platforms;
809
+ } else {
810
+ const adapters2 = await detectPlatforms();
811
+ platformNames = adapters2.map((a) => a.name);
812
+ }
813
+ const skillsDir = getSkillsDir();
814
+ const localPath = path8.join(skillsDir, skillId);
815
+ const platformLinksDir = getPlatformLinksDir();
816
+ const platformLinks = [];
817
+ for (const platform of PLATFORMS) {
818
+ const linkPath = path8.join(platformLinksDir, platform, "skills", skillId);
819
+ if (await fs9.pathExists(linkPath)) {
820
+ platformLinks.push(linkPath);
821
+ }
822
+ }
823
+ return { skillInfo, platforms: platformNames, localPath, platformLinks };
824
+ }
790
825
  async function uninstallSkill(skillId, options) {
791
826
  const registry = await loadRegistry();
792
827
  if (!(skillId in registry.skills)) {
793
- console.log(`Skill "${skillId}" is not installed.`);
794
- return;
828
+ console.log(`\u274C Skill "${skillId}" is not installed.`);
829
+ return false;
795
830
  }
796
831
  const skillInfo = registry.skills[skillId];
797
- console.log(`Uninstalling ${skillId}@${skillInfo.version}...`);
832
+ if (options?.dryRun) {
833
+ const preview = await getUninstallPreview(skillId, options);
834
+ console.log(`
835
+ \u{1F4CB} Uninstall Preview for "${skillId}":`);
836
+ console.log(` Version: ${skillInfo.version}`);
837
+ console.log(` Installed: ${skillInfo.installedAt}`);
838
+ console.log(` Platforms (from registry): ${preview.platforms.join(", ") || "none"}`);
839
+ console.log(`
840
+ Local files to remove:`);
841
+ console.log(` - ${preview.localPath}`);
842
+ if (preview.platformLinks.length > 0) {
843
+ console.log(`
844
+ Platform links to remove:`);
845
+ for (const link of preview.platformLinks) {
846
+ console.log(` - ${link}`);
847
+ }
848
+ }
849
+ console.log(`
850
+ \u26A0\uFE0F This was a dry-run. No files were actually deleted.`);
851
+ return true;
852
+ }
853
+ if (!options?.yes) {
854
+ const confirmed = await askConfirmation(`Are you sure you want to uninstall "${skillId}"?`);
855
+ if (!confirmed) {
856
+ console.log("Uninstall cancelled.");
857
+ return false;
858
+ }
859
+ }
860
+ console.log(`
861
+ Uninstalling ${skillId}@${skillInfo.version}...`);
798
862
  let targetAdapters = [];
863
+ let platformUninstallErrors = [];
799
864
  if (options?.platforms && options.platforms.length > 0) {
800
865
  for (const platformStr of options.platforms) {
801
866
  const platform = platformStr;
@@ -814,24 +879,99 @@ Uninstalling from ${validAdapters.length} platform(s)...
814
879
  await adapter.uninstall(skillId);
815
880
  console.log(`${adapter.name.padEnd(12)} \u2705 Uninstalled`);
816
881
  } catch (error) {
817
- console.log(`${adapter.name.padEnd(12)} \u274C Failed: ${error}`);
882
+ const errorMsg = error instanceof Error ? error.message : String(error);
883
+ console.log(`${adapter.name.padEnd(12)} \u274C Failed: ${errorMsg}`);
884
+ platformUninstallErrors.push({ name: adapter.name, error: errorMsg });
818
885
  }
819
886
  }
820
887
  }
888
+ if (platformUninstallErrors.length > 0 && !options?.yes) {
889
+ const continueAnyway = await askConfirmation(
890
+ `\u26A0\uFE0F ${platformUninstallErrors.length} platform(s) failed to uninstall. Continue with local cleanup?`
891
+ );
892
+ if (!continueAnyway) {
893
+ console.log("Uninstall aborted. Platform files may still exist.");
894
+ return false;
895
+ }
896
+ }
821
897
  const skillsDir = getSkillsDir();
822
898
  const skillDir = path8.join(skillsDir, skillId);
823
- await fs9.remove(skillDir);
899
+ if (await fs9.pathExists(skillDir)) {
900
+ await fs9.remove(skillDir);
901
+ console.log(`\u2705 Removed local files: ${skillDir}`);
902
+ }
824
903
  const platformLinksDir = getPlatformLinksDir();
904
+ let removedLinks = 0;
825
905
  for (const platform of PLATFORMS) {
826
906
  const linkPath = path8.join(platformLinksDir, platform, "skills", skillId);
827
907
  if (await fs9.pathExists(linkPath)) {
828
908
  await fs9.remove(linkPath);
909
+ removedLinks++;
829
910
  }
830
911
  }
912
+ if (removedLinks > 0) {
913
+ console.log(`\u2705 Removed ${removedLinks} platform link(s)`);
914
+ }
831
915
  delete registry.skills[skillId];
832
916
  await saveRegistry(registry);
917
+ console.log(`\u2705 Registry updated`);
833
918
  console.log(`
834
919
  \u2705 ${skillId} uninstalled successfully!`);
920
+ return true;
921
+ }
922
+ async function uninstallAll(options) {
923
+ const registry = await loadRegistry();
924
+ const installedSkills = Object.keys(registry.skills);
925
+ if (installedSkills.length === 0) {
926
+ console.log("No skills installed.");
927
+ return { success: 0, failed: 0 };
928
+ }
929
+ console.log(`
930
+ Found ${installedSkills.length} installed skill(s):`);
931
+ for (const skillId of installedSkills) {
932
+ const info = registry.skills[skillId];
933
+ console.log(` - ${skillId}@${info.version}`);
934
+ }
935
+ if (options?.dryRun) {
936
+ console.log(`
937
+ \u{1F4CB} Dry-run: Would uninstall ${installedSkills.length} skill(s).`);
938
+ console.log(`\u26A0\uFE0F No files were actually deleted.`);
939
+ return { success: installedSkills.length, failed: 0 };
940
+ }
941
+ if (!options?.yes) {
942
+ const confirmed = await askConfirmation(
943
+ `
944
+ \u26A0\uFE0F Are you sure you want to uninstall ALL ${installedSkills.length} skill(s)? This action cannot be undone.`
945
+ );
946
+ if (!confirmed) {
947
+ console.log("Uninstall cancelled.");
948
+ return { success: 0, failed: 0 };
949
+ }
950
+ }
951
+ console.log(`
952
+ Uninstalling all skills...
953
+ `);
954
+ let successCount = 0;
955
+ let failedCount = 0;
956
+ for (const skillId of installedSkills) {
957
+ try {
958
+ const success = await uninstallSkill(skillId, { ...options, yes: true });
959
+ if (success) {
960
+ successCount++;
961
+ } else {
962
+ failedCount++;
963
+ }
964
+ } catch (error) {
965
+ console.log(`\u274C Failed to uninstall ${skillId}: ${error}`);
966
+ failedCount++;
967
+ }
968
+ }
969
+ console.log(`
970
+ \u{1F4CA} Summary:`);
971
+ console.log(` \u2705 Success: ${successCount}`);
972
+ console.log(` \u274C Failed: ${failedCount}`);
973
+ console.log(` \u{1F4E6} Total: ${installedSkills.length}`);
974
+ return { success: successCount, failed: failedCount };
835
975
  }
836
976
 
837
977
  // src/cli.ts
@@ -862,6 +1002,9 @@ Commands:
862
1002
  --force Overwrite if already installed
863
1003
  uninstall <skill> Remove an installed skill
864
1004
  --platform Target platforms
1005
+ --all Uninstall ALL installed skills
1006
+ --dry-run Preview without deleting
1007
+ -y, --yes Skip confirmation
865
1008
  update [options] Update skills
866
1009
  --all Update all skills
867
1010
  sync Synchronize platform links
@@ -881,8 +1024,11 @@ Examples:
881
1024
  skm install brainstorming --platform opencode Install to OpenCode only
882
1025
  skm install brainstorming --platform claude,vscode Install to multiple
883
1026
  skm uninstall brainstorming
1027
+ skm uninstall --all Uninstall all skills (with confirmation)
1028
+ skm uninstall --all --yes Force uninstall all without confirmation
1029
+ skm uninstall brainstorming --dry-run Preview uninstall
884
1030
  skm platforms Show available platforms
885
- `);
1031
+ `);
886
1032
  process.exit(0);
887
1033
  }
888
1034
  });
@@ -919,10 +1065,26 @@ installCmd.argument("<skill>", "Skill ID to install (e.g., brainstorming or @sco
919
1065
  }
920
1066
  });
921
1067
  var uninstallCmd = program.command("uninstall").description("Remove an installed skill from local and platform directories");
922
- uninstallCmd.argument("<skill>", "Skill ID to uninstall").option("-p, --platform <platforms>", "Target platforms (comma-separated)").action(async (skill, opts) => {
1068
+ uninstallCmd.argument("[skill]", "Skill ID to uninstall (required unless using --all)").option("-p, --platform <platforms>", "Target platforms (comma-separated)").option("-a, --all", "Uninstall ALL installed skills (requires confirmation)").option("-d, --dry-run", "Preview what would be uninstalled without actually deleting").option("-y, --yes", "Skip confirmation prompts").action(async (skill, opts) => {
923
1069
  try {
924
1070
  const platforms = opts.platform ? opts.platform.split(",").map((p) => p.trim()) : void 0;
925
- await uninstallSkill(skill, { platforms });
1071
+ if (opts.all) {
1072
+ await uninstallAll({
1073
+ platforms,
1074
+ dryRun: opts.dryRun,
1075
+ yes: opts.yes
1076
+ });
1077
+ return;
1078
+ }
1079
+ if (!skill) {
1080
+ console.error("Error: Skill ID is required (or use --all to uninstall all)");
1081
+ process.exit(1);
1082
+ }
1083
+ await uninstallSkill(skill, {
1084
+ platforms,
1085
+ dryRun: opts.dryRun,
1086
+ yes: opts.yes
1087
+ });
926
1088
  } catch (err) {
927
1089
  console.error("Uninstall failed:", err);
928
1090
  process.exit(1);
@@ -0,0 +1,57 @@
1
+ # SkillMarket 本周更新日志 (2026-04-23 ~ 2026-04-29)
2
+
3
+ ## 📦 版本发布
4
+
5
+ ### v1.3.1 - 2026-04-29
6
+
7
+ **新功能:**
8
+ - ✨ 新增独立 `skm search` 命令,支持关键词匹配搜索技能
9
+ - 🔍 优化搜索精度,增加本地过滤支持
10
+
11
+ **修复:**
12
+ - 🐛 修复 GitHub Actions workflow 中版本重复更新的问题
13
+ - 🔧 改进 `publish-npm.yml` 的版本判断逻辑
14
+
15
+ **发布详情:**
16
+ - npm: https://www.npmjs.com/package/itismyskillmarket/v/1.3.1
17
+ - GitHub Release: https://github.com/wxc2004/market/releases/tag/v1.3.1
18
+ - 对比: https://github.com/wxc2004/market/compare/v1.2.9...v1.3.1
19
+
20
+ ---
21
+
22
+ ## 📊 本周统计
23
+
24
+ | 指标 | 数据 |
25
+ |------|------|
26
+ | 新增功能 | 1 个 |
27
+ | Bug 修复 | 1 个 |
28
+ | 版本发布 | 1 个 (v1.3.1) |
29
+ | Commits | 2 个 |
30
+ | 代码行变更 | ~150 行 |
31
+
32
+ ---
33
+
34
+ ## 🔜 下周计划
35
+
36
+ - [ ] 增加技能评分/评论功能
37
+ - [ ] 支持更多 AI 编码工具平台
38
+ - [ ] 优化安装速度(增量更新)
39
+ - [ ] 增加技能依赖检查
40
+
41
+ ---
42
+
43
+ ## 📝 详细变更
44
+
45
+ ### feat: add independent search command with keyword matching (1e91352)
46
+ - 新增 `skm search <keyword>` 命令
47
+ - 支持按名称、描述关键词搜索已安装和远程技能
48
+ - 本地缓存优先,减少网络请求
49
+
50
+ ### fix: skip npm version when version unchanged in workflow (2213d4a)
51
+ - 修复 GitHub Actions 发布时 `npm version` 报错问题
52
+ - 增加版本号判断逻辑,避免重复设置
53
+ - 优化 CI/CD 流程稳定性
54
+
55
+ ---
56
+
57
+ *生成时间: 2026-04-29*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "itismyskillmarket",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "Cross-platform skill manager for AI coding tools",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -47,7 +47,7 @@ import { showSkillInfo } from './commands/info.js'; // 信息命令
47
47
  import { installSkill } from './commands/install.js'; // 安装命令
48
48
  import { syncPlatformLinks } from './commands/sync.js'; // 同步命令
49
49
  import { updateSkill } from './commands/update.js'; // 更新命令
50
- import { uninstallSkill } from './commands/uninstall.js'; // 卸载命令
50
+ import { uninstallSkill, uninstallAll } from './commands/uninstall.js'; // 卸载命令
51
51
  import { detectPlatforms, getAllAdapters, OpenCodeAdapter, ClaudeAdapter, VSCodeAdapter } from './adapters/index.js'; // 平台适配器
52
52
 
53
53
  // -----------------------------------------------------------------------------
@@ -108,6 +108,9 @@ Commands:
108
108
  --force Overwrite if already installed
109
109
  uninstall <skill> Remove an installed skill
110
110
  --platform Target platforms
111
+ --all Uninstall ALL installed skills
112
+ --dry-run Preview without deleting
113
+ -y, --yes Skip confirmation
111
114
  update [options] Update skills
112
115
  --all Update all skills
113
116
  sync Synchronize platform links
@@ -127,8 +130,11 @@ Examples:
127
130
  skm install brainstorming --platform opencode Install to OpenCode only
128
131
  skm install brainstorming --platform claude,vscode Install to multiple
129
132
  skm uninstall brainstorming
133
+ skm uninstall --all Uninstall all skills (with confirmation)
134
+ skm uninstall --all --yes Force uninstall all without confirmation
135
+ skm uninstall brainstorming --dry-run Preview uninstall
130
136
  skm platforms Show available platforms
131
- `);
137
+ `);
132
138
  process.exit(0);
133
139
  }
134
140
  });
@@ -264,22 +270,55 @@ installCmd
264
270
  * 用法:
265
271
  * - skm uninstall <skill> 卸载所有平台
266
272
  * - skm uninstall <skill> --platform opencode 卸载特定平台
273
+ * - skm uninstall --all 卸载所有已安装的 skills
274
+ * - skm uninstall --dry-run 预览删除内容
275
+ *
276
+ * 新增 (v1.4.0):
277
+ * --all: 卸载所有已安装的 skills(需要确认)
278
+ * --dry-run: 预览模式,不实际删除
279
+ * -y, --yes: 跳过确认提示
267
280
  *
268
281
  * @example
269
282
  * skm uninstall brainstorming
270
283
  * skm uninstall brainstorming --platform claude
284
+ * skm uninstall --all
285
+ * skm uninstall --all --yes
286
+ * skm uninstall brainstorming --dry-run
271
287
  */
272
288
  const uninstallCmd = program.command('uninstall').description('Remove an installed skill from local and platform directories');
273
289
  uninstallCmd
274
- .argument('<skill>', 'Skill ID to uninstall')
290
+ .argument('[skill]', 'Skill ID to uninstall (required unless using --all)')
275
291
  .option('-p, --platform <platforms>', 'Target platforms (comma-separated)')
292
+ .option('-a, --all', 'Uninstall ALL installed skills (requires confirmation)')
293
+ .option('-d, --dry-run', 'Preview what would be uninstalled without actually deleting')
294
+ .option('-y, --yes', 'Skip confirmation prompts')
276
295
  .action(async (skill, opts) => {
277
296
  try {
278
297
  const platforms = opts.platform
279
298
  ? opts.platform.split(',').map((p: string) => p.trim())
280
299
  : undefined;
281
300
 
282
- await uninstallSkill(skill, { platforms });
301
+ // 处理 --all 选项
302
+ if (opts.all) {
303
+ await uninstallAll({
304
+ platforms,
305
+ dryRun: opts.dryRun,
306
+ yes: opts.yes
307
+ });
308
+ return;
309
+ }
310
+
311
+ // skill 参数是必需的(除非使用 --all)
312
+ if (!skill) {
313
+ console.error('Error: Skill ID is required (or use --all to uninstall all)');
314
+ process.exit(1);
315
+ }
316
+
317
+ await uninstallSkill(skill, {
318
+ platforms,
319
+ dryRun: opts.dryRun,
320
+ yes: opts.yes
321
+ });
283
322
  } catch (err) {
284
323
  console.error('Uninstall failed:', err);
285
324
  process.exit(1);
@@ -12,6 +12,12 @@
12
12
  * 4. 删除各平台的软链接
13
13
  * 5. 从注册表中移除记录
14
14
  *
15
+ * 新增功能 (v1.4.0):
16
+ * - 支持 --all 卸载所有 skills
17
+ * - 支持 --dry-run 预览删除内容
18
+ * - 添加确认提示(--all 时强制确认)
19
+ * - 改进错误处理:平台卸载失败时暂停本地删除
20
+ *
15
21
  * @module commands/uninstall
16
22
  */
17
23
 
@@ -21,12 +27,13 @@
21
27
 
22
28
  import fs from 'fs-extra'; // 文件系统操作
23
29
  import path from 'path'; // 路径处理
24
- import { loadRegistry, saveRegistry } from './registry.js'; // 注册表操作
30
+ import readline from 'readline'; // 用户交互确认
31
+ import { loadRegistry, saveRegistry, getInstalledSkills } from './registry.js'; // 注册表操作
25
32
  import { getSkillsDir, getPlatformLinksDir } from '../utils/dirs.js'; // 目录工具
26
33
  import { PLATFORMS } from '../constants.js'; // 平台常量
27
34
  import { detectPlatforms, getAdapterByPlatform } from '../adapters/index.js'; // 平台适配器
28
35
  import type { Platform } from '../constants.js';
29
- import type { PlatformAdapter } from '../types.js';
36
+ import type { PlatformAdapter, InstalledSkill } from '../types.js';
30
37
 
31
38
  // -----------------------------------------------------------------------------
32
39
  // 卸载选项接口
@@ -35,10 +42,91 @@ import type { PlatformAdapter } from '../types.js';
35
42
  export interface UninstallOptions {
36
43
  /** 目标平台列表(留空则卸载所有平台) */
37
44
  platforms?: string[];
45
+ /** 卸载所有已安装的 skills */
46
+ all?: boolean;
47
+ /** 预览模式:不实际删除,只显示将要删除的内容 */
48
+ dryRun?: boolean;
49
+ /** 跳过确认提示 */
50
+ yes?: boolean;
51
+ }
52
+
53
+ // -----------------------------------------------------------------------------
54
+ // 工具函数:用户确认提示
55
+ // -----------------------------------------------------------------------------
56
+
57
+ /**
58
+ * 请求用户确认
59
+ *
60
+ * @param {string} message - 确认提示信息
61
+ * @returns {Promise<boolean>} 用户是否确认
62
+ */
63
+ async function askConfirmation(message: string): Promise<boolean> {
64
+ const rl = readline.createInterface({
65
+ input: process.stdin,
66
+ output: process.stdout
67
+ });
68
+
69
+ return new Promise((resolve) => {
70
+ rl.question(`${message} (y/N): `, (answer) => {
71
+ rl.close();
72
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
73
+ });
74
+ });
75
+ }
76
+
77
+ // -----------------------------------------------------------------------------
78
+ // 工具函数:收集 skill 删除预览信息
79
+ // -----------------------------------------------------------------------------
80
+
81
+ /**
82
+ * 收集指定 skill 的删除预览信息
83
+ *
84
+ * @param {string} skillId - Skill 标识符
85
+ * @param {UninstallOptions} options - 卸载选项
86
+ * @returns {Promise<{skillInfo: InstalledSkill, platforms: string[], localFiles: string[]}>} 预览信息
87
+ */
88
+ async function getUninstallPreview(
89
+ skillId: string,
90
+ options?: UninstallOptions
91
+ ): Promise<{
92
+ skillInfo: InstalledSkill;
93
+ platforms: string[];
94
+ localPath: string;
95
+ platformLinks: string[];
96
+ }> {
97
+ const registry = await loadRegistry();
98
+ const skillInfo = registry.skills[skillId];
99
+
100
+ // 收集目标平台
101
+ let platformNames: string[] = [];
102
+
103
+ if (options?.platforms && options.platforms.length > 0) {
104
+ platformNames = options.platforms;
105
+ } else {
106
+ const adapters = await detectPlatforms();
107
+ platformNames = adapters.map(a => a.name);
108
+ }
109
+
110
+ // 本地文件路径
111
+ const skillsDir = getSkillsDir();
112
+ const localPath = path.join(skillsDir, skillId);
113
+
114
+ // 平台软链接路径
115
+ const platformLinksDir = getPlatformLinksDir();
116
+ const platformLinks: string[] = [];
117
+
118
+ for (const platform of PLATFORMS) {
119
+ const linkPath = path.join(platformLinksDir, platform, 'skills', skillId);
120
+ if (await fs.pathExists(linkPath)) {
121
+ platformLinks.push(linkPath);
122
+ }
123
+ }
124
+
125
+ return { skillInfo, platforms: platformNames, localPath, platformLinks };
38
126
  }
39
127
 
40
128
  // -----------------------------------------------------------------------------
41
- // 卸载函数
129
+ // 卸载单个 skill
42
130
  // -----------------------------------------------------------------------------
43
131
 
44
132
  /**
@@ -46,7 +134,7 @@ export interface UninstallOptions {
46
134
  *
47
135
  * @param {string} skillId - Skill 标识符
48
136
  * @param {UninstallOptions} [options] - 卸载选项
49
- * @returns {Promise<void>}
137
+ * @returns {Promise<boolean>} 是否成功卸载
50
138
  *
51
139
  * @example
52
140
  * // 卸载 brainstorming
@@ -58,7 +146,7 @@ export interface UninstallOptions {
58
146
  export async function uninstallSkill(
59
147
  skillId: string,
60
148
  options?: UninstallOptions
61
- ): Promise<void> {
149
+ ): Promise<boolean> {
62
150
  // ==========================================================================
63
151
  // 步骤 1: 检查是否已安装
64
152
  // ==========================================================================
@@ -67,20 +155,58 @@ export async function uninstallSkill(
67
155
 
68
156
  // 检查注册表中是否存在该 skill
69
157
  if (!(skillId in registry.skills)) {
70
- console.log(`Skill "${skillId}" is not installed.`);
71
- return;
158
+ console.log(`❌ Skill "${skillId}" is not installed.`);
159
+ return false;
72
160
  }
73
161
 
74
162
  // 获取 skill 信息(用于显示)
75
163
  const skillInfo = registry.skills[skillId];
76
164
 
77
- console.log(`Uninstalling ${skillId}@${skillInfo.version}...`);
165
+ // ==========================================================================
166
+ // 步骤 2: Dry-run 模式 - 只显示预览
167
+ // ==========================================================================
168
+
169
+ if (options?.dryRun) {
170
+ const preview = await getUninstallPreview(skillId, options);
171
+
172
+ console.log(`\n📋 Uninstall Preview for "${skillId}":`);
173
+ console.log(` Version: ${skillInfo.version}`);
174
+ console.log(` Installed: ${skillInfo.installedAt}`);
175
+ console.log(` Platforms (from registry): ${preview.platforms.join(', ') || 'none'}`);
176
+ console.log(`\n Local files to remove:`);
177
+ console.log(` - ${preview.localPath}`);
178
+
179
+ if (preview.platformLinks.length > 0) {
180
+ console.log(`\n Platform links to remove:`);
181
+ for (const link of preview.platformLinks) {
182
+ console.log(` - ${link}`);
183
+ }
184
+ }
185
+
186
+ console.log(`\n⚠️ This was a dry-run. No files were actually deleted.`);
187
+ return true;
188
+ }
78
189
 
79
190
  // ==========================================================================
80
- // 步骤 2: 从平台卸载 (NEW)
191
+ // 步骤 3: 确认提示(除非使用 --yes)
192
+ // ==========================================================================
193
+
194
+ if (!options?.yes) {
195
+ const confirmed = await askConfirmation(`Are you sure you want to uninstall "${skillId}"?`);
196
+ if (!confirmed) {
197
+ console.log('Uninstall cancelled.');
198
+ return false;
199
+ }
200
+ }
201
+
202
+ console.log(`\nUninstalling ${skillId}@${skillInfo.version}...`);
203
+
204
+ // ==========================================================================
205
+ // 步骤 4: 从平台卸载
81
206
  // ==========================================================================
82
207
 
83
208
  let targetAdapters: (PlatformAdapter | undefined)[] = [];
209
+ let platformUninstallErrors: { name: string; error: string }[] = [];
84
210
 
85
211
  if (options?.platforms && options.platforms.length > 0) {
86
212
  // 用户指定了平台
@@ -104,27 +230,46 @@ export async function uninstallSkill(
104
230
  await adapter.uninstall(skillId);
105
231
  console.log(`${adapter.name.padEnd(12)} ✅ Uninstalled`);
106
232
  } catch (error) {
107
- console.log(`${adapter.name.padEnd(12)} ❌ Failed: ${error}`);
233
+ const errorMsg = error instanceof Error ? error.message : String(error);
234
+ console.log(`${adapter.name.padEnd(12)} ❌ Failed: ${errorMsg}`);
235
+ platformUninstallErrors.push({ name: adapter.name, error: errorMsg });
108
236
  }
109
237
  }
110
238
  }
111
239
 
112
240
  // ==========================================================================
113
- // 步骤 3: 删除 skill 主目录
241
+ // 步骤 5: 如果有平台卸载失败,询问是否继续
242
+ // ==========================================================================
243
+
244
+ if (platformUninstallErrors.length > 0 && !options?.yes) {
245
+ const continueAnyway = await askConfirmation(
246
+ `⚠️ ${platformUninstallErrors.length} platform(s) failed to uninstall. Continue with local cleanup?`
247
+ );
248
+ if (!continueAnyway) {
249
+ console.log('Uninstall aborted. Platform files may still exist.');
250
+ return false;
251
+ }
252
+ }
253
+
254
+ // ==========================================================================
255
+ // 步骤 6: 删除 skill 主目录
114
256
  // ==========================================================================
115
257
 
116
258
  // skill 主目录包含所有版本和 latest 软链接
117
259
  const skillsDir = getSkillsDir();
118
260
  const skillDir = path.join(skillsDir, skillId);
119
261
 
120
- // 递归删除整个目录
121
- await fs.remove(skillDir);
262
+ if (await fs.pathExists(skillDir)) {
263
+ await fs.remove(skillDir);
264
+ console.log(`✅ Removed local files: ${skillDir}`);
265
+ }
122
266
 
123
267
  // ==========================================================================
124
- // 步骤 4: 删除平台软链接
268
+ // 步骤 7: 删除平台软链接
125
269
  // ==========================================================================
126
270
 
127
271
  const platformLinksDir = getPlatformLinksDir();
272
+ let removedLinks = 0;
128
273
 
129
274
  // 遍历所有平台,删除对应的软链接
130
275
  for (const platform of PLATFORMS) {
@@ -134,11 +279,16 @@ export async function uninstallSkill(
134
279
  // 如果软链接存在则删除
135
280
  if (await fs.pathExists(linkPath)) {
136
281
  await fs.remove(linkPath);
282
+ removedLinks++;
137
283
  }
138
284
  }
139
285
 
286
+ if (removedLinks > 0) {
287
+ console.log(`✅ Removed ${removedLinks} platform link(s)`);
288
+ }
289
+
140
290
  // ==========================================================================
141
- // 步骤 5: 更新注册表
291
+ // 步骤 8: 更新注册表
142
292
  // ==========================================================================
143
293
 
144
294
  // 从注册表中删除该 skill 的记录
@@ -146,10 +296,105 @@ export async function uninstallSkill(
146
296
 
147
297
  // 保存更新后的注册表
148
298
  await saveRegistry(registry);
299
+ console.log(`✅ Registry updated`);
149
300
 
150
301
  // ==========================================================================
151
302
  // 完成
152
303
  // ==========================================================================
153
304
 
154
305
  console.log(`\n✅ ${skillId} uninstalled successfully!`);
306
+ return true;
307
+ }
308
+
309
+ // -----------------------------------------------------------------------------
310
+ // 卸载所有已安装的 skills
311
+ // -----------------------------------------------------------------------------
312
+
313
+ /**
314
+ * 卸载所有已安装的 skills
315
+ *
316
+ * @param {UninstallOptions} [options] - 卸载选项
317
+ * @returns {Promise<{success: number, failed: number}>} 卸载结果统计
318
+ *
319
+ * @example
320
+ * // 卸载所有,带确认提示
321
+ * await uninstallAll({ yes: false });
322
+ *
323
+ * // 强制卸载所有,跳过确认
324
+ * await uninstallAll({ yes: true });
325
+ */
326
+ export async function uninstallAll(
327
+ options?: UninstallOptions
328
+ ): Promise<{ success: number; failed: number }> {
329
+ const registry = await loadRegistry();
330
+ const installedSkills = Object.keys(registry.skills);
331
+
332
+ if (installedSkills.length === 0) {
333
+ console.log('No skills installed.');
334
+ return { success: 0, failed: 0 };
335
+ }
336
+
337
+ console.log(`\nFound ${installedSkills.length} installed skill(s):`);
338
+ for (const skillId of installedSkills) {
339
+ const info = registry.skills[skillId];
340
+ console.log(` - ${skillId}@${info.version}`);
341
+ }
342
+
343
+ // ==========================================================================
344
+ // Dry-run 模式
345
+ // ==========================================================================
346
+
347
+ if (options?.dryRun) {
348
+ console.log(`\n📋 Dry-run: Would uninstall ${installedSkills.length} skill(s).`);
349
+ console.log(`⚠️ No files were actually deleted.`);
350
+ return { success: installedSkills.length, failed: 0 };
351
+ }
352
+
353
+ // ==========================================================================
354
+ // 确认提示(--all 时强制要求确认,除非使用 --yes)
355
+ // ==========================================================================
356
+
357
+ if (!options?.yes) {
358
+ const confirmed = await askConfirmation(
359
+ `\n⚠️ Are you sure you want to uninstall ALL ${installedSkills.length} skill(s)? This action cannot be undone.`
360
+ );
361
+ if (!confirmed) {
362
+ console.log('Uninstall cancelled.');
363
+ return { success: 0, failed: 0 };
364
+ }
365
+ }
366
+
367
+ // ==========================================================================
368
+ // 逐个卸载
369
+ // ==========================================================================
370
+
371
+ console.log(`\nUninstalling all skills...\n`);
372
+
373
+ let successCount = 0;
374
+ let failedCount = 0;
375
+
376
+ for (const skillId of installedSkills) {
377
+ try {
378
+ const success = await uninstallSkill(skillId, { ...options, yes: true }); // 已经确认过,跳过子确认
379
+ if (success) {
380
+ successCount++;
381
+ } else {
382
+ failedCount++;
383
+ }
384
+ } catch (error) {
385
+ console.log(`❌ Failed to uninstall ${skillId}: ${error}`);
386
+ failedCount++;
387
+ }
388
+ }
389
+
390
+ // ==========================================================================
391
+ // 完成统计
392
+ // ==========================================================================
393
+
394
+ console.log(`\n📊 Summary:`);
395
+ console.log(` ✅ Success: ${successCount}`);
396
+ console.log(` ❌ Failed: ${failedCount}`);
397
+ console.log(` 📦 Total: ${installedSkills.length}`);
398
+
399
+ return { success: successCount, failed: failedCount };
155
400
  }