@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 +65 -4
- package/dist/index.js +49 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
196
|
-
3.
|
|
197
|
-
4.
|
|
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
|
|
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
|
-
|
|
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
|
|
2199
|
-
const
|
|
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.
|
|
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
|
});
|