@cannbot-ai/install-helper 0.0.2-beta.11 → 0.0.2-beta.14

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
@@ -18,6 +18,22 @@ npx @cannbot-ai/install-helper
18
18
 
19
19
  交互式向导会自动检测已安装的 AI 编程工具(OpenCode / Claude Code / Trae / Cursor),通过箭头键选择场景即可完成安装。
20
20
 
21
+ ## 主要特性
22
+
23
+ ### 核心功能
24
+ - **动态 Skill 发现**:自动扫描仓库中的 SKILL.md 文件,实时发现可用 Skills
25
+ - **智能缓存机制**:扫描结果缓存 24 小时,大幅提升后续命令执行速度
26
+ - **备份与恢复**:安装新插件时自动备份 AGENTS.md,支持一键恢复
27
+ - **参数校验**:`--tool` 和 `--level` 参数支持校验,提供友好的错误提示(支持中英文)
28
+ - **智能记忆**:记住上次选择的工具和安装级别,下次运行自动填充
29
+
30
+ ### 安装管理
31
+ - **交互式向导**:箭头键选择,空格切换,回车确认
32
+ - **插件安装**:支持 10 个官方插件,自动处理依赖
33
+ - **Skill 安装**:支持 89 个 Skills,按 11 个类别组织
34
+ - **智能卸载**:根据安装记录精确删除,自动清理空目录
35
+ - **健康检查**:检测安装状态,自动修复常见问题
36
+
21
37
  ## 命令一览
22
38
 
23
39
  | 命令 | 说明 |
@@ -168,6 +184,10 @@ install-helper uninstall ops-direct-invoke --tool opencode
168
184
 
169
185
  ## 可用场景
170
186
 
187
+ install-helper 支持 **10 个官方插件** 和 **89 个 Skills**(动态发现,数量可能随仓库更新而变化)。
188
+
189
+ #### 官方插件
190
+
171
191
  | # | 场景 | ID | 别名 |
172
192
  |---|------|----|------|
173
193
  | 1 | AscendC Kernel 直调 | ops-direct-invoke | ops-direct, direct, kernel |
@@ -181,6 +201,23 @@ install-helper uninstall ops-direct-invoke --tool opencode
181
201
  | 9 | 代码检视 | ops-code-reviewer | code-review, reviewer |
182
202
  | 10 | torch.compile 图模式 | torch-compile | torch, compile |
183
203
 
204
+ #### Skills 类别
205
+
206
+ Skills 按 11 个类别组织(动态发现,数量可能随仓库更新而变化):
207
+ - 知识与参考
208
+ - 环境与工具
209
+ - 调试与诊断
210
+ - 测试与质量
211
+ - AscendC 开发
212
+ - PyPTO 开发
213
+ - TileLang 开发
214
+ - Triton 开发
215
+ - 模型推理优化
216
+ - 图模式
217
+ - 平台工具
218
+
219
+ 使用 `install-helper install --list` 查看完整的 Skills 列表。
220
+
184
221
  ## 前置条件
185
222
 
186
223
  - Node.js >= 18
@@ -192,17 +229,21 @@ install-helper uninstall ops-direct-invoke --tool opencode
192
229
  install-helper 是一个编排工具:
193
230
 
194
231
  1. **自动检测**已安装的 AI 编程工具
195
- 2. **克隆或更新** cannbot-skills 仓库(缓存到 `~/.cannbot/repo/`)
196
- 3. **调用**各插件的 `init.sh` / `install.sh` 脚本执行实际安装
197
- 4. **追踪**安装状态(`~/.cannbot/config.yaml` + manifest)
232
+ 2. **动态扫描**仓库中的 SKILL.md 文件,发现可用 Skills
233
+ 3. **缓存扫描结果**到 `~/.cannbot/scan-cache.json`(24 小时有效)
234
+ 4. **克隆或更新** cannbot-skills 仓库(缓存到 `~/.cannbot/repo/`)
235
+ 5. **备份现有配置**(如果检测到已安装的 AGENTS.md)
236
+ 6. **调用**各插件的 `init.sh` / `install.sh` 脚本执行实际安装
237
+ 7. **追踪**安装状态(`~/.cannbot/config.yaml` + manifest)
198
238
 
199
239
  安装逻辑仍由各插件的 init.sh 脚本负责,install-helper 提供统一的交互式入口和状态管理。
200
240
 
201
241
  ## 配置文件
202
242
 
203
- 配置保存在 `~/.cannbot/config.yaml`:
243
+ 配置保存在 `~/.cannbot/` 目录:
204
244
 
205
245
  ```yaml
246
+ # config.yaml - 用户配置
206
247
  language: zh_CN # 界面语言
207
248
  lastTool: opencode # 上次使用的 AI 工具
208
249
  lastLevel: project # 上次安装级别
@@ -211,6 +252,26 @@ installedPlugins: # 已安装的插件列表
211
252
  - pypto-op-orchestrator
212
253
  ```
213
254
 
255
+ ```json
256
+ // scan-cache.json - 扫描缓存(自动生成,24 小时有效)
257
+ {
258
+ "skills": [...],
259
+ "timestamp": 1234567890,
260
+ "repoPath": "/path/to/repo"
261
+ }
262
+ ```
263
+
264
+ ```json
265
+ // installs/<plugin>.json - 安装记录(每个插件一个文件)
266
+ {
267
+ "pluginId": "ops-direct-invoke",
268
+ "installTime": "2026-06-23T10:00:00Z",
269
+ "configRoot": "/path/to/config",
270
+ "files": [...],
271
+ "directories": [...]
272
+ }
273
+ ```
274
+
214
275
  ## 开发
215
276
 
216
277
  ```bash
package/dist/index.js CHANGED
@@ -682,6 +682,9 @@ function scanInstalled() {
682
682
  for (const manifest of manifests) {
683
683
  const plugin = plugins.find((p) => p.id === manifest.team);
684
684
  if (!plugin) continue;
685
+ if (!verifyPluginFilesExist(configRoot, manifest, tool, level)) {
686
+ continue;
687
+ }
685
688
  const alreadyAdded = installed.some(
686
689
  (p) => p.id === plugin.id && p.tool === tool && p.level === level
687
690
  );
@@ -701,6 +704,32 @@ function scanInstalled() {
701
704
  }
702
705
  return installed;
703
706
  }
707
+ function verifyPluginFilesExist(configRoot, manifest, tool, level) {
708
+ const skillsDir = join3(configRoot, "skills");
709
+ const agentsDir = join3(configRoot, "agents");
710
+ if (manifest.installed_skills && manifest.installed_skills.length > 0) {
711
+ const hasAnySkill = manifest.installed_skills.some((skillId) => {
712
+ const skillPath = join3(skillsDir, skillId);
713
+ return existsSync3(skillPath);
714
+ });
715
+ if (!hasAnySkill) return false;
716
+ }
717
+ if (manifest.installed_agents && manifest.installed_agents.length > 0) {
718
+ const hasAnyAgent = manifest.installed_agents.some((agentId) => {
719
+ const agentPath = join3(agentsDir, agentId);
720
+ return existsSync3(agentPath);
721
+ });
722
+ if (!hasAnyAgent) return false;
723
+ }
724
+ let agentsMdPath;
725
+ if (tool === "opencode" && level === "project") {
726
+ agentsMdPath = join3(configRoot, "..", "AGENTS.md");
727
+ } else {
728
+ agentsMdPath = join3(configRoot, "AGENTS.md");
729
+ }
730
+ if (!existsSync3(agentsMdPath)) return false;
731
+ return true;
732
+ }
704
733
 
705
734
  // src/utils/logger.ts
706
735
  import chalk from "chalk";
@@ -1096,7 +1125,7 @@ import { execa as execa2 } from "execa";
1096
1125
 
1097
1126
  // src/core/scanner.ts
1098
1127
  import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1099
- import { join as join5 } from "path";
1128
+ import { join as join5, dirname } from "path";
1100
1129
  import { parse as parseYaml } from "yaml";
1101
1130
  var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
1102
1131
  "node_modules",
@@ -1779,7 +1808,13 @@ function removeSkillsFromRecord(skillIds, tool, level, installPath) {
1779
1808
  }
1780
1809
  function getInstalledSkills(tool, level, installPath) {
1781
1810
  const record = readSkillRecord();
1782
- return record[tool]?.[level]?.[installPath]?.skills || [];
1811
+ const recordedSkills = record[tool]?.[level]?.[installPath]?.skills || [];
1812
+ const configRoot = getConfigRoot(tool, level);
1813
+ const skillsDir = join7(configRoot, "skills");
1814
+ return recordedSkills.filter((skillId) => {
1815
+ const skillPath = join7(skillsDir, skillId);
1816
+ return existsSync7(skillPath) || isSymlink(skillPath);
1817
+ });
1783
1818
  }
1784
1819
 
1785
1820
  // src/core/backup.ts
@@ -2159,7 +2194,7 @@ async function uninstallSkills(skillIds, tool, level) {
2159
2194
  }
2160
2195
  return results;
2161
2196
  }
2162
- async function interactiveSkillSelect() {
2197
+ async function interactiveSkillSelect(tool, level) {
2163
2198
  const BACK3 = "__back__";
2164
2199
  const CANCEL3 = "__cancel__";
2165
2200
  const categories = getAllCategories();
@@ -2195,15 +2230,8 @@ async function interactiveSkillSelect() {
2195
2230
  const category = categories.find((c) => c.id === selectedCategoryId);
2196
2231
  if (!category) return [];
2197
2232
  printBoxTitle(t("wizard_step_4_skill_title").replace("{category}", category.name));
2198
- const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
2199
- const levels = ["project", "global"];
2200
- const installedSkills = [];
2201
- for (const tk of tools) {
2202
- for (const l of levels) {
2203
- const path = l === "project" ? process.cwd() : getConfigRoot(tk, l);
2204
- installedSkills.push(...getInstalledSkills(tk, l, path));
2205
- }
2206
- }
2233
+ const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
2234
+ const installedSkills = getInstalledSkills(tool, level, installPath);
2207
2235
  const installedSet = new Set(installedSkills);
2208
2236
  const skillChoices = category.skills.map((skill) => {
2209
2237
  const isInstalled = installedSet.has(skill.id);
@@ -2536,7 +2564,7 @@ async function skillInstallFlow() {
2536
2564
  break;
2537
2565
  }
2538
2566
  case 2: {
2539
- const selectedSkills = await interactiveSkillSelect();
2567
+ const selectedSkills = await interactiveSkillSelect(tool, level);
2540
2568
  if (selectedSkills === "back") {
2541
2569
  step = 1;
2542
2570
  break;
@@ -2807,20 +2835,22 @@ async function installCommand(names, options) {
2807
2835
  tool2 = validateTool(options.tool);
2808
2836
  } else {
2809
2837
  const detected = await selectToolWithDetection();
2810
- if (detected === "back") return;
2838
+ if (detected === "back" || detected === "cancel") return;
2811
2839
  tool2 = detected;
2812
2840
  }
2841
+ const level2 = options.level ? validateLevel(options.level) : "project";
2813
2842
  let step = 1;
2814
2843
  while (true) {
2815
2844
  switch (step) {
2816
2845
  case 1: {
2817
- const selectedSkills = await interactiveSkillSelect();
2846
+ const selectedSkills = await interactiveSkillSelect(tool2, level2);
2818
2847
  if (selectedSkills === "back") {
2819
2848
  const redetected = await selectToolWithDetection();
2820
- if (redetected === "back") return;
2849
+ if (redetected === "back" || redetected === "cancel") return;
2821
2850
  tool2 = redetected;
2822
2851
  break;
2823
2852
  }
2853
+ if (selectedSkills === "cancel") return;
2824
2854
  if (selectedSkills.length === 0) {
2825
2855
  logger.info(t("install_no_skill_selected"));
2826
2856
  return;
@@ -2832,7 +2862,6 @@ async function installCommand(names, options) {
2832
2862
  const repoPath = await repoManager.ensureRepo();
2833
2863
  spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
2834
2864
  logger.info(`${t("skill_install_progress")} ${chalk9.bold(names.length)} \u4E2A Skills...`);
2835
- const level2 = options.level ? validateLevel(options.level) : "project";
2836
2865
  const results = await installSkills(names, tool2, level2, repoPath);
2837
2866
  let successCount = 0;
2838
2867
  let failCount = 0;
@@ -2896,7 +2925,7 @@ async function installCommand(names, options) {
2896
2925
  tool = validateTool(options.tool);
2897
2926
  } else {
2898
2927
  const detected = await selectToolWithDetection();
2899
- if (detected === "back") return;
2928
+ if (detected === "back" || detected === "cancel") return;
2900
2929
  tool = detected;
2901
2930
  }
2902
2931
  const level = options.level ? validateLevel(options.level) : "project";
@@ -3133,7 +3162,7 @@ async function updateCommand(pluginNames, options) {
3133
3162
  tool = validateTool(options.tool);
3134
3163
  } else {
3135
3164
  const detected = await selectToolWithDetection();
3136
- if (detected === "back") return;
3165
+ if (detected === "back" || detected === "cancel") return;
3137
3166
  tool = detected;
3138
3167
  }
3139
3168
  const level = options.level ? validateLevel(options.level) : "project";
@@ -3295,7 +3324,7 @@ function createCLI() {
3295
3324
  setLanguage(config.language);
3296
3325
  }
3297
3326
  const program2 = new Command();
3298
- program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.11");
3327
+ program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.14");
3299
3328
  program2.command("init", { isDefault: false }).description("Run interactive installation wizard").action(async () => {
3300
3329
  await initCommand();
3301
3330
  });