@cannbot-ai/install-helper 0.0.2-beta.5 → 0.0.2-beta.50

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