@cannbot-ai/install-helper 0.0.2-beta.19 → 0.0.2-beta.20

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,20 @@ npx @cannbot-ai/install-helper
18
18
 
19
19
  交互式向导会自动检测已安装的 AI 编程工具(OpenCode / Claude Code / Trae / Cursor),通过箭头键选择场景即可完成安装。
20
20
 
21
+ ## 一键安装所有 Skills
22
+
23
+ 如果 opencode 等工具已安装,可直接一键安装全部 Skills(无需逐个点选):
24
+
25
+ ```bash
26
+ # 一键安装全部 Skills 到当前项目(跳过确认)
27
+ install-helper install --all --tool opencode --yes
28
+
29
+ # 一键安装全部 Skills 到全局
30
+ install-helper install --all --tool opencode --level global --yes
31
+ ```
32
+
33
+ > 交互式向导也支持「全选所有 Skills」和「全选当前类别」,详见下文。
34
+
21
35
  ## 主要特性
22
36
 
23
37
  ### 核心功能
@@ -42,6 +56,7 @@ npx @cannbot-ai/install-helper
42
56
  | `install-helper list` | 列出可用场景及安装状态 |
43
57
  | `install-helper install <name>` | 安装指定插件或 Skill(自动识别) |
44
58
  | `install-helper install --list` | 按类别列出所有可用 Skills |
59
+ | `install-helper install --all --tool opencode --yes` | 一键安装全部 Skills |
45
60
  | `install-helper update [plugin]` | 更新已安装的 Skills |
46
61
  | `install-helper uninstall <plugin>` | 卸载指定场景 |
47
62
  | `install-helper status` | 查看已安装插件详情 |
@@ -114,14 +129,19 @@ Skills 按 11 个大类组织:知识与参考、环境与工具、调试与诊
114
129
 
115
130
  `install` 命令自动识别输入是插件还是 Skill,无需区分。
116
131
 
132
+ **交互式「全选」**:运行 `install-helper install`(不带参数)进入交互式选择时:
133
+ - 类别列表顶部有 **「全选所有 Skills」**,选中即一键安装全部 Skills。
134
+ - 进入某类别后,顶部有 **「全选当前类别」**,勾选后回车即选中该类别全部 Skills。
135
+ - 选完一个类别后会提示「继续选择其他类别 / 确认安装」,可跨类别累积选择后再统一安装。
136
+
117
137
  **选项:**
118
138
 
119
139
  | 选项 | 说明 |
120
140
  |------|------|
121
141
  | `--tool <tool>` | 指定 AI 工具(opencode, claude, trae, cursor, copilot) |
122
142
  | `--level <level>` | 安装级别(project, global),默认 project |
123
- | `--all` | 安装全部场景 |
124
- | `--yes` | 跳过所有确认提示 |
143
+ | `--all` `-a` | 安装全部 Skills(一键安装) |
144
+ | `--yes` `-y` | 跳过所有确认提示 |
125
145
 
126
146
  **已装检测**:如果插件已安装,会提示是否覆盖安装。使用 `--yes` 可跳过提示直接覆盖。
127
147
 
package/dist/index.js CHANGED
@@ -48,7 +48,6 @@ function loadPluginsFromYaml() {
48
48
  }
49
49
  } catch {
50
50
  }
51
- plugins.sort((a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0);
52
51
  return plugins;
53
52
  }
54
53
  var PLUGIN_REGISTRY = loadPluginsFromYaml();
@@ -1906,22 +1905,34 @@ async function uninstallSkills(skillIds, tool, level) {
1906
1905
  }
1907
1906
  return results;
1908
1907
  }
1908
+ var SELECT_ALL = "__select_all__";
1909
+ var SELECT_ALL_CATEGORY = "__select_all_category__";
1910
+ var CONTINUE_SELECT = "__continue_select__";
1909
1911
  async function interactiveSkillSelect(tool, level) {
1910
1912
  const BACK3 = "__back__";
1911
1913
  const CANCEL3 = "__cancel__";
1912
1914
  const categories = getAllCategories();
1915
+ const allSkillIds = getAllSkills().map((s) => s.id);
1916
+ let accumulated = [];
1913
1917
  let step = 0;
1914
1918
  let selectedCategoryId = "";
1915
1919
  while (true) {
1916
1920
  switch (step) {
1917
1921
  case 0: {
1918
- printBoxTitle(t("wizard_step_3_skill_title"));
1922
+ const titleSuffix = accumulated.length > 0 ? `${t("wizard_step_3_skill_title")}\uFF08${t("skill_count_format").replace("{count}", String(accumulated.length))}\uFF09` : t("wizard_step_3_skill_title");
1923
+ printBoxTitle(titleSuffix);
1919
1924
  const categoryChoices = [
1925
+ {
1926
+ name: `>> ${t("skill_select_all").replace("{count}", String(allSkillIds.length))}`,
1927
+ value: SELECT_ALL
1928
+ },
1929
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1920
1930
  ...categories.map((cat) => ({
1921
1931
  name: `> ${cat.name} (${cat.skills.length} skills)`,
1922
1932
  value: cat.id
1923
1933
  })),
1924
1934
  new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1935
+ ...accumulated.length > 0 ? [{ name: `>> ${t("skill_confirm_install_count").replace("{count}", String(accumulated.length))}`, value: "install" }] : [],
1925
1936
  { name: "<- " + t("wizard_back"), value: BACK3 },
1926
1937
  { name: "x " + t("wizard_cancel"), value: CANCEL3 }
1927
1938
  ];
@@ -1935,6 +1946,12 @@ async function interactiveSkillSelect(tool, level) {
1935
1946
  });
1936
1947
  if (selectedCategoryId === BACK3) return "back";
1937
1948
  if (selectedCategoryId === CANCEL3) return "cancel";
1949
+ if (selectedCategoryId === SELECT_ALL) {
1950
+ return allSkillIds;
1951
+ }
1952
+ if (selectedCategoryId === "install") {
1953
+ return accumulated;
1954
+ }
1938
1955
  step = 1;
1939
1956
  break;
1940
1957
  }
@@ -1945,15 +1962,26 @@ async function interactiveSkillSelect(tool, level) {
1945
1962
  const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
1946
1963
  const installedSkills = getInstalledSkills(tool, level, installPath);
1947
1964
  const installedSet = new Set(installedSkills);
1948
- const skillChoices = category.skills.map((skill) => {
1949
- const isInstalled = installedSet.has(skill.id);
1950
- const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : "";
1951
- return {
1952
- name: `${skill.id}${suffix} \u2014 ${skill.description}`,
1953
- value: skill.id,
1954
- checked: false
1955
- };
1956
- });
1965
+ const categorySkillIds = new Set(category.skills.map((s) => s.id));
1966
+ const accumulatedInCategory = new Set(accumulated.filter((id) => categorySkillIds.has(id)));
1967
+ const skillChoices = [
1968
+ {
1969
+ name: `>> ${t("skill_select_all_category")}`,
1970
+ value: SELECT_ALL_CATEGORY,
1971
+ checked: accumulatedInCategory.size === category.skills.length
1972
+ },
1973
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1974
+ ...category.skills.map((skill) => {
1975
+ const isInstalled = installedSet.has(skill.id);
1976
+ const isAccumulated = accumulatedInCategory.has(skill.id);
1977
+ const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : isAccumulated ? " [+]" : "";
1978
+ return {
1979
+ name: `${skill.id}${suffix} \u2014 ${skill.description}`,
1980
+ value: skill.id,
1981
+ checked: isAccumulated
1982
+ };
1983
+ })
1984
+ ];
1957
1985
  showOperationHints(true);
1958
1986
  const selectedSkills = await checkbox2({
1959
1987
  message: t("skill_select_items"),
@@ -1963,11 +1991,21 @@ async function interactiveSkillSelect(tool, level) {
1963
1991
  theme: checkboxTheme,
1964
1992
  pageSize: 15
1965
1993
  });
1966
- const skillIds = selectedSkills;
1994
+ let skillIds;
1995
+ if (selectedSkills.includes(SELECT_ALL_CATEGORY)) {
1996
+ skillIds = category.skills.map((s) => s.id);
1997
+ } else {
1998
+ skillIds = selectedSkills.filter((id) => id !== SELECT_ALL_CATEGORY);
1999
+ }
1967
2000
  if (skillIds.length === 0) {
1968
- const action = await select3({
1969
- message: t("wizard_no_selection"),
1970
- choices: [
2001
+ accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
2002
+ const action2 = await select3({
2003
+ message: accumulated.length > 0 ? t("skill_continue_or_install").replace("{count}", String(accumulated.length)) : t("wizard_no_selection"),
2004
+ choices: accumulated.length > 0 ? [
2005
+ { name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
2006
+ { name: "<- " + t("wizard_back_to_reselect"), value: "back" },
2007
+ { name: "x " + t("wizard_cancel"), value: "cancel" }
2008
+ ] : [
1971
2009
  new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1972
2010
  { name: "<- " + t("wizard_back_to_reselect"), value: "back" },
1973
2011
  { name: "x " + t("wizard_cancel"), value: "cancel" }
@@ -1975,13 +2013,38 @@ async function interactiveSkillSelect(tool, level) {
1975
2013
  loop: false,
1976
2014
  theme: selectTheme
1977
2015
  });
1978
- if (action === "back") {
2016
+ if (action2 === "install") return accumulated;
2017
+ if (action2 === "back") {
1979
2018
  step = 0;
1980
2019
  break;
1981
2020
  }
1982
2021
  return "cancel";
1983
2022
  }
1984
- return skillIds;
2023
+ accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
2024
+ for (const id of skillIds) {
2025
+ if (!accumulated.includes(id)) {
2026
+ accumulated.push(id);
2027
+ }
2028
+ }
2029
+ const action = await select3({
2030
+ message: t("skill_continue_or_install").replace("{count}", String(accumulated.length)),
2031
+ choices: [
2032
+ { name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
2033
+ { name: "> " + t("skill_continue_select"), value: CONTINUE_SELECT },
2034
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2035
+ { name: "x " + t("wizard_cancel"), value: "cancel" }
2036
+ ],
2037
+ loop: false,
2038
+ theme: selectTheme
2039
+ });
2040
+ if (action === "install") {
2041
+ return accumulated;
2042
+ }
2043
+ if (action === CONTINUE_SELECT) {
2044
+ step = 0;
2045
+ break;
2046
+ }
2047
+ return "cancel";
1985
2048
  }
1986
2049
  }
1987
2050
  }
@@ -2536,6 +2599,61 @@ async function installCommand(names, options) {
2536
2599
  listAllSkills();
2537
2600
  return;
2538
2601
  }
2602
+ if (options.all) {
2603
+ const scanSpinner2 = createSpinner(t("loading_skills_list"));
2604
+ scanSpinner2.start();
2605
+ try {
2606
+ const scanRepoManager = createRepositoryManager();
2607
+ await scanRepoManager.ensureRepoAndScan();
2608
+ scanSpinner2.succeed(t("loading_skills_list_complete"));
2609
+ } catch {
2610
+ scanSpinner2.warn(t("loading_skills_list_failed"));
2611
+ }
2612
+ const allSkills = getAllSkills();
2613
+ const skillIds = allSkills.map((s) => s.id);
2614
+ if (skillIds.length === 0) {
2615
+ logger.error(t("install_no_skill_selected"));
2616
+ return;
2617
+ }
2618
+ let tool2;
2619
+ if (options.tool) {
2620
+ tool2 = validateTool(options.tool);
2621
+ } else {
2622
+ const detected = await selectToolWithDetection();
2623
+ if (detected === "back" || detected === "cancel") return;
2624
+ tool2 = detected;
2625
+ }
2626
+ const level2 = options.level ? validateLevel(options.level) : "project";
2627
+ const repoManager = createRepositoryManager();
2628
+ const spinner = createSpinner(t("loading_plugin_data"));
2629
+ spinner.start();
2630
+ const repoPath = await repoManager.ensureRepo();
2631
+ spinner.succeed(t("loading_plugin_data_complete"));
2632
+ logger.info(`${t("skill_all_install_progress").replace("{count}", chalk9.bold(String(skillIds.length)))}`);
2633
+ const results = await installSkills(skillIds, tool2, level2, repoPath);
2634
+ let successCount = 0;
2635
+ let failCount = 0;
2636
+ const total = results.length;
2637
+ for (let i = 0; i < results.length; i++) {
2638
+ const result = results[i];
2639
+ const progress = `[${i + 1}/${total}]`;
2640
+ if (result.success) {
2641
+ logger.success(`${progress} ${result.skillId}`);
2642
+ successCount++;
2643
+ } else {
2644
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
2645
+ failCount++;
2646
+ }
2647
+ }
2648
+ const configRoot = getConfigRoot(tool2, level2);
2649
+ logger.blank();
2650
+ logger.success(`${t("skill_install_done")}: ${chalk9.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk9.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk9.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2651
+ logger.blank();
2652
+ logger.info(`${t("install_to")}: ${chalk9.cyan(configRoot + "/skills/")}`);
2653
+ logger.info(`${t("start_to_use").replace("{tool}", chalk9.green(tool2))}`);
2654
+ logger.blank();
2655
+ return;
2656
+ }
2539
2657
  if (names.length === 0) {
2540
2658
  const scanSpinner2 = createSpinner(t("loading_skills_list"));
2541
2659
  scanSpinner2.start();
@@ -3040,7 +3158,7 @@ function createCLI() {
3040
3158
  setLanguage(config.language);
3041
3159
  }
3042
3160
  const program2 = new Command();
3043
- program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.19");
3161
+ program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.20");
3044
3162
  program2.command("init", { isDefault: false }).description("Run interactive installation wizard").action(async () => {
3045
3163
  await initCommand();
3046
3164
  });
@@ -3053,7 +3171,7 @@ function createCLI() {
3053
3171
  program2.command("status").description("Show installed plugins").action(async () => {
3054
3172
  await statusCommand();
3055
3173
  });
3056
- program2.command("install [names...]").description("Install plugins or skills (auto-detects type)").option("-t, --tool <tool>", "AI tool (opencode, claude, trae, cursor, copilot)").option("-l, --level <level>", "Install level (project, global)", "project").option("-y, --yes", "Skip confirmation prompts").option("--list", "List all available skills by category").action(async (names, options) => {
3174
+ program2.command("install [names...]").description("Install plugins or skills (auto-detects type)").option("-t, --tool <tool>", "AI tool (opencode, claude, trae, cursor, copilot)").option("-l, --level <level>", "Install level (project, global)", "project").option("-y, --yes", "Skip confirmation prompts").option("-a, --all", "Install ALL available skills").option("--list", "List all available skills by category").action(async (names, options) => {
3057
3175
  await installCommand(names, options);
3058
3176
  });
3059
3177
  program2.command("uninstall <names...>").description("Uninstall plugins or skills (auto-detects type)").option("-t, --tool <tool>", "AI tool (opencode, claude, trae, cursor, copilot)").option("-l, --level <level>", "Install level (project, global)", "project").action(async (names, options) => {