@cannbot-ai/install-helper 1.1.6-beta.9 → 1.1.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/README.md CHANGED
@@ -125,13 +125,45 @@ install-helper uninstall --all --tool opencode --level project # 指定范围
125
125
 
126
126
  根据安装记录精确删除所有产物,自动清理空目录。
127
127
 
128
- 交互式卸载:已安装项全部预勾选(✓=保留),取消勾选后确认即可批量卸载。
128
+ **交互式卸载**:
129
+
130
+ ```bash
131
+ install-helper uninstall
132
+ ```
133
+
134
+ - 自动检测已安装内容,跳过只有单一选项的步骤(如仅一个工具/级别有内容时自动选中)
135
+ - 若 Skills 和 Plugins 都已安装,先选择卸载类型
136
+ - ≤15 个已安装 Skill 时显示扁平列表,>15 个时按类别导航
137
+ - **勾选=卸载**(标准 checkbox 语义),勾选后确认即可批量卸载
138
+ - 支持跨类别累积选择
139
+
140
+ **`--all` 一键卸载**:
141
+
142
+ ```bash
143
+ install-helper uninstall --all --yes
144
+ ```
145
+
146
+ 卸载指定范围内全部已安装的 Skills 和 Plugins。通过 `--tool` / `--level` 限定范围,未指定时默认 project 级别。
147
+
148
+ **`--recent` 批次卸载**:
149
+
150
+ ```bash
151
+ install-helper uninstall --recent --yes
152
+ ```
153
+
154
+ 仅卸载最近一次安装批次中的 Skills。每次 `install-helper install` 会自动记录安装批次,`--recent` 精确卸载最后一批,不影响更早安装的内容。
155
+
156
+ **安全机制**:
157
+
158
+ - 仅删除 `install-helper` 安装的 symlink,不影响用户手动创建的目录或文件
159
+ - 无安装记录时自动回退到文件系统扫描(识别 symlink)和 manifest 文件,确保残留内容可清理
160
+ - Plugin 卸载时完整清理 skills、agents、配置文件(AGENTS.md/CLAUDE.md)、manifest、外部仓库链接
129
161
 
130
162
  **选项:**
131
163
 
132
164
  | 选项 | 说明 |
133
165
  |------|------|
134
- | `--tool <tool>` | 指定 AI 工具 |
166
+ | `--tool <tool>` | 指定 AI 工具(opencode, claude, trae, cursor, copilot, codearts) |
135
167
  | `--level <level>` | 安装级别(project, global),默认 project |
136
168
  | `--yes` `-y` | 跳过确认提示 |
137
169
  | `--all` `-a` | 卸载全部已安装 Skills 和 Plugins |
package/dist/index.js CHANGED
@@ -479,7 +479,7 @@ var init_embedded_plugins = __esm({
479
479
  aliases: [
480
480
  "triton"
481
481
  ],
482
- skills: 7,
482
+ skills: 8,
483
483
  agents: 0,
484
484
  description: "Triton-Ascend \u7B97\u5B50 6 \u9636\u6BB5\u81EA\u52A8\u751F\u6210\u4E0E\u4F18\u5316\uFF08\u63CF\u8FF0\u2192\u4EE3\u7801\u2192\u9A8C\u8BC1\u2192\u4F18\u5316\uFF09",
485
485
  version: "1.0.2",
@@ -495,7 +495,8 @@ var init_embedded_plugins = __esm({
495
495
  "triton-op-verifier",
496
496
  "triton-latency-optimizer",
497
497
  "npu-arch",
498
- "triton-precision-debug"
498
+ "triton-precision-debug",
499
+ "triton-simulator-optimizer"
499
500
  ]
500
501
  }
501
502
  ],
@@ -1467,20 +1468,21 @@ function verifyPluginFilesExist(configRoot, manifest, tool, level) {
1467
1468
  if (manifest.installed_skills && manifest.installed_skills.length > 0) {
1468
1469
  const hasAnySkill = manifest.installed_skills.some((skillId) => {
1469
1470
  const skillPath = join5(skillsDir, skillId);
1470
- return existsSync5(skillPath);
1471
+ return existsSync5(skillPath) || isSymlink(skillPath);
1471
1472
  });
1472
1473
  if (hasAnySkill) hasAnyComponent = true;
1473
1474
  }
1474
1475
  if (manifest.installed_agents && manifest.installed_agents.length > 0) {
1475
1476
  const hasAnyAgent = manifest.installed_agents.some((agentId) => {
1476
1477
  const agentPath = join5(agentsDir, agentId);
1477
- return existsSync5(agentPath);
1478
+ const agentPathMd = join5(agentsDir, agentId + ".md");
1479
+ return existsSync5(agentPath) || isSymlink(agentPath) || existsSync5(agentPathMd) || isSymlink(agentPathMd);
1478
1480
  });
1479
1481
  if (hasAnyAgent) hasAnyComponent = true;
1480
1482
  }
1481
1483
  const agentsFileName = getAgentsFileName(tool);
1482
1484
  const configFilePath = level === "project" ? join5(configRoot, "..", agentsFileName) : join5(configRoot, agentsFileName);
1483
- if (existsSync5(configFilePath)) hasAnyComponent = true;
1485
+ if (existsSync5(configFilePath) || isSymlink(configFilePath)) hasAnyComponent = true;
1484
1486
  const pluginManifestPath = join5(configRoot, `${manifest.team}-manifest.json`);
1485
1487
  if (existsSync5(pluginManifestPath)) hasAnyComponent = true;
1486
1488
  return hasAnyComponent;
@@ -1490,6 +1492,7 @@ var init_manifest = __esm({
1490
1492
  "use strict";
1491
1493
  init_paths();
1492
1494
  init_registry();
1495
+ init_fs_helpers();
1493
1496
  }
1494
1497
  });
1495
1498
 
@@ -2854,12 +2857,14 @@ function addSkillsToRecord(skillIds, tool, level, installPath) {
2854
2857
  }
2855
2858
  }
2856
2859
  entry.installTime = now;
2857
- if (!entry.batches) entry.batches = [];
2858
- entry.batches.push({
2859
- batchId: `batch-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
2860
- installedAt: now,
2861
- skills: newSkills.length > 0 ? newSkills : [...skillIds]
2862
- });
2860
+ if (newSkills.length > 0) {
2861
+ if (!entry.batches) entry.batches = [];
2862
+ entry.batches.push({
2863
+ batchId: `batch-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
2864
+ installedAt: now,
2865
+ skills: newSkills
2866
+ });
2867
+ }
2863
2868
  writeSkillRecord(record);
2864
2869
  }
2865
2870
  function removeSkillsFromRecord(skillIds, tool, level, installPath) {
@@ -2887,7 +2892,7 @@ function removeSkillsFromRecord(skillIds, tool, level, installPath) {
2887
2892
  }
2888
2893
  writeSkillRecord(record);
2889
2894
  }
2890
- function getInstalledSkills(tool, level, installPath) {
2895
+ function getInstalledSkills(tool, level, installPath, allowFsScan = true) {
2891
2896
  const record = readSkillRecord();
2892
2897
  const recordedSkills = record[tool]?.[level]?.[installPath]?.skills || [];
2893
2898
  const configRoot = getConfigRoot(tool, level);
@@ -2896,6 +2901,9 @@ function getInstalledSkills(tool, level, installPath) {
2896
2901
  const skillPath = join10(skillsDir, skillId);
2897
2902
  return existsSync11(skillPath) || isSymlink(skillPath);
2898
2903
  });
2904
+ if (!allowFsScan) {
2905
+ return fromRecord;
2906
+ }
2899
2907
  const fromFs = [];
2900
2908
  if (existsSync11(skillsDir)) {
2901
2909
  try {
@@ -2919,7 +2927,7 @@ function getLastBatchSkills(tool, level, installPath) {
2919
2927
  return null;
2920
2928
  }
2921
2929
  const lastBatch = entry.batches[entry.batches.length - 1];
2922
- const configRoot = getConfigRoot(tool, level);
2930
+ const configRoot = level === "project" ? getConfigRoot(tool, level, installPath) : getConfigRoot(tool, level);
2923
2931
  const skillsDir = join10(configRoot, "skills");
2924
2932
  return lastBatch.skills.filter((skillId) => {
2925
2933
  const skillPath = join10(skillsDir, skillId);
@@ -4991,7 +4999,7 @@ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes
4991
4999
 
4992
5000
  // src/commands/uninstall.ts
4993
5001
  init_registry();
4994
- import { existsSync as existsSync18, unlinkSync as unlinkSync6, rmdirSync, readdirSync as readdirSync7 } from "fs";
5002
+ import { existsSync as existsSync18, unlinkSync as unlinkSync6, rmdirSync, readdirSync as readdirSync7, readFileSync as readFileSync9 } from "fs";
4995
5003
  import { resolve, basename as basename3, sep, join as join18 } from "path";
4996
5004
  import chalk11 from "chalk";
4997
5005
  import { select as select5, checkbox as checkbox2, Separator as Separator4, confirm as confirm3 } from "@inquirer/prompts";
@@ -5004,6 +5012,8 @@ init_manifest();
5004
5012
  init_theme();
5005
5013
  init_wizard();
5006
5014
  async function uninstallCommand(names, options) {
5015
+ const userSpecifiedTool = !!options.tool;
5016
+ const userSpecifiedLevel = !!options.level;
5007
5017
  const tool = options.tool ? validateTool(options.tool) : "opencode";
5008
5018
  const level = options.level ? validateLevel(options.level) : "project";
5009
5019
  if (options.all) {
@@ -5015,7 +5025,7 @@ async function uninstallCommand(names, options) {
5015
5025
  return;
5016
5026
  }
5017
5027
  if (names.length === 0) {
5018
- await uninstallInteractive(tool, level);
5028
+ await uninstallInteractive(tool, level, userSpecifiedTool, userSpecifiedLevel);
5019
5029
  return;
5020
5030
  }
5021
5031
  const plugins = [];
@@ -5038,7 +5048,7 @@ async function uninstallCommand(names, options) {
5038
5048
  }
5039
5049
  async function uninstallAll(tool, level, yes) {
5040
5050
  const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
5041
- const installedSkills = getInstalledSkills(tool, level, installPath);
5051
+ const installedSkills = getInstalledSkills(tool, level, installPath, !yes);
5042
5052
  const installedPlugins = scanInstalled().filter((p) => p.tool === tool && p.level === level);
5043
5053
  const totalItems = installedSkills.length + installedPlugins.length;
5044
5054
  if (totalItems === 0) {
@@ -5107,18 +5117,18 @@ async function uninstallRecent(tool, level, yes) {
5107
5117
  logger.success(t("skill_uninstall_done"));
5108
5118
  logger.blank();
5109
5119
  }
5110
- async function uninstallInteractive(defaultTool, defaultLevel) {
5120
+ async function uninstallInteractive(defaultTool, defaultLevel, userSpecifiedTool, userSpecifiedLevel) {
5111
5121
  let tool = defaultTool;
5112
5122
  let level = defaultLevel;
5113
5123
  const allInstalledPlugins = scanInstalled();
5114
5124
  const toolsWithPlugins = new Set(allInstalledPlugins.map((p) => `${p.tool}:${p.level}`));
5115
5125
  const toolsWithSkills = /* @__PURE__ */ new Set();
5116
- for (const t2 of ["opencode", "claude", "trae", "cursor", "copilot", "codearts"]) {
5117
- for (const l of ["project", "global"]) {
5118
- const ip = l === "project" ? process.cwd() : getConfigRoot(t2, l);
5119
- const skills = getInstalledSkills(t2, l, ip);
5126
+ for (const toolName of ["opencode", "claude", "trae", "cursor", "copilot", "codearts"]) {
5127
+ for (const lvl of ["project", "global"]) {
5128
+ const ip = lvl === "project" ? process.cwd() : getConfigRoot(toolName, lvl);
5129
+ const skills = getInstalledSkills(toolName, lvl, ip);
5120
5130
  if (skills.length > 0) {
5121
- toolsWithSkills.add(`${t2}:${l}`);
5131
+ toolsWithSkills.add(`${toolName}:${lvl}`);
5122
5132
  }
5123
5133
  }
5124
5134
  }
@@ -5132,9 +5142,9 @@ async function uninstallInteractive(defaultTool, defaultLevel) {
5132
5142
  if (hasSkills && hasPlugins) {
5133
5143
  printBoxTitle(t("uninstall_interactive_title"));
5134
5144
  const skillCount = [...toolsWithSkills].reduce((sum, key) => {
5135
- const [t2, l] = key.split(":");
5136
- const ip = l === "project" ? process.cwd() : getConfigRoot(t2, l);
5137
- return sum + getInstalledSkills(t2, l, ip).length;
5145
+ const [toolName, lvl] = key.split(":");
5146
+ const ip = lvl === "project" ? process.cwd() : getConfigRoot(toolName, lvl);
5147
+ return sum + getInstalledSkills(toolName, lvl, ip).length;
5138
5148
  }, 0);
5139
5149
  const pluginCount = allInstalledPlugins.length;
5140
5150
  const typeChoices = [
@@ -5167,7 +5177,9 @@ async function uninstallInteractive(defaultTool, defaultLevel) {
5167
5177
  }
5168
5178
  if (toolsWithContent.size === 1) {
5169
5179
  tool = [...toolsWithContent][0];
5170
- } else if (!process.env.INSTALL_HELPER_TOOL) {
5180
+ } else if (userSpecifiedTool && toolsWithContent.has(defaultTool)) {
5181
+ tool = defaultTool;
5182
+ } else {
5171
5183
  const detected = await selectToolWithDetection();
5172
5184
  if (detected === "back" || detected === "cancel") {
5173
5185
  logger.info(t("init_cancelled"));
@@ -5177,14 +5189,16 @@ async function uninstallInteractive(defaultTool, defaultLevel) {
5177
5189
  }
5178
5190
  const levelsWithContent = /* @__PURE__ */ new Set();
5179
5191
  for (const key of relevantKeys) {
5180
- const [t2, l] = key.split(":");
5181
- if (t2 === tool) {
5182
- levelsWithContent.add(l);
5192
+ const [toolName, lvl] = key.split(":");
5193
+ if (toolName === tool) {
5194
+ levelsWithContent.add(lvl);
5183
5195
  }
5184
5196
  }
5185
5197
  if (levelsWithContent.size === 1) {
5186
5198
  level = [...levelsWithContent][0];
5187
- } else if (!process.env.INSTALL_HELPER_LEVEL) {
5199
+ } else if (userSpecifiedLevel && levelsWithContent.has(defaultLevel)) {
5200
+ level = defaultLevel;
5201
+ } else {
5188
5202
  const { stepLevel: stepLevel2 } = await Promise.resolve().then(() => (init_wizard(), wizard_exports));
5189
5203
  const levelResult = await stepLevel2();
5190
5204
  if (levelResult === "__back__" || levelResult === "__cancel__") {
@@ -5267,7 +5281,36 @@ async function uninstallPlugins(pluginIds, batchMode) {
5267
5281
  async function uninstallPluginById(pluginId, batchMode) {
5268
5282
  const plugin = findPlugin(pluginId);
5269
5283
  if (!plugin) return;
5270
- const record = readRecord(plugin.id);
5284
+ let record = readRecord(plugin.id);
5285
+ if (!record) {
5286
+ for (const toolName of VALID_TOOLS) {
5287
+ for (const lvl of ["project", "global"]) {
5288
+ const configRoot2 = getConfigRoot(toolName, lvl);
5289
+ const manifestPath = join18(configRoot2, `${pluginId}-manifest.json`);
5290
+ if (!existsSync18(manifestPath)) continue;
5291
+ let manifest = null;
5292
+ try {
5293
+ manifest = JSON.parse(readFileSync9(manifestPath, "utf-8"));
5294
+ } catch {
5295
+ continue;
5296
+ }
5297
+ const installPath = lvl === "project" ? process.cwd() : configRoot2;
5298
+ record = scanInstalledFiles(
5299
+ pluginId,
5300
+ plugin.displayName,
5301
+ toolName,
5302
+ lvl,
5303
+ installPath,
5304
+ configRoot2,
5305
+ manifest,
5306
+ plugin.externalRepos?.map((r) => basename3(r.dir)),
5307
+ plugin.configRootConfigLink
5308
+ );
5309
+ break;
5310
+ }
5311
+ if (record) break;
5312
+ }
5313
+ }
5271
5314
  if (!record) {
5272
5315
  logger.warn(`${plugin.displayName} ${t("uninstall_no_record")}`);
5273
5316
  logger.info(`${t("uninstall_no_record_hint")}`);
@@ -5559,7 +5602,7 @@ async function langCommand(action, value) {
5559
5602
  // src/cli.ts
5560
5603
  init_config();
5561
5604
  init_i18n();
5562
- import { readFileSync as readFileSync9, existsSync as existsSync20 } from "fs";
5605
+ import { readFileSync as readFileSync10, existsSync as existsSync20 } from "fs";
5563
5606
  import { fileURLToPath as fileURLToPath4 } from "url";
5564
5607
  import { dirname as dirname6, join as join20 } from "path";
5565
5608
  function readVersion() {
@@ -5579,7 +5622,7 @@ function readVersion() {
5579
5622
  ];
5580
5623
  for (const pkgPath of candidates) {
5581
5624
  if (existsSync20(pkgPath)) {
5582
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
5625
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
5583
5626
  if (pkg.version && (pkg.name === "@cannbot-ai/install-helper" || pkg.name?.startsWith("@cannbot-ai/install-helper-"))) {
5584
5627
  return pkg.version;
5585
5628
  }