@cannbot-ai/install-helper 0.0.2-beta.6 → 0.0.2-beta.61

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/dist/index.js CHANGED
@@ -4,116 +4,1139 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
- import { select as select3, Separator as Separator3 } from "@inquirer/prompts";
8
- import chalk5 from "chalk";
7
+ import { select as select4, Separator as Separator3 } from "@inquirer/prompts";
8
+ import chalk7 from "chalk";
9
9
 
10
10
  // src/ui/wizard.ts
11
11
  import { select, checkbox, Separator } from "@inquirer/prompts";
12
- import chalk2 from "chalk";
12
+ import chalk3 from "chalk";
13
13
 
14
14
  // src/core/registry.ts
15
- var PLUGIN_REGISTRY = [
15
+ import { readdirSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
16
+ import { join as join2, dirname as dirname2 } from "path";
17
+ import { fileURLToPath as fileURLToPath2 } from "url";
18
+ import { parse as parseYaml } from "yaml";
19
+
20
+ // src/utils/logger.ts
21
+ import chalk2 from "chalk";
22
+ import ora from "ora";
23
+
24
+ // src/utils/i18n.ts
25
+ import { readFileSync, existsSync } from "fs";
26
+ import { join, dirname } from "path";
27
+ import { fileURLToPath } from "url";
28
+
29
+ // src/locales/zh_CN.json
30
+ var zh_CN_default = {
31
+ wizard_title: "CANNBot Install Helper",
32
+ wizard_detect: "\u6B63\u5728\u68C0\u6D4B\u5DF2\u5B89\u88C5\u7684 AI \u7F16\u7A0B\u5DE5\u5177...",
33
+ wizard_select_tool: "\u9009\u62E9 AI \u7F16\u7A0B\u5DE5\u5177",
34
+ wizard_select_level: "\u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E",
35
+ wizard_select_plugins: "\u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6",
36
+ wizard_confirm: "\u786E\u8BA4\u5B89\u88C5",
37
+ wizard_detected_tool: "\u68C0\u6D4B\u5230",
38
+ wizard_confirm_tool: "\u4F7F\u7528",
39
+ wizard_last_used: "\u4E0A\u6B21\u4F7F\u7528",
40
+ wizard_already_installed: "\u5DF2\u5B89\u88C5",
41
+ install_repo_ready: "\u4ED3\u5E93\u5C31\u7EEA",
42
+ install_progress: "\u6B63\u5728\u5B89\u88C5",
43
+ install_done: "\u5B89\u88C5\u5B8C\u6210",
44
+ install_success: "\u6210\u529F",
45
+ install_already_installed: "\u5DF2\u5B89\u88C5",
46
+ install_reinstall_confirm: "\u662F\u5426\u91CD\u65B0\u5B89\u88C5\uFF08\u8986\u76D6\uFF09",
47
+ install_skip: "\u8DF3\u8FC7",
48
+ install_enhanced_next_steps: "\u4E0B\u4E00\u6B65",
49
+ install_enhanced_launch: "\u542F\u52A8",
50
+ install_enhanced_try: "\u8BD5\u8BD5",
51
+ install_enhanced_more: "\u66F4\u591A",
52
+ install_enhanced_check: "\u68C0\u67E5",
53
+ install_enhanced_docs: "\u6587\u6863",
54
+ update_updating: "\u6B63\u5728\u66F4\u65B0",
55
+ update_done: "\u66F4\u65B0\u5B8C\u6210",
56
+ update_no_plugins: "\u6682\u65E0\u5DF2\u5B89\u88C5\u7684\u63D2\u4EF6",
57
+ doctor_title: "CANNBot Doctor",
58
+ doctor_tools: "AI \u5DE5\u5177",
59
+ doctor_plugins: "\u5DF2\u5B89\u88C5\u63D2\u4EF6",
60
+ doctor_links: "\u94FE\u63A5\u5B8C\u6574\u6027",
61
+ doctor_config: "\u914D\u7F6E\u6587\u4EF6",
62
+ doctor_result: "\u7ED3\u679C",
63
+ doctor_fix_cleaning: "\u6E05\u7406\u5931\u6548\u94FE\u63A5",
64
+ doctor_fix_rebuilding: "\u91CD\u5EFA\u76EE\u5F55",
65
+ info_description: "\u63CF\u8FF0",
66
+ info_skills: "Skills",
67
+ info_agents: "Agents",
68
+ info_status: "\u72B6\u6001",
69
+ info_quickstart: "\u5FEB\u901F\u5F00\u59CB",
70
+ info_not_found: "\u672A\u627E\u5230\u63D2\u4EF6",
71
+ lang_title: "\u8BED\u8A00\u8BBE\u7F6E",
72
+ lang_current: "\u5F53\u524D\u8BED\u8A00",
73
+ lang_set: "\u8BED\u8A00\u5DF2\u8BBE\u7F6E\u4E3A",
74
+ lang_invalid: "\u65E0\u6548\u7684\u8BED\u8A00\uFF0C\u652F\u6301: zh_CN, en_US",
75
+ list_title: "\u53EF\u7528\u573A\u666F",
76
+ list_id: "\u5E8F\u53F7",
77
+ list_name: "\u573A\u666F\u540D",
78
+ list_status: "\u72B6\u6001",
79
+ list_description: "\u63CF\u8FF0",
80
+ status_installed: "\u5DF2\u88C5",
81
+ status_not_installed: "\u672A\u88C5",
82
+ error_tool_not_found: "\u672A\u68C0\u6D4B\u5230\u4EFB\u4F55 AI \u7F16\u7A0B\u5DE5\u5177",
83
+ error_plugin_not_found: "\u672A\u627E\u5230\u63D2\u4EF6",
84
+ error_invalid_tool: "\u6682\u4E0D\u652F\u6301\u7684 AI \u5DE5\u5177: {tool}\n\u652F\u6301\u7684\u5DE5\u5177: {tools}",
85
+ error_invalid_level: "\u6682\u4E0D\u652F\u6301\u7684\u5B89\u88C5\u7EA7\u522B: {level}\n\u652F\u6301\u7684\u7EA7\u522B: {levels}",
86
+ error_skill_not_found: "\u672A\u5728\u6CE8\u518C\u8868\u4E2D\u627E\u5230 Skill",
87
+ error_source_not_found: "\u672A\u627E\u5230\u6E90\u6587\u4EF6: {path}",
88
+ error_script_not_found: "\u672A\u627E\u5230\u5B89\u88C5\u811A\u672C: {path}",
89
+ error_unknown: "\u672A\u77E5\u9519\u8BEF",
90
+ error_not_installed: "\u672A\u5B89\u88C5",
91
+ error_no_shell: "\u672A\u627E\u5230 bash \u6216 sh\uFF0C\u65E0\u6CD5\u6267\u884C\u5B89\u88C5\u811A\u672C\u3002\u8BF7\u5B89\u88C5 Git Bash \u6216 WSL",
92
+ error_symlink_windows: "\u521B\u5EFA\u7B26\u53F7\u94FE\u63A5\u5931\u8D25\uFF0C\u8BF7\u4EE5\u7BA1\u7406\u5458\u8EAB\u4EFD\u8FD0\u884C\u6216\u5F00\u542F Windows \u5F00\u53D1\u8005\u6A21\u5F0F",
93
+ quick_start: "\u542F\u52A8\u547D\u4EE4",
94
+ quick_start_hint: "\u8BD5\u8BD5\u8F93\u5165: \u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50",
95
+ skill_select_category: "\u9009\u62E9 Skill \u7C7B\u522B",
96
+ skill_select_items: "\u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills",
97
+ skill_already_installed: "\u5DF2\u5B89\u88C5",
98
+ skill_select_all: "\u5168\u9009\u6240\u6709 Skills\uFF08\u5171 {count} \u4E2A\uFF09",
99
+ skill_select_all_category: "\u5168\u9009\u5F53\u524D\u7C7B\u522B",
100
+ skill_continue_or_install: "\u5DF2\u9009 {count} \u4E2A Skills\uFF0C\u8BF7\u9009\u62E9\u64CD\u4F5C",
101
+ skill_continue_select: "\u7EE7\u7EED\u9009\u62E9\u5176\u4ED6\u7C7B\u522B",
102
+ skill_confirm_install_count: "\u786E\u8BA4\u5B89\u88C5\uFF08\u5DF2\u9009 {count} \u4E2A Skills\uFF09",
103
+ skill_all_install_progress: "\u6B63\u5728\u5B89\u88C5\u5168\u90E8 {count} \u4E2A Skills...",
104
+ skill_install_progress: "\u6B63\u5728\u5B89\u88C5",
105
+ skill_install_done: "Skill \u5B89\u88C5\u5B8C\u6210",
106
+ skill_uninstall_done: "Skill \u5378\u8F7D\u5B8C\u6210",
107
+ skill_list_title: "\u53EF\u7528 Skills",
108
+ wizard_select_mode: "\u9009\u62E9\u5B89\u88C5\u7C7B\u578B",
109
+ wizard_mode_plugin: "\u5B89\u88C5 Plugin",
110
+ wizard_mode_plugin_desc: "\u5B8C\u6574\u5F00\u53D1\u5DE5\u4F5C\u6D41\uFF08Skills + Agents + \u914D\u7F6E\u6587\u4EF6\uFF09",
111
+ wizard_mode_skill: "\u5B89\u88C5 Skill",
112
+ wizard_mode_skill_desc: "\u4EC5\u5B89\u88C5\u9886\u57DF\u77E5\u8BC6\u6280\u80FD\uFF0C\u8BA9 AI \u638C\u63E1\u7279\u5B9A\u80FD\u529B",
113
+ wizard_back: "\u8FD4\u56DE",
114
+ wizard_cancel: "\u9000\u51FA",
115
+ wizard_no_selection: "\u672A\u9009\u62E9\u4EFB\u4F55\u9879\uFF0C\u8BF7\u9009\u62E9\u64CD\u4F5C",
116
+ wizard_back_to_reselect: "\u8FD4\u56DE",
117
+ status_title: "\u5DF2\u5B89\u88C5\u63D2\u4EF6",
118
+ status_none: "\u6682\u65E0\u5DF2\u5B89\u88C5\u7684\u63D2\u4EF6",
119
+ status_hint: "\u8FD0\u884C install-helper init \u5F00\u59CB\u5B89\u88C5",
120
+ status_install_time: "\u5B89\u88C5\u65F6\u95F4",
121
+ install_installed_to: "\u5B89\u88C5\u5230",
122
+ install_skills_count: "Skills",
123
+ install_agents_count: "Agents",
124
+ lang_supported: "\u652F\u6301\u7684\u8BED\u8A00: zh_CN, en_US",
125
+ lang_usage: "\u7528\u6CD5: install-helper lang set <language>",
126
+ lang_specify: "\u8BF7\u6307\u5B9A\u8BED\u8A00: install-helper lang set zh_CN",
127
+ lang_unknown_action: "\u672A\u77E5\u64CD\u4F5C",
128
+ lang_supported_actions: "\u652F\u6301\u7684\u64CD\u4F5C: show, set",
129
+ info_aliases: "\u522B\u540D",
130
+ uninstall_not_found: "\u672A\u627E\u5230",
131
+ uninstall_no_record: "\u672A\u627E\u5230\u5B89\u88C5\u8BB0\u5F55\uFF0C\u53EF\u80FD\u672A\u5B89\u88C5\u6216\u5B89\u88C5\u8BB0\u5F55\u5DF2\u635F\u574F",
132
+ uninstall_no_record_hint: "\u5982\u9700\u5F3A\u5236\u6E05\u7406\uFF0C\u8BF7\u624B\u52A8\u5220\u9664 .opencode/ \u6216 .claude/ \u76EE\u5F55\u4E2D\u7684\u76F8\u5173\u6587\u4EF6",
133
+ uninstall_in_progress: "\u6B63\u5728\u5378\u8F7D",
134
+ uninstall_removed: "\u5DF2\u5378\u8F7D",
135
+ uninstall_summary_format: "{name}\uFF08\u79FB\u9664 {files} \u4E2A\u6587\u4EF6\uFF0C{dirs} \u4E2A\u76EE\u5F55\uFF09",
136
+ uninstall_files_gone: "\u7684\u6587\u4EF6\u5DF2\u4E0D\u5B58\u5728\uFF0C\u5DF2\u6E05\u7406\u5B89\u88C5\u8BB0\u5F55",
137
+ uninstall_skills_in_progress: "\u6B63\u5728\u5378\u8F7D",
138
+ init_pkg_dir_warn: "\u5F53\u524D\u76EE\u5F55\u770B\u8D77\u6765\u662F install-helper \u5305\u76EE\u5F55",
139
+ init_pkg_dir_hint: "\u8BF7\u5728\u4F60\u7684\u9879\u76EE\u76EE\u5F55\u4E2D\u8FD0\u884C\u6B64\u547D\u4EE4\uFF0C\u800C\u4E0D\u662F\u5728 install-helper \u5B89\u88C5\u76EE\u5F55\u4E2D",
140
+ init_pkg_dir_example: "\u793A\u4F8B\uFF1Acd ~/my-project && install-helper",
141
+ init_cancelled: "\u5DF2\u53D6\u6D88\u5B89\u88C5",
142
+ backup_detected: "\u68C0\u6D4B\u5230\u5F53\u524D\u76EE\u5F55\u5DF2\u5B89\u88C5\u63D2\u4EF6",
143
+ backup_current: "\u5F53\u524D\u5B89\u88C5",
144
+ backup_installing: "\u5373\u5C06\u5B89\u88C5",
145
+ backup_overwrite_warning: "\u7EE7\u7EED\u5B89\u88C5\u5C06\u8986\u76D6\u73B0\u6709\u7684 AGENTS.md \u914D\u7F6E\u6587\u4EF6\u3002",
146
+ backup_auto_create: "\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u521B\u5EFA\u5907\u4EFD\uFF0C\u4EE5\u4FBF\u540E\u7EED\u6062\u590D\u3002",
147
+ backup_overwrite: "\u8986\u76D6\u5B89\u88C5\uFF08\u81EA\u52A8\u5907\u4EFD\uFF09",
148
+ backup_cancel: "\u53D6\u6D88\u5B89\u88C5",
149
+ backup_restore_prompt: "\u68C0\u6D4B\u5230\u5907\u4EFD\u914D\u7F6E\uFF0C\u662F\u5426\u6062\u590D\uFF1F",
150
+ backup_restore: "\u6062\u590D",
151
+ backup_only_uninstall: "\u4EC5\u5378\u8F7D\uFF08\u4E0D\u6062\u590D\uFF09",
152
+ backup_select_action: "\u8BF7\u9009\u62E9\u64CD\u4F5C",
153
+ backup_restore_success: "\u5DF2\u6062\u590D\u914D\u7F6E",
154
+ backup_created: "\u5DF2\u521B\u5EFA\u5907\u4EFD",
155
+ backup_failed: "\u5907\u4EFD\u521B\u5EFA\u5931\u8D25\uFF0C\u5B89\u88C5\u5DF2\u4E2D\u6B62\u4EE5\u4FDD\u62A4\u73B0\u6709\u914D\u7F6E",
156
+ loading_skills_list: "\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...",
157
+ loading_skills_list_complete: "Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210",
158
+ loading_plugin_data: "\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...",
159
+ loading_plugin_data_complete: "\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210",
160
+ loading_skills_list_failed: "Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E",
161
+ repo_pull_failed: "\u65E0\u6CD5\u62C9\u53D6\u6700\u65B0\u5185\u5BB9\uFF0C\u4F7F\u7528\u7F13\u5B58\u6570\u636E",
162
+ repo_cloning: "\u9996\u6B21\u4F7F\u7528\uFF0C\u6B63\u5728\u514B\u9686 cannbot-skills \u4ED3\u5E93...",
163
+ config_corrupted: "\u914D\u7F6E\u6587\u4EF6\u5DF2\u635F\u574F\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u914D\u7F6E",
164
+ record_corrupted: "\u5B89\u88C5\u8BB0\u5F55\u5DF2\u635F\u574F\uFF1A{file}",
165
+ skill_record_corrupted: "Skill \u5B89\u88C5\u8BB0\u5F55\u5DF2\u635F\u574F",
166
+ plugin_yaml_error: "\u63D2\u4EF6\u914D\u7F6E\u89E3\u6790\u5931\u8D25\uFF1A{file}",
167
+ record_write_failed: "\u5B89\u88C5\u8BB0\u5F55\u5199\u5165\u5931\u8D25\uFF0C\u5378\u8F7D\u65F6\u53EF\u80FD\u65E0\u6CD5\u81EA\u52A8\u6E05\u7406",
168
+ install_no_skill_selected: "\u672A\u9009\u62E9\u4EFB\u4F55 Skill",
169
+ install_to: "\u5B89\u88C5\u5230",
170
+ start_to_use: "\u542F\u52A8 {tool} \u5373\u53EF\u4F7F\u7528",
171
+ wizard_step_1_title: "[1/4] \u9009\u62E9 AI \u7F16\u7A0B\u5DE5\u5177",
172
+ wizard_step_2_title: "[2/4] \u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E",
173
+ wizard_step_3_title: "[3/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6",
174
+ wizard_step_4_title: "[4/4] \u786E\u8BA4\u5B89\u88C5",
175
+ wizard_step_3_skill_title: "[3/4] \u9009\u62E9 Skill \u7C7B\u522B",
176
+ wizard_step_4_skill_title: "[4/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills \u2014 {category}",
177
+ install_level_project: "\u5B89\u88C5\u5230\u5F53\u524D\u76EE\u5F55\uFF08\u63A8\u8350\uFF0C\u4EC5\u5F53\u524D\u9879\u76EE\u53EF\u7528\uFF09",
178
+ install_level_global: "\u5B89\u88C5\u5230\u5168\u5C40\uFF08\u6240\u6709\u9879\u76EE\u5171\u4EAB\uFF09",
179
+ wizard_select_mode_title: "\u9009\u62E9\u5B89\u88C5\u7C7B\u578B",
180
+ skill_count_format: "{count} \u4E2A Skills",
181
+ result_success_format: "{count} \u6210\u529F",
182
+ result_failed_format: "{count} \u5931\u8D25",
183
+ uninstall_remove_label: "\u79FB\u9664",
184
+ uninstall_remove_file_format: "\u79FB\u9664 {count} \u4E2A\u6587\u4EF6",
185
+ uninstall_remove_dir_format: "{count} \u4E2A\u76EE\u5F55",
186
+ uninstall_clean_empty_dir: "\u6E05\u7406\u7A7A\u76EE\u5F55",
187
+ uninstall_skill_not_installed: "\u672A\u5B89\u88C5",
188
+ available_plugins: "\u53EF\u7528\u63D2\u4EF6",
189
+ available_skills: "\u53EF\u7528 Skills",
190
+ skill_total_count: "\u5171 {count} \u4E2A Skills",
191
+ wizard_confirm_install_format: "\u5373\u5C06\u5B89\u88C5 {count} \u4E2A\u63D2\u4EF6\u5230 {tool}\uFF08{level} \u7EA7\u522B\uFF09\uFF1A",
192
+ wizard_install_path: "\u5B89\u88C5\u8DEF\u5F84",
193
+ update_install_hint: "\u4F7F\u7528 {cmd} \u5B89\u88C5\u63D2\u4EF6",
194
+ hint_select_move: "\u79FB\u52A8",
195
+ hint_select_space: "\u7A7A\u683C",
196
+ hint_select_check: "\u52FE\u9009",
197
+ hint_select_confirm: "\u786E\u8BA4",
198
+ hint_checkbox_suffix: "\u4E0D\u9009\u6309\u56DE\u8F66\u53EF\u8FD4\u56DE",
199
+ doctor_not_installed: "\u672A\u5B89\u88C5",
200
+ doctor_links_valid: "{count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548",
201
+ doctor_broken_links: "{count} \u4E2A\u5931\u6548\u94FE\u63A5",
202
+ doctor_fixed: "\u5DF2\u6E05\u7406 {count} \u4E2A\u5931\u6548\u94FE\u63A5",
203
+ doctor_not_exist: "\u4E0D\u5B58\u5728",
204
+ doctor_created: "\u5DF2\u521B\u5EFA {path}",
205
+ doctor_config_exists: "\u5B58\u5728",
206
+ doctor_config_not_exist: "\u4E0D\u5B58\u5728",
207
+ doctor_result_with_fix: "{warnings} \u4E2A\u8B66\u544A, {fixes} \u9879\u5DF2\u4FEE\u590D",
208
+ doctor_result_without_fix: "{warnings} \u4E2A\u8B66\u544A",
209
+ count_suffix: "\u4E2A",
210
+ try_prompt: "\u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50"
211
+ };
212
+
213
+ // src/locales/en_US.json
214
+ var en_US_default = {
215
+ wizard_title: "CANNBot Install Helper",
216
+ wizard_detect: "Detecting installed AI coding tools...",
217
+ wizard_select_tool: "Select AI coding tool",
218
+ wizard_select_level: "Select install level",
219
+ wizard_select_plugins: "Select scenarios to install",
220
+ wizard_confirm: "Confirm installation",
221
+ wizard_detected_tool: "Detected",
222
+ wizard_confirm_tool: "Confirm using",
223
+ wizard_last_used: "last used",
224
+ wizard_already_installed: "installed",
225
+ install_repo_ready: "Repository ready",
226
+ install_progress: "Installing",
227
+ install_done: "Installation complete",
228
+ install_success: "success",
229
+ install_already_installed: "already installed",
230
+ install_reinstall_confirm: "Reinstall (overwrite)",
231
+ install_skip: "skipped",
232
+ install_enhanced_next_steps: "Next steps",
233
+ install_enhanced_launch: "Launch",
234
+ install_enhanced_try: "Try",
235
+ install_enhanced_more: "More",
236
+ install_enhanced_check: "Check",
237
+ install_enhanced_docs: "Docs",
238
+ update_updating: "Updating",
239
+ update_done: "Update complete",
240
+ update_no_plugins: "No installed plugins",
241
+ doctor_title: "CANNBot Doctor",
242
+ doctor_tools: "AI Tools",
243
+ doctor_plugins: "Installed Plugins",
244
+ doctor_links: "Link Integrity",
245
+ doctor_config: "Configuration",
246
+ doctor_result: "Result",
247
+ doctor_fix_cleaning: "Cleaning broken links",
248
+ doctor_fix_rebuilding: "Rebuilding directories",
249
+ info_description: "Description",
250
+ info_skills: "Skills",
251
+ info_agents: "Agents",
252
+ info_status: "Status",
253
+ info_quickstart: "Quick Start",
254
+ info_not_found: "Plugin not found",
255
+ lang_title: "Language Settings",
256
+ lang_current: "Current language",
257
+ lang_set: "Language set to",
258
+ lang_invalid: "Invalid language, supported: zh_CN, en_US",
259
+ list_title: "Available Scenarios",
260
+ list_id: "#",
261
+ list_name: "Name",
262
+ list_status: "Status",
263
+ list_description: "Description",
264
+ status_installed: "installed",
265
+ status_not_installed: "not installed",
266
+ error_tool_not_found: "No AI coding tool detected",
267
+ error_plugin_not_found: "Plugin not found",
268
+ error_invalid_tool: "Unsupported AI tool: {tool}\nSupported tools: {tools}",
269
+ error_invalid_level: "Unsupported install level: {level}\nSupported levels: {levels}",
270
+ error_skill_not_found: "Skill not found in registry",
271
+ error_source_not_found: "Source not found: {path}",
272
+ error_script_not_found: "Script not found: {path}",
273
+ error_unknown: "Unknown error",
274
+ error_not_installed: "Not installed",
275
+ error_no_shell: "bash or sh not found, cannot execute install script. Please install Git Bash or WSL",
276
+ error_symlink_windows: "Failed to create symlink, please run as administrator or enable Windows Developer Mode",
277
+ quick_start: "Launch command",
278
+ quick_start_hint: "Try: help me develop an Abs operator",
279
+ skill_select_category: "Select Skill category",
280
+ skill_select_items: "Select Skills to install (space to toggle, enter to confirm)",
281
+ skill_already_installed: "installed",
282
+ skill_select_all: "Select ALL Skills ({count} total)",
283
+ skill_select_all_category: "Select all in this category",
284
+ skill_continue_or_install: "{count} Skills selected, choose an action",
285
+ skill_continue_select: "Continue selecting from other categories",
286
+ skill_confirm_install_count: "Install now ({count} Skills selected)",
287
+ skill_all_install_progress: "Installing all {count} Skills...",
288
+ skill_install_progress: "Installing",
289
+ skill_install_done: "Skill installation complete",
290
+ skill_uninstall_done: "Skill uninstallation complete",
291
+ skill_list_title: "Available Skills",
292
+ wizard_select_mode: "Select installation mode",
293
+ wizard_mode_plugin: "Install Plugin",
294
+ wizard_mode_plugin_desc: "Full development workflow (Skills + Agents + config)",
295
+ wizard_mode_skill: "Install Skill",
296
+ wizard_mode_skill_desc: "Domain knowledge only, give AI specific capabilities",
297
+ wizard_back: "Go back",
298
+ wizard_cancel: "Cancel installation",
299
+ wizard_no_selection: "No items selected, please choose an action",
300
+ wizard_back_to_reselect: "Go back",
301
+ status_title: "Installed Plugins",
302
+ status_none: "No installed plugins",
303
+ status_hint: "Run install-helper init to start",
304
+ status_install_time: "Installed at",
305
+ install_installed_to: "Installed to",
306
+ install_skills_count: "Skills",
307
+ install_agents_count: "Agents",
308
+ lang_supported: "Supported languages: zh_CN, en_US",
309
+ lang_usage: "Usage: install-helper lang set <language>",
310
+ lang_specify: "Please specify language: install-helper lang set zh_CN",
311
+ lang_unknown_action: "Unknown action",
312
+ lang_supported_actions: "Supported actions: show, set",
313
+ info_aliases: "Aliases",
314
+ uninstall_not_found: "Not found",
315
+ uninstall_no_record: "No install record found, may not be installed or record is corrupted",
316
+ uninstall_no_record_hint: "For manual cleanup, delete related files in .opencode/ or .claude/ directory",
317
+ uninstall_in_progress: "Uninstalling",
318
+ uninstall_removed: "Uninstalled",
319
+ uninstall_summary_format: "{name} (removed {files} files, {dirs} directories)",
320
+ uninstall_files_gone: " files no longer exist, install record cleaned",
321
+ uninstall_skills_in_progress: "Uninstalling",
322
+ init_pkg_dir_warn: "Current directory appears to be the install-helper package directory",
323
+ init_pkg_dir_hint: "Please run this command in your project directory, not in the install-helper package directory",
324
+ init_pkg_dir_example: "Example: cd ~/my-project && install-helper",
325
+ init_cancelled: "Installation cancelled",
326
+ backup_detected: "Plugin already installed in current directory",
327
+ backup_current: "Currently installed",
328
+ backup_installing: "About to install",
329
+ backup_overwrite_warning: "Continuing will overwrite existing AGENTS.md configuration.",
330
+ backup_auto_create: "System will automatically create backup for future restoration.",
331
+ backup_overwrite: "Overwrite (auto backup)",
332
+ backup_cancel: "Cancel installation",
333
+ backup_restore_prompt: "Backup configuration detected, restore?",
334
+ backup_restore: "Restore",
335
+ backup_only_uninstall: "Uninstall only (no restore)",
336
+ backup_select_action: "Please select an action",
337
+ backup_restore_success: "Configuration restored",
338
+ backup_created: "Backup created",
339
+ backup_failed: "Backup creation failed, installation aborted to protect existing config",
340
+ loading_skills_list: "Loading Skills list...",
341
+ loading_skills_list_complete: "Skills list loaded",
342
+ loading_plugin_data: "Loading plugin data...",
343
+ loading_plugin_data_complete: "Plugin data loaded",
344
+ loading_skills_list_failed: "Failed to load Skills list, using built-in data",
345
+ repo_pull_failed: "Failed to pull latest updates, using cached data",
346
+ repo_cloning: "First-time setup: cloning cannbot-skills repository...",
347
+ config_corrupted: "Config file corrupted, using defaults",
348
+ record_corrupted: "Install record corrupted: {file}",
349
+ skill_record_corrupted: "Skill install record corrupted",
350
+ plugin_yaml_error: "Plugin config parse error: {file}",
351
+ record_write_failed: "Install record write failed, uninstall may not work properly",
352
+ install_no_skill_selected: "No Skill selected",
353
+ install_to: "Install to",
354
+ start_to_use: "Start {tool} to use",
355
+ wizard_step_1_title: "[1/4] Select AI coding tool",
356
+ wizard_step_2_title: "[2/4] Select installation location",
357
+ wizard_step_3_title: "[3/4] Select plugins to install",
358
+ wizard_step_4_title: "[4/4] Confirm installation",
359
+ wizard_step_3_skill_title: "[3/4] Select Skill category",
360
+ wizard_step_4_skill_title: "[4/4] Select Skills to install \u2014 {category}",
361
+ install_level_project: "Install to current directory (recommended, current project only)",
362
+ install_level_global: "Install globally (all projects)",
363
+ wizard_select_mode_title: "Select installation type",
364
+ skill_count_format: "{count} Skills",
365
+ result_success_format: "{count} succeeded",
366
+ result_failed_format: "{count} failed",
367
+ uninstall_remove_label: "Removed",
368
+ uninstall_remove_file_format: "Removed {count} files",
369
+ uninstall_remove_dir_format: "{count} directories",
370
+ uninstall_clean_empty_dir: "Cleaned empty directory",
371
+ uninstall_skill_not_installed: "Not installed",
372
+ available_plugins: "Available plugins",
373
+ available_skills: "Available Skills",
374
+ skill_total_count: "Total {count} Skills",
375
+ wizard_confirm_install_format: "About to install {count} plugins to {tool} ({level} level):",
376
+ wizard_install_path: "Install path",
377
+ update_install_hint: "Use {cmd} to install plugins",
378
+ hint_select_move: "Navigate",
379
+ hint_select_space: "Space",
380
+ hint_select_check: "Toggle",
381
+ hint_select_confirm: "Confirm",
382
+ hint_checkbox_suffix: "Press enter without selection to go back",
383
+ doctor_not_installed: "Not installed",
384
+ doctor_links_valid: "{count} symlinks all valid",
385
+ doctor_broken_links: "{count} broken links",
386
+ doctor_fixed: "Cleaned {count} broken links",
387
+ doctor_not_exist: "Does not exist",
388
+ doctor_created: "Created {path}",
389
+ doctor_config_exists: "Exists",
390
+ doctor_config_not_exist: "Does not exist",
391
+ doctor_result_with_fix: "{warnings} warnings, {fixes} fixed",
392
+ doctor_result_without_fix: "{warnings} warnings",
393
+ count_suffix: "",
394
+ try_prompt: "help me develop an Abs operator"
395
+ };
396
+
397
+ // src/utils/i18n.ts
398
+ var __filename = fileURLToPath(import.meta.url);
399
+ var __dirname = dirname(__filename);
400
+ var EMBEDDED_LOCALES = {
401
+ zh_CN: zh_CN_default,
402
+ en_US: en_US_default
403
+ };
404
+ function loadLocale(locale) {
405
+ const embedded = EMBEDDED_LOCALES[locale];
406
+ if (embedded) return embedded;
407
+ const localePath = join(__dirname, "locales", `${locale}.json`);
408
+ if (existsSync(localePath)) {
409
+ try {
410
+ const content = readFileSync(localePath, "utf-8");
411
+ return JSON.parse(content);
412
+ } catch {
413
+ }
414
+ }
415
+ const fallbackPath = join(__dirname, "locales", "zh_CN.json");
416
+ if (existsSync(fallbackPath)) {
417
+ try {
418
+ const content = readFileSync(fallbackPath, "utf-8");
419
+ return JSON.parse(content);
420
+ } catch {
421
+ }
422
+ }
423
+ return EMBEDDED_LOCALES.zh_CN || {};
424
+ }
425
+ var currentLang = "zh_CN";
426
+ var messages = loadLocale("zh_CN");
427
+ function setLanguage(lang) {
428
+ currentLang = lang;
429
+ messages = loadLocale(lang);
430
+ }
431
+ function t(key) {
432
+ return messages[key] !== void 0 ? messages[key] : key;
433
+ }
434
+
435
+ // src/ui/theme.ts
436
+ import chalk from "chalk";
437
+ var selectTheme = {
438
+ helpMode: "never"
439
+ };
440
+ function isCJK(code) {
441
+ return code >= 11904 && code <= 40959 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65103 || code >= 65280 && code <= 65519 || code >= 13312 && code <= 19903 || code >= 131072 && code <= 173791 || code >= 173824 && code <= 177983 || code >= 177984 && code <= 178207 || code >= 178208 && code <= 183983;
442
+ }
443
+ function isEmoji(code) {
444
+ return code >= 127744 && code <= 129791 || code >= 9728 && code <= 10175;
445
+ }
446
+ function getDisplayWidth(str) {
447
+ let width = 0;
448
+ for (const ch of str) {
449
+ const code = ch.codePointAt(0);
450
+ if (isCJK(code) || isEmoji(code)) {
451
+ width += 2;
452
+ } else {
453
+ width += 1;
454
+ }
455
+ }
456
+ return width;
457
+ }
458
+ function wrapDescription(text) {
459
+ const prefix = " \u{1F4A1} ";
460
+ const indent = " ";
461
+ const cols = process.stdout.columns || 120;
462
+ const maxLen = Math.max(20, cols - getDisplayWidth(prefix) - 2);
463
+ const tokens = [];
464
+ let i = 0;
465
+ while (i < text.length) {
466
+ const code = text[i].codePointAt(0);
467
+ if (isCJK(code)) {
468
+ tokens.push({ text: text[i], width: 2 });
469
+ i++;
470
+ } else {
471
+ let j = i;
472
+ while (j < text.length) {
473
+ const c = text[j].codePointAt(0);
474
+ if (isCJK(c)) break;
475
+ j++;
476
+ }
477
+ const word = text.slice(i, j);
478
+ tokens.push({ text: word, width: getDisplayWidth(word) });
479
+ i = j;
480
+ }
481
+ }
482
+ const lines = [];
483
+ let line = "";
484
+ let lineWidth = 0;
485
+ for (const token of tokens) {
486
+ if (lineWidth + token.width > maxLen && line) {
487
+ lines.push(line.trimEnd());
488
+ line = token.text;
489
+ lineWidth = token.width;
490
+ } else {
491
+ line += token.text;
492
+ lineWidth += token.width;
493
+ }
494
+ }
495
+ if (line) lines.push(line.trimEnd());
496
+ return lines.map((l, i2) => chalk.cyan(i2 === 0 ? prefix + l : indent + l)).join("\n");
497
+ }
498
+ var checkboxTheme = {
499
+ icon: {
500
+ checked: "\x1B[32m\u2611\x1B[22m",
501
+ unchecked: "\u2610",
502
+ cursor: "\u276F"
503
+ },
504
+ helpMode: "never",
505
+ style: {
506
+ description: (text) => wrapDescription(text)
507
+ }
508
+ };
509
+
510
+ // src/utils/logger.ts
511
+ var logger = {
512
+ success: (msg) => console.log(chalk2.green("\u2713") + " " + msg),
513
+ error: (msg) => console.log(chalk2.red("\u2717") + " " + msg),
514
+ warn: (msg) => console.log(chalk2.yellow("\u26A0") + " " + msg),
515
+ info: (msg) => console.log(chalk2.cyan("\u2192") + " " + msg),
516
+ step: (msg) => console.log(chalk2.dim(msg)),
517
+ blank: () => console.log()
518
+ };
519
+ function createSpinner(text) {
520
+ return ora({ text, color: "cyan" });
521
+ }
522
+ function printBanner(subtitle) {
523
+ console.log(chalk2.cyan(`
524
+ ____ _ _ _ _ _ ____ _
525
+ / ___| / \\ | \\ | | \\ | | __ ) ___ | |_
526
+ | | / _ \\ | \\| | \\| | _ \\ / _ \\| __|
527
+ | |___ / ___ \\| |\\ | |\\ | |_) | (_) | |_
528
+ \\____/_/ \\_\\_| \\_|_| \\_|____/ \\___/ \\__|
529
+ `));
530
+ if (subtitle) {
531
+ console.log(chalk2.bold(` ${subtitle}`));
532
+ console.log();
533
+ }
534
+ }
535
+ function printBoxTitle(title, width = 65) {
536
+ const border = "\u2550".repeat(width);
537
+ const displayWidth = getDisplayWidth(title);
538
+ const totalPadding = width - displayWidth;
539
+ const leftPadding = Math.floor(totalPadding / 2);
540
+ const rightPadding = totalPadding - leftPadding;
541
+ const paddedTitle = " ".repeat(Math.max(0, leftPadding)) + title + " ".repeat(Math.max(0, rightPadding));
542
+ console.log();
543
+ console.log(chalk2.cyan.bold(` \u2554${border}\u2557`));
544
+ console.log(chalk2.cyan.bold(` \u2551`) + chalk2.cyan.bold(paddedTitle) + chalk2.cyan.bold(`\u2551`));
545
+ console.log(chalk2.cyan.bold(` \u255A${border}\u255D`));
546
+ console.log();
547
+ }
548
+ function showOperationHints(isCheckbox = false) {
549
+ if (isCheckbox) {
550
+ console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m${t("hint_select_space")}\x1B[0m ${t("hint_select_check")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}\uFF08${t("hint_checkbox_suffix")}\uFF09
551
+ `);
552
+ } else {
553
+ console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}
554
+ `);
555
+ }
556
+ }
557
+
558
+ // src/utils/fs-helpers.ts
559
+ import { lstatSync, statSync } from "fs";
560
+ function isSymlink(path) {
561
+ try {
562
+ return lstatSync(path).isSymbolicLink();
563
+ } catch {
564
+ return false;
565
+ }
566
+ }
567
+ function isDirectory(path) {
568
+ try {
569
+ return statSync(path).isDirectory();
570
+ } catch {
571
+ return false;
572
+ }
573
+ }
574
+
575
+ // src/embedded-plugins.json
576
+ var embedded_plugins_default = [
16
577
  {
17
578
  id: "ops-direct-invoke",
18
579
  dir: "plugins-official/ops-direct-invoke",
19
580
  displayName: "AscendC Kernel \u76F4\u8C03",
20
581
  script: "init.sh",
21
- aliases: ["ops-direct", "ascendc-direct", "direct", "kernel"],
582
+ aliases: [
583
+ "ops-direct",
584
+ "ascendc-direct",
585
+ "direct",
586
+ "kernel"
587
+ ],
22
588
  skills: 16,
23
589
  agents: 3,
24
- description: "Ascend C Kernel <<<>>> \u76F4\u8C03\u7B97\u5B50\u5F00\u53D1\u5168\u6D41\u7A0B"
590
+ description: "Ascend C \u76F4\u8C03\u5168\u6D41\u7A0B\uFF1A\u73AF\u5883\u2192\u8BBE\u8BA1\u2192\u5F00\u53D1\u2192\u5BA1\u67E5\u2192\u6027\u80FD\u9A8C\u6536",
591
+ version: "1.2.2",
592
+ configFile: "AGENTS.md",
593
+ configRootConfigLink: false,
594
+ installSkills: [
595
+ {
596
+ dir: "ops",
597
+ skills: [
598
+ "ascendc-tiling-design",
599
+ "npu-arch",
600
+ "ascendc-api-best-practices",
601
+ "ascendc-regbase-best-practice",
602
+ "ascendc-blaze-best-practice",
603
+ "ops-precision-standard",
604
+ "ascendc-docs-search",
605
+ "ascendc-env-check",
606
+ "ascendc-precision-debug",
607
+ "ops-profiling",
608
+ "ascendc-direct-invoke-template",
609
+ "torch-ascendc-op-extension",
610
+ "ascendc-runtime-debug",
611
+ "ascendc-crash-debug",
612
+ "ascendc-code-review"
613
+ ]
614
+ },
615
+ {
616
+ dir: "infra",
617
+ skills: [
618
+ "gitcode-toolkit",
619
+ "gitcode-pr-handler",
620
+ "gitcode-issue-gen",
621
+ "gitcode-issue-handler"
622
+ ]
623
+ }
624
+ ],
625
+ installAgents: [
626
+ "ascendc-kernel-architect",
627
+ "ascendc-kernel-design-reviewer",
628
+ "ascendc-kernel-developer",
629
+ "ascendc-kernel-reviewer"
630
+ ],
631
+ externalRepos: [
632
+ {
633
+ url: "https://gitcode.com/cann/asc-devkit.git",
634
+ dir: "plugins-official/ops-direct-invoke/asc-devkit",
635
+ configRootLink: false
636
+ }
637
+ ]
25
638
  },
26
639
  {
27
640
  id: "ops-direct-invoke-flash",
28
641
  dir: "plugins-official/ops-direct-invoke-flash",
29
- displayName: "AscendC Kernel \u4ECE\u96F6\u6784\u5EFA",
642
+ displayName: "AscendC Kernel \u76F4\u8C03\uFF08\u5FEB\u901F\u7248\uFF09",
30
643
  script: "init.sh",
31
- aliases: ["flash", "kernel-flash"],
644
+ aliases: [
645
+ "flash",
646
+ "kernel-flash"
647
+ ],
32
648
  skills: 1,
33
649
  agents: 1,
34
- description: "Ascend C / Ascend950 Reg API \u6838\u51FD\u6570\u4ECE\u96F6\u6784\u5EFA"
650
+ description: "\u4ECE\u6570\u5B66\u516C\u5F0F\u6216\u4EE3\u7801\u7247\u6BB5\u5FEB\u901F\u6784\u5EFA\u5E76\u9A8C\u8BC1 Ascend NPU \u6838\u51FD\u6570",
651
+ version: "1.2.2",
652
+ configFile: "AGENTS.md",
653
+ configRootConfigLink: true,
654
+ installSkills: [
655
+ {
656
+ dir: "plugins-official/ops-direct-invoke-flash/skills",
657
+ skills: [
658
+ "ops-direct-invoke-flash"
659
+ ]
660
+ },
661
+ {
662
+ dir: "ops",
663
+ skills: [
664
+ "ascendc-st-design",
665
+ "ascendc-whitebox-design"
666
+ ]
667
+ },
668
+ {
669
+ dir: "infra",
670
+ skills: [
671
+ "gitcode-toolkit",
672
+ "gitcode-pr-handler",
673
+ "gitcode-issue-gen",
674
+ "gitcode-issue-handler"
675
+ ]
676
+ }
677
+ ],
678
+ installAgents: [
679
+ "ops-direct-invoke-flash-reviewer"
680
+ ]
35
681
  },
36
682
  {
37
683
  id: "ops-registry-invoke",
38
684
  dir: "plugins-official/ops-registry-invoke",
39
685
  displayName: "AscendC \u7B97\u5B50\u6CE8\u518C\u8C03\u7528",
40
686
  script: "init.sh",
41
- aliases: ["ops-registry", "ascendc-registry", "registry"],
687
+ aliases: [
688
+ "ops-registry",
689
+ "ascendc-registry",
690
+ "registry"
691
+ ],
42
692
  skills: 12,
43
693
  agents: 4,
44
- description: "Ascend C \u7B97\u5B50\u6CE8\u518C\u8C03\u7528\u5F00\u53D1\u6D41\u7A0B"
45
- },
46
- {
47
- id: "pypto-op-orchestrator",
48
- dir: "plugins-official/pypto-op-orchestrator",
49
- displayName: "PyPTO \u7B97\u5B50",
50
- script: "init.sh",
51
- aliases: ["pypto", "pytorch"],
52
- skills: 8,
53
- agents: 3,
54
- description: "PyPTO \u7B97\u5B50\u7AEF\u5230\u7AEF\u5F00\u53D1\u7F16\u6392"
55
- },
56
- {
57
- id: "triton-op-generator",
58
- dir: "plugins-official/triton-op-generator",
59
- displayName: "Triton \u7B97\u5B50\u751F\u6210",
60
- script: "install.sh",
61
- aliases: ["triton"],
62
- skills: 6,
63
- agents: 0,
64
- description: "Triton-Ascend \u7B97\u5B50\u4EE3\u7801\u751F\u6210\u4E0E\u4F18\u5316"
694
+ description: "\u751F\u4EA7\u7EA7 ACLNN \u6CE8\u518C\u7B97\u5B50\u5168\u6D41\u7A0B\uFF1A\u4EE3\u7801\u2192UT/ST\u2192\u4E0A\u5E93",
695
+ version: "1.2.2",
696
+ configFile: "AGENTS.md",
697
+ configRootConfigLink: false,
698
+ installSkills: [
699
+ {
700
+ dir: "ops",
701
+ skills: [
702
+ "ascendc-blaze-best-practice",
703
+ "ascendc-api-best-practices",
704
+ "ascendc-code-review",
705
+ "ascendc-crash-debug",
706
+ "ascendc-direct-invoke-template",
707
+ "ascendc-docs-gen",
708
+ "ascendc-docs-search",
709
+ "ascendc-env-check",
710
+ "npu-arch",
711
+ "ascendc-performance-best-practices",
712
+ "ascendc-precision-debug",
713
+ "ascendc-regbase-best-practice",
714
+ "ascendc-registry-invoke-template",
715
+ "ascendc-runtime-debug",
716
+ "ascendc-st-design",
717
+ "ascendc-tiling-design",
718
+ "ascendc-ut-develop",
719
+ "ascendc-whitebox-design",
720
+ "ops-precision-standard",
721
+ "ops-profiling",
722
+ "ops-spec-gen"
723
+ ]
724
+ },
725
+ {
726
+ dir: "infra",
727
+ skills: [
728
+ "gitcode-toolkit",
729
+ "gitcode-pr-handler",
730
+ "gitcode-issue-gen",
731
+ "gitcode-issue-handler"
732
+ ]
733
+ },
734
+ {
735
+ dir: "plugins-official/ops-registry-invoke",
736
+ skills: [
737
+ {
738
+ name: "workflow",
739
+ as: "ops-registry-invoke-workflow"
740
+ }
741
+ ]
742
+ }
743
+ ],
744
+ installAgents: [
745
+ "ascendc-ops-architect",
746
+ "ascendc-ops-developer",
747
+ "ascendc-ops-tester"
748
+ ],
749
+ externalRepos: [
750
+ {
751
+ url: "https://gitcode.com/cann/asc-devkit.git",
752
+ dir: "plugins-official/ops-registry-invoke/asc-devkit",
753
+ configRootLink: false
754
+ }
755
+ ]
65
756
  },
66
757
  {
67
- id: "tilelang-op-orchestrator",
68
- dir: "plugins-official/tilelang-op-orchestrator",
69
- displayName: "TileLang \u7B97\u5B50",
758
+ id: "catlass-op-generator",
759
+ dir: "plugins-official/catlass-op-generator",
760
+ displayName: "Catlass \u7B97\u5B50\u76F4\u8C03",
70
761
  script: "init.sh",
71
- aliases: ["tilelang"],
72
- skills: 9,
762
+ aliases: [
763
+ "catlass"
764
+ ],
765
+ skills: 10,
73
766
  agents: 3,
74
- description: "TileLang \u7B97\u5B50\u5F00\u53D1\u6D41\u7A0B"
767
+ description: "Catlass \u6A21\u677F\u62FC\u88C5 GEMM/Matmul/Epilogue \u7B97\u5B50\u5F00\u53D1",
768
+ version: "1.2.2",
769
+ configFile: "AGENTS.md",
770
+ configRootConfigLink: true,
771
+ installSkills: [
772
+ {
773
+ dir: "ops",
774
+ skills: [
775
+ "catlass-op-design",
776
+ "catlass-op-develop",
777
+ "catlass-op-perf-tune",
778
+ "ascendc-tiling-design",
779
+ "npu-arch",
780
+ "ascendc-api-best-practices",
781
+ "ops-precision-standard",
782
+ "ascendc-docs-search",
783
+ "ascendc-env-check",
784
+ "ascendc-precision-debug",
785
+ "ops-profiling",
786
+ "ascendc-runtime-debug",
787
+ "ascendc-code-review",
788
+ "torch-ascendc-op-extension"
789
+ ]
790
+ }
791
+ ],
792
+ installAgents: [
793
+ "catlass-op-architect",
794
+ "catlass-op-generator",
795
+ "catlass-op-reviewer"
796
+ ],
797
+ externalRepos: [
798
+ {
799
+ url: "https://gitcode.com/cann/asc-devkit.git",
800
+ dir: "plugins-official/catlass-op-generator/asc-devkit",
801
+ configRootLink: false
802
+ }
803
+ ]
75
804
  },
76
805
  {
77
806
  id: "model-infer-optimize",
78
807
  dir: "plugins-official/model-infer-optimize",
79
808
  displayName: "NPU \u63A8\u7406\u4F18\u5316",
80
809
  script: "init.sh",
81
- aliases: ["model-infer", "infer", "inference"],
810
+ aliases: [
811
+ "model-infer",
812
+ "infer",
813
+ "inference"
814
+ ],
82
815
  skills: 11,
83
816
  agents: 3,
84
- description: "NPU \u6A21\u578B\u63A8\u7406\u7AEF\u5230\u7AEF\u4F18\u5316"
817
+ description: "NPU \u6A21\u578B\u63A8\u7406\u7AEF\u5230\u7AEF\u4F18\u5316\uFF08\u5E76\u884C/KVCache/\u878D\u5408/\u91CF\u5316/\u56FE\u6A21\u5F0F/\u591A\u6D41/SuperKernel\uFF09",
818
+ version: "1.2.2",
819
+ configFile: "AGENTS.md",
820
+ configRootConfigLink: true,
821
+ installSkills: [
822
+ {
823
+ dir: "model",
824
+ skills: [
825
+ "model-infer-migrator",
826
+ "model-infer-parallel-analysis",
827
+ "model-infer-parallel-impl",
828
+ "model-infer-kvcache",
829
+ "model-infer-fusion",
830
+ "model-infer-quantization",
831
+ "model-infer-graph-mode",
832
+ "model-infer-precision-debug",
833
+ "model-infer-runtime-debug",
834
+ "model-infer-multi-stream",
835
+ "model-infer-prefetch",
836
+ "model-infer-superkernel"
837
+ ]
838
+ }
839
+ ],
840
+ installAgents: [
841
+ "model-infer-analyzer",
842
+ "model-infer-implementer",
843
+ "model-infer-reviewer"
844
+ ],
845
+ externalRepos: [
846
+ {
847
+ url: "https://gitcode.com/cann/cann-recipes-infer.git",
848
+ dir: "plugins-official/model-infer-optimize/cann-recipes-infer",
849
+ depth: 1,
850
+ configRootLink: true
851
+ }
852
+ ]
85
853
  },
86
854
  {
87
- id: "catlass-op-generator",
88
- dir: "plugins-official/catlass-op-generator",
89
- displayName: "Catlass \u7B97\u5B50\u76F4\u8C03",
855
+ id: "pypto-op-orchestrator",
856
+ dir: "plugins-official/pypto-op-orchestrator",
857
+ displayName: "PyPTO \u7B97\u5B50\u5F00\u53D1",
90
858
  script: "init.sh",
91
- aliases: ["catlass"],
92
- skills: 10,
859
+ aliases: [
860
+ "pypto",
861
+ "pytorch"
862
+ ],
863
+ skills: 8,
93
864
  agents: 3,
94
- description: "Catlass \u7B97\u5B50\u76F4\u8C03\u5F00\u53D1"
865
+ description: "PyPTO Python API \u7B97\u5B50\u5168\u6D41\u7A0B\u5F00\u53D1\uFF0C\u8986\u76D6\u9700\u6C42\u7406\u89E3\u3001API \u63A2\u7D22\u3001\u5B9E\u73B0\u4E0E\u8C03\u4F18",
866
+ version: "1.2.2",
867
+ configFile: "AGENTS.md",
868
+ configRootConfigLink: false,
869
+ installSkills: [
870
+ {
871
+ dir: "ops",
872
+ skills: [
873
+ "pypto-intent-understand",
874
+ "pypto-api-explore",
875
+ "pypto-op-design",
876
+ "pypto-op-develop",
877
+ "pypto-precision-debug",
878
+ "pypto-precision-compare",
879
+ "pypto-op-perf-tune",
880
+ "pypto-golden-generate"
881
+ ]
882
+ }
883
+ ],
884
+ installAgents: [
885
+ "pypto-op-analyst",
886
+ "pypto-op-developer",
887
+ "pypto-op-perf-tuner"
888
+ ],
889
+ externalRepos: [
890
+ {
891
+ url: "https://gitcode.com/cann/pypto.git",
892
+ dir: "plugins-official/pypto-op-orchestrator/pypto",
893
+ configRootLink: false
894
+ }
895
+ ]
95
896
  },
96
897
  {
97
- id: "ops-code-reviewer",
98
- dir: "plugins-official/ops-code-reviewer",
99
- displayName: "\u4EE3\u7801\u68C0\u89C6",
898
+ id: "tilelang-op-orchestrator",
899
+ dir: "plugins-official/tilelang-op-orchestrator",
900
+ displayName: "TileLang \u7B97\u5B50\u5F00\u53D1",
100
901
  script: "init.sh",
101
- aliases: ["code-review", "reviewer", "review"],
102
- skills: 5,
902
+ aliases: [
903
+ "tilelang"
904
+ ],
905
+ skills: 9,
906
+ agents: 3,
907
+ description: "TileLang Python DSL + @tilelang.jit \u7B97\u5B50\u5F00\u53D1\uFF08\u652F\u6301 Developer/Expert \u53CC\u6A21\u5F0F\uFF09",
908
+ version: "1.2.2",
909
+ configFile: "AGENTS.md",
910
+ configRootConfigLink: false,
911
+ installSkills: [
912
+ {
913
+ dir: "ops",
914
+ skills: [
915
+ "tilelang-api-best-practices",
916
+ "tilelang-env-check",
917
+ "tilelang-op-design",
918
+ "tilelang-op-develop",
919
+ "tilelang-op-test-design",
920
+ "tilelang-perf-optimization",
921
+ "tilelang-programming-model-guide",
922
+ "tilelang-review",
923
+ "tilelang-submodule-pull"
924
+ ]
925
+ }
926
+ ],
927
+ installAgents: [
928
+ "tilelang-op-analyst",
929
+ "tilelang-op-developer",
930
+ "tilelang-op-perf-tuner"
931
+ ],
932
+ externalRepos: [
933
+ {
934
+ url: "https://github.com/tile-ai/tilelang-ascend.git",
935
+ dir: "plugins-official/tilelang-op-orchestrator/tilelang-ascend",
936
+ depth: 1,
937
+ recursive: true,
938
+ configRootLink: false
939
+ }
940
+ ]
941
+ },
942
+ {
943
+ id: "triton-op-generator",
944
+ dir: "plugins-official/triton-op-generator",
945
+ displayName: "Triton \u7B97\u5B50\u5F00\u53D1",
946
+ script: "init.sh",
947
+ aliases: [
948
+ "triton"
949
+ ],
950
+ skills: 6,
103
951
  agents: 1,
104
- description: "\u4EE3\u7801\u68C0\u89C6\u4E0E\u89C4\u8303\u68C0\u67E5"
952
+ 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",
953
+ version: "1.2.2",
954
+ configFile: "AGENTS.md",
955
+ configRootConfigLink: false,
956
+ installSkills: [
957
+ {
958
+ dir: "ops",
959
+ skills: [
960
+ "triton-task-extractor",
961
+ "triton-op-designer",
962
+ "triton-op-coding",
963
+ "triton-op-verifier",
964
+ "triton-latency-optimizer",
965
+ "npu-arch"
966
+ ]
967
+ }
968
+ ],
969
+ installAgents: []
105
970
  },
106
971
  {
107
972
  id: "torch-compile",
108
973
  dir: "plugins-official/torch-compile",
109
974
  displayName: "torch.compile \u56FE\u6A21\u5F0F",
110
975
  script: "init.sh",
111
- aliases: ["torch", "compile", "graph"],
976
+ aliases: [
977
+ "torch",
978
+ "compile",
979
+ "graph"
980
+ ],
112
981
  skills: 6,
113
982
  agents: 1,
114
- description: "PyTorch torch.compile \u56FE\u6A21\u5F0F\u9002\u914D"
983
+ description: "NPU \u56FE\u6A21\u5F0F npugraph_ex \u914D\u7F6E\u3001\u811A\u672C\u8FC1\u79FB\u3001\u7F16\u8BD1\u8BCA\u65AD\u4E0E\u81EA\u5B9A\u4E49\u7B97\u5B50\u5165\u56FE",
984
+ version: "1.2.2",
985
+ configFile: "AGENTS.md",
986
+ configRootConfigLink: true,
987
+ installSkills: [
988
+ {
989
+ dir: "graph",
990
+ skills: [
991
+ "torch-npugraph-ex-knowledge",
992
+ "torch-npugraph-ex-template",
993
+ "torch-npugraph-ex-dfx-triage",
994
+ "torch-npugraph-ex-compile-error-diagnosis",
995
+ "torch-npugraph-ex-runtime-error-diagnosis",
996
+ "torch-custom-ops-guide"
997
+ ]
998
+ }
999
+ ],
1000
+ installAgents: [
1001
+ "torch-npugraph-ex"
1002
+ ]
1003
+ },
1004
+ {
1005
+ id: "ops-code-reviewer",
1006
+ dir: "plugins-official/ops-code-reviewer",
1007
+ displayName: "\u4EE3\u7801\u68C0\u89C6",
1008
+ script: "init.sh",
1009
+ aliases: [
1010
+ "code-review",
1011
+ "reviewer",
1012
+ "review"
1013
+ ],
1014
+ skills: 5,
1015
+ agents: 1,
1016
+ description: "Ascend C \u7B97\u5B50\u4EE3\u7801 4 \u9636\u6BB5\u5168\u91CF\u68C0\u89C6\uFF08\u4E3B Agent \u7F16\u6392 + \u5B50 Agent \u5E76\u884C\u68C0\u89C6\uFF09",
1017
+ version: "1.2.2",
1018
+ configFile: "AGENTS.md",
1019
+ configRootConfigLink: false,
1020
+ installSkills: [
1021
+ {
1022
+ dir: "ops",
1023
+ skills: [
1024
+ "ascendc-code-review",
1025
+ "ascendc-docs-search",
1026
+ "ascendc-task-focus",
1027
+ "ascendc-api-best-practices",
1028
+ "npu-arch"
1029
+ ]
1030
+ }
1031
+ ],
1032
+ installAgents: [
1033
+ "ascendc-code-summarizer",
1034
+ "ascendc-ops-reviewer"
1035
+ ]
115
1036
  }
116
1037
  ];
1038
+
1039
+ // src/core/registry.ts
1040
+ var __filename2 = fileURLToPath2(import.meta.url);
1041
+ var __dirname2 = dirname2(__filename2);
1042
+ function findPluginsDir() {
1043
+ const candidates = [
1044
+ join2(__dirname2, "plugins.d"),
1045
+ join2(__dirname2, "..", "plugins.d"),
1046
+ join2(__dirname2, "..", "..", "plugins.d")
1047
+ ];
1048
+ for (const dir of candidates) {
1049
+ if (existsSync2(dir)) return dir;
1050
+ }
1051
+ return candidates[0];
1052
+ }
1053
+ function loadPluginsFromYaml() {
1054
+ const pluginsDir = findPluginsDir();
1055
+ const defaultsPath = join2(pluginsDir, "_defaults.yml");
1056
+ let defaults = {};
1057
+ try {
1058
+ defaults = parseYaml(readFileSync2(defaultsPath, "utf-8")) || {};
1059
+ } catch {
1060
+ }
1061
+ const plugins = [];
1062
+ try {
1063
+ for (const file of readdirSync(pluginsDir)) {
1064
+ if (file.startsWith("_") || !file.endsWith(".yml")) continue;
1065
+ try {
1066
+ const content = parseYaml(readFileSync2(join2(pluginsDir, file), "utf-8"));
1067
+ if (!content || !content.id) continue;
1068
+ plugins.push({
1069
+ id: content.id,
1070
+ dir: content.dir,
1071
+ displayName: content.displayName || content.id,
1072
+ script: content.script || defaults.script || "init.sh",
1073
+ aliases: content.aliases || [],
1074
+ skills: content.skills ?? defaults.skills ?? 0,
1075
+ agents: content.agents ?? defaults.agents ?? 0,
1076
+ description: content.description || "",
1077
+ version: content.version,
1078
+ configFile: content.configFile,
1079
+ configRootConfigLink: content.configRootConfigLink,
1080
+ installSkills: content.installSkills,
1081
+ installAgents: content.installAgents,
1082
+ externalRepos: content.externalRepos
1083
+ });
1084
+ } catch {
1085
+ logger.warn(t("plugin_yaml_error").replace("{file}", file));
1086
+ }
1087
+ }
1088
+ } catch {
1089
+ }
1090
+ if (plugins.length === 0) {
1091
+ return [...embedded_plugins_default];
1092
+ }
1093
+ plugins.sort(
1094
+ (a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
1095
+ );
1096
+ return plugins;
1097
+ }
1098
+ var PLUGIN_REGISTRY = loadPluginsFromYaml();
1099
+ var PLUGIN_DIRS = ["plugins-official", "plugins-community"];
1100
+ function mergeDynamicPlugins(repoPath) {
1101
+ if (!existsSync2(repoPath)) return;
1102
+ const existingIds = new Set(PLUGIN_REGISTRY.map((p) => p.id));
1103
+ const dynamic = [];
1104
+ for (const pluginDir of PLUGIN_DIRS) {
1105
+ const parent = join2(repoPath, pluginDir);
1106
+ if (!existsSync2(parent)) continue;
1107
+ let entries = [];
1108
+ try {
1109
+ entries = readdirSync(parent);
1110
+ } catch {
1111
+ continue;
1112
+ }
1113
+ for (const entry of entries) {
1114
+ const pluginPath = join2(parent, entry);
1115
+ if (!isDirectory(pluginPath)) continue;
1116
+ if (entry === "install-helper") continue;
1117
+ const initSh = join2(pluginPath, "init.sh");
1118
+ if (!existsSync2(initSh)) continue;
1119
+ const pluginId = entry;
1120
+ if (existingIds.has(pluginId)) continue;
1121
+ dynamic.push({
1122
+ id: pluginId,
1123
+ dir: `${pluginDir}/${entry}`,
1124
+ displayName: entry,
1125
+ script: "init.sh",
1126
+ aliases: [],
1127
+ skills: 0,
1128
+ agents: 0,
1129
+ description: ""
1130
+ });
1131
+ existingIds.add(pluginId);
1132
+ }
1133
+ }
1134
+ if (dynamic.length > 0) {
1135
+ PLUGIN_REGISTRY = [...PLUGIN_REGISTRY, ...dynamic].sort(
1136
+ (a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
1137
+ );
1138
+ }
1139
+ }
117
1140
  function findPlugin(query) {
118
1141
  const normalized = query.toLowerCase().trim();
119
1142
  const exactMatch = PLUGIN_REGISTRY.find(
@@ -137,19 +1160,37 @@ function getPluginById(id) {
137
1160
 
138
1161
  // src/core/detector.ts
139
1162
  import { execa } from "execa";
140
- import { existsSync as existsSync2 } from "fs";
1163
+ import { existsSync as existsSync4 } from "fs";
141
1164
  import { homedir as homedir2 } from "os";
142
- import { join as join2 } from "path";
1165
+ import { join as join4 } from "path";
143
1166
 
144
1167
  // src/utils/paths.ts
145
1168
  import { homedir } from "os";
146
- import { join } from "path";
147
- import { existsSync } from "fs";
1169
+ import { join as join3 } from "path";
1170
+ import { existsSync as existsSync3 } from "fs";
1171
+ var VALID_TOOLS = ["opencode", "claude", "trae", "cursor", "copilot"];
1172
+ var VALID_LEVELS = ["project", "global"];
1173
+ function validateTool(tool) {
1174
+ if (!VALID_TOOLS.includes(tool)) {
1175
+ throw new Error(
1176
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
1177
+ );
1178
+ }
1179
+ return tool;
1180
+ }
1181
+ function validateLevel(level) {
1182
+ if (!VALID_LEVELS.includes(level)) {
1183
+ throw new Error(
1184
+ t("error_invalid_level").replace("{level}", level).replace("{levels}", VALID_LEVELS.join(", "))
1185
+ );
1186
+ }
1187
+ return level;
1188
+ }
148
1189
  function detectTraeVariant() {
149
1190
  const home = homedir();
150
- if (existsSync(join(home, ".trae-cn"))) return "ide";
151
- if (existsSync(join(home, ".marscode"))) return "plugin";
152
- if (existsSync(join(home, ".traecli"))) return "cli";
1191
+ if (existsSync3(join3(home, ".trae-cn"))) return "ide";
1192
+ if (existsSync3(join3(home, ".marscode"))) return "plugin";
1193
+ if (existsSync3(join3(home, ".traecli"))) return "cli";
153
1194
  return "unknown";
154
1195
  }
155
1196
  function getConfigRoot(tool, level, base) {
@@ -157,66 +1198,77 @@ function getConfigRoot(tool, level, base) {
157
1198
  if (level === "global") {
158
1199
  switch (tool) {
159
1200
  case "opencode":
160
- return join(home, ".config", "opencode");
1201
+ return join3(home, ".config", "opencode");
161
1202
  case "claude":
162
- return join(home, ".claude");
1203
+ return join3(home, ".claude");
163
1204
  case "trae": {
164
1205
  const variant = detectTraeVariant();
165
1206
  switch (variant) {
166
1207
  case "plugin":
167
- return join(home, ".marscode");
1208
+ return join3(home, ".marscode");
168
1209
  case "cli":
169
- return join(home, ".traecli");
1210
+ return join3(home, ".traecli");
170
1211
  default:
171
- return join(home, ".trae-cn");
1212
+ return join3(home, ".trae-cn");
172
1213
  }
173
1214
  }
174
1215
  case "cursor":
175
- return join(home, ".cursor");
1216
+ return join3(home, ".cursor");
176
1217
  case "copilot":
177
- return join(home, ".copilot");
1218
+ return join3(home, ".copilot");
1219
+ default:
1220
+ throw new Error(
1221
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
1222
+ );
178
1223
  }
179
1224
  }
180
1225
  const baseDir = base || process.cwd();
181
1226
  switch (tool) {
182
1227
  case "opencode":
183
- return join(baseDir, ".opencode");
1228
+ return join3(baseDir, ".opencode");
184
1229
  case "claude":
185
- return join(baseDir, ".claude");
1230
+ return join3(baseDir, ".claude");
186
1231
  case "trae": {
187
1232
  const variant = detectTraeVariant();
188
1233
  switch (variant) {
189
1234
  case "plugin":
190
- return join(baseDir, ".marscode");
1235
+ return join3(baseDir, ".marscode");
191
1236
  case "cli":
192
- return join(baseDir, ".traecli");
1237
+ return join3(baseDir, ".traecli");
193
1238
  default:
194
- return join(baseDir, ".trae");
1239
+ return join3(baseDir, ".trae");
195
1240
  }
196
1241
  }
197
1242
  case "cursor":
198
- return join(baseDir, ".cursor");
1243
+ return join3(baseDir, ".cursor");
199
1244
  case "copilot":
200
- return join(baseDir, ".github");
1245
+ return join3(baseDir, ".github");
1246
+ default:
1247
+ throw new Error(
1248
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
1249
+ );
201
1250
  }
202
1251
  }
203
1252
  function getConfigFileName(tool) {
204
1253
  return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
205
1254
  }
1255
+ function getAgentsFileName(tool) {
1256
+ return getConfigFileName(tool);
1257
+ }
206
1258
  function getSkillsDir(configRoot) {
207
- return join(configRoot, "skills");
1259
+ return join3(configRoot, "skills");
208
1260
  }
209
1261
  function getAgentsDir(configRoot) {
210
- return join(configRoot, "agents");
1262
+ return join3(configRoot, "agents");
211
1263
  }
212
1264
  function getManifestPath(configRoot) {
213
- return join(configRoot, "cannbot-manifest.json");
1265
+ return join3(configRoot, "cannbot-manifest.json");
214
1266
  }
215
1267
  function getCannbotConfigDir() {
216
- return join(homedir(), ".cannbot");
1268
+ return join3(homedir(), ".cannbot");
217
1269
  }
218
1270
  function getCannbotRepoPath() {
219
- return join(getCannbotConfigDir(), "repo");
1271
+ return join3(getCannbotConfigDir(), "repo");
220
1272
  }
221
1273
 
222
1274
  // src/core/detector.ts
@@ -232,8 +1284,9 @@ async function getCommandVersion(cmd, args = ["--version"]) {
232
1284
  }
233
1285
  async function getCommandPath(cmd) {
234
1286
  try {
235
- const result = await execa("sh", ["-c", `command -v ${cmd}`], { timeout: 5e3 });
236
- return result.stdout.trim();
1287
+ const whichCmd = process.platform === "win32" ? "where" : "which";
1288
+ const result = await execa(whichCmd, [cmd], { timeout: 5e3 });
1289
+ return result.stdout.trim().split("\n")[0];
237
1290
  } catch {
238
1291
  return void 0;
239
1292
  }
@@ -254,12 +1307,12 @@ async function detectTrae() {
254
1307
  const home = homedir2();
255
1308
  const variant = detectTraeVariant();
256
1309
  let path;
257
- if (variant === "ide" && existsSync2(join2(home, ".trae-cn"))) {
258
- path = join2(home, ".trae-cn");
259
- } else if (variant === "plugin" && existsSync2(join2(home, ".marscode"))) {
260
- path = join2(home, ".marscode");
261
- } else if (variant === "cli" && existsSync2(join2(home, ".traecli"))) {
262
- path = join2(home, ".traecli");
1310
+ if (variant === "ide" && existsSync4(join4(home, ".trae-cn"))) {
1311
+ path = join4(home, ".trae-cn");
1312
+ } else if (variant === "plugin" && existsSync4(join4(home, ".marscode"))) {
1313
+ path = join4(home, ".marscode");
1314
+ } else if (variant === "cli" && existsSync4(join4(home, ".traecli"))) {
1315
+ path = join4(home, ".traecli");
263
1316
  } else if (variant === "unknown") {
264
1317
  const cmdPath = await getCommandPath("trae");
265
1318
  if (cmdPath) path = cmdPath;
@@ -275,7 +1328,7 @@ async function detectCursor() {
275
1328
  }
276
1329
  if (process.platform === "darwin") {
277
1330
  const appPath = "/Applications/Cursor.app";
278
- if (existsSync2(appPath)) {
1331
+ if (existsSync4(appPath)) {
279
1332
  return { name: "cursor", path: appPath };
280
1333
  }
281
1334
  }
@@ -319,17 +1372,17 @@ function getToolDisplayName(tool) {
319
1372
  return names[tool];
320
1373
  }
321
1374
  function getAllTools() {
322
- return ["opencode", "claude", "trae", "cursor", "copilot"];
1375
+ return VALID_TOOLS;
323
1376
  }
324
1377
 
325
1378
  // src/core/manifest.ts
326
- import { existsSync as existsSync3, readFileSync } from "fs";
327
- import { join as join3 } from "path";
1379
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
1380
+ import { join as join5 } from "path";
328
1381
  function readManifest(configRoot) {
329
1382
  const manifestPath = getManifestPath(configRoot);
330
- if (existsSync3(manifestPath)) {
1383
+ if (existsSync5(manifestPath)) {
331
1384
  try {
332
- const content = readFileSync(manifestPath, "utf-8");
1385
+ const content = readFileSync3(manifestPath, "utf-8");
333
1386
  return JSON.parse(content);
334
1387
  } catch {
335
1388
  }
@@ -340,18 +1393,18 @@ function readAllManifests(configRoot) {
340
1393
  const manifests = [];
341
1394
  const plugins = getAllPlugins();
342
1395
  const standardPath = getManifestPath(configRoot);
343
- if (existsSync3(standardPath)) {
1396
+ if (existsSync5(standardPath)) {
344
1397
  try {
345
- const content = readFileSync(standardPath, "utf-8");
1398
+ const content = readFileSync3(standardPath, "utf-8");
346
1399
  manifests.push(JSON.parse(content));
347
1400
  } catch {
348
1401
  }
349
1402
  }
350
1403
  for (const plugin of plugins) {
351
- const pluginManifestPath = join3(configRoot, `${plugin.id}-manifest.json`);
352
- if (existsSync3(pluginManifestPath)) {
1404
+ const pluginManifestPath = join5(configRoot, `${plugin.id}-manifest.json`);
1405
+ if (existsSync5(pluginManifestPath)) {
353
1406
  try {
354
- const content = readFileSync(pluginManifestPath, "utf-8");
1407
+ const content = readFileSync3(pluginManifestPath, "utf-8");
355
1408
  manifests.push(JSON.parse(content));
356
1409
  } catch {
357
1410
  }
@@ -362,7 +1415,7 @@ function readAllManifests(configRoot) {
362
1415
  function scanInstalled() {
363
1416
  const plugins = getAllPlugins();
364
1417
  const installed = [];
365
- const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
1418
+ const tools = VALID_TOOLS;
366
1419
  const levels = ["project", "global"];
367
1420
  for (const tool of tools) {
368
1421
  for (const level of levels) {
@@ -371,6 +1424,9 @@ function scanInstalled() {
371
1424
  for (const manifest of manifests) {
372
1425
  const plugin = plugins.find((p) => p.id === manifest.team);
373
1426
  if (!plugin) continue;
1427
+ if (!verifyPluginFilesExist(configRoot, manifest, tool, level)) {
1428
+ continue;
1429
+ }
374
1430
  const alreadyAdded = installed.some(
375
1431
  (p) => p.id === plugin.id && p.tool === tool && p.level === level
376
1432
  );
@@ -387,278 +1443,77 @@ function scanInstalled() {
387
1443
  });
388
1444
  }
389
1445
  }
390
- }
391
- return installed;
392
- }
393
-
394
- // src/utils/i18n.ts
395
- var zh_CN = {
396
- wizard_title: "CANNBot Install Helper",
397
- wizard_detect: "\u6B63\u5728\u68C0\u6D4B\u5DF2\u5B89\u88C5\u7684 AI \u7F16\u7A0B\u5DE5\u5177...",
398
- wizard_select_tool: "\u9009\u62E9 AI \u7F16\u7A0B\u5DE5\u5177",
399
- wizard_select_level: "\u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E",
400
- wizard_select_plugins: "\u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6",
401
- wizard_confirm: "\u786E\u8BA4\u5B89\u88C5",
402
- wizard_detected_tool: "\u68C0\u6D4B\u5230",
403
- wizard_confirm_tool: "\u4F7F\u7528",
404
- wizard_last_used: "\u4E0A\u6B21\u4F7F\u7528",
405
- wizard_already_installed: "\u5DF2\u5B89\u88C5",
406
- install_cloning: "\u6B63\u5728\u514B\u9686 cannbot-skills \u4ED3\u5E93...",
407
- install_repo_ready: "\u4ED3\u5E93\u5C31\u7EEA",
408
- install_progress: "\u6B63\u5728\u5B89\u88C5",
409
- install_done: "\u5B89\u88C5\u5B8C\u6210",
410
- install_success: "\u6210\u529F",
411
- install_failed: "\u5931\u8D25",
412
- install_already_installed: "\u5DF2\u5B89\u88C5",
413
- install_reinstall_confirm: "\u662F\u5426\u91CD\u65B0\u5B89\u88C5\uFF08\u8986\u76D6\uFF09",
414
- install_skip: "\u8DF3\u8FC7",
415
- install_enhanced_next_steps: "\u4E0B\u4E00\u6B65",
416
- install_enhanced_launch: "\u542F\u52A8",
417
- install_enhanced_try: "\u8BD5\u8BD5",
418
- install_enhanced_more: "\u66F4\u591A",
419
- install_enhanced_check: "\u68C0\u67E5",
420
- install_enhanced_docs: "\u6587\u6863",
421
- update_title: "\u66F4\u65B0\u5DF2\u5B89\u88C5\u7684 Skills",
422
- update_updating: "\u6B63\u5728\u66F4\u65B0",
423
- update_done: "\u66F4\u65B0\u5B8C\u6210",
424
- update_no_plugins: "\u6682\u65E0\u5DF2\u5B89\u88C5\u7684\u63D2\u4EF6",
425
- doctor_title: "CANNBot Doctor",
426
- doctor_tools: "AI \u5DE5\u5177",
427
- doctor_plugins: "\u5DF2\u5B89\u88C5\u63D2\u4EF6",
428
- doctor_links: "\u94FE\u63A5\u5B8C\u6574\u6027",
429
- doctor_config: "\u914D\u7F6E\u6587\u4EF6",
430
- doctor_result: "\u7ED3\u679C",
431
- doctor_fix_title: "\u81EA\u52A8\u4FEE\u590D",
432
- doctor_fix_cleaning: "\u6E05\u7406\u5931\u6548\u94FE\u63A5",
433
- doctor_fix_rebuilding: "\u91CD\u5EFA\u76EE\u5F55",
434
- doctor_fix_done: "\u4FEE\u590D\u5B8C\u6210",
435
- info_title: "\u63D2\u4EF6\u8BE6\u60C5",
436
- info_description: "\u63CF\u8FF0",
437
- info_skills: "Skills",
438
- info_agents: "Agents",
439
- info_status: "\u72B6\u6001",
440
- info_quickstart: "\u5FEB\u901F\u5F00\u59CB",
441
- info_not_found: "\u672A\u627E\u5230\u63D2\u4EF6",
442
- lang_title: "\u8BED\u8A00\u8BBE\u7F6E",
443
- lang_current: "\u5F53\u524D\u8BED\u8A00",
444
- lang_set: "\u8BED\u8A00\u5DF2\u8BBE\u7F6E\u4E3A",
445
- lang_invalid: "\u65E0\u6548\u7684\u8BED\u8A00\uFF0C\u652F\u6301: zh_CN, en_US",
446
- list_title: "\u53EF\u7528\u573A\u666F",
447
- list_id: "\u5E8F\u53F7",
448
- list_name: "\u573A\u666F\u540D",
449
- list_status: "\u72B6\u6001",
450
- list_skills: "Skills",
451
- list_agents: "Agents",
452
- status_installed: "\u5DF2\u88C5",
453
- status_not_installed: "\u672A\u88C5",
454
- error_tool_not_found: "\u672A\u68C0\u6D4B\u5230\u4EFB\u4F55 AI \u7F16\u7A0B\u5DE5\u5177",
455
- error_plugin_not_found: "\u672A\u627E\u5230\u63D2\u4EF6",
456
- error_repo_not_found: "\u4ED3\u5E93\u672A\u627E\u5230",
457
- quick_start: "\u542F\u52A8\u547D\u4EE4",
458
- quick_start_hint: "\u8BD5\u8BD5\u8F93\u5165: \u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50",
459
- skill_select_category: "\u9009\u62E9 Skill \u7C7B\u522B",
460
- skill_select_items: "\u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills",
461
- skill_already_installed: "\u5DF2\u5B89\u88C5",
462
- skill_install_progress: "\u6B63\u5728\u5B89\u88C5",
463
- skill_install_done: "Skill \u5B89\u88C5\u5B8C\u6210",
464
- skill_uninstall_done: "Skill \u5378\u8F7D\u5B8C\u6210",
465
- skill_list_title: "\u53EF\u7528 Skills",
466
- wizard_select_mode: "\u9009\u62E9\u5B89\u88C5\u7C7B\u578B",
467
- wizard_mode_plugin: "\u5B89\u88C5 Plugin",
468
- wizard_mode_plugin_desc: "\u5B8C\u6574\u5F00\u53D1\u5DE5\u4F5C\u6D41\uFF08Skills + Agents + \u914D\u7F6E\u6587\u4EF6\uFF09",
469
- wizard_mode_skill: "\u5B89\u88C5 Skill",
470
- wizard_mode_skill_desc: "\u4EC5\u5B89\u88C5\u9886\u57DF\u77E5\u8BC6\u6280\u80FD\uFF0C\u8BA9 AI \u638C\u63E1\u7279\u5B9A\u80FD\u529B",
471
- wizard_back: "\u8FD4\u56DE",
472
- wizard_cancel: "\u9000\u51FA",
473
- wizard_no_selection: "\u672A\u9009\u62E9\u4EFB\u4F55\u9879\uFF0C\u8BF7\u9009\u62E9\u64CD\u4F5C",
474
- wizard_back_to_reselect: "\u8FD4\u56DE",
475
- status_title: "\u5DF2\u5B89\u88C5\u63D2\u4EF6",
476
- status_none: "\u6682\u65E0\u5DF2\u5B89\u88C5\u7684\u63D2\u4EF6",
477
- status_hint: "\u8FD0\u884C install-helper init \u5F00\u59CB\u5B89\u88C5",
478
- status_install_time: "\u5B89\u88C5\u65F6\u95F4",
479
- install_installed_to: "\u5B89\u88C5\u5230",
480
- install_skills_count: "Skills",
481
- install_agents_count: "Agents"
482
- };
483
- var en_US = {
484
- wizard_title: "CANNBot Skills Setup Wizard",
485
- wizard_detect: "Detecting installed AI coding tools...",
486
- wizard_select_tool: "Select AI coding tool",
487
- wizard_select_level: "Select install level",
488
- wizard_select_plugins: "Select scenarios to install",
489
- wizard_confirm: "Confirm installation",
490
- wizard_detected_tool: "Detected",
491
- wizard_confirm_tool: "Confirm using",
492
- wizard_last_used: "last used",
493
- wizard_already_installed: "installed",
494
- install_cloning: "Cloning cannbot-skills repository...",
495
- install_repo_ready: "Repository ready",
496
- install_progress: "Installing",
497
- install_done: "Installation complete",
498
- install_success: "success",
499
- install_failed: "failed",
500
- install_already_installed: "already installed",
501
- install_reinstall_confirm: "Reinstall (overwrite)",
502
- install_skip: "skipped",
503
- install_enhanced_next_steps: "Next steps",
504
- install_enhanced_launch: "Launch",
505
- install_enhanced_try: "Try",
506
- install_enhanced_more: "More",
507
- install_enhanced_check: "Check",
508
- install_enhanced_docs: "Docs",
509
- update_title: "Update installed Skills",
510
- update_updating: "Updating",
511
- update_done: "Update complete",
512
- update_no_plugins: "No installed plugins",
513
- doctor_title: "CANNBot Doctor",
514
- doctor_tools: "AI Tools",
515
- doctor_plugins: "Installed Plugins",
516
- doctor_links: "Link Integrity",
517
- doctor_config: "Configuration",
518
- doctor_result: "Result",
519
- doctor_fix_title: "Auto-fix",
520
- doctor_fix_cleaning: "Cleaning broken links",
521
- doctor_fix_rebuilding: "Rebuilding directories",
522
- doctor_fix_done: "Fix complete",
523
- info_title: "Plugin Details",
524
- info_description: "Description",
525
- info_skills: "Skills",
526
- info_agents: "Agents",
527
- info_status: "Status",
528
- info_quickstart: "Quick Start",
529
- info_not_found: "Plugin not found",
530
- lang_title: "Language Settings",
531
- lang_current: "Current language",
532
- lang_set: "Language set to",
533
- lang_invalid: "Invalid language, supported: zh_CN, en_US",
534
- list_title: "Available Scenarios",
535
- list_id: "#",
536
- list_name: "Name",
537
- list_status: "Status",
538
- list_skills: "Skills",
539
- list_agents: "Agents",
540
- status_installed: "installed",
541
- status_not_installed: "not installed",
542
- error_tool_not_found: "No AI coding tool detected",
543
- error_plugin_not_found: "Plugin not found",
544
- error_repo_not_found: "Repository not found",
545
- quick_start: "Launch command",
546
- quick_start_hint: "Try: help me develop an Abs operator",
547
- skill_select_category: "Select Skill category",
548
- skill_select_items: "Select Skills to install (space to toggle, enter to confirm)",
549
- skill_already_installed: "installed",
550
- skill_install_progress: "Installing",
551
- skill_install_done: "Skill installation complete",
552
- skill_uninstall_done: "Skill uninstallation complete",
553
- skill_list_title: "Available Skills",
554
- wizard_select_mode: "Select installation mode",
555
- wizard_mode_plugin: "Install Plugin",
556
- wizard_mode_plugin_desc: "Full development workflow (Skills + Agents + config)",
557
- wizard_mode_skill: "Install Skill",
558
- wizard_mode_skill_desc: "Domain knowledge only, give AI specific capabilities",
559
- wizard_back: "Go back",
560
- wizard_cancel: "Cancel installation",
561
- wizard_no_selection: "No items selected, please choose an action",
562
- wizard_back_to_reselect: "Go back",
563
- status_title: "Installed Plugins",
564
- status_none: "No installed plugins",
565
- status_hint: "Run install-helper init to start",
566
- status_install_time: "Installed at",
567
- install_installed_to: "Installed to",
568
- install_skills_count: "Skills",
569
- install_agents_count: "Agents"
570
- };
571
- var currentLang = "zh_CN";
572
- function setLanguage(lang) {
573
- currentLang = lang;
574
- }
575
- function t(key) {
576
- return currentLang === "zh_CN" ? zh_CN[key] : en_US[key];
577
- }
578
-
579
- // src/utils/logger.ts
580
- import chalk from "chalk";
581
- import ora from "ora";
582
- var logger = {
583
- success: (msg) => console.log(chalk.green("\u2713") + " " + msg),
584
- error: (msg) => console.log(chalk.red("\u2717") + " " + msg),
585
- warn: (msg) => console.log(chalk.yellow("\u26A0") + " " + msg),
586
- info: (msg) => console.log(chalk.cyan("\u2192") + " " + msg),
587
- step: (msg) => console.log(chalk.dim(msg)),
588
- blank: () => console.log()
589
- };
590
- function createSpinner(text) {
591
- return ora({ text, color: "cyan" });
592
- }
593
- function printBanner(subtitle) {
594
- console.log(chalk.cyan(`
595
- ____ _ _ _ _ _ ____ _
596
- / ___| / \\ | \\ | | \\ | | __ ) ___ | |_
597
- | | / _ \\ | \\| | \\| | _ \\ / _ \\| __|
598
- | |___ / ___ \\| |\\ | |\\ | |_) | (_) | |_
599
- \\____/_/ \\_\\_| \\_|_| \\_|____/ \\___/ \\__|
600
- `));
601
- if (subtitle) {
602
- console.log(chalk.bold(` ${subtitle}`));
603
- console.log();
604
- }
605
- }
606
- function getDisplayWidth(str) {
607
- let width = 0;
608
- for (const ch of str) {
609
- const code = ch.codePointAt(0);
610
- if (code >= 11904 && code <= 40959 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65103 || code >= 65280 && code <= 65519 || code >= 13312 && code <= 19903 || code >= 131072 && code <= 173791 || code >= 173824 && code <= 177983 || code >= 177984 && code <= 178207 || code >= 178208 && code <= 183983) {
611
- width += 2;
612
- } else {
613
- width += 1;
614
- }
615
- }
616
- return width;
617
- }
618
- function printBoxTitle(title, width = 65) {
619
- const border = "\u2550".repeat(width);
620
- const displayWidth = getDisplayWidth(title);
621
- const totalPadding = width - displayWidth;
622
- const leftPadding = Math.floor(totalPadding / 2);
623
- const rightPadding = totalPadding - leftPadding;
624
- const paddedTitle = " ".repeat(Math.max(0, leftPadding)) + title + " ".repeat(Math.max(0, rightPadding));
625
- console.log();
626
- console.log(chalk.cyan.bold(` \u2554${border}\u2557`));
627
- console.log(chalk.cyan.bold(` \u2551`) + chalk.cyan.bold(paddedTitle) + chalk.cyan.bold(`\u2551`));
628
- console.log(chalk.cyan.bold(` \u255A${border}\u255D`));
629
- console.log();
1446
+ }
1447
+ return installed;
630
1448
  }
631
- function showOperationHints(isCheckbox = false) {
632
- if (isCheckbox) {
633
- console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m \u79FB\u52A8 | \x1B[36m\u7A7A\u683C\x1B[0m \u52FE\u9009 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4\uFF08\u4E0D\u9009\u6309\u56DE\u8F66\u53EF\u8FD4\u56DE\uFF09
634
- `);
1449
+ function verifyPluginFilesExist(configRoot, manifest, tool, level) {
1450
+ const skillsDir = join5(configRoot, "skills");
1451
+ const agentsDir = join5(configRoot, "agents");
1452
+ if (manifest.installed_skills && manifest.installed_skills.length > 0) {
1453
+ const hasAnySkill = manifest.installed_skills.some((skillId) => {
1454
+ const skillPath = join5(skillsDir, skillId);
1455
+ return existsSync5(skillPath);
1456
+ });
1457
+ if (!hasAnySkill) return false;
1458
+ }
1459
+ if (manifest.installed_agents && manifest.installed_agents.length > 0) {
1460
+ const hasAnyAgent = manifest.installed_agents.some((agentId) => {
1461
+ const agentPath = join5(agentsDir, agentId);
1462
+ return existsSync5(agentPath);
1463
+ });
1464
+ if (!hasAnyAgent) return false;
1465
+ }
1466
+ let agentsMdPath;
1467
+ if (tool === "opencode" && level === "project") {
1468
+ agentsMdPath = join5(configRoot, "..", "AGENTS.md");
635
1469
  } else {
636
- console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m \u79FB\u52A8 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4
637
- `);
1470
+ agentsMdPath = join5(configRoot, "AGENTS.md");
638
1471
  }
1472
+ if (!existsSync5(agentsMdPath)) return false;
1473
+ return true;
639
1474
  }
640
1475
 
641
1476
  // src/utils/config.ts
642
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
643
- import { join as join4 } from "path";
644
- import { homedir as homedir3 } from "os";
1477
+ import { existsSync as existsSync7, mkdirSync, readFileSync as readFileSync4 } from "fs";
1478
+ import { join as join6 } from "path";
645
1479
  import { parse, stringify } from "yaml";
1480
+
1481
+ // src/utils/fs.ts
1482
+ import { writeFileSync, renameSync, existsSync as existsSync6, unlinkSync } from "fs";
1483
+ import { dirname as dirname3 } from "path";
1484
+ function atomicWriteFileSync(filePath, data, encoding = "utf-8") {
1485
+ const dir = dirname3(filePath);
1486
+ if (!existsSync6(dir)) {
1487
+ throw new Error(`Directory does not exist: ${dir}`);
1488
+ }
1489
+ const tmpPath = `${filePath}.tmp`;
1490
+ writeFileSync(tmpPath, data, encoding);
1491
+ if (existsSync6(filePath)) {
1492
+ try {
1493
+ unlinkSync(filePath);
1494
+ } catch {
1495
+ }
1496
+ }
1497
+ renameSync(tmpPath, filePath);
1498
+ }
1499
+
1500
+ // src/utils/config.ts
646
1501
  function getConfigDir() {
647
- return join4(homedir3(), ".cannbot");
1502
+ return getCannbotConfigDir();
648
1503
  }
649
1504
  function getConfigPath() {
650
- return join4(getConfigDir(), "config.yaml");
1505
+ return join6(getConfigDir(), "config.yaml");
651
1506
  }
652
1507
  function readConfig() {
653
1508
  const configPath = getConfigPath();
654
- if (!existsSync4(configPath)) {
1509
+ if (!existsSync7(configPath)) {
655
1510
  return {
656
1511
  language: "zh_CN",
657
1512
  installedPlugins: []
658
1513
  };
659
1514
  }
660
1515
  try {
661
- const content = readFileSync2(configPath, "utf-8");
1516
+ const content = readFileSync4(configPath, "utf-8");
662
1517
  const parsed = parse(content);
663
1518
  return {
664
1519
  language: parsed.language || "zh_CN",
@@ -668,6 +1523,7 @@ function readConfig() {
668
1523
  installedPlugins: parsed.installedPlugins || []
669
1524
  };
670
1525
  } catch {
1526
+ logger.warn(t("config_corrupted"));
671
1527
  return {
672
1528
  language: "zh_CN",
673
1529
  installedPlugins: []
@@ -676,12 +1532,12 @@ function readConfig() {
676
1532
  }
677
1533
  function writeConfig(config) {
678
1534
  const configDir = getConfigDir();
679
- if (!existsSync4(configDir)) {
1535
+ if (!existsSync7(configDir)) {
680
1536
  mkdirSync(configDir, { recursive: true });
681
1537
  }
682
1538
  const configPath = getConfigPath();
683
1539
  const content = stringify(config);
684
- writeFileSync(configPath, content, "utf-8");
1540
+ atomicWriteFileSync(configPath, content);
685
1541
  }
686
1542
  function updateConfig(updates) {
687
1543
  const config = readConfig();
@@ -704,22 +1560,11 @@ function removeInstalledPlugin(pluginId) {
704
1560
  writeConfig(config);
705
1561
  }
706
1562
 
707
- // src/ui/theme.ts
708
- var selectTheme = {
709
- helpMode: "never"
710
- };
711
- var checkboxTheme = {
712
- icon: {
713
- checked: "\x1B[32m\u2611\x1B[22m",
714
- unchecked: "\u2610",
715
- cursor: "\u276F"
716
- },
717
- helpMode: "never"
718
- };
719
-
720
- // src/ui/wizard.ts
1563
+ // src/utils/constants.ts
721
1564
  var BACK = "__back__";
722
1565
  var CANCEL = "__cancel__";
1566
+
1567
+ // src/ui/wizard.ts
723
1568
  async function selectToolWithDetection() {
724
1569
  const config = readConfig();
725
1570
  logger.blank();
@@ -789,7 +1634,7 @@ async function runWizard() {
789
1634
  step = 2;
790
1635
  break;
791
1636
  }
792
- if (result === CANCEL || result === false) {
1637
+ if (result === CANCEL) {
793
1638
  return { language: config.language || "zh_CN", tool: selectedTool, level, plugins: [], confirmed: false };
794
1639
  }
795
1640
  updateConfig({ lastTool: selectedTool, lastLevel: level });
@@ -799,11 +1644,11 @@ async function runWizard() {
799
1644
  }
800
1645
  }
801
1646
  async function stepTool(detectedTools, lastTool) {
802
- printBoxTitle("[1/4] \u9009\u62E9 AI \u7F16\u7A0B\u5DE5\u5177");
1647
+ printBoxTitle(t("wizard_step_1_title"));
803
1648
  if (detectedTools.length === 0) {
804
1649
  logger.error(t("error_tool_not_found"));
805
1650
  const choices2 = [
806
- ...["opencode", "claude", "trae", "cursor", "copilot"].map((tool) => ({
1651
+ ...VALID_TOOLS.map((tool) => ({
807
1652
  name: `> ${getToolDisplayName(tool)}`,
808
1653
  value: tool
809
1654
  })),
@@ -874,10 +1719,10 @@ async function stepToolManual(lastTool) {
874
1719
  return result;
875
1720
  }
876
1721
  async function stepLevel(lastLevel) {
877
- printBoxTitle("[2/4] \u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E");
1722
+ printBoxTitle(t("wizard_step_2_title"));
878
1723
  const choices = [
879
- { name: "> project \u2014 \u5B89\u88C5\u5230\u5F53\u524D\u76EE\u5F55\uFF08\u63A8\u8350\uFF0C\u4EC5\u5F53\u524D\u9879\u76EE\u53EF\u7528\uFF09", value: "project" },
880
- { name: "> global \u2014 \u5B89\u88C5\u5230\u5168\u5C40\uFF08\u6240\u6709\u9879\u76EE\u5171\u4EAB\uFF09", value: "global" },
1724
+ { name: `> project \u2014 ${t("install_level_project")}`, value: "project" },
1725
+ { name: `> global \u2014 ${t("install_level_global")}`, value: "global" },
881
1726
  new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
882
1727
  { name: "<- " + t("wizard_back"), value: BACK },
883
1728
  { name: "x " + t("wizard_cancel"), value: CANCEL }
@@ -895,7 +1740,7 @@ async function stepLevel(lastLevel) {
895
1740
  return result;
896
1741
  }
897
1742
  async function stepPlugins(tool, level) {
898
- printBoxTitle("[3/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6");
1743
+ printBoxTitle(t("wizard_step_3_title"));
899
1744
  const plugins = getAllPlugins();
900
1745
  const configRoot = getConfigRoot(tool, level);
901
1746
  const manifests = readAllManifests(configRoot);
@@ -904,9 +1749,10 @@ async function stepPlugins(tool, level) {
904
1749
  const isInstalled = installedSet.has(p.id);
905
1750
  const suffix = isInstalled ? ` [${t("wizard_already_installed")}]` : "";
906
1751
  return {
907
- name: `${p.displayName}${suffix} \u2014 ${p.description}`,
1752
+ name: `${chalk3.cyan(p.displayName)}${suffix}`,
908
1753
  value: p.id,
909
- checked: false
1754
+ checked: false,
1755
+ description: p.description
910
1756
  };
911
1757
  });
912
1758
  showOperationHints(true);
@@ -936,17 +1782,17 @@ async function stepPlugins(tool, level) {
936
1782
  return pluginIds;
937
1783
  }
938
1784
  async function stepConfirm(tool, level, plugins) {
939
- printBoxTitle("[4/4] \u786E\u8BA4\u5B89\u88C5");
1785
+ printBoxTitle(t("wizard_step_4_title"));
940
1786
  const allPlugins = getAllPlugins();
941
1787
  const selectedPlugins = plugins.map((id) => allPlugins.find((p) => p.id === id));
942
1788
  const displayPath = getConfigRoot(tool, level);
943
1789
  const toolName = getToolDisplayName(tool);
944
1790
  const levelText = level === "project" ? "project" : "global";
945
- logger.info(`\u5373\u5C06\u5B89\u88C5 ${chalk2.bold(plugins.length)} \u4E2A\u63D2\u4EF6\u5230 ${chalk2.green(toolName)}\uFF08${chalk2.cyan(levelText)} \u7EA7\u522B\uFF09\uFF1A`);
946
- logger.info(`\u5B89\u88C5\u8DEF\u5F84: ${chalk2.cyan(displayPath)}`);
1791
+ logger.info(t("wizard_confirm_install_format").replace("{count}", chalk3.bold(String(plugins.length))).replace("{tool}", chalk3.green(toolName)).replace("{level}", chalk3.cyan(levelText)));
1792
+ logger.info(`${t("wizard_install_path")}: ${chalk3.cyan(displayPath)}`);
947
1793
  for (const plugin of selectedPlugins) {
948
1794
  if (plugin) {
949
- logger.step(` \u2022 ${chalk2.bold(plugin.displayName)}`);
1795
+ logger.step(` \u2022 ${chalk3.bold(plugin.displayName)}`);
950
1796
  }
951
1797
  }
952
1798
  logger.blank();
@@ -964,14 +1810,19 @@ async function stepConfirm(tool, level, plugins) {
964
1810
  }
965
1811
 
966
1812
  // src/core/repository.ts
967
- import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
968
- import { join as join6 } from "path";
1813
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
1814
+ import { join as join8, dirname as dirname5 } from "path";
1815
+ import { fileURLToPath as fileURLToPath4 } from "url";
969
1816
  import { execa as execa2 } from "execa";
1817
+ import { parse as parseYaml3 } from "yaml";
970
1818
 
971
1819
  // src/core/scanner.ts
972
- import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
973
- import { join as join5, dirname } from "path";
974
- import { parse as parseYaml } from "yaml";
1820
+ import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync5, mkdirSync as mkdirSync2 } from "fs";
1821
+ import { join as join7, dirname as dirname4 } from "path";
1822
+ import { fileURLToPath as fileURLToPath3 } from "url";
1823
+ import { parse as parseYaml2 } from "yaml";
1824
+ var __filename3 = fileURLToPath3(import.meta.url);
1825
+ var __dirname3 = dirname4(__filename3);
975
1826
  var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
976
1827
  "node_modules",
977
1828
  ".git",
@@ -992,46 +1843,70 @@ var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
992
1843
  "docs",
993
1844
  "scripts"
994
1845
  ]);
995
- var SKILL_SCAN_DIRS = [
996
- "ops",
997
- "model",
998
- "graph",
999
- "infra",
1000
- "ops-lab"
1001
- ];
1002
- var PLUGIN_SCAN_DIRS = [
1003
- "plugins-official",
1004
- "plugins-community"
1005
- ];
1846
+ function loadScanConfig() {
1847
+ const configPath = join7(__dirname3, "config", "repository.yaml");
1848
+ try {
1849
+ const config = parseYaml2(readFileSync5(configPath, "utf-8"));
1850
+ const ttlHours = config.scanCacheTtlHours || 24;
1851
+ return {
1852
+ skillDirs: config.scanDirs || ["ops", "model", "graph", "infra", "ops-lab", "runtime"],
1853
+ pluginDirs: config.pluginDirs || ["plugins-official", "plugins-community"],
1854
+ cacheTtlMs: ttlHours * 60 * 60 * 1e3
1855
+ };
1856
+ } catch {
1857
+ return {
1858
+ skillDirs: ["ops", "model", "graph", "infra", "ops-lab", "runtime"],
1859
+ pluginDirs: ["plugins-official", "plugins-community"],
1860
+ cacheTtlMs: 24 * 60 * 60 * 1e3
1861
+ };
1862
+ }
1863
+ }
1864
+ var scanConfig = loadScanConfig();
1865
+ var SKILL_SCAN_DIRS = scanConfig.skillDirs;
1866
+ var PLUGIN_SCAN_DIRS = scanConfig.pluginDirs;
1006
1867
  function scanSkills(repoPath) {
1007
1868
  const skills = [];
1008
1869
  const seen = /* @__PURE__ */ new Set();
1009
1870
  for (const dir of SKILL_SCAN_DIRS) {
1010
- const fullPath = join5(repoPath, dir);
1011
- if (!existsSync5(fullPath)) continue;
1871
+ const fullPath = join7(repoPath, dir);
1872
+ if (!existsSync8(fullPath)) continue;
1012
1873
  scanDirectory(fullPath, repoPath, dir, skills, seen);
1013
1874
  }
1014
1875
  for (const pluginDir of PLUGIN_SCAN_DIRS) {
1015
- const pluginParent = join5(repoPath, pluginDir);
1016
- if (!existsSync5(pluginParent)) continue;
1876
+ const pluginParent = join7(repoPath, pluginDir);
1877
+ if (!existsSync8(pluginParent)) continue;
1017
1878
  for (const plugin of safeReaddir(pluginParent)) {
1018
- const pluginPath = join5(pluginParent, plugin);
1879
+ const pluginPath = join7(pluginParent, plugin);
1019
1880
  if (!isDirectory(pluginPath)) continue;
1020
- const skillsDir = join5(pluginPath, "skills");
1021
- if (existsSync5(skillsDir)) {
1881
+ const skillsDir = join7(pluginPath, "skills");
1882
+ if (existsSync8(skillsDir)) {
1022
1883
  scanDirectory(skillsDir, repoPath, `${pluginDir}/${plugin}/skills`, skills, seen);
1023
1884
  }
1024
- const skillDir = join5(pluginPath, "skill");
1025
- if (existsSync5(skillDir)) {
1885
+ const skillDir = join7(pluginPath, "skill");
1886
+ if (existsSync8(skillDir)) {
1026
1887
  scanDirectory(skillDir, repoPath, `${pluginDir}/${plugin}/skill`, skills, seen);
1027
1888
  }
1889
+ const rootSkillMd = join7(pluginPath, "SKILL.md");
1890
+ const initSh = join7(pluginPath, "init.sh");
1891
+ if (existsSync8(rootSkillMd) && !existsSync8(initSh)) {
1892
+ const frontmatter = parseFrontmatter(rootSkillMd);
1893
+ if (frontmatter && !seen.has(frontmatter.name)) {
1894
+ seen.add(frontmatter.name);
1895
+ skills.push({
1896
+ id: frontmatter.name,
1897
+ description: frontmatter.description,
1898
+ source: `${pluginDir}/${plugin}`,
1899
+ filePath: rootSkillMd
1900
+ });
1901
+ }
1902
+ }
1028
1903
  }
1029
1904
  }
1030
1905
  return skills;
1031
1906
  }
1032
1907
  function parseFrontmatter(filePath) {
1033
1908
  try {
1034
- const content = readFileSync3(filePath, "utf-8");
1909
+ const content = readFileSync5(filePath, "utf-8");
1035
1910
  const lines = content.split("\n");
1036
1911
  if (lines.length < 3) return null;
1037
1912
  if (lines[0].trim() !== "---") return null;
@@ -1044,11 +1919,15 @@ function parseFrontmatter(filePath) {
1044
1919
  }
1045
1920
  if (endIndex === -1) return null;
1046
1921
  const yamlBlock = lines.slice(1, endIndex).map((l) => l.replace(/\r$/, "")).join("\n");
1047
- const parsed = parseYaml(yamlBlock);
1922
+ const parsed = parseYaml2(yamlBlock);
1048
1923
  if (!parsed || typeof parsed !== "object") return null;
1049
1924
  if (!parsed.name || typeof parsed.name !== "string") return null;
1925
+ const trimmedName = parsed.name.trim();
1926
+ if (!trimmedName || /[\/\\]|\.\./.test(trimmedName) || !/^[a-zA-Z0-9._-]+$/.test(trimmedName)) {
1927
+ return null;
1928
+ }
1050
1929
  return {
1051
- name: parsed.name.trim(),
1930
+ name: trimmedName,
1052
1931
  description: (parsed.description || "").trim()
1053
1932
  };
1054
1933
  } catch {
@@ -1057,17 +1936,17 @@ function parseFrontmatter(filePath) {
1057
1936
  }
1058
1937
  function getCurrentCommit(repoPath) {
1059
1938
  try {
1060
- const headFile = join5(repoPath, ".git", "HEAD");
1061
- if (!existsSync5(headFile)) return "unknown";
1062
- const headContent = readFileSync3(headFile, "utf-8").trim();
1939
+ const headFile = join7(repoPath, ".git", "HEAD");
1940
+ if (!existsSync8(headFile)) return "unknown";
1941
+ const headContent = readFileSync5(headFile, "utf-8").trim();
1063
1942
  if (headContent.startsWith("ref: ")) {
1064
- const refPath = join5(repoPath, ".git", headContent.slice(5));
1065
- if (existsSync5(refPath)) {
1066
- return readFileSync3(refPath, "utf-8").trim();
1943
+ const refPath = join7(repoPath, ".git", headContent.slice(5));
1944
+ if (existsSync8(refPath)) {
1945
+ return readFileSync5(refPath, "utf-8").trim();
1067
1946
  }
1068
- const packedRefsPath = join5(repoPath, ".git", "packed-refs");
1069
- if (existsSync5(packedRefsPath)) {
1070
- const packedRefs = readFileSync3(packedRefsPath, "utf-8");
1947
+ const packedRefsPath = join7(repoPath, ".git", "packed-refs");
1948
+ if (existsSync8(packedRefsPath)) {
1949
+ const packedRefs = readFileSync5(packedRefsPath, "utf-8");
1071
1950
  const refName = headContent.slice(5);
1072
1951
  const lines = packedRefs.split("\n");
1073
1952
  for (const line of lines) {
@@ -1085,12 +1964,12 @@ function getCurrentCommit(repoPath) {
1085
1964
  function readScanCache() {
1086
1965
  try {
1087
1966
  const cachePath = getCachePath();
1088
- if (!existsSync5(cachePath)) return null;
1089
- const content = readFileSync3(cachePath, "utf-8");
1967
+ if (!existsSync8(cachePath)) return null;
1968
+ const content = readFileSync5(cachePath, "utf-8");
1090
1969
  const cache = JSON.parse(content);
1091
1970
  if (!cache.skills || !cache.timestamp) return null;
1092
1971
  const age = Date.now() - cache.timestamp;
1093
- if (age > 24 * 60 * 60 * 1e3) return null;
1972
+ if (age > scanConfig.cacheTtlMs) return null;
1094
1973
  return cache;
1095
1974
  } catch {
1096
1975
  return null;
@@ -1099,39 +1978,24 @@ function readScanCache() {
1099
1978
  function writeScanCache(cache) {
1100
1979
  try {
1101
1980
  const cachePath = getCachePath();
1102
- const cacheDir = dirname(cachePath);
1103
- if (!existsSync5(cacheDir)) {
1981
+ const cacheDir = dirname4(cachePath);
1982
+ if (!existsSync8(cacheDir)) {
1104
1983
  mkdirSync2(cacheDir, { recursive: true });
1105
1984
  }
1106
- writeFileSync2(cachePath, JSON.stringify(cache, null, 2));
1985
+ atomicWriteFileSync(cachePath, JSON.stringify(cache, null, 2));
1107
1986
  } catch {
1108
1987
  }
1109
1988
  }
1110
1989
  function getCachePath() {
1111
- const homeDir = process.env.HOME || process.env.USERPROFILE || "/tmp";
1112
- return join5(homeDir, ".cannbot", "scan-cache.json");
1990
+ return join7(getCannbotConfigDir(), "scan-cache.json");
1113
1991
  }
1114
1992
  function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1115
- const selfSkillMd = join5(dirPath, "SKILL.md");
1116
- if (existsSync5(selfSkillMd)) {
1117
- const frontmatter = parseFrontmatter(selfSkillMd);
1118
- if (frontmatter && !seen.has(frontmatter.name)) {
1119
- seen.add(frontmatter.name);
1120
- skills.push({
1121
- id: frontmatter.name,
1122
- description: frontmatter.description,
1123
- source: sourcePrefix,
1124
- filePath: selfSkillMd
1125
- });
1126
- }
1127
- return;
1128
- }
1129
1993
  for (const entry of safeReaddir(dirPath)) {
1130
- const fullPath = join5(dirPath, entry);
1994
+ const fullPath = join7(dirPath, entry);
1131
1995
  if (EXCLUDE_DIRS.has(entry)) continue;
1132
1996
  if (!isDirectory(fullPath)) continue;
1133
- const skillMd = join5(fullPath, "SKILL.md");
1134
- if (existsSync5(skillMd)) {
1997
+ const skillMd = join7(fullPath, "SKILL.md");
1998
+ if (existsSync8(skillMd)) {
1135
1999
  const frontmatter = parseFrontmatter(skillMd);
1136
2000
  if (frontmatter && !seen.has(frontmatter.name)) {
1137
2001
  seen.add(frontmatter.name);
@@ -1144,38 +2008,32 @@ function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1144
2008
  }
1145
2009
  continue;
1146
2010
  }
1147
- scanDirectory(fullPath, repoPath, sourcePrefix, skills, seen);
2011
+ scanDirectory(fullPath, repoPath, sourcePrefix + "/" + entry, skills, seen);
1148
2012
  }
1149
2013
  }
1150
2014
  function safeReaddir(dir) {
1151
2015
  try {
1152
- return readdirSync(dir);
2016
+ return readdirSync2(dir);
1153
2017
  } catch {
1154
2018
  return [];
1155
2019
  }
1156
2020
  }
1157
- function isDirectory(path) {
1158
- try {
1159
- return statSync(path).isDirectory();
1160
- } catch {
1161
- return false;
1162
- }
1163
- }
1164
2021
 
1165
2022
  // src/core/skill-registry.ts
1166
2023
  var CATEGORY_DEFS = [
1167
- { id: "knowledge", name: "\u77E5\u8BC6\u4E0E\u53C2\u8003" },
1168
- { id: "env-tools", name: "\u73AF\u5883\u4E0E\u5DE5\u5177" },
1169
- { id: "debug", name: "\u8C03\u8BD5\u4E0E\u8BCA\u65AD" },
1170
- { id: "testing", name: "\u6D4B\u8BD5\u4E0E\u8D28\u91CF" },
1171
- { id: "ascendc", name: "AscendC \u5F00\u53D1" },
1172
- { id: "pypto", name: "PyPTO \u5F00\u53D1" },
1173
- { id: "tilelang", name: "TileLang \u5F00\u53D1" },
1174
- { id: "triton", name: "Triton \u5F00\u53D1" },
1175
- { id: "model", name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316" },
1176
- { id: "graph", name: "\u56FE\u6A21\u5F0F" },
1177
- { id: "platform", name: "\u5E73\u53F0\u5DE5\u5177" },
1178
- { id: "other", name: "\u5176\u4ED6 Skills" }
2024
+ { id: "knowledge", name: "\u77E5\u8BC6\u4E0E\u53C2\u8003", nameEn: "Knowledge & Reference" },
2025
+ { id: "env-tools", name: "\u73AF\u5883\u4E0E\u5DE5\u5177", nameEn: "Environment & Tools" },
2026
+ { id: "debug", name: "\u8C03\u8BD5\u4E0E\u8BCA\u65AD", nameEn: "Debug & Diagnostics" },
2027
+ { id: "testing", name: "\u6D4B\u8BD5\u4E0E\u8D28\u91CF", nameEn: "Testing & Quality" },
2028
+ { id: "ascendc", name: "AscendC \u5F00\u53D1", nameEn: "AscendC Development" },
2029
+ { id: "pypto", name: "PyPTO \u5F00\u53D1", nameEn: "PyPTO Development" },
2030
+ { id: "tilelang", name: "TileLang \u5F00\u53D1", nameEn: "TileLang Development" },
2031
+ { id: "triton", name: "Triton \u5F00\u53D1", nameEn: "Triton Development" },
2032
+ { id: "model", name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316", nameEn: "Model Inference Optimization" },
2033
+ { id: "runtime", name: "Runtime \u8FC1\u79FB", nameEn: "Runtime Migration" },
2034
+ { id: "graph", name: "\u56FE\u6A21\u5F0F", nameEn: "Graph Mode" },
2035
+ { id: "platform", name: "\u5E73\u53F0\u5DE5\u5177", nameEn: "Platform Tools" },
2036
+ { id: "other", name: "\u5176\u4ED6 Skills", nameEn: "Other Skills" }
1179
2037
  ];
1180
2038
  var dynamicSkills = [];
1181
2039
  var initialized = false;
@@ -1183,7 +2041,8 @@ function initFromScan(scanned) {
1183
2041
  dynamicSkills = scanned.map((s) => ({
1184
2042
  id: s.id,
1185
2043
  description: s.description,
1186
- source: s.source
2044
+ source: s.source,
2045
+ filePath: s.filePath
1187
2046
  }));
1188
2047
  initialized = true;
1189
2048
  }
@@ -1259,9 +2118,7 @@ var STATIC_SKILL_CATEGORIES = [
1259
2118
  { id: "catlass-op-develop", description: "Catlass \u7B97\u5B50\u5F00\u53D1", source: "ops" },
1260
2119
  { id: "catlass-op-perf-tune", description: "Catlass \u6027\u80FD\u8C03\u4F18", source: "ops" },
1261
2120
  { id: "cuda2ascend-simt", description: "CUDA \u8FC1\u79FB\u5230 Ascend C SIMT", source: "ops-lab" },
1262
- { id: "ops-direct-invoke-flash", description: "\u4ECE\u96F6\u6784\u5EFA Ascend C \u6838\u51FD\u6570", source: "plugins-official/ops-direct-invoke-flash/skills" },
1263
- { id: "ops-registry-invoke-workflow", description: "\u6CE8\u518C\u8C03\u7528\u5DE5\u4F5C\u6D41", source: "plugins-official/ops-registry-invoke" },
1264
- { id: "ops-easyasc-dsl", description: "EasyASC DSL \u7B97\u5B50\u5F00\u53D1", source: "plugins-community/ops-easyasc-dsl/skill" }
2121
+ { id: "ops-direct-invoke-flash", description: "\u4ECE\u96F6\u6784\u5EFA Ascend C \u6838\u51FD\u6570", source: "plugins-official/ops-direct-invoke-flash/skills" }
1265
2122
  ]
1266
2123
  },
1267
2124
  {
@@ -1318,7 +2175,9 @@ var STATIC_SKILL_CATEGORIES = [
1318
2175
  { id: "model-infer-prefetch", description: "\u6743\u91CD\u9884\u53D6\u9002\u914D", source: "model" },
1319
2176
  { id: "model-infer-superkernel", description: "SuperKernel \u9002\u914D", source: "model" },
1320
2177
  { id: "model-infer-precision-debug", description: "NPU \u63A8\u7406\u7CBE\u5EA6\u8BCA\u65AD", source: "model" },
1321
- { id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" }
2178
+ { id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" },
2179
+ { id: "model-infer-harmony", description: "\u7AEF\u4FA7\u9E3F\u8499 ASR \u91CF\u5316\u8F6C\u6362\u4E0E\u6253\u5305", source: "model" },
2180
+ { id: "science-model-npu-migration", description: "NPU \u4EE3\u7801\u7EA7\u8FC1\u79FB\uFF08\u73AF\u5883\u95E8\u7981/\u811A\u672C\u9002\u914D/\u7CBE\u5EA6\u6027\u80FD\u5BF9\u6BD4\uFF09", source: "plugins-community/science-model-npu-migration", filePath: "plugins-community/science-model-npu-migration/SKILL.md" }
1322
2181
  ]
1323
2182
  },
1324
2183
  {
@@ -1334,6 +2193,13 @@ var STATIC_SKILL_CATEGORIES = [
1334
2193
  { id: "torch-custom-ops-guide", description: "\u81EA\u5B9A\u4E49\u7B97\u5B50\u5165\u56FE\u6307\u5357", source: "graph" }
1335
2194
  ]
1336
2195
  },
2196
+ {
2197
+ id: "runtime",
2198
+ name: "Runtime \u8FC1\u79FB",
2199
+ skills: [
2200
+ { id: "runtime_migration", description: "CUDA \u5E94\u7528\u8FC1\u79FB\u5230 CANN \u5E73\u53F0\u6307\u5357", source: "runtime" }
2201
+ ]
2202
+ },
1337
2203
  {
1338
2204
  id: "platform",
1339
2205
  name: "\u5E73\u53F0\u5DE5\u5177",
@@ -1377,9 +2243,10 @@ function getAllSkills() {
1377
2243
  }
1378
2244
  function getAllCategories() {
1379
2245
  const skills = getActiveSkills();
2246
+ const isEn = t("list_name") === "Name";
1380
2247
  return CATEGORY_DEFS.map((def) => ({
1381
2248
  id: def.id,
1382
- name: def.name,
2249
+ name: isEn ? def.nameEn : def.name,
1383
2250
  skills: skills.filter((s) => {
1384
2251
  const catId = CATEGORY_MAP[s.id];
1385
2252
  if (catId) return catId === def.id;
@@ -1389,11 +2256,27 @@ function getAllCategories() {
1389
2256
  }
1390
2257
 
1391
2258
  // src/core/repository.ts
1392
- var REPO_URL = "https://gitcode.com/cann/cannbot-skills.git";
2259
+ var __filename4 = fileURLToPath4(import.meta.url);
2260
+ var __dirname4 = dirname5(__filename4);
2261
+ function loadRepoUrl() {
2262
+ if (process.env.CANNBOT_REPO_URL) {
2263
+ return process.env.CANNBOT_REPO_URL;
2264
+ }
2265
+ const configPath = join8(__dirname4, "config", "repository.yaml");
2266
+ try {
2267
+ const config = parseYaml3(readFileSync6(configPath, "utf-8"));
2268
+ return config.repository?.url || "https://gitcode.com/yanhf/cann_skills.git";
2269
+ } catch {
2270
+ return "https://gitcode.com/yanhf/cann_skills.git";
2271
+ }
2272
+ }
2273
+ var REPO_URL = loadRepoUrl();
1393
2274
  var RepositoryManager = class {
1394
2275
  repoPath;
2276
+ customPath;
1395
2277
  constructor(customPath) {
1396
2278
  this.repoPath = customPath;
2279
+ this.customPath = customPath;
1397
2280
  }
1398
2281
  async ensureRepo() {
1399
2282
  if (this.repoPath && this.isValidRepo(this.repoPath)) {
@@ -1414,10 +2297,16 @@ var RepositoryManager = class {
1414
2297
  this.repoPath = cachedPath;
1415
2298
  return cachedPath;
1416
2299
  }
1417
- return await this.cloneRepo();
2300
+ const clonedPath = await this.cloneRepo();
2301
+ return clonedPath;
1418
2302
  }
1419
2303
  async ensureRepoAndScan() {
1420
2304
  const repoPath = await this.ensureRepo();
2305
+ const isUserProvided = !!this.customPath || !!process.env.CANNBOT_REPO_PATH;
2306
+ if (!isUserProvided) {
2307
+ await this.updateRepo();
2308
+ }
2309
+ mergeDynamicPlugins(repoPath);
1421
2310
  const cache = readScanCache();
1422
2311
  if (cache && cache.repoCommit === getCurrentCommit(repoPath)) {
1423
2312
  initFromScan(cache.skills);
@@ -1437,7 +2326,9 @@ var RepositoryManager = class {
1437
2326
  try {
1438
2327
  await execa2("git", ["pull", "--quiet"], { cwd: repoPath, timeout: 3e4 });
1439
2328
  } catch {
2329
+ logger.warn(t("repo_pull_failed"));
1440
2330
  }
2331
+ mergeDynamicPlugins(repoPath);
1441
2332
  }
1442
2333
  getRepoPath() {
1443
2334
  if (!this.repoPath) {
@@ -1446,24 +2337,25 @@ var RepositoryManager = class {
1446
2337
  return this.repoPath;
1447
2338
  }
1448
2339
  isValidRepo(path) {
1449
- if (!existsSync6(path)) return false;
1450
- const gitDir = join6(path, ".git");
1451
- const pluginsDir = join6(path, "plugins-official");
1452
- return existsSync6(gitDir) && existsSync6(pluginsDir);
2340
+ if (!existsSync9(path)) return false;
2341
+ const gitDir = join8(path, ".git");
2342
+ const pluginsDir = join8(path, "plugins-official");
2343
+ return existsSync9(gitDir) && existsSync9(pluginsDir);
1453
2344
  }
1454
2345
  async cloneRepo() {
1455
2346
  const configDir = getCannbotConfigDir();
1456
- if (!existsSync6(configDir)) {
2347
+ if (!existsSync9(configDir)) {
1457
2348
  mkdirSync3(configDir, { recursive: true });
1458
2349
  }
1459
2350
  const targetPath = getCannbotRepoPath();
1460
- if (existsSync6(targetPath)) {
2351
+ if (existsSync9(targetPath)) {
1461
2352
  if (this.isValidRepo(targetPath)) {
1462
2353
  this.repoPath = targetPath;
1463
2354
  return targetPath;
1464
2355
  }
1465
2356
  }
1466
2357
  try {
2358
+ logger.info(t("repo_cloning"));
1467
2359
  await execa2(
1468
2360
  "git",
1469
2361
  ["clone", "--depth", "1", REPO_URL, targetPath],
@@ -1483,182 +2375,598 @@ function createRepositoryManager(customPath) {
1483
2375
  }
1484
2376
 
1485
2377
  // src/core/installer.ts
1486
- import { existsSync as existsSync8 } from "fs";
1487
- import { join as join8 } from "path";
1488
- import { execa as execa3 } from "execa";
2378
+ import { existsSync as existsSync13 } from "fs";
2379
+ import { join as join12, basename as basename2 } from "path";
2380
+ import { execa as execa4, execaSync } from "execa";
1489
2381
 
1490
2382
  // src/core/record.ts
1491
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, lstatSync, unlinkSync } from "fs";
1492
- import { join as join7 } from "path";
2383
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
2384
+ import { join as join9 } from "path";
1493
2385
  function getInstallsDir() {
1494
- return join7(getCannbotConfigDir(), "installs");
2386
+ return join9(getCannbotConfigDir(), "installs");
1495
2387
  }
1496
2388
  function getRecordPath(pluginId) {
1497
- return join7(getInstallsDir(), `${pluginId}.json`);
2389
+ return join9(getInstallsDir(), `${pluginId}.json`);
1498
2390
  }
1499
2391
  function readRecord(pluginId) {
1500
2392
  const recordPath = getRecordPath(pluginId);
1501
- if (!existsSync7(recordPath)) {
2393
+ if (!existsSync10(recordPath)) {
1502
2394
  return null;
1503
2395
  }
1504
2396
  try {
1505
- const content = readFileSync4(recordPath, "utf-8");
2397
+ const content = readFileSync7(recordPath, "utf-8");
1506
2398
  return JSON.parse(content);
1507
2399
  } catch {
2400
+ logger.warn(t("record_corrupted").replace("{file}", recordPath));
1508
2401
  return null;
1509
2402
  }
1510
2403
  }
1511
2404
  function writeRecord(record) {
1512
2405
  const installsDir = getInstallsDir();
1513
- if (!existsSync7(installsDir)) {
2406
+ if (!existsSync10(installsDir)) {
1514
2407
  mkdirSync4(installsDir, { recursive: true });
1515
2408
  }
1516
2409
  const recordPath = getRecordPath(record.pluginId);
1517
- writeFileSync4(recordPath, JSON.stringify(record, null, 2), "utf-8");
2410
+ atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
1518
2411
  }
1519
2412
  function deleteRecord(pluginId) {
1520
2413
  const recordPath = getRecordPath(pluginId);
1521
- if (existsSync7(recordPath)) {
1522
- unlinkSync(recordPath);
2414
+ if (existsSync10(recordPath)) {
2415
+ unlinkSync2(recordPath);
1523
2416
  }
1524
2417
  }
1525
- function scanInstalledFiles(pluginId, displayName, tool, level, installPath, configRoot, manifest) {
2418
+ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, configRoot, manifest, externalRepoNames) {
1526
2419
  const files = [];
1527
2420
  const directories = [];
1528
2421
  if (manifest) {
1529
- const skillsDir = join7(configRoot, "skills");
1530
- if (existsSync7(skillsDir)) {
2422
+ const skillsDir = join9(configRoot, "skills");
2423
+ if (existsSync10(skillsDir)) {
1531
2424
  directories.push(skillsDir);
1532
2425
  for (const skillName of manifest.installed_skills || []) {
1533
- const skillPath = join7(skillsDir, skillName);
1534
- if (existsSync7(skillPath) || isSymlink(skillPath)) {
2426
+ const skillPath = join9(skillsDir, skillName);
2427
+ if (existsSync10(skillPath) || isSymlink(skillPath)) {
1535
2428
  files.push(skillPath);
1536
2429
  }
1537
2430
  }
1538
2431
  }
1539
- const agentsDir = join7(configRoot, "agents");
1540
- if (existsSync7(agentsDir)) {
2432
+ const agentsDir = join9(configRoot, "agents");
2433
+ if (existsSync10(agentsDir)) {
1541
2434
  directories.push(agentsDir);
1542
2435
  for (const agentName of manifest.installed_agents || []) {
1543
- const agentPath = join7(agentsDir, agentName);
1544
- const agentPathMd = join7(agentsDir, agentName + ".md");
1545
- if (existsSync7(agentPath) || isSymlink(agentPath)) {
2436
+ const agentPath = join9(agentsDir, agentName);
2437
+ const agentPathMd = join9(agentsDir, agentName + ".md");
2438
+ if (existsSync10(agentPath) || isSymlink(agentPath)) {
1546
2439
  files.push(agentPath);
1547
- } else if (existsSync7(agentPathMd) || isSymlink(agentPathMd)) {
2440
+ } else if (existsSync10(agentPathMd) || isSymlink(agentPathMd)) {
1548
2441
  files.push(agentPathMd);
1549
2442
  }
1550
2443
  }
1551
2444
  }
1552
2445
  }
1553
- const workflowsLink = join7(configRoot, "workflows");
2446
+ const workflowsLink = join9(configRoot, "workflows");
1554
2447
  if (isSymlink(workflowsLink)) {
1555
2448
  files.push(workflowsLink);
1556
2449
  }
1557
- const manifestPath = join7(configRoot, "cannbot-manifest.json");
1558
- if (existsSync7(manifestPath)) {
2450
+ const manifestPath = join9(configRoot, "cannbot-manifest.json");
2451
+ if (existsSync10(manifestPath)) {
1559
2452
  files.push(manifestPath);
1560
2453
  }
1561
- const pluginManifestPath = join7(configRoot, `${pluginId}-manifest.json`);
1562
- if (existsSync7(pluginManifestPath)) {
2454
+ const pluginManifestPath = join9(configRoot, `${pluginId}-manifest.json`);
2455
+ if (existsSync10(pluginManifestPath)) {
1563
2456
  files.push(pluginManifestPath);
1564
2457
  }
1565
2458
  const configFileName = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
1566
- const configFilePath = level === "project" ? join7(installPath, configFileName) : join7(configRoot, configFileName);
1567
- if (existsSync7(configFilePath)) {
2459
+ const configFilePath = level === "project" ? join9(installPath, configFileName) : join9(configRoot, configFileName);
2460
+ if (existsSync10(configFilePath)) {
1568
2461
  files.push(configFilePath);
1569
2462
  }
1570
- const repoLinks = ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples"];
2463
+ const repoLinks = externalRepoNames && externalRepoNames.length > 0 ? externalRepoNames : ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples"];
1571
2464
  for (const repoName of repoLinks) {
1572
- const repoLinkPath = join7(installPath, repoName);
2465
+ const repoLinkPath = join9(installPath, repoName);
1573
2466
  if (isSymlink(repoLinkPath)) {
1574
2467
  files.push(repoLinkPath);
1575
2468
  }
1576
- const repoLinkInConfig = join7(configRoot, repoName);
2469
+ const repoLinkInConfig = join9(configRoot, repoName);
1577
2470
  if (isSymlink(repoLinkInConfig)) {
1578
2471
  files.push(repoLinkInConfig);
1579
2472
  }
1580
2473
  }
1581
- directories.push(configRoot);
1582
- return {
1583
- pluginId,
1584
- displayName,
1585
- tool,
1586
- level,
1587
- installPath,
1588
- configRoot,
1589
- installTime: (/* @__PURE__ */ new Date()).toISOString(),
1590
- files,
1591
- directories
1592
- };
2474
+ return {
2475
+ pluginId,
2476
+ displayName,
2477
+ tool,
2478
+ level,
2479
+ installPath,
2480
+ configRoot,
2481
+ installTime: (/* @__PURE__ */ new Date()).toISOString(),
2482
+ files,
2483
+ directories
2484
+ };
2485
+ }
2486
+ function getSkillRecordPath() {
2487
+ return join9(getInstallsDir(), "skills.json");
2488
+ }
2489
+ function readSkillRecord() {
2490
+ const recordPath = getSkillRecordPath();
2491
+ if (!existsSync10(recordPath)) {
2492
+ return {};
2493
+ }
2494
+ try {
2495
+ const content = readFileSync7(recordPath, "utf-8");
2496
+ return JSON.parse(content);
2497
+ } catch {
2498
+ logger.warn(t("skill_record_corrupted"));
2499
+ return {};
2500
+ }
2501
+ }
2502
+ function writeSkillRecord(record) {
2503
+ const installsDir = getInstallsDir();
2504
+ if (!existsSync10(installsDir)) {
2505
+ mkdirSync4(installsDir, { recursive: true });
2506
+ }
2507
+ const recordPath = getSkillRecordPath();
2508
+ atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
2509
+ }
2510
+ function addSkillsToRecord(skillIds, tool, level, installPath) {
2511
+ const record = readSkillRecord();
2512
+ if (!record[tool]) record[tool] = {};
2513
+ if (!record[tool][level]) record[tool][level] = {};
2514
+ if (!record[tool][level][installPath]) {
2515
+ record[tool][level][installPath] = { skills: [], installTime: "" };
2516
+ }
2517
+ const entry = record[tool][level][installPath];
2518
+ for (const id of skillIds) {
2519
+ if (!entry.skills.includes(id)) {
2520
+ entry.skills.push(id);
2521
+ }
2522
+ }
2523
+ entry.installTime = (/* @__PURE__ */ new Date()).toISOString();
2524
+ writeSkillRecord(record);
2525
+ }
2526
+ function removeSkillsFromRecord(skillIds, tool, level, installPath) {
2527
+ const record = readSkillRecord();
2528
+ if (!record[tool]?.[level]?.[installPath]) return;
2529
+ const entry = record[tool][level][installPath];
2530
+ entry.skills = entry.skills.filter((id) => !skillIds.includes(id));
2531
+ if (entry.skills.length === 0) {
2532
+ delete record[tool][level][installPath];
2533
+ if (Object.keys(record[tool][level]).length === 0) {
2534
+ delete record[tool][level];
2535
+ if (Object.keys(record[tool]).length === 0) {
2536
+ delete record[tool];
2537
+ }
2538
+ }
2539
+ }
2540
+ writeSkillRecord(record);
2541
+ }
2542
+ function getInstalledSkills(tool, level, installPath) {
2543
+ const record = readSkillRecord();
2544
+ const recordedSkills = record[tool]?.[level]?.[installPath]?.skills || [];
2545
+ const configRoot = getConfigRoot(tool, level);
2546
+ const skillsDir = join9(configRoot, "skills");
2547
+ return recordedSkills.filter((skillId) => {
2548
+ const skillPath = join9(skillsDir, skillId);
2549
+ return existsSync10(skillPath) || isSymlink(skillPath);
2550
+ });
2551
+ }
2552
+
2553
+ // src/core/backup.ts
2554
+ import { existsSync as existsSync11, copyFileSync, readdirSync as readdirSync3, unlinkSync as unlinkSync3 } from "fs";
2555
+ import { join as join10 } from "path";
2556
+ function detectCurrentPlugin(configRoot, tool) {
2557
+ const manifest = readManifest(configRoot);
2558
+ if (manifest?.team) {
2559
+ const plugin = findPlugin(manifest.team);
2560
+ if (plugin) {
2561
+ return {
2562
+ pluginId: plugin.id,
2563
+ pluginName: plugin.displayName
2564
+ };
2565
+ }
2566
+ }
2567
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
2568
+ if (!existsSync11(agentsFile)) {
2569
+ return null;
2570
+ }
2571
+ const allRecords = readAllRecords();
2572
+ for (const record of allRecords) {
2573
+ if (record.configRoot === configRoot && record.tool === tool) {
2574
+ const plugin = findPlugin(record.pluginId);
2575
+ if (plugin) {
2576
+ return {
2577
+ pluginId: plugin.id,
2578
+ pluginName: plugin.displayName
2579
+ };
2580
+ }
2581
+ }
2582
+ }
2583
+ return null;
2584
+ }
2585
+ function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
2586
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
2587
+ if (!existsSync11(agentsFile)) {
2588
+ return null;
2589
+ }
2590
+ const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
2591
+ const backupFileName = `${getAgentsFileName(tool)}.cannbot-backup.${fromPluginId}.${timestamp}`;
2592
+ const backupPath = join10(configRoot, backupFileName);
2593
+ try {
2594
+ copyFileSync(agentsFile, backupPath);
2595
+ return {
2596
+ filePath: backupPath,
2597
+ pluginId: fromPluginId,
2598
+ pluginName: fromPluginName,
2599
+ backupTime: timestamp
2600
+ };
2601
+ } catch {
2602
+ return null;
2603
+ }
2604
+ }
2605
+ function findBackups(configRoot) {
2606
+ if (!existsSync11(configRoot)) {
2607
+ return [];
2608
+ }
2609
+ const files = readdirSync3(configRoot);
2610
+ const backups = [];
2611
+ for (const file of files) {
2612
+ if (file.includes(".cannbot-backup.")) {
2613
+ const parts = file.split(".cannbot-backup.");
2614
+ if (parts.length === 2) {
2615
+ const pluginIdAndTime = parts[1];
2616
+ const lastDotIndex = pluginIdAndTime.lastIndexOf(".");
2617
+ if (lastDotIndex > 0) {
2618
+ const pluginId = pluginIdAndTime.substring(0, lastDotIndex);
2619
+ const timestamp = pluginIdAndTime.substring(lastDotIndex + 1);
2620
+ const plugin = findPlugin(pluginId);
2621
+ backups.push({
2622
+ filePath: join10(configRoot, file),
2623
+ pluginId,
2624
+ pluginName: plugin?.displayName || pluginId,
2625
+ backupTime: timestamp
2626
+ });
2627
+ }
2628
+ }
2629
+ }
2630
+ }
2631
+ return backups.sort((a, b) => b.backupTime.localeCompare(a.backupTime));
2632
+ }
2633
+ function restoreBackup(backupPath, configRoot, tool) {
2634
+ try {
2635
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
2636
+ copyFileSync(backupPath, agentsFile);
2637
+ return true;
2638
+ } catch {
2639
+ return false;
2640
+ }
2641
+ }
2642
+ function deleteBackup(backupPath) {
2643
+ try {
2644
+ if (existsSync11(backupPath)) {
2645
+ unlinkSync3(backupPath);
2646
+ return true;
2647
+ }
2648
+ return false;
2649
+ } catch {
2650
+ return false;
2651
+ }
2652
+ }
2653
+ function formatTimestamp(date) {
2654
+ const year = date.getFullYear();
2655
+ const month = String(date.getMonth() + 1).padStart(2, "0");
2656
+ const day = String(date.getDate()).padStart(2, "0");
2657
+ const hours = String(date.getHours()).padStart(2, "0");
2658
+ const minutes = String(date.getMinutes()).padStart(2, "0");
2659
+ const seconds = String(date.getSeconds()).padStart(2, "0");
2660
+ return `${year}${month}${day}-${hours}${minutes}${seconds}`;
2661
+ }
2662
+ function readAllRecords() {
2663
+ const records = [];
2664
+ const plugins = getAllPlugins();
2665
+ for (const plugin of plugins) {
2666
+ const record = readRecord(plugin.id);
2667
+ if (record) {
2668
+ records.push(record);
2669
+ }
2670
+ }
2671
+ return records;
2672
+ }
2673
+
2674
+ // src/ui/backup-prompts.ts
2675
+ import { select as select2 } from "@inquirer/prompts";
2676
+ import chalk4 from "chalk";
2677
+ async function showOverwriteWarning(currentPluginName, newPluginName) {
2678
+ console.log();
2679
+ console.log(chalk4.yellow(` \u26A0\uFE0F ${t("backup_detected")}`));
2680
+ console.log();
2681
+ console.log(` ${t("backup_current")}: ${chalk4.cyan(currentPluginName)}`);
2682
+ console.log(` ${t("backup_installing")}: ${chalk4.cyan(newPluginName)}`);
2683
+ console.log();
2684
+ console.log(` ${t("backup_overwrite_warning")}`);
2685
+ console.log(` ${t("backup_auto_create")}`);
2686
+ console.log();
2687
+ const choice = await select2({
2688
+ message: t("backup_select_action"),
2689
+ choices: [
2690
+ { name: t("backup_overwrite"), value: "overwrite" },
2691
+ { name: t("backup_cancel"), value: "cancel" }
2692
+ ]
2693
+ });
2694
+ return choice;
2695
+ }
2696
+ async function showRestorePrompt(backups) {
2697
+ console.log();
2698
+ console.log(chalk4.cyan(` ${t("backup_restore_prompt")}`));
2699
+ console.log();
2700
+ const choices = backups.map((b) => ({
2701
+ name: `${t("backup_restore")} ${b.pluginName} (${b.backupTime})`,
2702
+ value: b.filePath
2703
+ }));
2704
+ choices.push({ name: t("backup_only_uninstall"), value: "none" });
2705
+ const choice = await select2({
2706
+ message: t("backup_select_action"),
2707
+ choices
2708
+ });
2709
+ return choice;
2710
+ }
2711
+
2712
+ // src/core/plugin-installer.ts
2713
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync4, realpathSync, copyFileSync as copyFileSync2, cpSync, writeFileSync as writeFileSync3, rmSync } from "fs";
2714
+ import { join as join11, basename } from "path";
2715
+ import { execa as execa3 } from "execa";
2716
+ function installLink(source, target) {
2717
+ try {
2718
+ const resolved = realpathSync(source);
2719
+ if (existsSync12(target) || isSymlink(target)) {
2720
+ unlinkSync4(target);
2721
+ }
2722
+ try {
2723
+ symlinkSync(resolved, target);
2724
+ return "symlink";
2725
+ } catch (e) {
2726
+ if (e.code === "EPERM") {
2727
+ cpSync(resolved, target, { recursive: true });
2728
+ return "copy";
2729
+ }
2730
+ throw e;
2731
+ }
2732
+ } catch {
2733
+ return "failed";
2734
+ }
2735
+ }
2736
+ function installConfigFile(pluginDir, configFile, tool, level, configRoot, installPath, configRootConfigLink) {
2737
+ const sourcePath = join11(pluginDir, configFile);
2738
+ if (!existsSync12(sourcePath)) return;
2739
+ const agentsFileName = getAgentsFileName(tool);
2740
+ const isProjectLevel = level === "project";
2741
+ let primaryTarget;
2742
+ if (isProjectLevel) {
2743
+ primaryTarget = join11(installPath, agentsFileName);
2744
+ } else {
2745
+ primaryTarget = join11(configRoot, agentsFileName);
2746
+ }
2747
+ try {
2748
+ const resolvedSource = realpathSync(sourcePath);
2749
+ if (existsSync12(primaryTarget) || isSymlink(primaryTarget)) {
2750
+ unlinkSync4(primaryTarget);
2751
+ }
2752
+ try {
2753
+ symlinkSync(resolvedSource, primaryTarget);
2754
+ } catch (e) {
2755
+ if (e.code === "EPERM") {
2756
+ copyFileSync2(sourcePath, primaryTarget);
2757
+ } else {
2758
+ throw e;
2759
+ }
2760
+ }
2761
+ } catch (e) {
2762
+ logger.warn(` Failed to install config file: ${e.message}`);
2763
+ }
2764
+ if (isProjectLevel && primaryTarget !== join11(configRoot, agentsFileName)) {
2765
+ if (configRootConfigLink === false) return;
2766
+ const configRootTarget = join11(configRoot, agentsFileName);
2767
+ if (existsSync12(configRootTarget) || isSymlink(configRootTarget)) {
2768
+ unlinkSync4(configRootTarget);
2769
+ }
2770
+ try {
2771
+ symlinkSync(realpathSync(sourcePath), configRootTarget);
2772
+ } catch (e) {
2773
+ if (e.code === "EPERM") {
2774
+ copyFileSync2(sourcePath, configRootTarget);
2775
+ }
2776
+ }
2777
+ }
2778
+ }
2779
+ async function installExternalRepos(repos, repoPath, configRoot, installPath, level) {
2780
+ if (!repos || repos.length === 0) return;
2781
+ for (const repo of repos) {
2782
+ const targetDir = join11(repoPath, repo.dir);
2783
+ if (!existsSync12(targetDir)) {
2784
+ const args = ["clone"];
2785
+ if (repo.depth) {
2786
+ args.push("--depth", String(repo.depth));
2787
+ }
2788
+ if (repo.recursive) {
2789
+ args.push("--recursive");
2790
+ }
2791
+ args.push(repo.url, targetDir);
2792
+ try {
2793
+ await execa3("git", args, { timeout: 12e4 });
2794
+ } catch {
2795
+ logger.warn(` git clone failed: ${repo.url}, clone manually: git clone ${repo.url} ${repo.dir}`);
2796
+ try {
2797
+ rmSync(targetDir, { recursive: true, force: true });
2798
+ } catch {
2799
+ }
2800
+ continue;
2801
+ }
2802
+ }
2803
+ const linkName = basename(repo.dir);
2804
+ const resolved = realpathSync(targetDir);
2805
+ const shouldConfigRootLink = repo.configRootLink !== false && (level === "global" || repo.configRootLink === true);
2806
+ if (shouldConfigRootLink) {
2807
+ const configRootLink = join11(configRoot, linkName);
2808
+ try {
2809
+ if (existsSync12(configRootLink) || isSymlink(configRootLink)) {
2810
+ unlinkSync4(configRootLink);
2811
+ }
2812
+ try {
2813
+ symlinkSync(resolved, configRootLink);
2814
+ } catch (e) {
2815
+ if (e.code === "EPERM") {
2816
+ cpSync(resolved, configRootLink, { recursive: true });
2817
+ }
2818
+ }
2819
+ } catch {
2820
+ logger.warn(` Failed to link ${linkName} to config directory`);
2821
+ }
2822
+ }
2823
+ if (level === "project") {
2824
+ const projectLink = join11(installPath, linkName);
2825
+ try {
2826
+ if (existsSync12(projectLink) || isSymlink(projectLink)) {
2827
+ unlinkSync4(projectLink);
2828
+ }
2829
+ try {
2830
+ symlinkSync(resolved, projectLink);
2831
+ } catch (e) {
2832
+ if (e.code === "EPERM") {
2833
+ cpSync(resolved, projectLink, { recursive: true });
2834
+ }
2835
+ }
2836
+ } catch {
2837
+ logger.warn(` Failed to link ${linkName} to project directory`);
2838
+ }
2839
+ }
2840
+ }
1593
2841
  }
1594
- function isSymlink(path) {
2842
+ function installWorkflows(pluginDir, configRoot) {
2843
+ const workflowsDir = join11(pluginDir, "workflows");
2844
+ if (!existsSync12(workflowsDir)) return;
2845
+ const targetPath = join11(configRoot, "workflows");
1595
2846
  try {
1596
- return lstatSync(path).isSymbolicLink();
2847
+ const resolved = realpathSync(workflowsDir);
2848
+ if (existsSync12(targetPath) || isSymlink(targetPath)) {
2849
+ unlinkSync4(targetPath);
2850
+ }
2851
+ try {
2852
+ symlinkSync(resolved, targetPath);
2853
+ } catch (e) {
2854
+ if (e.code === "EPERM") {
2855
+ cpSync(resolved, targetPath, { recursive: true });
2856
+ }
2857
+ }
1597
2858
  } catch {
1598
- return false;
2859
+ logger.warn(` Failed to link workflows to config directory`);
1599
2860
  }
1600
2861
  }
1601
- function getSkillRecordPath() {
1602
- return join7(getInstallsDir(), "skills.json");
2862
+ function writeCannbotManifest(configRoot, pluginId, version, level, tool, skills, agents) {
2863
+ const manifest = {
2864
+ brand: "CANNBot",
2865
+ version,
2866
+ team: pluginId,
2867
+ level,
2868
+ tool,
2869
+ installed_skills: skills,
2870
+ installed_agents: agents,
2871
+ brand_dir: configRoot,
2872
+ install_time: (/* @__PURE__ */ new Date()).toISOString()
2873
+ };
2874
+ const manifestPath = join11(configRoot, "cannbot-manifest.json");
2875
+ writeFileSync3(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
2876
+ return manifest;
1603
2877
  }
1604
- function readSkillRecord() {
1605
- const recordPath = getSkillRecordPath();
1606
- if (!existsSync7(recordPath)) {
1607
- return {};
1608
- }
2878
+ async function installViaManifest(plugin, repoPath, tool, level, installPath) {
2879
+ const cwd = installPath || process.cwd();
2880
+ const configRoot = getConfigRoot(tool, level, installPath);
2881
+ const pluginDir = join11(repoPath, plugin.dir);
1609
2882
  try {
1610
- const content = readFileSync4(recordPath, "utf-8");
1611
- return JSON.parse(content);
1612
- } catch {
1613
- return {};
1614
- }
1615
- }
1616
- function writeSkillRecord(record) {
1617
- const installsDir = getInstallsDir();
1618
- if (!existsSync7(installsDir)) {
1619
- mkdirSync4(installsDir, { recursive: true });
1620
- }
1621
- const recordPath = getSkillRecordPath();
1622
- writeFileSync4(recordPath, JSON.stringify(record, null, 2), "utf-8");
1623
- }
1624
- function addSkillsToRecord(skillIds, tool, level, installPath) {
1625
- const record = readSkillRecord();
1626
- if (!record[tool]) record[tool] = {};
1627
- if (!record[tool][level]) record[tool][level] = {};
1628
- if (!record[tool][level][installPath]) {
1629
- record[tool][level][installPath] = { skills: [], installTime: "" };
1630
- }
1631
- const entry = record[tool][level][installPath];
1632
- for (const id of skillIds) {
1633
- if (!entry.skills.includes(id)) {
1634
- entry.skills.push(id);
2883
+ const skillsDir = join11(configRoot, "skills");
2884
+ const agentsDir = join11(configRoot, "agents");
2885
+ mkdirSync5(skillsDir, { recursive: true });
2886
+ mkdirSync5(agentsDir, { recursive: true });
2887
+ const installedSkills = [];
2888
+ const installedAgents = [];
2889
+ for (const source of plugin.installSkills || []) {
2890
+ const sourceDir = join11(repoPath, source.dir);
2891
+ if (!existsSync12(sourceDir)) {
2892
+ logger.warn(` Skill source not found: ${source.dir}`);
2893
+ continue;
2894
+ }
2895
+ for (const skillEntry of source.skills) {
2896
+ const skillName = typeof skillEntry === "string" ? skillEntry : skillEntry.name;
2897
+ const skillAlias = typeof skillEntry === "string" ? skillName : skillEntry.as || skillName;
2898
+ const skillPath = join11(sourceDir, skillName);
2899
+ if (!existsSync12(skillPath)) {
2900
+ logger.warn(` Skill not found: ${skillName} in ${source.dir}`);
2901
+ continue;
2902
+ }
2903
+ const targetPath = join11(skillsDir, skillAlias);
2904
+ const result = installLink(skillPath, targetPath);
2905
+ if (result !== "failed") {
2906
+ installedSkills.push(skillAlias);
2907
+ } else {
2908
+ logger.warn(` Failed to install skill: ${skillAlias}`);
2909
+ }
2910
+ }
1635
2911
  }
1636
- }
1637
- entry.installTime = (/* @__PURE__ */ new Date()).toISOString();
1638
- writeSkillRecord(record);
1639
- }
1640
- function removeSkillsFromRecord(skillIds, tool, level, installPath) {
1641
- const record = readSkillRecord();
1642
- if (!record[tool]?.[level]?.[installPath]) return;
1643
- const entry = record[tool][level][installPath];
1644
- entry.skills = entry.skills.filter((id) => !skillIds.includes(id));
1645
- if (entry.skills.length === 0) {
1646
- delete record[tool][level][installPath];
1647
- if (Object.keys(record[tool][level]).length === 0) {
1648
- delete record[tool][level];
1649
- if (Object.keys(record[tool]).length === 0) {
1650
- delete record[tool];
2912
+ for (const agentName of plugin.installAgents || []) {
2913
+ const agentFile = agentName.endsWith(".md") ? agentName : `${agentName}.md`;
2914
+ const agentPath = join11(pluginDir, "agents", agentFile);
2915
+ if (!existsSync12(agentPath)) {
2916
+ logger.warn(` Agent not found: ${agentFile}`);
2917
+ continue;
2918
+ }
2919
+ const targetPath = join11(agentsDir, agentFile);
2920
+ const result = installLink(agentPath, targetPath);
2921
+ if (result !== "failed") {
2922
+ installedAgents.push(agentFile);
2923
+ } else {
2924
+ logger.warn(` Failed to install agent: ${agentFile}`);
1651
2925
  }
1652
2926
  }
2927
+ installConfigFile(pluginDir, plugin.configFile || "AGENTS.md", tool, level, configRoot, cwd, plugin.configRootConfigLink);
2928
+ installWorkflows(pluginDir, configRoot);
2929
+ await installExternalRepos(plugin.externalRepos, repoPath, configRoot, cwd, level);
2930
+ const cannbotManifest = writeCannbotManifest(
2931
+ configRoot,
2932
+ plugin.id,
2933
+ plugin.version || "1.0.0",
2934
+ level,
2935
+ tool,
2936
+ installedSkills,
2937
+ installedAgents
2938
+ );
2939
+ return {
2940
+ success: true,
2941
+ skillsCount: installedSkills.length,
2942
+ agentsCount: installedAgents.length,
2943
+ errors: [],
2944
+ manifest: cannbotManifest
2945
+ };
2946
+ } catch (error) {
2947
+ return {
2948
+ success: false,
2949
+ skillsCount: 0,
2950
+ agentsCount: 0,
2951
+ errors: [error instanceof Error ? error.message : t("error_unknown")],
2952
+ manifest: null
2953
+ };
1653
2954
  }
1654
- writeSkillRecord(record);
1655
- }
1656
- function getInstalledSkills(tool, level, installPath) {
1657
- const record = readSkillRecord();
1658
- return record[tool]?.[level]?.[installPath]?.skills || [];
1659
2955
  }
1660
2956
 
1661
2957
  // src/core/installer.ts
2958
+ function findShell() {
2959
+ const candidates = process.platform === "win32" ? ["bash", "sh"] : ["bash", "sh"];
2960
+ for (const cmd of candidates) {
2961
+ try {
2962
+ const detectCmd = process.platform === "win32" ? "where" : "which";
2963
+ execaSync(detectCmd, [cmd], { timeout: 3e3 });
2964
+ return cmd;
2965
+ } catch {
2966
+ }
2967
+ }
2968
+ return null;
2969
+ }
1662
2970
  async function installPlugin(opts) {
1663
2971
  const plugin = getPluginById(opts.pluginId);
1664
2972
  if (!plugin) {
@@ -1667,18 +2975,115 @@ async function installPlugin(opts) {
1667
2975
  pluginId: opts.pluginId,
1668
2976
  skillsCount: 0,
1669
2977
  agentsCount: 0,
1670
- errors: [`Plugin not found: ${opts.pluginId}`],
2978
+ errors: [t("error_plugin_not_found") + ": " + opts.pluginId],
1671
2979
  warnings: []
1672
2980
  };
1673
2981
  }
1674
- const scriptPath = join8(opts.repoPath, plugin.dir, plugin.script);
1675
- if (!existsSync8(scriptPath)) {
2982
+ const pluginDir = join12(opts.repoPath, plugin.dir);
2983
+ const cwd = opts.installPath || process.cwd();
2984
+ const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
2985
+ const agentsFile = join12(configRoot, getAgentsFileName(opts.tool));
2986
+ let backupInfo = null;
2987
+ if (existsSync13(agentsFile)) {
2988
+ const currentPlugin = detectCurrentPlugin(configRoot, opts.tool);
2989
+ if (currentPlugin && currentPlugin.pluginId !== opts.pluginId) {
2990
+ let choice = "overwrite";
2991
+ if (!opts.yes) {
2992
+ choice = await showOverwriteWarning(
2993
+ currentPlugin.pluginName,
2994
+ plugin.displayName
2995
+ );
2996
+ }
2997
+ if (choice === "cancel") {
2998
+ logger.info(t("backup_cancel"));
2999
+ return {
3000
+ success: false,
3001
+ pluginId: opts.pluginId,
3002
+ skillsCount: 0,
3003
+ agentsCount: 0,
3004
+ errors: [t("backup_cancel")],
3005
+ warnings: []
3006
+ };
3007
+ }
3008
+ backupInfo = createBackup(
3009
+ configRoot,
3010
+ opts.tool,
3011
+ currentPlugin.pluginId,
3012
+ currentPlugin.pluginName
3013
+ );
3014
+ if (!backupInfo) {
3015
+ logger.error(t("backup_failed"));
3016
+ return {
3017
+ success: false,
3018
+ pluginId: opts.pluginId,
3019
+ skillsCount: 0,
3020
+ agentsCount: 0,
3021
+ errors: [t("backup_failed")],
3022
+ warnings: []
3023
+ };
3024
+ }
3025
+ logger.success(`${t("backup_created")}: ${backupInfo.filePath}`);
3026
+ }
3027
+ }
3028
+ if (plugin.installSkills && plugin.installSkills.length > 0) {
3029
+ const result = await installViaManifest(
3030
+ plugin,
3031
+ opts.repoPath,
3032
+ opts.tool,
3033
+ opts.level,
3034
+ opts.installPath
3035
+ );
3036
+ if (result.success) {
3037
+ try {
3038
+ const record = scanInstalledFiles(
3039
+ opts.pluginId,
3040
+ plugin.displayName,
3041
+ opts.tool,
3042
+ opts.level,
3043
+ cwd,
3044
+ configRoot,
3045
+ result.manifest,
3046
+ plugin.externalRepos?.map((r) => basename2(r.dir))
3047
+ );
3048
+ if (backupInfo) {
3049
+ record.backup = {
3050
+ filePath: backupInfo.filePath,
3051
+ fromPluginId: backupInfo.pluginId,
3052
+ fromPluginName: backupInfo.pluginName,
3053
+ backupTime: backupInfo.backupTime
3054
+ };
3055
+ }
3056
+ writeRecord(record);
3057
+ } catch {
3058
+ logger.warn(t("record_write_failed"));
3059
+ }
3060
+ return {
3061
+ success: true,
3062
+ pluginId: opts.pluginId,
3063
+ skillsCount: result.skillsCount,
3064
+ agentsCount: result.agentsCount,
3065
+ errors: result.errors,
3066
+ warnings: []
3067
+ };
3068
+ } else {
3069
+ return {
3070
+ success: false,
3071
+ pluginId: opts.pluginId,
3072
+ skillsCount: 0,
3073
+ agentsCount: 0,
3074
+ errors: result.errors,
3075
+ warnings: []
3076
+ };
3077
+ }
3078
+ }
3079
+ const scriptPath = join12(pluginDir, plugin.script);
3080
+ if (!existsSync13(scriptPath)) {
1676
3081
  return {
1677
3082
  success: false,
1678
3083
  pluginId: opts.pluginId,
1679
3084
  skillsCount: 0,
1680
3085
  agentsCount: 0,
1681
- errors: [`Script not found: ${scriptPath}`],
3086
+ errors: [t("error_script_not_found").replace("{path}", scriptPath)],
1682
3087
  warnings: []
1683
3088
  };
1684
3089
  }
@@ -1686,14 +3091,25 @@ async function installPlugin(opts) {
1686
3091
  if (opts.installPath) {
1687
3092
  args.push(opts.installPath);
1688
3093
  }
1689
- const cwd = opts.installPath || process.cwd();
1690
3094
  try {
1691
- await execa3("bash", [scriptPath, ...args], {
3095
+ const shell = findShell();
3096
+ if (!shell) {
3097
+ return {
3098
+ success: false,
3099
+ pluginId: opts.pluginId,
3100
+ skillsCount: 0,
3101
+ agentsCount: 0,
3102
+ errors: [t("error_no_shell")],
3103
+ warnings: []
3104
+ };
3105
+ }
3106
+ const execOptions = {
1692
3107
  cwd,
1693
3108
  timeout: 3e5,
1694
- stdio: "pipe"
1695
- });
1696
- const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
3109
+ stdio: "pipe",
3110
+ input: opts.yes ? "y\n" : void 0
3111
+ };
3112
+ await execa4(shell, [scriptPath, ...args], execOptions);
1697
3113
  const manifest = readManifest(configRoot);
1698
3114
  let skillsCount = 0;
1699
3115
  let agentsCount = 0;
@@ -1712,10 +3128,20 @@ async function installPlugin(opts) {
1712
3128
  opts.level,
1713
3129
  cwd,
1714
3130
  configRoot,
1715
- manifest
3131
+ manifest,
3132
+ plugin.externalRepos?.map((r) => basename2(r.dir))
1716
3133
  );
3134
+ if (backupInfo) {
3135
+ record.backup = {
3136
+ filePath: backupInfo.filePath,
3137
+ fromPluginId: backupInfo.pluginId,
3138
+ fromPluginName: backupInfo.pluginName,
3139
+ backupTime: backupInfo.backupTime
3140
+ };
3141
+ }
1717
3142
  writeRecord(record);
1718
3143
  } catch {
3144
+ logger.warn(t("record_write_failed"));
1719
3145
  }
1720
3146
  return {
1721
3147
  success: true,
@@ -1726,7 +3152,7 @@ async function installPlugin(opts) {
1726
3152
  warnings: []
1727
3153
  };
1728
3154
  } catch (error) {
1729
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3155
+ const errorMessage = error instanceof Error ? error.message : t("error_unknown");
1730
3156
  return {
1731
3157
  success: false,
1732
3158
  pluginId: opts.pluginId,
@@ -1739,41 +3165,46 @@ async function installPlugin(opts) {
1739
3165
  }
1740
3166
 
1741
3167
  // src/core/skill-installer.ts
1742
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync2, readdirSync as readdirSync3, rmdirSync, realpathSync, lstatSync as lstatSync2 } from "fs";
1743
- import { join as join9 } from "path";
1744
- import { select as select2, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
1745
- import chalk3 from "chalk";
3168
+ import { existsSync as existsSync14, mkdirSync as mkdirSync6, symlinkSync as symlinkSync2, unlinkSync as unlinkSync5, readdirSync as readdirSync4, rmdirSync, realpathSync as realpathSync2 } from "fs";
3169
+ import { join as join13, dirname as dirname6 } from "path";
3170
+ import { select as select3, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
3171
+ import chalk5 from "chalk";
1746
3172
  import Table from "cli-table3";
1747
3173
  async function installSkills(skillIds, tool, level, repoPath) {
1748
3174
  const configRoot = getConfigRoot(tool, level);
1749
- const skillsDir = join9(configRoot, "skills");
3175
+ const skillsDir = join13(configRoot, "skills");
1750
3176
  const installPath = level === "project" ? process.cwd() : configRoot;
1751
- if (!existsSync9(skillsDir)) {
1752
- mkdirSync5(skillsDir, { recursive: true });
3177
+ if (!existsSync14(skillsDir)) {
3178
+ mkdirSync6(skillsDir, { recursive: true });
1753
3179
  }
1754
3180
  const results = [];
1755
3181
  const installedIds = [];
1756
3182
  for (const skillId of skillIds) {
1757
3183
  const skill = findSkill(skillId);
1758
3184
  if (!skill) {
1759
- results.push({ skillId, success: false, error: "Skill not found in registry" });
3185
+ results.push({ skillId, success: false, error: t("error_skill_not_found") });
1760
3186
  continue;
1761
3187
  }
1762
- const sourcePath = join9(repoPath, skill.source, skill.id);
1763
- const targetPath = join9(skillsDir, skillId);
1764
- if (!existsSync9(sourcePath)) {
1765
- results.push({ skillId, success: false, error: `Source not found: ${sourcePath}` });
3188
+ const sourcePath = resolveSkillSourcePath(skill, repoPath);
3189
+ const targetPath = join13(skillsDir, skillId);
3190
+ if (!existsSync14(sourcePath)) {
3191
+ results.push({ skillId, success: false, error: t("error_source_not_found").replace("{path}", sourcePath) });
1766
3192
  continue;
1767
3193
  }
1768
3194
  try {
1769
- if (existsSync9(targetPath) || isSymlink2(targetPath)) {
1770
- unlinkSync2(targetPath);
3195
+ const resolvedSource = realpathSync2(sourcePath);
3196
+ if (existsSync14(targetPath) || isSymlink(targetPath)) {
3197
+ unlinkSync5(targetPath);
1771
3198
  }
1772
- symlinkSync(realpathSync(sourcePath), targetPath);
3199
+ symlinkSync2(resolvedSource, targetPath);
1773
3200
  results.push({ skillId, success: true });
1774
3201
  installedIds.push(skillId);
1775
3202
  } catch (error) {
1776
- results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
3203
+ let msg = error instanceof Error ? error.message : t("error_unknown");
3204
+ if (process.platform === "win32" && msg.includes("EPERM")) {
3205
+ msg = t("error_symlink_windows");
3206
+ }
3207
+ results.push({ skillId, success: false, error: msg });
1777
3208
  }
1778
3209
  }
1779
3210
  if (installedIds.length > 0) {
@@ -1783,106 +3214,118 @@ async function installSkills(skillIds, tool, level, repoPath) {
1783
3214
  }
1784
3215
  async function uninstallSkills(skillIds, tool, level) {
1785
3216
  const configRoot = getConfigRoot(tool, level);
1786
- const skillsDir = join9(configRoot, "skills");
3217
+ const skillsDir = join13(configRoot, "skills");
1787
3218
  const installPath = level === "project" ? process.cwd() : configRoot;
1788
3219
  const results = [];
1789
3220
  const removedIds = [];
1790
3221
  for (const skillId of skillIds) {
1791
- const targetPath = join9(skillsDir, skillId);
3222
+ const targetPath = join13(skillsDir, skillId);
1792
3223
  try {
1793
- if (existsSync9(targetPath) || isSymlink2(targetPath)) {
1794
- unlinkSync2(targetPath);
3224
+ if (existsSync14(targetPath) || isSymlink(targetPath)) {
3225
+ unlinkSync5(targetPath);
1795
3226
  results.push({ skillId, success: true });
1796
3227
  removedIds.push(skillId);
1797
- logger.step(` \u79FB\u9664: ${skillId}`);
3228
+ logger.step(` ${t("uninstall_remove_label")}: ${skillId}`);
1798
3229
  } else {
1799
- results.push({ skillId, success: false, error: "Not installed" });
1800
- logger.warn(`${skillId} \u672A\u5B89\u88C5`);
3230
+ results.push({ skillId, success: false, error: t("error_not_installed") });
3231
+ logger.warn(`${skillId} ${t("uninstall_skill_not_installed")}`);
1801
3232
  }
1802
3233
  } catch (error) {
1803
- results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
3234
+ results.push({ skillId, success: false, error: error instanceof Error ? error.message : t("error_unknown") });
1804
3235
  }
1805
3236
  }
1806
3237
  if (removedIds.length > 0) {
1807
3238
  removeSkillsFromRecord(removedIds, tool, level, installPath);
1808
3239
  }
1809
- if (existsSync9(skillsDir)) {
3240
+ if (existsSync14(skillsDir)) {
1810
3241
  try {
1811
- const entries = readdirSync3(skillsDir);
3242
+ const entries = readdirSync4(skillsDir);
1812
3243
  if (entries.length === 0) {
1813
3244
  rmdirSync(skillsDir);
1814
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: skills/`);
1815
- }
1816
- } catch {
1817
- }
1818
- }
1819
- if (existsSync9(configRoot)) {
1820
- try {
1821
- const entries = readdirSync3(configRoot);
1822
- if (entries.length === 0) {
1823
- rmdirSync(configRoot);
1824
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${configRoot.split("/").pop()}/`);
3245
+ logger.step(` ${t("uninstall_clean_empty_dir")}: skills/`);
1825
3246
  }
1826
3247
  } catch {
1827
3248
  }
1828
3249
  }
1829
3250
  return results;
1830
3251
  }
1831
- async function interactiveSkillSelect() {
1832
- const BACK3 = "__back__";
1833
- const CANCEL3 = "__cancel__";
3252
+ var SELECT_ALL = "__select_all__";
3253
+ var SELECT_ALL_CATEGORY = "__select_all_category__";
3254
+ var CONTINUE_SELECT = "__continue_select__";
3255
+ async function interactiveSkillSelect(tool, level) {
1834
3256
  const categories = getAllCategories();
3257
+ const allSkillIds = getAllSkills().map((s) => s.id);
3258
+ let accumulated = [];
1835
3259
  let step = 0;
1836
3260
  let selectedCategoryId = "";
1837
3261
  while (true) {
1838
3262
  switch (step) {
1839
3263
  case 0: {
1840
- printBoxTitle("[3/4] \u9009\u62E9 Skill \u7C7B\u522B");
3264
+ 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");
3265
+ printBoxTitle(titleSuffix);
1841
3266
  const categoryChoices = [
3267
+ {
3268
+ name: `>> ${t("skill_select_all").replace("{count}", String(allSkillIds.length))}`,
3269
+ value: SELECT_ALL
3270
+ },
3271
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1842
3272
  ...categories.map((cat) => ({
1843
3273
  name: `> ${cat.name} (${cat.skills.length} skills)`,
1844
3274
  value: cat.id
1845
3275
  })),
1846
3276
  new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1847
- { name: "<- " + t("wizard_back"), value: BACK3 },
1848
- { name: "x " + t("wizard_cancel"), value: CANCEL3 }
3277
+ ...accumulated.length > 0 ? [{ name: `>> ${t("skill_confirm_install_count").replace("{count}", String(accumulated.length))}`, value: "install" }] : [],
3278
+ { name: "<- " + t("wizard_back"), value: BACK },
3279
+ { name: "x " + t("wizard_cancel"), value: CANCEL }
1849
3280
  ];
1850
3281
  showOperationHints();
1851
- selectedCategoryId = await select2({
3282
+ selectedCategoryId = await select3({
1852
3283
  message: t("skill_select_category"),
1853
3284
  choices: categoryChoices,
1854
3285
  loop: false,
1855
3286
  theme: selectTheme,
1856
3287
  pageSize: 15
1857
3288
  });
1858
- if (selectedCategoryId === BACK3) return "back";
1859
- if (selectedCategoryId === CANCEL3) return "cancel";
3289
+ if (selectedCategoryId === BACK) return "back";
3290
+ if (selectedCategoryId === CANCEL) return "cancel";
3291
+ if (selectedCategoryId === SELECT_ALL) {
3292
+ return allSkillIds;
3293
+ }
3294
+ if (selectedCategoryId === "install") {
3295
+ return accumulated;
3296
+ }
1860
3297
  step = 1;
1861
3298
  break;
1862
3299
  }
1863
3300
  case 1: {
1864
3301
  const category = categories.find((c) => c.id === selectedCategoryId);
1865
3302
  if (!category) return [];
1866
- printBoxTitle(`[4/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills \u2014 ${category.name}`);
1867
- const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
1868
- const levels = ["project", "global"];
1869
- const installedSkills = [];
1870
- for (const tk of tools) {
1871
- for (const l of levels) {
1872
- const path = l === "project" ? process.cwd() : getConfigRoot(tk, l);
1873
- installedSkills.push(...getInstalledSkills(tk, l, path));
1874
- }
1875
- }
3303
+ printBoxTitle(t("wizard_step_4_skill_title").replace("{category}", category.name));
3304
+ const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
3305
+ const installedSkills = getInstalledSkills(tool, level, installPath);
1876
3306
  const installedSet = new Set(installedSkills);
1877
- const skillChoices = category.skills.map((skill) => {
1878
- const isInstalled = installedSet.has(skill.id);
1879
- const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : "";
1880
- return {
1881
- name: `${skill.id}${suffix} \u2014 ${skill.description}`,
1882
- value: skill.id,
1883
- checked: false
1884
- };
1885
- });
3307
+ const categorySkillIds = new Set(category.skills.map((s) => s.id));
3308
+ const accumulatedInCategory = new Set(accumulated.filter((id) => categorySkillIds.has(id)));
3309
+ const skillChoices = [
3310
+ {
3311
+ name: `>> ${t("skill_select_all_category")}`,
3312
+ value: SELECT_ALL_CATEGORY,
3313
+ checked: accumulatedInCategory.size === category.skills.length,
3314
+ description: t("skill_select_all_category")
3315
+ },
3316
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
3317
+ ...category.skills.map((skill) => {
3318
+ const isInstalled = installedSet.has(skill.id);
3319
+ const isAccumulated = accumulatedInCategory.has(skill.id);
3320
+ const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : isAccumulated ? " [+]" : "";
3321
+ return {
3322
+ name: `${chalk5.cyan(skill.id)}${suffix}`,
3323
+ value: skill.id,
3324
+ checked: isAccumulated,
3325
+ description: skill.description
3326
+ };
3327
+ })
3328
+ ];
1886
3329
  showOperationHints(true);
1887
3330
  const selectedSkills = await checkbox2({
1888
3331
  message: t("skill_select_items"),
@@ -1892,11 +3335,21 @@ async function interactiveSkillSelect() {
1892
3335
  theme: checkboxTheme,
1893
3336
  pageSize: 15
1894
3337
  });
1895
- const skillIds = selectedSkills;
3338
+ let skillIds;
3339
+ if (selectedSkills.includes(SELECT_ALL_CATEGORY)) {
3340
+ skillIds = category.skills.map((s) => s.id);
3341
+ } else {
3342
+ skillIds = selectedSkills.filter((id) => id !== SELECT_ALL_CATEGORY);
3343
+ }
1896
3344
  if (skillIds.length === 0) {
1897
- const action = await select2({
1898
- message: t("wizard_no_selection"),
1899
- choices: [
3345
+ accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
3346
+ const action2 = await select3({
3347
+ message: accumulated.length > 0 ? t("skill_continue_or_install").replace("{count}", String(accumulated.length)) : t("wizard_no_selection"),
3348
+ choices: accumulated.length > 0 ? [
3349
+ { name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
3350
+ { name: "<- " + t("wizard_back_to_reselect"), value: "back" },
3351
+ { name: "x " + t("wizard_cancel"), value: "cancel" }
3352
+ ] : [
1900
3353
  new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1901
3354
  { name: "<- " + t("wizard_back_to_reselect"), value: "back" },
1902
3355
  { name: "x " + t("wizard_cancel"), value: "cancel" }
@@ -1904,13 +3357,38 @@ async function interactiveSkillSelect() {
1904
3357
  loop: false,
1905
3358
  theme: selectTheme
1906
3359
  });
1907
- if (action === "back") {
3360
+ if (action2 === "install") return accumulated;
3361
+ if (action2 === "back") {
1908
3362
  step = 0;
1909
3363
  break;
1910
3364
  }
1911
3365
  return "cancel";
1912
3366
  }
1913
- return skillIds;
3367
+ accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
3368
+ for (const id of skillIds) {
3369
+ if (!accumulated.includes(id)) {
3370
+ accumulated.push(id);
3371
+ }
3372
+ }
3373
+ const action = await select3({
3374
+ message: t("skill_continue_or_install").replace("{count}", String(accumulated.length)),
3375
+ choices: [
3376
+ { name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
3377
+ { name: "> " + t("skill_continue_select"), value: CONTINUE_SELECT },
3378
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
3379
+ { name: "x " + t("wizard_cancel"), value: "cancel" }
3380
+ ],
3381
+ loop: false,
3382
+ theme: selectTheme
3383
+ });
3384
+ if (action === "install") {
3385
+ return accumulated;
3386
+ }
3387
+ if (action === CONTINUE_SELECT) {
3388
+ step = 0;
3389
+ break;
3390
+ }
3391
+ return "cancel";
1914
3392
  }
1915
3393
  }
1916
3394
  }
@@ -1918,11 +3396,11 @@ async function interactiveSkillSelect() {
1918
3396
  function listAllSkills() {
1919
3397
  const categories = getAllCategories();
1920
3398
  console.log();
1921
- console.log(chalk3.bold(` ${t("skill_list_title")}`));
1922
- console.log(chalk3.dim(" " + "\u2500".repeat(60)));
3399
+ console.log(chalk5.bold(` ${t("skill_list_title")}`));
3400
+ console.log(chalk5.dim(" " + "\u2500".repeat(60)));
1923
3401
  for (const category of categories) {
1924
3402
  console.log();
1925
- console.log(chalk3.bold(` ${category.name}`) + chalk3.dim(` (${category.skills.length} skills)`));
3403
+ console.log(chalk5.bold(` ${category.name}`) + chalk5.dim(` (${category.skills.length} skills)`));
1926
3404
  const table = new Table({
1927
3405
  style: { head: [], border: [] },
1928
3406
  colWidths: [35, 45],
@@ -1930,51 +3408,54 @@ function listAllSkills() {
1930
3408
  });
1931
3409
  for (const skill of category.skills) {
1932
3410
  table.push([
1933
- chalk3.cyan(skill.id),
3411
+ chalk5.cyan(skill.id),
1934
3412
  skill.description
1935
3413
  ]);
1936
3414
  }
1937
3415
  console.log(table.toString());
1938
3416
  }
1939
3417
  console.log();
1940
- console.log(chalk3.dim(` \u5171 ${getAllSkills().length} \u4E2A Skills`));
3418
+ console.log(chalk5.dim(` ${t("skill_total_count").replace("{count}", String(getAllSkills().length))}`));
1941
3419
  console.log();
1942
3420
  }
1943
- function isSymlink2(path) {
1944
- try {
1945
- return lstatSync2(path).isSymbolicLink();
1946
- } catch {
1947
- return false;
3421
+ function resolveSkillSourcePath(skill, repoPath) {
3422
+ if (skill.filePath) {
3423
+ const absPath = skill.filePath.startsWith("/") ? skill.filePath : join13(repoPath, skill.filePath);
3424
+ if (existsSync14(absPath)) {
3425
+ return dirname6(absPath);
3426
+ }
1948
3427
  }
3428
+ return join13(repoPath, skill.source, skill.id);
1949
3429
  }
1950
3430
 
1951
3431
  // src/ui/display.ts
1952
- import chalk4 from "chalk";
3432
+ import chalk6 from "chalk";
1953
3433
  import Table2 from "cli-table3";
1954
3434
  function printPluginList(plugins, installed) {
1955
3435
  const table = new Table2({
1956
3436
  head: [
1957
- chalk4.cyan(t("list_id")),
1958
- chalk4.cyan(t("list_name")),
1959
- chalk4.cyan(t("list_status")),
1960
- chalk4.cyan(t("list_skills")),
1961
- chalk4.cyan(t("list_agents"))
3437
+ chalk6.cyan(t("list_id")),
3438
+ chalk6.cyan(t("list_name")),
3439
+ chalk6.cyan(t("list_status")),
3440
+ chalk6.cyan(t("list_description"))
1962
3441
  ],
1963
- style: { head: [], border: [] }
3442
+ style: { head: [], border: [] },
3443
+ colWidths: [6, 32, 8, 50],
3444
+ wordWrap: true
1964
3445
  });
1965
3446
  plugins.forEach((plugin, index) => {
1966
3447
  const isInstalled = installed.has(plugin.id);
1967
- const status = isInstalled ? chalk4.green(`\u2713 ${t("status_installed")}`) : chalk4.dim(`\u2014`);
3448
+ const status = isInstalled ? chalk6.green(`\u2713 ${t("status_installed")}`) : chalk6.dim(`\u2014`);
1968
3449
  table.push([
1969
3450
  String(index + 1),
1970
3451
  plugin.displayName,
1971
3452
  status,
1972
- String(plugin.skills),
1973
- String(plugin.agents)
3453
+ plugin.description || chalk6.dim("\u2014")
1974
3454
  ]);
1975
3455
  });
1976
3456
  console.log();
1977
- console.log(chalk4.bold(` ${t("list_title")} (${plugins.length} \u4E2A)`));
3457
+ const countSuffix = t("count_suffix");
3458
+ console.log(chalk6.bold(` ${t("list_title")} (${plugins.length}${countSuffix ? " " + countSuffix : ""})`));
1978
3459
  console.log(table.toString());
1979
3460
  console.log();
1980
3461
  }
@@ -1983,7 +3464,7 @@ function printInstallSummary(results) {
1983
3464
  const totalCount = results.length;
1984
3465
  console.log();
1985
3466
  console.log(
1986
- chalk4.bold(
3467
+ chalk6.bold(
1987
3468
  ` ${t("install_done")}! ${successCount}/${totalCount} ${t("install_success")}`
1988
3469
  )
1989
3470
  );
@@ -1991,10 +3472,10 @@ function printInstallSummary(results) {
1991
3472
  for (const result of results) {
1992
3473
  if (result.success) {
1993
3474
  console.log(
1994
- chalk4.green(" \u2713") + ` ${result.displayName} (${result.skillsCount} skills, ${result.agentsCount} agents)`
3475
+ chalk6.green(" \u2713") + ` ${result.displayName} (${result.skillsCount} skills, ${result.agentsCount} agents)`
1995
3476
  );
1996
3477
  } else {
1997
- console.log(chalk4.red(" \u2717") + ` ${result.displayName}`);
3478
+ console.log(chalk6.red(" \u2717") + ` ${result.displayName}`);
1998
3479
  }
1999
3480
  }
2000
3481
  console.log();
@@ -2007,22 +3488,25 @@ function printEnhancedSummary(results, tool, configRoot) {
2007
3488
  return;
2008
3489
  }
2009
3490
  const toolName = getToolDisplayName(tool);
3491
+ const countSuffix = t("count_suffix");
2010
3492
  const lines = [
2011
- chalk4.bold(` ${t("install_done")}!`),
3493
+ chalk6.bold(` ${t("install_done")}!`),
2012
3494
  "",
2013
- ` ${chalk4.dim(t("install_installed_to") + ":")} ${configRoot}`,
2014
- ` ${chalk4.dim(t("install_skills_count") + ":")} ${totalSkills} \u4E2A ${chalk4.dim("|")} ${chalk4.dim(t("install_agents_count") + ":")} ${totalAgents} \u4E2A`,
3495
+ ` ${chalk6.dim(t("install_installed_to") + ":")} ${configRoot}`,
3496
+ ` ${chalk6.dim(t("install_skills_count") + ":")} ${totalSkills}${countSuffix ? " " + countSuffix : ""} ${chalk6.dim("|")} ${chalk6.dim(t("install_agents_count") + ":")} ${totalAgents}${countSuffix ? " " + countSuffix : ""}`,
2015
3497
  "",
2016
- chalk4.bold(` ${t("install_enhanced_next_steps")}:`),
2017
- ` ${chalk4.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk4.green(toolName.toLowerCase())}`,
2018
- ` ${chalk4.cyan("2.")} ${t("install_enhanced_try")}: ${chalk4.green("\u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50")}`,
2019
- ` ${chalk4.cyan("3.")} ${t("install_enhanced_more")}: ${chalk4.green("install-helper list")}`,
2020
- ` ${chalk4.cyan("4.")} ${t("install_enhanced_check")}: ${chalk4.green("install-helper doctor")}`,
3498
+ chalk6.bold(` ${t("install_enhanced_next_steps")}:`),
3499
+ ` ${chalk6.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk6.green(toolName.toLowerCase())}`,
3500
+ ` ${chalk6.cyan("2.")} ${t("install_enhanced_try")}: ${chalk6.green(t("try_prompt"))}`,
3501
+ ` ${chalk6.cyan("3.")} ${t("install_enhanced_more")}: ${chalk6.green("install-helper list")}`,
3502
+ ` ${chalk6.cyan("4.")} ${t("install_enhanced_check")}: ${chalk6.green("install-helper doctor")}`,
2021
3503
  ""
2022
3504
  ];
2023
3505
  if (successResults.length === 1) {
3506
+ const plugin = findPlugin(successResults[0].pluginId);
3507
+ const docsPath = plugin ? `${plugin.dir}/quickstart.md` : `${successResults[0].pluginId}/quickstart.md`;
2024
3508
  lines.push(
2025
- ` ${chalk4.dim(t("install_enhanced_docs"))}: ${chalk4.dim(`plugins-official/${successResults[0].pluginId}/quickstart.md`)}`
3509
+ ` ${chalk6.dim(t("install_enhanced_docs"))}: ${chalk6.dim(docsPath)}`
2026
3510
  );
2027
3511
  }
2028
3512
  console.log();
@@ -2033,34 +3517,32 @@ function printEnhancedSummary(results, tool, configRoot) {
2033
3517
  }
2034
3518
 
2035
3519
  // src/commands/init.ts
2036
- import { existsSync as existsSync10, readFileSync as readFileSync5 } from "fs";
2037
- import { join as join10 } from "path";
2038
- var BACK2 = "__back__";
2039
- var CANCEL2 = "__cancel__";
3520
+ import { existsSync as existsSync15, readFileSync as readFileSync8 } from "fs";
3521
+ import { join as join14 } from "path";
2040
3522
  async function initCommand() {
2041
3523
  const cwd = process.cwd();
2042
- if (existsSync10(join10(cwd, "package.json")) && (() => {
3524
+ if (existsSync15(join14(cwd, "package.json")) && (() => {
2043
3525
  try {
2044
- const pkg = JSON.parse(readFileSync5(join10(cwd, "package.json"), "utf-8"));
3526
+ const pkg = JSON.parse(readFileSync8(join14(cwd, "package.json"), "utf-8"));
2045
3527
  return pkg.name === "@cannbot-ai/install-helper";
2046
3528
  } catch {
2047
3529
  return false;
2048
3530
  }
2049
3531
  })()) {
2050
- logger.warn("\u5F53\u524D\u76EE\u5F55\u770B\u8D77\u6765\u662F install-helper \u5305\u76EE\u5F55");
2051
- logger.info("\u8BF7\u5728\u4F60\u7684\u9879\u76EE\u76EE\u5F55\u4E2D\u8FD0\u884C\u6B64\u547D\u4EE4\uFF0C\u800C\u4E0D\u662F\u5728 install-helper \u5B89\u88C5\u76EE\u5F55\u4E2D");
2052
- logger.info("\u793A\u4F8B\uFF1Acd ~/my-project && install-helper");
3532
+ logger.warn(t("init_pkg_dir_warn"));
3533
+ logger.info(t("init_pkg_dir_hint"));
3534
+ logger.info(t("init_pkg_dir_example"));
2053
3535
  return;
2054
3536
  }
2055
3537
  printBanner(t("wizard_title"));
2056
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
3538
+ const spinner = createSpinner(t("loading_skills_list"));
2057
3539
  spinner.start();
2058
3540
  try {
2059
3541
  const repoManager = createRepositoryManager();
2060
3542
  await repoManager.ensureRepoAndScan();
2061
- spinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
3543
+ spinner.succeed(t("loading_skills_list_complete"));
2062
3544
  } catch {
2063
- spinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
3545
+ spinner.warn(t("loading_skills_list_failed"));
2064
3546
  }
2065
3547
  let step = 0;
2066
3548
  let mode = "plugin";
@@ -2094,8 +3576,7 @@ async function initCommand() {
2094
3576
  }
2095
3577
  }
2096
3578
  async function selectMode() {
2097
- printBoxTitle("\u9009\u62E9\u5B89\u88C5\u7C7B\u578B");
2098
- const CANCEL3 = "__cancel__";
3579
+ printBoxTitle(t("wizard_select_mode_title"));
2099
3580
  const choices = [
2100
3581
  {
2101
3582
  name: `> ${t("wizard_mode_plugin")} \u2014 ${t("wizard_mode_plugin_desc")}`,
@@ -2106,30 +3587,30 @@ async function selectMode() {
2106
3587
  value: "skill"
2107
3588
  },
2108
3589
  new Separator3("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2109
- { name: "x " + t("wizard_cancel"), value: CANCEL3 }
3590
+ { name: "x " + t("wizard_cancel"), value: CANCEL }
2110
3591
  ];
2111
3592
  showOperationHints();
2112
- const result = await select3({
3593
+ const result = await select4({
2113
3594
  message: t("wizard_select_mode"),
2114
3595
  choices,
2115
3596
  loop: false,
2116
3597
  theme: selectTheme
2117
3598
  });
2118
- if (result === CANCEL3) return null;
3599
+ if (result === CANCEL) return null;
2119
3600
  return result;
2120
3601
  }
2121
3602
  async function pluginInstallFlow() {
2122
3603
  const answers = await runWizard();
2123
3604
  if (!answers.confirmed) {
2124
3605
  if (answers.back) return "back";
2125
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
3606
+ logger.info(t("init_cancelled"));
2126
3607
  return "done";
2127
3608
  }
2128
3609
  const repoManager = createRepositoryManager();
2129
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...");
3610
+ const spinner = createSpinner(t("loading_plugin_data"));
2130
3611
  spinner.start();
2131
3612
  const repoPath = await repoManager.ensureRepo();
2132
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
3613
+ spinner.succeed(t("loading_plugin_data_complete"));
2133
3614
  const allPlugins = getAllPlugins();
2134
3615
  const selectedPlugins = answers.plugins.map(
2135
3616
  (id) => allPlugins.find((p) => p.id === id)
@@ -2183,7 +3664,7 @@ async function skillInstallFlow() {
2183
3664
  const result = await selectToolWithDetection();
2184
3665
  if (result === "back") return "back";
2185
3666
  if (result === "cancel") {
2186
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
3667
+ logger.info(t("init_cancelled"));
2187
3668
  return "done";
2188
3669
  }
2189
3670
  tool = result;
@@ -2192,12 +3673,12 @@ async function skillInstallFlow() {
2192
3673
  }
2193
3674
  case 1: {
2194
3675
  const result = await stepLevel();
2195
- if (result === BACK2) {
3676
+ if (result === BACK) {
2196
3677
  step = 0;
2197
3678
  break;
2198
3679
  }
2199
- if (result === CANCEL2) {
2200
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
3680
+ if (result === CANCEL) {
3681
+ logger.info(t("init_cancelled"));
2201
3682
  return "done";
2202
3683
  }
2203
3684
  level = result;
@@ -2205,25 +3686,25 @@ async function skillInstallFlow() {
2205
3686
  break;
2206
3687
  }
2207
3688
  case 2: {
2208
- const selectedSkills = await interactiveSkillSelect();
3689
+ const selectedSkills = await interactiveSkillSelect(tool, level);
2209
3690
  if (selectedSkills === "back") {
2210
3691
  step = 1;
2211
3692
  break;
2212
3693
  }
2213
3694
  if (selectedSkills === "cancel") {
2214
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
3695
+ logger.info(t("init_cancelled"));
2215
3696
  return "done";
2216
3697
  }
2217
3698
  if (selectedSkills.length === 0) {
2218
- logger.info("\u672A\u9009\u62E9\u4EFB\u4F55 Skill");
3699
+ logger.info(t("install_no_skill_selected"));
2219
3700
  return "done";
2220
3701
  }
2221
3702
  const repoManager = createRepositoryManager();
2222
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...");
3703
+ const spinner = createSpinner(t("loading_plugin_data"));
2223
3704
  spinner.start();
2224
3705
  const repoPath = await repoManager.ensureRepo();
2225
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
2226
- logger.info(`${t("skill_install_progress")} ${chalk5.bold(selectedSkills.length)} \u4E2A Skills...`);
3706
+ spinner.succeed(t("loading_plugin_data_complete"));
3707
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk7.bold(String(selectedSkills.length)))}...`);
2227
3708
  const results = await installSkills(selectedSkills, tool, level, repoPath);
2228
3709
  let successCount = 0;
2229
3710
  let failCount = 0;
@@ -2241,10 +3722,10 @@ async function skillInstallFlow() {
2241
3722
  }
2242
3723
  const configRoot = getConfigRoot(tool, level);
2243
3724
  logger.blank();
2244
- logger.success(`${t("skill_install_done")}: ${chalk5.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk5.red(failCount + " \u5931\u8D25") : chalk5.dim(failCount + " \u5931\u8D25")}`);
3725
+ logger.success(`${t("skill_install_done")}: ${chalk7.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk7.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk7.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2245
3726
  logger.blank();
2246
- logger.info(`\u5B89\u88C5\u5230: ${chalk5.cyan(configRoot + "/skills/")}`);
2247
- logger.info(`\u542F\u52A8 ${chalk5.green(tool)} \u5373\u53EF\u4F7F\u7528`);
3727
+ logger.info(`${t("install_to")}: ${chalk7.cyan(configRoot + "/skills/")}`);
3728
+ logger.info(`${t("start_to_use").replace("{tool}", chalk7.green(tool))}`);
2248
3729
  logger.blank();
2249
3730
  return "done";
2250
3731
  }
@@ -2266,32 +3747,31 @@ async function listCommand() {
2266
3747
  }
2267
3748
 
2268
3749
  // src/commands/doctor.ts
2269
- import chalk6 from "chalk";
2270
- import { existsSync as existsSync11, readdirSync as readdirSync4, lstatSync as lstatSync3, unlinkSync as unlinkSync3, mkdirSync as mkdirSync6 } from "fs";
2271
- import { join as join11 } from "path";
3750
+ import chalk8 from "chalk";
3751
+ import { existsSync as existsSync16, readdirSync as readdirSync5, lstatSync as lstatSync2, unlinkSync as unlinkSync6, mkdirSync as mkdirSync7 } from "fs";
3752
+ import { join as join15 } from "path";
2272
3753
  async function doctorCommand(options = {}) {
2273
3754
  console.log();
2274
- console.log(chalk6.bold(` ${t("doctor_title")}`));
2275
- console.log(chalk6.dim(" " + "\u2500".repeat(46)));
2276
- let errors = 0;
3755
+ console.log(chalk8.bold(` ${t("doctor_title")}`));
3756
+ console.log(chalk8.dim(" " + "\u2500".repeat(46)));
2277
3757
  let warnings = 0;
2278
3758
  let fixes = 0;
2279
3759
  console.log();
2280
- console.log(chalk6.bold(` ${t("doctor_tools")}`));
3760
+ console.log(chalk8.bold(` ${t("doctor_tools")}`));
2281
3761
  const detectedTools = await detectTools();
2282
3762
  const allTools = getAllTools();
2283
3763
  for (const tool of allTools) {
2284
3764
  const detected = detectedTools.find((d) => d.name === tool);
2285
3765
  if (detected) {
2286
3766
  console.log(
2287
- chalk6.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
3767
+ chalk8.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
2288
3768
  );
2289
3769
  } else {
2290
- console.log(chalk6.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 \u672A\u5B89\u88C5`);
3770
+ console.log(chalk8.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 ${t("doctor_not_installed")}`);
2291
3771
  }
2292
3772
  }
2293
3773
  console.log();
2294
- console.log(chalk6.bold(` ${t("doctor_plugins")}`));
3774
+ console.log(chalk8.bold(` ${t("doctor_plugins")}`));
2295
3775
  const plugins = getAllPlugins();
2296
3776
  const installed = scanInstalled();
2297
3777
  const installedMap = new Map(installed.map((p) => [p.id, p]));
@@ -2299,80 +3779,80 @@ async function doctorCommand(options = {}) {
2299
3779
  const inst = installedMap.get(plugin.id);
2300
3780
  if (inst) {
2301
3781
  console.log(
2302
- chalk6.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
3782
+ chalk8.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
2303
3783
  );
2304
3784
  } else {
2305
- console.log(chalk6.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 \u672A\u5B89\u88C5`);
3785
+ console.log(chalk8.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 ${t("doctor_not_installed")}`);
2306
3786
  }
2307
3787
  }
2308
3788
  console.log();
2309
- console.log(chalk6.bold(` ${t("doctor_links")}`));
3789
+ console.log(chalk8.bold(` ${t("doctor_links")}`));
2310
3790
  const primaryTool = detectedTools[0]?.name || "opencode";
2311
3791
  const configRoot = getConfigRoot(primaryTool, "project");
2312
3792
  const skillsDir = getSkillsDir(configRoot);
2313
3793
  const agentsDir = getAgentsDir(configRoot);
2314
- if (existsSync11(skillsDir)) {
3794
+ if (existsSync16(skillsDir)) {
2315
3795
  const brokenLinks = checkBrokenLinks(skillsDir);
2316
3796
  if (brokenLinks === 0) {
2317
- const count = readdirSync4(skillsDir).length;
2318
- console.log(chalk6.green(" \u2713") + ` ${skillsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
3797
+ const count = readdirSync5(skillsDir).length;
3798
+ console.log(chalk8.green(" \u2713") + ` ${skillsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2319
3799
  } else {
2320
- console.log(chalk6.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
3800
+ console.log(chalk8.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2321
3801
  warnings++;
2322
3802
  if (options.fix) {
2323
3803
  const fixed = fixBrokenLinks(skillsDir);
2324
3804
  fixes += fixed;
2325
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
3805
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2326
3806
  }
2327
3807
  }
2328
3808
  } else {
2329
- console.log(chalk6.dim(" \u2014") + ` ${skillsDir} \u2014 \u4E0D\u5B58\u5728`);
3809
+ console.log(chalk8.dim(" \u2014") + ` ${skillsDir} \u2014 ${t("doctor_not_exist")}`);
2330
3810
  if (options.fix) {
2331
- mkdirSync6(skillsDir, { recursive: true });
3811
+ mkdirSync7(skillsDir, { recursive: true });
2332
3812
  fixes++;
2333
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${skillsDir}`);
3813
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", skillsDir)}`);
2334
3814
  }
2335
3815
  }
2336
- if (existsSync11(agentsDir)) {
3816
+ if (existsSync16(agentsDir)) {
2337
3817
  const brokenLinks = checkBrokenLinks(agentsDir);
2338
3818
  if (brokenLinks === 0) {
2339
- const count = readdirSync4(agentsDir).length;
2340
- console.log(chalk6.green(" \u2713") + ` ${agentsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
3819
+ const count = readdirSync5(agentsDir).length;
3820
+ console.log(chalk8.green(" \u2713") + ` ${agentsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2341
3821
  } else {
2342
- console.log(chalk6.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
3822
+ console.log(chalk8.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2343
3823
  warnings++;
2344
3824
  if (options.fix) {
2345
3825
  const fixed = fixBrokenLinks(agentsDir);
2346
3826
  fixes += fixed;
2347
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
3827
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2348
3828
  }
2349
3829
  }
2350
3830
  } else {
2351
- console.log(chalk6.dim(" \u2014") + ` ${agentsDir} \u2014 \u4E0D\u5B58\u5728`);
3831
+ console.log(chalk8.dim(" \u2014") + ` ${agentsDir} \u2014 ${t("doctor_not_exist")}`);
2352
3832
  if (options.fix) {
2353
- mkdirSync6(agentsDir, { recursive: true });
3833
+ mkdirSync7(agentsDir, { recursive: true });
2354
3834
  fixes++;
2355
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${agentsDir}`);
3835
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", agentsDir)}`);
2356
3836
  }
2357
3837
  }
2358
3838
  console.log();
2359
- console.log(chalk6.bold(` ${t("doctor_config")}`));
3839
+ console.log(chalk8.bold(` ${t("doctor_config")}`));
2360
3840
  const configFile = getConfigFileName(primaryTool);
2361
- const configPath = join11(process.cwd(), configFile);
2362
- if (existsSync11(configPath)) {
2363
- console.log(chalk6.green(" \u2713") + ` ${configFile} \u5B58\u5728`);
3841
+ const configPath = join15(process.cwd(), configFile);
3842
+ if (existsSync16(configPath)) {
3843
+ console.log(chalk8.green(" \u2713") + ` ${configFile} ${t("doctor_config_exists")}`);
2364
3844
  } else {
2365
- console.log(chalk6.dim(" \u2014") + ` ${configFile} \u4E0D\u5B58\u5728`);
3845
+ console.log(chalk8.dim(" \u2014") + ` ${configFile} ${t("doctor_config_not_exist")}`);
2366
3846
  }
2367
3847
  console.log();
2368
- console.log(chalk6.dim(" " + "\u2500".repeat(46)));
3848
+ console.log(chalk8.dim(" " + "\u2500".repeat(46)));
2369
3849
  if (options.fix && fixes > 0) {
2370
3850
  console.log(
2371
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF, ${fixes} \u9879\u5DF2\u4FEE\u590D`
3851
+ ` ${t("doctor_result")}: ${t("doctor_result_with_fix").replace("{warnings}", String(warnings)).replace("{fixes}", String(fixes))}`
2372
3852
  );
2373
3853
  } else {
2374
3854
  console.log(
2375
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF`
3855
+ ` ${t("doctor_result")}: ${t("doctor_result_without_fix").replace("{warnings}", String(warnings))}`
2376
3856
  );
2377
3857
  }
2378
3858
  console.log();
@@ -2380,13 +3860,13 @@ async function doctorCommand(options = {}) {
2380
3860
  function checkBrokenLinks(dir) {
2381
3861
  let broken = 0;
2382
3862
  try {
2383
- const entries = readdirSync4(dir);
3863
+ const entries = readdirSync5(dir);
2384
3864
  for (const entry of entries) {
2385
- const fullPath = join11(dir, entry);
3865
+ const fullPath = join15(dir, entry);
2386
3866
  try {
2387
- const stats = lstatSync3(fullPath);
3867
+ const stats = lstatSync2(fullPath);
2388
3868
  if (stats.isSymbolicLink()) {
2389
- if (!existsSync11(fullPath)) {
3869
+ if (!existsSync16(fullPath)) {
2390
3870
  broken++;
2391
3871
  }
2392
3872
  }
@@ -2401,14 +3881,14 @@ function checkBrokenLinks(dir) {
2401
3881
  function fixBrokenLinks(dir) {
2402
3882
  let fixed = 0;
2403
3883
  try {
2404
- const entries = readdirSync4(dir);
3884
+ const entries = readdirSync5(dir);
2405
3885
  for (const entry of entries) {
2406
- const fullPath = join11(dir, entry);
3886
+ const fullPath = join15(dir, entry);
2407
3887
  try {
2408
- const stats = lstatSync3(fullPath);
3888
+ const stats = lstatSync2(fullPath);
2409
3889
  if (stats.isSymbolicLink()) {
2410
- if (!existsSync11(fullPath)) {
2411
- unlinkSync3(fullPath);
3890
+ if (!existsSync16(fullPath)) {
3891
+ unlinkSync6(fullPath);
2412
3892
  fixed++;
2413
3893
  }
2414
3894
  }
@@ -2421,36 +3901,35 @@ function fixBrokenLinks(dir) {
2421
3901
  }
2422
3902
 
2423
3903
  // src/commands/status.ts
2424
- import chalk7 from "chalk";
3904
+ import chalk9 from "chalk";
2425
3905
  async function statusCommand() {
2426
- const plugins = getAllPlugins();
2427
3906
  const installed = scanInstalled();
2428
3907
  const installedMap = new Map(installed.map((p) => [p.id, p]));
2429
3908
  console.log();
2430
- console.log(chalk7.bold(` ${t("status_title")}`));
3909
+ console.log(chalk9.bold(` ${t("status_title")}`));
2431
3910
  console.log();
2432
3911
  if (installed.length === 0) {
2433
- console.log(chalk7.dim(` ${t("status_none")}`));
2434
- console.log(chalk7.dim(` ${t("status_hint")}`));
3912
+ console.log(chalk9.dim(` ${t("status_none")}`));
3913
+ console.log(chalk9.dim(` ${t("status_hint")}`));
2435
3914
  console.log();
2436
3915
  return;
2437
3916
  }
2438
3917
  for (const inst of installed) {
2439
3918
  console.log(
2440
- chalk7.green(" \u2713") + ` ${inst.displayName}` + chalk7.dim(` (${inst.tool}, ${inst.level})`)
3919
+ chalk9.green(" \u2713") + ` ${inst.displayName}` + chalk9.dim(` (${inst.tool}, ${inst.level})`)
2441
3920
  );
2442
3921
  console.log(
2443
- chalk7.dim(` ${inst.skillsCount} skills, ${inst.agentsCount} agents`)
3922
+ chalk9.dim(` ${inst.skillsCount} skills, ${inst.agentsCount} agents`)
2444
3923
  );
2445
- console.log(chalk7.dim(` ${inst.configRoot}`));
2446
- console.log(chalk7.dim(` ${t("status_install_time")}: ${inst.installTime}`));
3924
+ console.log(chalk9.dim(` ${inst.configRoot}`));
3925
+ console.log(chalk9.dim(` ${t("status_install_time")}: ${inst.installTime}`));
2447
3926
  console.log();
2448
3927
  }
2449
3928
  }
2450
3929
 
2451
3930
  // src/commands/install.ts
2452
3931
  import { confirm } from "@inquirer/prompts";
2453
- import chalk8 from "chalk";
3932
+ import chalk10 from "chalk";
2454
3933
  async function installCommand(names, options) {
2455
3934
  if (options.list) {
2456
3935
  try {
@@ -2461,79 +3940,150 @@ async function installCommand(names, options) {
2461
3940
  listAllSkills();
2462
3941
  return;
2463
3942
  }
3943
+ if (options.all) {
3944
+ const scanSpinner2 = createSpinner(t("loading_skills_list"));
3945
+ scanSpinner2.start();
3946
+ try {
3947
+ const scanRepoManager = createRepositoryManager();
3948
+ await scanRepoManager.ensureRepoAndScan();
3949
+ scanSpinner2.succeed(t("loading_skills_list_complete"));
3950
+ } catch {
3951
+ scanSpinner2.warn(t("loading_skills_list_failed"));
3952
+ }
3953
+ const allSkills = getAllSkills();
3954
+ const skillIds = allSkills.map((s) => s.id);
3955
+ if (skillIds.length === 0) {
3956
+ logger.error(t("install_no_skill_selected"));
3957
+ return;
3958
+ }
3959
+ let tool2;
3960
+ if (options.tool) {
3961
+ tool2 = validateTool(options.tool);
3962
+ } else {
3963
+ const detected = await selectToolWithDetection();
3964
+ if (detected === "back" || detected === "cancel") {
3965
+ logger.info(t("init_cancelled"));
3966
+ return;
3967
+ }
3968
+ tool2 = detected;
3969
+ }
3970
+ const level2 = options.level ? validateLevel(options.level) : "project";
3971
+ const repoManager = createRepositoryManager();
3972
+ const spinner = createSpinner(t("loading_plugin_data"));
3973
+ spinner.start();
3974
+ const repoPath = await repoManager.ensureRepo();
3975
+ spinner.succeed(t("loading_plugin_data_complete"));
3976
+ logger.info(`${t("skill_all_install_progress").replace("{count}", chalk10.bold(String(skillIds.length)))}`);
3977
+ const results = await installSkills(skillIds, tool2, level2, repoPath);
3978
+ let successCount = 0;
3979
+ let failCount = 0;
3980
+ const total = results.length;
3981
+ for (let i = 0; i < results.length; i++) {
3982
+ const result = results[i];
3983
+ const progress = `[${i + 1}/${total}]`;
3984
+ if (result.success) {
3985
+ logger.success(`${progress} ${result.skillId}`);
3986
+ successCount++;
3987
+ } else {
3988
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
3989
+ failCount++;
3990
+ }
3991
+ }
3992
+ const configRoot = getConfigRoot(tool2, level2);
3993
+ logger.blank();
3994
+ logger.success(`${t("skill_install_done")}: ${chalk10.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk10.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk10.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
3995
+ logger.blank();
3996
+ logger.info(`${t("install_to")}: ${chalk10.cyan(configRoot + "/skills/")}`);
3997
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
3998
+ logger.blank();
3999
+ return;
4000
+ }
2464
4001
  if (names.length === 0) {
2465
- const scanSpinner2 = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
4002
+ const scanSpinner2 = createSpinner(t("loading_skills_list"));
2466
4003
  scanSpinner2.start();
2467
4004
  try {
2468
4005
  const scanRepoManager = createRepositoryManager();
2469
4006
  await scanRepoManager.ensureRepoAndScan();
2470
- scanSpinner2.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
4007
+ scanSpinner2.succeed(t("loading_skills_list_complete"));
2471
4008
  } catch {
2472
- scanSpinner2.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
4009
+ scanSpinner2.warn(t("loading_skills_list_failed"));
2473
4010
  }
2474
4011
  let tool2;
2475
4012
  if (options.tool) {
2476
- tool2 = options.tool;
4013
+ tool2 = validateTool(options.tool);
2477
4014
  } else {
2478
4015
  const detected = await selectToolWithDetection();
2479
- if (detected === "back") return;
4016
+ if (detected === "back" || detected === "cancel") {
4017
+ logger.info(t("init_cancelled"));
4018
+ return;
4019
+ }
2480
4020
  tool2 = detected;
2481
4021
  }
4022
+ const level2 = options.level ? validateLevel(options.level) : "project";
2482
4023
  let step = 1;
2483
4024
  while (true) {
2484
4025
  switch (step) {
2485
4026
  case 1: {
2486
- const selectedSkills = await interactiveSkillSelect();
4027
+ const selectedSkills = await interactiveSkillSelect(tool2, level2);
2487
4028
  if (selectedSkills === "back") {
2488
4029
  const redetected = await selectToolWithDetection();
2489
- if (redetected === "back") return;
4030
+ if (redetected === "back" || redetected === "cancel") {
4031
+ logger.info(t("init_cancelled"));
4032
+ return;
4033
+ }
2490
4034
  tool2 = redetected;
2491
4035
  break;
2492
4036
  }
4037
+ if (selectedSkills === "cancel") {
4038
+ logger.info(t("init_cancelled"));
4039
+ return;
4040
+ }
2493
4041
  if (selectedSkills.length === 0) {
2494
- logger.info("\u672A\u9009\u62E9\u4EFB\u4F55 Skill");
4042
+ logger.info(t("install_no_skill_selected"));
2495
4043
  return;
2496
4044
  }
2497
4045
  names = selectedSkills;
2498
4046
  const repoManager = createRepositoryManager();
2499
- const spinner = createSpinner(t("install_cloning"));
4047
+ const spinner = createSpinner(t("loading_plugin_data"));
2500
4048
  spinner.start();
2501
4049
  const repoPath = await repoManager.ensureRepo();
2502
- spinner.succeed(t("install_repo_ready"));
2503
- logger.info(`${t("skill_install_progress")} ${chalk8.bold(names.length)} \u4E2A Skills...`);
2504
- const level2 = options.level || "project";
4050
+ spinner.succeed(t("loading_plugin_data_complete"));
4051
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk10.bold(String(names.length)))}...`);
2505
4052
  const results = await installSkills(names, tool2, level2, repoPath);
2506
4053
  let successCount = 0;
2507
4054
  let failCount = 0;
2508
- for (const result of results) {
4055
+ const total = results.length;
4056
+ for (let i = 0; i < results.length; i++) {
4057
+ const result = results[i];
4058
+ const progress = `[${i + 1}/${total}]`;
2509
4059
  if (result.success) {
2510
- logger.success(`${result.skillId}`);
4060
+ logger.success(`${progress} ${result.skillId}`);
2511
4061
  successCount++;
2512
4062
  } else {
2513
- logger.error(`${result.skillId}: ${result.error}`);
4063
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
2514
4064
  failCount++;
2515
4065
  }
2516
4066
  }
2517
4067
  const configRoot = getConfigRoot(tool2, level2);
2518
4068
  logger.blank();
2519
- logger.success(`${t("skill_install_done")}: ${chalk8.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk8.red(failCount + " \u5931\u8D25") : chalk8.dim(failCount + " \u5931\u8D25")}`);
4069
+ logger.success(`${t("skill_install_done")}: ${chalk10.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk10.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk10.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2520
4070
  logger.blank();
2521
- logger.info(`\u5B89\u88C5\u5230: ${chalk8.cyan(configRoot + "/skills/")}`);
2522
- logger.info(`\u542F\u52A8 ${chalk8.green(tool2)} \u5373\u53EF\u4F7F\u7528`);
4071
+ logger.info(`${t("install_to")}: ${chalk10.cyan(configRoot + "/skills/")}`);
4072
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
2523
4073
  logger.blank();
2524
4074
  return;
2525
4075
  }
2526
4076
  }
2527
4077
  }
2528
4078
  }
2529
- const scanSpinner = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
4079
+ const scanSpinner = createSpinner(t("loading_skills_list"));
2530
4080
  scanSpinner.start();
2531
4081
  try {
2532
4082
  const scanRepoManager = createRepositoryManager();
2533
4083
  await scanRepoManager.ensureRepoAndScan();
2534
- scanSpinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
4084
+ scanSpinner.succeed(t("loading_skills_list_complete"));
2535
4085
  } catch {
2536
- scanSpinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
4086
+ scanSpinner.warn(t("loading_skills_list_failed"));
2537
4087
  }
2538
4088
  const plugins = [];
2539
4089
  const skills = [];
@@ -2550,22 +4100,25 @@ async function installCommand(names, options) {
2550
4100
  }
2551
4101
  logger.error(`${t("error_plugin_not_found")}: ${name}`);
2552
4102
  const allPlugins = getAllPlugins();
2553
- logger.info("\u53EF\u7528\u63D2\u4EF6:");
4103
+ logger.info(t("available_plugins") + ":");
2554
4104
  for (const p of allPlugins) {
2555
4105
  logger.step(` ${p.id} (${p.aliases.join(", ")})`);
2556
4106
  }
2557
- logger.info(`\u53EF\u7528 Skills: ${chalk8.cyan("install-helper install --list")}`);
4107
+ logger.info(`${t("available_skills")}: ${chalk10.cyan("install-helper install --list")}`);
2558
4108
  return;
2559
4109
  }
2560
4110
  let tool;
2561
4111
  if (options.tool) {
2562
- tool = options.tool;
4112
+ tool = validateTool(options.tool);
2563
4113
  } else {
2564
4114
  const detected = await selectToolWithDetection();
2565
- if (detected === "back") return;
4115
+ if (detected === "back" || detected === "cancel") {
4116
+ logger.info(t("init_cancelled"));
4117
+ return;
4118
+ }
2566
4119
  tool = detected;
2567
4120
  }
2568
- const level = options.level || "project";
4121
+ const level = options.level ? validateLevel(options.level) : "project";
2569
4122
  if (plugins.length > 0) {
2570
4123
  const configRoot = getConfigRoot(tool, level);
2571
4124
  const manifests = readAllManifests(configRoot);
@@ -2593,16 +4146,18 @@ async function installCommand(names, options) {
2593
4146
  }
2594
4147
  if (pluginsToInstall.length > 0) {
2595
4148
  const repoManager = createRepositoryManager();
2596
- const spinner = createSpinner(t("install_cloning"));
4149
+ const spinner = createSpinner(t("loading_plugin_data"));
2597
4150
  spinner.start();
2598
4151
  const repoPath = await repoManager.ensureRepo();
2599
- spinner.succeed(t("install_repo_ready"));
4152
+ spinner.succeed(t("loading_plugin_data_complete"));
2600
4153
  const allPlugins = getAllPlugins();
2601
4154
  const results = await installPlugins(
2602
4155
  pluginsToInstall,
2603
4156
  tool,
2604
4157
  level,
2605
- repoPath
4158
+ repoPath,
4159
+ void 0,
4160
+ options.yes
2606
4161
  );
2607
4162
  const summary = results.map((result) => {
2608
4163
  const plugin = allPlugins.find((p) => p.id === result.pluginId);
@@ -2620,32 +4175,35 @@ async function installCommand(names, options) {
2620
4175
  }
2621
4176
  if (skills.length > 0) {
2622
4177
  const repoManager = createRepositoryManager();
2623
- const spinner = createSpinner(t("install_cloning"));
4178
+ const spinner = createSpinner(t("loading_plugin_data"));
2624
4179
  spinner.start();
2625
4180
  const repoPath = await repoManager.ensureRepo();
2626
- spinner.succeed(t("install_repo_ready"));
2627
- logger.info(`${t("skill_install_progress")} ${chalk8.bold(skills.length)} \u4E2A Skills...`);
4181
+ spinner.succeed(t("loading_plugin_data_complete"));
4182
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk10.bold(String(skills.length)))}...`);
2628
4183
  const results = await installSkills(skills, tool, level, repoPath);
2629
4184
  let successCount = 0;
2630
4185
  let failCount = 0;
2631
- for (const result of results) {
4186
+ const total = results.length;
4187
+ for (let i = 0; i < results.length; i++) {
4188
+ const result = results[i];
4189
+ const progress = `[${i + 1}/${total}]`;
2632
4190
  if (result.success) {
2633
- logger.success(`${result.skillId}`);
4191
+ logger.success(`${progress} ${result.skillId}`);
2634
4192
  successCount++;
2635
4193
  } else {
2636
- logger.error(`${result.skillId}: ${result.error}`);
4194
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
2637
4195
  failCount++;
2638
4196
  }
2639
4197
  }
2640
4198
  logger.blank();
2641
- logger.success(`${t("skill_install_done")}: ${chalk8.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk8.red(failCount + " \u5931\u8D25") : chalk8.dim(failCount + " \u5931\u8D25")}`);
4199
+ logger.success(`${t("skill_install_done")}: ${chalk10.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk10.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk10.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2642
4200
  logger.blank();
2643
- logger.info(`\u5B89\u88C5\u5230: ${chalk8.cyan(getConfigRoot(tool, level) + "/skills/")}`);
2644
- logger.info(`\u542F\u52A8 ${chalk8.green(tool)} \u5373\u53EF\u4F7F\u7528`);
4201
+ logger.info(`${t("install_to")}: ${chalk10.cyan(getConfigRoot(tool, level) + "/skills/")}`);
4202
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool))}`);
2645
4203
  logger.blank();
2646
4204
  }
2647
4205
  }
2648
- async function installPlugins(pluginIds, tool, level, repoPath) {
4206
+ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes) {
2649
4207
  const results = [];
2650
4208
  const total = pluginIds.length;
2651
4209
  for (let i = 0; i < pluginIds.length; i++) {
@@ -2657,7 +4215,9 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
2657
4215
  pluginId,
2658
4216
  tool,
2659
4217
  level,
2660
- repoPath
4218
+ repoPath,
4219
+ installPath,
4220
+ yes
2661
4221
  });
2662
4222
  if (result.success) {
2663
4223
  spinner.succeed(
@@ -2673,11 +4233,12 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
2673
4233
  }
2674
4234
 
2675
4235
  // src/commands/uninstall.ts
2676
- import { existsSync as existsSync12, unlinkSync as unlinkSync4, rmdirSync as rmdirSync2, readdirSync as readdirSync5, lstatSync as lstatSync4 } from "fs";
2677
- import chalk9 from "chalk";
4236
+ import { existsSync as existsSync17, unlinkSync as unlinkSync7, rmdirSync as rmdirSync2, readdirSync as readdirSync6 } from "fs";
4237
+ import { resolve, basename as basename3, sep } from "path";
4238
+ import chalk11 from "chalk";
2678
4239
  async function uninstallCommand(names, options) {
2679
- const tool = options.tool || "opencode";
2680
- const level = options.level || "project";
4240
+ const tool = options.tool ? validateTool(options.tool) : "opencode";
4241
+ const level = options.level ? validateLevel(options.level) : "project";
2681
4242
  const plugins = [];
2682
4243
  const skills = [];
2683
4244
  for (const name of names) {
@@ -2691,40 +4252,50 @@ async function uninstallCommand(names, options) {
2691
4252
  skills.push(skill.id);
2692
4253
  continue;
2693
4254
  }
2694
- logger.error(`\u672A\u627E\u5230: ${name}`);
4255
+ logger.error(`${t("uninstall_not_found")}: ${name}`);
2695
4256
  }
2696
4257
  for (const pluginId of plugins) {
2697
4258
  const plugin = findPlugin(pluginId);
2698
4259
  if (!plugin) continue;
2699
4260
  const record = readRecord(plugin.id);
2700
4261
  if (!record) {
2701
- logger.warn(`${plugin.displayName} \u672A\u627E\u5230\u5B89\u88C5\u8BB0\u5F55\uFF0C\u53EF\u80FD\u672A\u5B89\u88C5\u6216\u5B89\u88C5\u8BB0\u5F55\u5DF2\u635F\u574F`);
2702
- logger.info(`\u5982\u9700\u5F3A\u5236\u6E05\u7406\uFF0C\u8BF7\u624B\u52A8\u5220\u9664 .opencode/ \u6216 .claude/ \u76EE\u5F55\u4E2D\u7684\u76F8\u5173\u6587\u4EF6`);
4262
+ logger.warn(`${plugin.displayName} ${t("uninstall_no_record")}`);
4263
+ logger.info(`${t("uninstall_no_record_hint")}`);
2703
4264
  continue;
2704
4265
  }
2705
- logger.info(`\u6B63\u5728\u5378\u8F7D ${plugin.displayName}...`);
4266
+ logger.info(`${t("uninstall_in_progress")} ${plugin.displayName}...`);
4267
+ const configRoot = getConfigRoot(record.tool, record.level);
4268
+ const allowedBases = [resolve(configRoot), resolve(record.installPath)];
4269
+ const isSafePath = (p) => {
4270
+ const resolved = resolve(p);
4271
+ return allowedBases.some((base) => resolved === base || resolved.startsWith(base + sep));
4272
+ };
2706
4273
  let removedFiles = 0;
2707
4274
  let removedDirs = 0;
2708
4275
  for (const filePath of record.files) {
4276
+ if (!isSafePath(filePath)) continue;
2709
4277
  try {
2710
- if (existsSync12(filePath) || isSymlink3(filePath)) {
2711
- unlinkSync4(filePath);
2712
- const name = filePath.split("/").pop() || filePath;
2713
- logger.step(` \u79FB\u9664: ${name}`);
4278
+ if (existsSync17(filePath) || isSymlink(filePath)) {
4279
+ unlinkSync7(filePath);
4280
+ const name = basename3(filePath);
4281
+ logger.step(` ${t("uninstall_remove_label")}: ${name}`);
2714
4282
  removedFiles++;
2715
4283
  }
2716
4284
  } catch {
2717
4285
  }
2718
4286
  }
2719
- const sortedDirs = [...record.directories].sort((a, b) => b.length - a.length);
4287
+ const sortedDirs = [...record.directories].filter(isSafePath).sort((a, b) => b.length - a.length);
2720
4288
  for (const dirPath of sortedDirs) {
2721
4289
  try {
2722
- if (existsSync12(dirPath)) {
2723
- const entries = readdirSync5(dirPath);
4290
+ if (resolve(dirPath) === resolve(configRoot)) {
4291
+ continue;
4292
+ }
4293
+ if (existsSync17(dirPath)) {
4294
+ const entries = readdirSync6(dirPath);
2724
4295
  if (entries.length === 0) {
2725
4296
  rmdirSync2(dirPath);
2726
- const name = dirPath.split("/").pop() || dirPath;
2727
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${name}/`);
4297
+ const name = basename3(dirPath);
4298
+ logger.step(` ${t("uninstall_clean_empty_dir")}: ${name}/`);
2728
4299
  removedDirs++;
2729
4300
  }
2730
4301
  }
@@ -2733,15 +4304,32 @@ async function uninstallCommand(names, options) {
2733
4304
  }
2734
4305
  deleteRecord(plugin.id);
2735
4306
  removeInstalledPlugin(plugin.id);
4307
+ const backups = findBackups(configRoot);
4308
+ const otherBackups = backups.filter((b) => b.pluginId !== pluginId);
4309
+ if (otherBackups.length > 0) {
4310
+ const choice = await showRestorePrompt(otherBackups);
4311
+ if (choice !== "none") {
4312
+ const restored = restoreBackup(choice, configRoot, record.tool);
4313
+ if (restored) {
4314
+ const backupInfo = otherBackups.find((b) => b.filePath === choice);
4315
+ logger.success(`${t("backup_restore_success")}: ${backupInfo?.pluginName || ""}`);
4316
+ deleteBackup(choice);
4317
+ }
4318
+ }
4319
+ }
4320
+ const currentBackups = backups.filter((b) => b.pluginId === pluginId);
4321
+ for (const backup of currentBackups) {
4322
+ deleteBackup(backup.filePath);
4323
+ }
2736
4324
  const totalRemoved = removedFiles + removedDirs;
2737
4325
  if (totalRemoved > 0) {
2738
- logger.success(`\u5DF2\u5378\u8F7D ${plugin.displayName}\uFF08\u79FB\u9664 ${removedFiles} \u4E2A\u6587\u4EF6\uFF0C${removedDirs} \u4E2A\u76EE\u5F55\uFF09`);
4326
+ logger.success(t("uninstall_summary_format").replace("{name}", plugin.displayName).replace("{files}", String(removedFiles)).replace("{dirs}", String(removedDirs)));
2739
4327
  } else {
2740
- logger.warn(`${plugin.displayName} \u7684\u6587\u4EF6\u5DF2\u4E0D\u5B58\u5728\uFF0C\u5DF2\u6E05\u7406\u5B89\u88C5\u8BB0\u5F55`);
4328
+ logger.warn(`${plugin.displayName}${t("uninstall_files_gone")}`);
2741
4329
  }
2742
4330
  }
2743
4331
  if (skills.length > 0) {
2744
- logger.info(`\u6B63\u5728\u5378\u8F7D ${skills.length} \u4E2A Skills...`);
4332
+ logger.info(`${t("uninstall_skills_in_progress")} ${t("skill_count_format").replace("{count}", String(skills.length))}...`);
2745
4333
  const results = await uninstallSkills(skills, tool, level);
2746
4334
  let successCount = 0;
2747
4335
  let failCount = 0;
@@ -2753,30 +4341,26 @@ async function uninstallCommand(names, options) {
2753
4341
  }
2754
4342
  }
2755
4343
  logger.blank();
2756
- logger.success(`${t("skill_uninstall_done")}: ${chalk9.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk9.red(failCount + " \u5931\u8D25") : chalk9.dim(failCount + " \u5931\u8D25")}`);
4344
+ logger.success(`${t("skill_uninstall_done")}: ${chalk11.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk11.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk11.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2757
4345
  logger.blank();
2758
4346
  }
2759
4347
  }
2760
- function isSymlink3(path) {
2761
- try {
2762
- return lstatSync4(path).isSymbolicLink();
2763
- } catch {
2764
- return false;
2765
- }
2766
- }
2767
4348
 
2768
4349
  // src/commands/update.ts
2769
- import chalk10 from "chalk";
4350
+ import chalk12 from "chalk";
2770
4351
  async function updateCommand(pluginNames, options) {
2771
4352
  let tool;
2772
4353
  if (options.tool) {
2773
- tool = options.tool;
4354
+ tool = validateTool(options.tool);
2774
4355
  } else {
2775
4356
  const detected = await selectToolWithDetection();
2776
- if (detected === "back") return;
4357
+ if (detected === "back" || detected === "cancel") {
4358
+ logger.info(t("init_cancelled"));
4359
+ return;
4360
+ }
2777
4361
  tool = detected;
2778
4362
  }
2779
- const level = options.level || "project";
4363
+ const level = options.level ? validateLevel(options.level) : "project";
2780
4364
  let pluginsToUpdate = [];
2781
4365
  if (pluginNames.length === 0) {
2782
4366
  const configRoot = getConfigRoot(tool, level);
@@ -2784,7 +4368,7 @@ async function updateCommand(pluginNames, options) {
2784
4368
  pluginsToUpdate = manifests.map((m) => m.team);
2785
4369
  if (pluginsToUpdate.length === 0) {
2786
4370
  logger.info(t("update_no_plugins"));
2787
- logger.info(`\u4F7F\u7528 ${chalk10.cyan("install-helper install <plugin>")} \u5B89\u88C5\u63D2\u4EF6`);
4371
+ logger.info(t("update_install_hint").replace("{cmd}", chalk12.cyan("install-helper install <plugin>")));
2788
4372
  return;
2789
4373
  }
2790
4374
  } else {
@@ -2800,9 +4384,7 @@ async function updateCommand(pluginNames, options) {
2800
4384
  const repoManager = createRepositoryManager();
2801
4385
  const updateSpinner = createSpinner(t("update_updating") + "...");
2802
4386
  updateSpinner.start();
2803
- await repoManager.updateRepo();
2804
- const repoPath = repoManager.getRepoPath();
2805
- await repoManager.ensureRepoAndScan();
4387
+ const repoPath = await repoManager.ensureRepoAndScan();
2806
4388
  updateSpinner.succeed(t("install_repo_ready"));
2807
4389
  const allPlugins = getAllPlugins();
2808
4390
  const results = [];
@@ -2846,42 +4428,43 @@ async function updateCommand(pluginNames, options) {
2846
4428
  }
2847
4429
 
2848
4430
  // src/commands/info.ts
2849
- import chalk11 from "chalk";
2850
- import { existsSync as existsSync13, readdirSync as readdirSync6 } from "fs";
2851
- import { join as join12 } from "path";
4431
+ import chalk13 from "chalk";
4432
+ import { existsSync as existsSync18, readdirSync as readdirSync7 } from "fs";
4433
+ import { join as join16 } from "path";
2852
4434
  async function infoCommand(pluginName) {
2853
4435
  const plugin = findPlugin(pluginName);
2854
4436
  if (!plugin) {
2855
- console.log(chalk11.red(` ${t("info_not_found")}: ${pluginName}`));
4437
+ console.log(chalk13.red(` ${t("info_not_found")}: ${pluginName}`));
2856
4438
  return;
2857
4439
  }
2858
4440
  const installed = scanInstalled();
2859
- const isInstalled = installed.some((p) => p.id === plugin.id);
2860
- const statusText = isInstalled ? chalk11.green(`\u2713 ${t("status_installed")}`) : chalk11.dim(`\u2014 ${t("status_not_installed")}`);
4441
+ const inst = installed.find((p) => p.id === plugin.id);
4442
+ const isInstalled = !!inst;
4443
+ const statusText = isInstalled ? chalk13.green(`\u2713 ${t("status_installed")}`) : chalk13.dim(`\u2014 ${t("status_not_installed")}`);
2861
4444
  console.log();
2862
- console.log(chalk11.bold(` ${plugin.displayName}`));
2863
- console.log(chalk11.dim(" " + "\u2500".repeat(40)));
4445
+ console.log(chalk13.bold(` ${plugin.displayName}`));
4446
+ console.log(chalk13.dim(" " + "\u2500".repeat(40)));
2864
4447
  console.log();
2865
- console.log(` ${chalk11.dim(t("info_description") + ":")} ${plugin.description}`);
2866
- console.log(` ${chalk11.dim("ID:")} ${plugin.id}`);
2867
- console.log(` ${chalk11.dim(t("info_status") + ":")} ${statusText}`);
2868
- console.log(` ${chalk11.dim(t("info_skills") + ":")} ${plugin.skills}`);
2869
- console.log(` ${chalk11.dim(t("info_agents") + ":")} ${plugin.agents}`);
2870
- console.log(` ${chalk11.dim("\u522B\u540D:")} ${plugin.aliases.join(", ")}`);
4448
+ console.log(` ${chalk13.dim(t("info_description") + ":")} ${plugin.description}`);
4449
+ console.log(` ${chalk13.dim("ID:")} ${plugin.id}`);
4450
+ console.log(` ${chalk13.dim(t("info_status") + ":")} ${statusText}`);
4451
+ console.log(` ${chalk13.dim(t("info_skills") + ":")} ${inst ? inst.skillsCount : plugin.skills}`);
4452
+ console.log(` ${chalk13.dim(t("info_agents") + ":")} ${inst ? inst.agentsCount : plugin.agents}`);
4453
+ console.log(` ${chalk13.dim(t("info_aliases") + ":")} ${plugin.aliases.join(", ")}`);
2871
4454
  console.log();
2872
4455
  try {
2873
4456
  const repoManager = createRepositoryManager();
2874
4457
  const repoPath = await repoManager.ensureRepo();
2875
- const quickstartPath = join12(repoPath, plugin.dir, "quickstart.md");
2876
- if (existsSync13(quickstartPath)) {
2877
- console.log(` ${chalk11.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
4458
+ const quickstartPath = join16(repoPath, plugin.dir, "quickstart.md");
4459
+ if (existsSync18(quickstartPath)) {
4460
+ console.log(` ${chalk13.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
2878
4461
  }
2879
- const agentsDir = join12(repoPath, plugin.dir, "agents");
2880
- if (existsSync13(agentsDir)) {
2881
- const agentFiles = readdirSync6(agentsDir).filter((f) => f.endsWith(".md"));
4462
+ const agentsDir = join16(repoPath, plugin.dir, "agents");
4463
+ if (existsSync18(agentsDir)) {
4464
+ const agentFiles = readdirSync7(agentsDir).filter((f) => f.endsWith(".md"));
2882
4465
  if (agentFiles.length > 0) {
2883
4466
  console.log();
2884
- console.log(` ${chalk11.bold("Agents:")}`);
4467
+ console.log(` ${chalk13.bold("Agents:")}`);
2885
4468
  for (const agent of agentFiles) {
2886
4469
  console.log(` \u2022 ${agent.replace(".md", "")}`);
2887
4470
  }
@@ -2893,49 +4476,80 @@ async function infoCommand(pluginName) {
2893
4476
  }
2894
4477
 
2895
4478
  // src/commands/lang.ts
2896
- import chalk12 from "chalk";
4479
+ import chalk14 from "chalk";
2897
4480
  async function langCommand(action, value) {
2898
4481
  const config = readConfig();
2899
4482
  if (!action || action === "show") {
2900
4483
  console.log();
2901
- console.log(chalk12.bold(` ${t("lang_title")}`));
2902
- console.log(chalk12.dim(" " + "\u2500".repeat(40)));
4484
+ console.log(chalk14.bold(` ${t("lang_title")}`));
4485
+ console.log(chalk14.dim(" " + "\u2500".repeat(40)));
2903
4486
  console.log();
2904
- console.log(` ${chalk12.dim(t("lang_current") + ":")} ${config.language}`);
4487
+ console.log(` ${chalk14.dim(t("lang_current") + ":")} ${config.language}`);
2905
4488
  console.log();
2906
- console.log(chalk12.dim(" \u652F\u6301\u7684\u8BED\u8A00: zh_CN, en_US"));
2907
- console.log(chalk12.dim(" \u7528\u6CD5: install-helper lang set <language>"));
4489
+ console.log(chalk14.dim(` ${t("lang_supported")}`));
4490
+ console.log(chalk14.dim(` ${t("lang_usage")}`));
2908
4491
  console.log();
2909
4492
  return;
2910
4493
  }
2911
4494
  if (action === "set") {
2912
4495
  if (!value) {
2913
- console.log(chalk12.red(" \u8BF7\u6307\u5B9A\u8BED\u8A00: install-helper lang set zh_CN"));
4496
+ console.log(chalk14.red(` ${t("lang_specify")}`));
2914
4497
  return;
2915
4498
  }
2916
4499
  if (value !== "zh_CN" && value !== "en_US") {
2917
- console.log(chalk12.red(` ${t("lang_invalid")}`));
4500
+ console.log(chalk14.red(` ${t("lang_invalid")}`));
2918
4501
  return;
2919
4502
  }
2920
4503
  updateConfig({ language: value });
2921
4504
  setLanguage(value);
2922
4505
  console.log();
2923
- console.log(chalk12.green(` \u2713 ${t("lang_set")} ${value}`));
4506
+ console.log(chalk14.green(` \u2713 ${t("lang_set")} ${value}`));
2924
4507
  console.log();
2925
4508
  return;
2926
4509
  }
2927
- console.log(chalk12.red(` \u672A\u77E5\u64CD\u4F5C: ${action}`));
2928
- console.log(chalk12.dim(" \u652F\u6301\u7684\u64CD\u4F5C: show, set"));
4510
+ console.log(chalk14.red(` ${t("lang_unknown_action")}: ${action}`));
4511
+ console.log(chalk14.dim(` ${t("lang_supported_actions")}`));
2929
4512
  }
2930
4513
 
2931
4514
  // src/cli.ts
4515
+ import { readFileSync as readFileSync9, existsSync as existsSync19 } from "fs";
4516
+ import { fileURLToPath as fileURLToPath5 } from "url";
4517
+ import { dirname as dirname7, join as join17 } from "path";
4518
+ function readVersion() {
4519
+ try {
4520
+ if (typeof INSTALL_HELPER_VERSION !== "undefined" && INSTALL_HELPER_VERSION) {
4521
+ return INSTALL_HELPER_VERSION;
4522
+ }
4523
+ } catch {
4524
+ }
4525
+ try {
4526
+ const __filename5 = fileURLToPath5(import.meta.url);
4527
+ const __dirname5 = dirname7(__filename5);
4528
+ const candidates = [
4529
+ join17(__dirname5, "..", "package.json"),
4530
+ join17(__dirname5, "package.json"),
4531
+ join17(process.cwd(), "package.json")
4532
+ ];
4533
+ for (const pkgPath of candidates) {
4534
+ if (existsSync19(pkgPath)) {
4535
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
4536
+ if (pkg.version && (pkg.name === "@cannbot-ai/install-helper" || pkg.name?.startsWith("@cannbot-ai/install-helper-"))) {
4537
+ return pkg.version;
4538
+ }
4539
+ }
4540
+ }
4541
+ return "0.0.0";
4542
+ } catch {
4543
+ return "0.0.0";
4544
+ }
4545
+ }
2932
4546
  function createCLI() {
2933
4547
  const config = readConfig();
2934
4548
  if (config.language) {
2935
4549
  setLanguage(config.language);
2936
4550
  }
2937
4551
  const program2 = new Command();
2938
- program2.name("install-helper").description("Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.6");
4552
+ program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version(readVersion());
2939
4553
  program2.command("init", { isDefault: false }).description("Run interactive installation wizard").action(async () => {
2940
4554
  await initCommand();
2941
4555
  });
@@ -2948,7 +4562,7 @@ function createCLI() {
2948
4562
  program2.command("status").description("Show installed plugins").action(async () => {
2949
4563
  await statusCommand();
2950
4564
  });
2951
- 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) => {
4565
+ 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) => {
2952
4566
  await installCommand(names, options);
2953
4567
  });
2954
4568
  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) => {
@@ -2970,13 +4584,28 @@ function createCLI() {
2970
4584
  }
2971
4585
 
2972
4586
  // src/index.ts
2973
- process.on("uncaughtException", (error) => {
2974
- if (error.name === "ExitPromptError") {
2975
- console.log("\n\u5DF2\u53D6\u6D88\u5B89\u88C5");
4587
+ function isExitError(error) {
4588
+ if (!error) return false;
4589
+ return error.name === "ExitPromptError" || error.message?.includes("force closed") || error.message?.includes("SIGINT") || error.constructor?.name === "ExitPromptError";
4590
+ }
4591
+ function handleExit(error) {
4592
+ if (isExitError(error)) {
4593
+ console.log("\n" + t("init_cancelled"));
2976
4594
  process.exit(0);
2977
4595
  }
4596
+ }
4597
+ process.on("SIGINT", () => {
4598
+ console.log("\n" + t("init_cancelled"));
4599
+ process.exit(130);
4600
+ });
4601
+ process.on("uncaughtException", (error) => {
4602
+ handleExit(error);
2978
4603
  throw error;
2979
4604
  });
4605
+ process.on("unhandledRejection", (error) => {
4606
+ handleExit(error);
4607
+ process.exit(1);
4608
+ });
2980
4609
  var program = createCLI();
2981
4610
  program.parse();
2982
4611
  //# sourceMappingURL=index.js.map