@cannbot-ai/install-helper 1.1.6-beta.8 → 1.1.6
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 +34 -2
- package/dist/index.js +96 -41
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
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
|
@@ -1467,20 +1467,21 @@ function verifyPluginFilesExist(configRoot, manifest, tool, level) {
|
|
|
1467
1467
|
if (manifest.installed_skills && manifest.installed_skills.length > 0) {
|
|
1468
1468
|
const hasAnySkill = manifest.installed_skills.some((skillId) => {
|
|
1469
1469
|
const skillPath = join5(skillsDir, skillId);
|
|
1470
|
-
return existsSync5(skillPath);
|
|
1470
|
+
return existsSync5(skillPath) || isSymlink(skillPath);
|
|
1471
1471
|
});
|
|
1472
1472
|
if (hasAnySkill) hasAnyComponent = true;
|
|
1473
1473
|
}
|
|
1474
1474
|
if (manifest.installed_agents && manifest.installed_agents.length > 0) {
|
|
1475
1475
|
const hasAnyAgent = manifest.installed_agents.some((agentId) => {
|
|
1476
1476
|
const agentPath = join5(agentsDir, agentId);
|
|
1477
|
-
|
|
1477
|
+
const agentPathMd = join5(agentsDir, agentId + ".md");
|
|
1478
|
+
return existsSync5(agentPath) || isSymlink(agentPath) || existsSync5(agentPathMd) || isSymlink(agentPathMd);
|
|
1478
1479
|
});
|
|
1479
1480
|
if (hasAnyAgent) hasAnyComponent = true;
|
|
1480
1481
|
}
|
|
1481
1482
|
const agentsFileName = getAgentsFileName(tool);
|
|
1482
1483
|
const configFilePath = level === "project" ? join5(configRoot, "..", agentsFileName) : join5(configRoot, agentsFileName);
|
|
1483
|
-
if (existsSync5(configFilePath)) hasAnyComponent = true;
|
|
1484
|
+
if (existsSync5(configFilePath) || isSymlink(configFilePath)) hasAnyComponent = true;
|
|
1484
1485
|
const pluginManifestPath = join5(configRoot, `${manifest.team}-manifest.json`);
|
|
1485
1486
|
if (existsSync5(pluginManifestPath)) hasAnyComponent = true;
|
|
1486
1487
|
return hasAnyComponent;
|
|
@@ -1490,6 +1491,7 @@ var init_manifest = __esm({
|
|
|
1490
1491
|
"use strict";
|
|
1491
1492
|
init_paths();
|
|
1492
1493
|
init_registry();
|
|
1494
|
+
init_fs_helpers();
|
|
1493
1495
|
}
|
|
1494
1496
|
});
|
|
1495
1497
|
|
|
@@ -2739,7 +2741,7 @@ function deleteRecord(pluginId) {
|
|
|
2739
2741
|
unlinkSync3(recordPath);
|
|
2740
2742
|
}
|
|
2741
2743
|
}
|
|
2742
|
-
function scanInstalledFiles(pluginId, displayName, tool, level, installPath, configRoot, manifest, externalRepoNames) {
|
|
2744
|
+
function scanInstalledFiles(pluginId, displayName, tool, level, installPath, configRoot, manifest, externalRepoNames, configRootConfigLink) {
|
|
2743
2745
|
const files = [];
|
|
2744
2746
|
const directories = [];
|
|
2745
2747
|
if (manifest) {
|
|
@@ -2781,9 +2783,15 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
|
|
|
2781
2783
|
}
|
|
2782
2784
|
const configFileName = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
|
|
2783
2785
|
const configFilePath = level === "project" ? join10(installPath, configFileName) : join10(configRoot, configFileName);
|
|
2784
|
-
if (existsSync11(configFilePath)) {
|
|
2786
|
+
if (existsSync11(configFilePath) || isSymlink(configFilePath)) {
|
|
2785
2787
|
files.push(configFilePath);
|
|
2786
2788
|
}
|
|
2789
|
+
if (level === "project" && configRootConfigLink !== false) {
|
|
2790
|
+
const configRootConfigPath = join10(configRoot, configFileName);
|
|
2791
|
+
if (configRootConfigPath !== configFilePath && (existsSync11(configRootConfigPath) || isSymlink(configRootConfigPath))) {
|
|
2792
|
+
files.push(configRootConfigPath);
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2787
2795
|
const repoLinks = externalRepoNames && externalRepoNames.length > 0 ? externalRepoNames : ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples"];
|
|
2788
2796
|
for (const repoName of repoLinks) {
|
|
2789
2797
|
const repoLinkPath = join10(installPath, repoName);
|
|
@@ -2848,12 +2856,14 @@ function addSkillsToRecord(skillIds, tool, level, installPath) {
|
|
|
2848
2856
|
}
|
|
2849
2857
|
}
|
|
2850
2858
|
entry.installTime = now;
|
|
2851
|
-
if (
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2859
|
+
if (newSkills.length > 0) {
|
|
2860
|
+
if (!entry.batches) entry.batches = [];
|
|
2861
|
+
entry.batches.push({
|
|
2862
|
+
batchId: `batch-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
2863
|
+
installedAt: now,
|
|
2864
|
+
skills: newSkills
|
|
2865
|
+
});
|
|
2866
|
+
}
|
|
2857
2867
|
writeSkillRecord(record);
|
|
2858
2868
|
}
|
|
2859
2869
|
function removeSkillsFromRecord(skillIds, tool, level, installPath) {
|
|
@@ -3454,7 +3464,8 @@ async function installPlugin(opts) {
|
|
|
3454
3464
|
cwd,
|
|
3455
3465
|
configRoot,
|
|
3456
3466
|
result.manifest,
|
|
3457
|
-
plugin.externalRepos?.map((r) => basename2(r.dir))
|
|
3467
|
+
plugin.externalRepos?.map((r) => basename2(r.dir)),
|
|
3468
|
+
plugin.configRootConfigLink
|
|
3458
3469
|
);
|
|
3459
3470
|
if (backupInfo) {
|
|
3460
3471
|
record.backup = {
|
|
@@ -3540,7 +3551,8 @@ async function installPlugin(opts) {
|
|
|
3540
3551
|
cwd,
|
|
3541
3552
|
configRoot,
|
|
3542
3553
|
manifest,
|
|
3543
|
-
plugin.externalRepos?.map((r) => basename2(r.dir))
|
|
3554
|
+
plugin.externalRepos?.map((r) => basename2(r.dir)),
|
|
3555
|
+
plugin.configRootConfigLink
|
|
3544
3556
|
);
|
|
3545
3557
|
if (backupInfo) {
|
|
3546
3558
|
record.backup = {
|
|
@@ -4983,8 +4995,8 @@ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes
|
|
|
4983
4995
|
|
|
4984
4996
|
// src/commands/uninstall.ts
|
|
4985
4997
|
init_registry();
|
|
4986
|
-
import { existsSync as existsSync18, unlinkSync as unlinkSync6, rmdirSync, readdirSync as readdirSync7 } from "fs";
|
|
4987
|
-
import { resolve, basename as basename3, sep } from "path";
|
|
4998
|
+
import { existsSync as existsSync18, unlinkSync as unlinkSync6, rmdirSync, readdirSync as readdirSync7, readFileSync as readFileSync9 } from "fs";
|
|
4999
|
+
import { resolve, basename as basename3, sep, join as join18 } from "path";
|
|
4988
5000
|
import chalk11 from "chalk";
|
|
4989
5001
|
import { select as select5, checkbox as checkbox2, Separator as Separator4, confirm as confirm3 } from "@inquirer/prompts";
|
|
4990
5002
|
init_config();
|
|
@@ -4996,6 +5008,8 @@ init_manifest();
|
|
|
4996
5008
|
init_theme();
|
|
4997
5009
|
init_wizard();
|
|
4998
5010
|
async function uninstallCommand(names, options) {
|
|
5011
|
+
const userSpecifiedTool = !!options.tool;
|
|
5012
|
+
const userSpecifiedLevel = !!options.level;
|
|
4999
5013
|
const tool = options.tool ? validateTool(options.tool) : "opencode";
|
|
5000
5014
|
const level = options.level ? validateLevel(options.level) : "project";
|
|
5001
5015
|
if (options.all) {
|
|
@@ -5007,7 +5021,7 @@ async function uninstallCommand(names, options) {
|
|
|
5007
5021
|
return;
|
|
5008
5022
|
}
|
|
5009
5023
|
if (names.length === 0) {
|
|
5010
|
-
await uninstallInteractive(tool, level);
|
|
5024
|
+
await uninstallInteractive(tool, level, userSpecifiedTool, userSpecifiedLevel);
|
|
5011
5025
|
return;
|
|
5012
5026
|
}
|
|
5013
5027
|
const plugins = [];
|
|
@@ -5099,18 +5113,18 @@ async function uninstallRecent(tool, level, yes) {
|
|
|
5099
5113
|
logger.success(t("skill_uninstall_done"));
|
|
5100
5114
|
logger.blank();
|
|
5101
5115
|
}
|
|
5102
|
-
async function uninstallInteractive(defaultTool, defaultLevel) {
|
|
5116
|
+
async function uninstallInteractive(defaultTool, defaultLevel, userSpecifiedTool, userSpecifiedLevel) {
|
|
5103
5117
|
let tool = defaultTool;
|
|
5104
5118
|
let level = defaultLevel;
|
|
5105
5119
|
const allInstalledPlugins = scanInstalled();
|
|
5106
5120
|
const toolsWithPlugins = new Set(allInstalledPlugins.map((p) => `${p.tool}:${p.level}`));
|
|
5107
5121
|
const toolsWithSkills = /* @__PURE__ */ new Set();
|
|
5108
|
-
for (const
|
|
5109
|
-
for (const
|
|
5110
|
-
const ip =
|
|
5111
|
-
const skills = getInstalledSkills(
|
|
5122
|
+
for (const toolName of ["opencode", "claude", "trae", "cursor", "copilot", "codearts"]) {
|
|
5123
|
+
for (const lvl of ["project", "global"]) {
|
|
5124
|
+
const ip = lvl === "project" ? process.cwd() : getConfigRoot(toolName, lvl);
|
|
5125
|
+
const skills = getInstalledSkills(toolName, lvl, ip);
|
|
5112
5126
|
if (skills.length > 0) {
|
|
5113
|
-
toolsWithSkills.add(`${
|
|
5127
|
+
toolsWithSkills.add(`${toolName}:${lvl}`);
|
|
5114
5128
|
}
|
|
5115
5129
|
}
|
|
5116
5130
|
}
|
|
@@ -5124,9 +5138,9 @@ async function uninstallInteractive(defaultTool, defaultLevel) {
|
|
|
5124
5138
|
if (hasSkills && hasPlugins) {
|
|
5125
5139
|
printBoxTitle(t("uninstall_interactive_title"));
|
|
5126
5140
|
const skillCount = [...toolsWithSkills].reduce((sum, key) => {
|
|
5127
|
-
const [
|
|
5128
|
-
const ip =
|
|
5129
|
-
return sum + getInstalledSkills(
|
|
5141
|
+
const [toolName, lvl] = key.split(":");
|
|
5142
|
+
const ip = lvl === "project" ? process.cwd() : getConfigRoot(toolName, lvl);
|
|
5143
|
+
return sum + getInstalledSkills(toolName, lvl, ip).length;
|
|
5130
5144
|
}, 0);
|
|
5131
5145
|
const pluginCount = allInstalledPlugins.length;
|
|
5132
5146
|
const typeChoices = [
|
|
@@ -5159,7 +5173,9 @@ async function uninstallInteractive(defaultTool, defaultLevel) {
|
|
|
5159
5173
|
}
|
|
5160
5174
|
if (toolsWithContent.size === 1) {
|
|
5161
5175
|
tool = [...toolsWithContent][0];
|
|
5162
|
-
} else if (
|
|
5176
|
+
} else if (userSpecifiedTool && toolsWithContent.has(defaultTool)) {
|
|
5177
|
+
tool = defaultTool;
|
|
5178
|
+
} else {
|
|
5163
5179
|
const detected = await selectToolWithDetection();
|
|
5164
5180
|
if (detected === "back" || detected === "cancel") {
|
|
5165
5181
|
logger.info(t("init_cancelled"));
|
|
@@ -5169,14 +5185,16 @@ async function uninstallInteractive(defaultTool, defaultLevel) {
|
|
|
5169
5185
|
}
|
|
5170
5186
|
const levelsWithContent = /* @__PURE__ */ new Set();
|
|
5171
5187
|
for (const key of relevantKeys) {
|
|
5172
|
-
const [
|
|
5173
|
-
if (
|
|
5174
|
-
levelsWithContent.add(
|
|
5188
|
+
const [toolName, lvl] = key.split(":");
|
|
5189
|
+
if (toolName === tool) {
|
|
5190
|
+
levelsWithContent.add(lvl);
|
|
5175
5191
|
}
|
|
5176
5192
|
}
|
|
5177
5193
|
if (levelsWithContent.size === 1) {
|
|
5178
5194
|
level = [...levelsWithContent][0];
|
|
5179
|
-
} else if (
|
|
5195
|
+
} else if (userSpecifiedLevel && levelsWithContent.has(defaultLevel)) {
|
|
5196
|
+
level = defaultLevel;
|
|
5197
|
+
} else {
|
|
5180
5198
|
const { stepLevel: stepLevel2 } = await Promise.resolve().then(() => (init_wizard(), wizard_exports));
|
|
5181
5199
|
const levelResult = await stepLevel2();
|
|
5182
5200
|
if (levelResult === "__back__" || levelResult === "__cancel__") {
|
|
@@ -5259,7 +5277,36 @@ async function uninstallPlugins(pluginIds, batchMode) {
|
|
|
5259
5277
|
async function uninstallPluginById(pluginId, batchMode) {
|
|
5260
5278
|
const plugin = findPlugin(pluginId);
|
|
5261
5279
|
if (!plugin) return;
|
|
5262
|
-
|
|
5280
|
+
let record = readRecord(plugin.id);
|
|
5281
|
+
if (!record) {
|
|
5282
|
+
for (const toolName of VALID_TOOLS) {
|
|
5283
|
+
for (const lvl of ["project", "global"]) {
|
|
5284
|
+
const configRoot2 = getConfigRoot(toolName, lvl);
|
|
5285
|
+
const manifestPath = join18(configRoot2, `${pluginId}-manifest.json`);
|
|
5286
|
+
if (!existsSync18(manifestPath)) continue;
|
|
5287
|
+
let manifest = null;
|
|
5288
|
+
try {
|
|
5289
|
+
manifest = JSON.parse(readFileSync9(manifestPath, "utf-8"));
|
|
5290
|
+
} catch {
|
|
5291
|
+
continue;
|
|
5292
|
+
}
|
|
5293
|
+
const installPath = lvl === "project" ? process.cwd() : configRoot2;
|
|
5294
|
+
record = scanInstalledFiles(
|
|
5295
|
+
pluginId,
|
|
5296
|
+
plugin.displayName,
|
|
5297
|
+
toolName,
|
|
5298
|
+
lvl,
|
|
5299
|
+
installPath,
|
|
5300
|
+
configRoot2,
|
|
5301
|
+
manifest,
|
|
5302
|
+
plugin.externalRepos?.map((r) => basename3(r.dir)),
|
|
5303
|
+
plugin.configRootConfigLink
|
|
5304
|
+
);
|
|
5305
|
+
break;
|
|
5306
|
+
}
|
|
5307
|
+
if (record) break;
|
|
5308
|
+
}
|
|
5309
|
+
}
|
|
5263
5310
|
if (!record) {
|
|
5264
5311
|
logger.warn(`${plugin.displayName} ${t("uninstall_no_record")}`);
|
|
5265
5312
|
logger.info(`${t("uninstall_no_record_hint")}`);
|
|
@@ -5274,7 +5321,15 @@ async function uninstallPluginById(pluginId, batchMode) {
|
|
|
5274
5321
|
};
|
|
5275
5322
|
let removedFiles = 0;
|
|
5276
5323
|
let removedDirs = 0;
|
|
5277
|
-
|
|
5324
|
+
const configFileName = record.tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
|
|
5325
|
+
const configRootConfigPath = join18(configRoot, configFileName);
|
|
5326
|
+
const installPathConfigPath = join18(record.installPath, configFileName);
|
|
5327
|
+
const extraFiles = [];
|
|
5328
|
+
if (record.level === "project" && configRootConfigPath !== installPathConfigPath && !record.files.includes(configRootConfigPath) && (existsSync18(configRootConfigPath) || isSymlink(configRootConfigPath))) {
|
|
5329
|
+
extraFiles.push(configRootConfigPath);
|
|
5330
|
+
}
|
|
5331
|
+
const allFiles = [...record.files, ...extraFiles];
|
|
5332
|
+
for (const filePath of allFiles) {
|
|
5278
5333
|
if (!isSafePath(filePath)) continue;
|
|
5279
5334
|
try {
|
|
5280
5335
|
if (existsSync18(filePath) || isSymlink(filePath)) {
|
|
@@ -5455,7 +5510,7 @@ async function updateCommand(pluginNames, options) {
|
|
|
5455
5510
|
init_registry();
|
|
5456
5511
|
import chalk13 from "chalk";
|
|
5457
5512
|
import { existsSync as existsSync19, readdirSync as readdirSync8 } from "fs";
|
|
5458
|
-
import { join as
|
|
5513
|
+
import { join as join19 } from "path";
|
|
5459
5514
|
init_manifest();
|
|
5460
5515
|
init_i18n();
|
|
5461
5516
|
async function infoCommand(pluginName) {
|
|
@@ -5482,11 +5537,11 @@ async function infoCommand(pluginName) {
|
|
|
5482
5537
|
try {
|
|
5483
5538
|
const repoManager = createRepositoryManager();
|
|
5484
5539
|
const repoPath = await repoManager.ensureRepo();
|
|
5485
|
-
const quickstartPath =
|
|
5540
|
+
const quickstartPath = join19(repoPath, plugin.dir, "quickstart.md");
|
|
5486
5541
|
if (existsSync19(quickstartPath)) {
|
|
5487
5542
|
console.log(` ${chalk13.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
|
|
5488
5543
|
}
|
|
5489
|
-
const agentsDir =
|
|
5544
|
+
const agentsDir = join19(repoPath, plugin.dir, "agents");
|
|
5490
5545
|
if (existsSync19(agentsDir)) {
|
|
5491
5546
|
const agentFiles = readdirSync8(agentsDir).filter((f) => f.endsWith(".md"));
|
|
5492
5547
|
if (agentFiles.length > 0) {
|
|
@@ -5543,9 +5598,9 @@ async function langCommand(action, value) {
|
|
|
5543
5598
|
// src/cli.ts
|
|
5544
5599
|
init_config();
|
|
5545
5600
|
init_i18n();
|
|
5546
|
-
import { readFileSync as
|
|
5601
|
+
import { readFileSync as readFileSync10, existsSync as existsSync20 } from "fs";
|
|
5547
5602
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
5548
|
-
import { dirname as dirname6, join as
|
|
5603
|
+
import { dirname as dirname6, join as join20 } from "path";
|
|
5549
5604
|
function readVersion() {
|
|
5550
5605
|
try {
|
|
5551
5606
|
if (typeof INSTALL_HELPER_VERSION !== "undefined" && INSTALL_HELPER_VERSION) {
|
|
@@ -5557,13 +5612,13 @@ function readVersion() {
|
|
|
5557
5612
|
const __filename4 = fileURLToPath4(import.meta.url);
|
|
5558
5613
|
const __dirname4 = dirname6(__filename4);
|
|
5559
5614
|
const candidates = [
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5615
|
+
join20(__dirname4, "..", "package.json"),
|
|
5616
|
+
join20(__dirname4, "package.json"),
|
|
5617
|
+
join20(process.cwd(), "package.json")
|
|
5563
5618
|
];
|
|
5564
5619
|
for (const pkgPath of candidates) {
|
|
5565
5620
|
if (existsSync20(pkgPath)) {
|
|
5566
|
-
const pkg = JSON.parse(
|
|
5621
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
5567
5622
|
if (pkg.version && (pkg.name === "@cannbot-ai/install-helper" || pkg.name?.startsWith("@cannbot-ai/install-helper-"))) {
|
|
5568
5623
|
return pkg.version;
|
|
5569
5624
|
}
|