@cannbot-ai/install-helper 0.0.2-beta.4 → 0.0.2-beta.41

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,802 @@
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"
25
587
  },
26
588
  {
27
589
  id: "ops-direct-invoke-flash",
28
590
  dir: "plugins-official/ops-direct-invoke-flash",
29
- displayName: "AscendC Kernel \u4ECE\u96F6\u6784\u5EFA",
591
+ displayName: "AscendC Kernel \u76F4\u8C03\uFF08\u5FEB\u901F\u7248\uFF09",
30
592
  script: "init.sh",
31
- aliases: ["flash", "kernel-flash"],
593
+ aliases: [
594
+ "flash",
595
+ "kernel-flash"
596
+ ],
32
597
  skills: 1,
33
598
  agents: 1,
34
- description: "Ascend C / Ascend950 Reg API \u6838\u51FD\u6570\u4ECE\u96F6\u6784\u5EFA"
599
+ description: "\u4ECE\u6570\u5B66\u516C\u5F0F\u6216\u4EE3\u7801\u7247\u6BB5\u5FEB\u901F\u6784\u5EFA\u5E76\u9A8C\u8BC1 Ascend NPU \u6838\u51FD\u6570"
35
600
  },
36
601
  {
37
602
  id: "ops-registry-invoke",
38
603
  dir: "plugins-official/ops-registry-invoke",
39
604
  displayName: "AscendC \u7B97\u5B50\u6CE8\u518C\u8C03\u7528",
40
605
  script: "init.sh",
41
- aliases: ["ops-registry", "ascendc-registry", "registry"],
606
+ aliases: [
607
+ "ops-registry",
608
+ "ascendc-registry",
609
+ "registry"
610
+ ],
42
611
  skills: 12,
43
612
  agents: 4,
44
- description: "Ascend C \u7B97\u5B50\u6CE8\u518C\u8C03\u7528\u5F00\u53D1\u6D41\u7A0B"
613
+ description: "\u751F\u4EA7\u7EA7 ACLNN \u6CE8\u518C\u7B97\u5B50\u5168\u6D41\u7A0B\uFF1A\u4EE3\u7801\u2192UT/ST\u2192\u4E0A\u5E93"
45
614
  },
46
615
  {
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"
65
- },
66
- {
67
- id: "tilelang-op-orchestrator",
68
- dir: "plugins-official/tilelang-op-orchestrator",
69
- displayName: "TileLang \u7B97\u5B50",
616
+ id: "catlass-op-generator",
617
+ dir: "plugins-official/catlass-op-generator",
618
+ displayName: "Catlass \u7B97\u5B50\u76F4\u8C03",
70
619
  script: "init.sh",
71
- aliases: ["tilelang"],
72
- skills: 9,
620
+ aliases: [
621
+ "catlass"
622
+ ],
623
+ skills: 10,
73
624
  agents: 3,
74
- description: "TileLang \u7B97\u5B50\u5F00\u53D1\u6D41\u7A0B"
625
+ description: "Catlass \u6A21\u677F\u62FC\u88C5 GEMM/Matmul/Epilogue \u7B97\u5B50\u5F00\u53D1"
75
626
  },
76
627
  {
77
628
  id: "model-infer-optimize",
78
629
  dir: "plugins-official/model-infer-optimize",
79
630
  displayName: "NPU \u63A8\u7406\u4F18\u5316",
80
631
  script: "init.sh",
81
- aliases: ["model-infer", "infer", "inference"],
632
+ aliases: [
633
+ "model-infer",
634
+ "infer",
635
+ "inference"
636
+ ],
82
637
  skills: 11,
83
638
  agents: 3,
84
- description: "NPU \u6A21\u578B\u63A8\u7406\u7AEF\u5230\u7AEF\u4F18\u5316"
639
+ 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"
85
640
  },
86
641
  {
87
- id: "catlass-op-generator",
88
- dir: "plugins-official/catlass-op-generator",
89
- displayName: "Catlass \u7B97\u5B50\u76F4\u8C03",
642
+ id: "pypto-op-orchestrator",
643
+ dir: "plugins-official/pypto-op-orchestrator",
644
+ displayName: "PyPTO \u7B97\u5B50\u5F00\u53D1",
90
645
  script: "init.sh",
91
- aliases: ["catlass"],
92
- skills: 10,
646
+ aliases: [
647
+ "pypto",
648
+ "pytorch"
649
+ ],
650
+ skills: 8,
93
651
  agents: 3,
94
- description: "Catlass \u7B97\u5B50\u76F4\u8C03\u5F00\u53D1"
652
+ 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"
95
653
  },
96
654
  {
97
- id: "ops-code-reviewer",
98
- dir: "plugins-official/ops-code-reviewer",
99
- displayName: "\u4EE3\u7801\u68C0\u89C6",
655
+ id: "tilelang-op-orchestrator",
656
+ dir: "plugins-official/tilelang-op-orchestrator",
657
+ displayName: "TileLang \u7B97\u5B50\u5F00\u53D1",
100
658
  script: "init.sh",
101
- aliases: ["code-review", "reviewer", "review"],
102
- skills: 5,
659
+ aliases: [
660
+ "tilelang"
661
+ ],
662
+ skills: 9,
663
+ agents: 3,
664
+ description: "TileLang Python DSL + @tilelang.jit \u7B97\u5B50\u5F00\u53D1\uFF08\u652F\u6301 Developer/Expert \u53CC\u6A21\u5F0F\uFF09"
665
+ },
666
+ {
667
+ id: "triton-op-generator",
668
+ dir: "plugins-official/triton-op-generator",
669
+ displayName: "Triton \u7B97\u5B50\u5F00\u53D1",
670
+ script: "init.sh",
671
+ aliases: [
672
+ "triton"
673
+ ],
674
+ skills: 6,
103
675
  agents: 1,
104
- description: "\u4EE3\u7801\u68C0\u89C6\u4E0E\u89C4\u8303\u68C0\u67E5"
676
+ 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"
105
677
  },
106
678
  {
107
679
  id: "torch-compile",
108
680
  dir: "plugins-official/torch-compile",
109
681
  displayName: "torch.compile \u56FE\u6A21\u5F0F",
110
682
  script: "init.sh",
111
- aliases: ["torch", "compile", "graph"],
683
+ aliases: [
684
+ "torch",
685
+ "compile",
686
+ "graph"
687
+ ],
112
688
  skills: 6,
113
689
  agents: 1,
114
- description: "PyTorch torch.compile \u56FE\u6A21\u5F0F\u9002\u914D"
690
+ 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"
691
+ },
692
+ {
693
+ id: "ops-code-reviewer",
694
+ dir: "plugins-official/ops-code-reviewer",
695
+ displayName: "\u4EE3\u7801\u68C0\u89C6",
696
+ script: "init.sh",
697
+ aliases: [
698
+ "code-review",
699
+ "reviewer",
700
+ "review"
701
+ ],
702
+ skills: 5,
703
+ agents: 1,
704
+ 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"
705
+ }
706
+ ];
707
+
708
+ // src/core/registry.ts
709
+ var __filename2 = fileURLToPath2(import.meta.url);
710
+ var __dirname2 = dirname2(__filename2);
711
+ function findPluginsDir() {
712
+ const candidates = [
713
+ join2(__dirname2, "plugins.d"),
714
+ join2(__dirname2, "..", "plugins.d"),
715
+ join2(__dirname2, "..", "..", "plugins.d")
716
+ ];
717
+ for (const dir of candidates) {
718
+ if (existsSync2(dir)) return dir;
719
+ }
720
+ return candidates[0];
721
+ }
722
+ function loadPluginsFromYaml() {
723
+ const pluginsDir = findPluginsDir();
724
+ const defaultsPath = join2(pluginsDir, "_defaults.yml");
725
+ let defaults = {};
726
+ try {
727
+ defaults = parseYaml(readFileSync2(defaultsPath, "utf-8")) || {};
728
+ } catch {
729
+ }
730
+ const plugins = [];
731
+ try {
732
+ for (const file of readdirSync(pluginsDir)) {
733
+ if (file.startsWith("_") || !file.endsWith(".yml")) continue;
734
+ try {
735
+ const content = parseYaml(readFileSync2(join2(pluginsDir, file), "utf-8"));
736
+ if (!content || !content.id) continue;
737
+ plugins.push({
738
+ id: content.id,
739
+ dir: content.dir,
740
+ displayName: content.displayName || content.id,
741
+ script: content.script || defaults.script || "init.sh",
742
+ aliases: content.aliases || [],
743
+ skills: content.skills ?? defaults.skills ?? 0,
744
+ agents: content.agents ?? defaults.agents ?? 0,
745
+ description: content.description || ""
746
+ });
747
+ } catch {
748
+ logger.warn(t("plugin_yaml_error").replace("{file}", file));
749
+ }
750
+ }
751
+ } catch {
752
+ }
753
+ if (plugins.length === 0) {
754
+ return [...embedded_plugins_default];
755
+ }
756
+ plugins.sort(
757
+ (a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
758
+ );
759
+ return plugins;
760
+ }
761
+ var PLUGIN_REGISTRY = loadPluginsFromYaml();
762
+ var PLUGIN_DIRS = ["plugins-official", "plugins-community"];
763
+ function mergeDynamicPlugins(repoPath) {
764
+ if (!existsSync2(repoPath)) return;
765
+ const existingIds = new Set(PLUGIN_REGISTRY.map((p) => p.id));
766
+ const dynamic = [];
767
+ for (const pluginDir of PLUGIN_DIRS) {
768
+ const parent = join2(repoPath, pluginDir);
769
+ if (!existsSync2(parent)) continue;
770
+ let entries = [];
771
+ try {
772
+ entries = readdirSync(parent);
773
+ } catch {
774
+ continue;
775
+ }
776
+ for (const entry of entries) {
777
+ const pluginPath = join2(parent, entry);
778
+ if (!isDirectory(pluginPath)) continue;
779
+ if (entry === "install-helper") continue;
780
+ const initSh = join2(pluginPath, "init.sh");
781
+ if (!existsSync2(initSh)) continue;
782
+ const pluginId = entry;
783
+ if (existingIds.has(pluginId)) continue;
784
+ dynamic.push({
785
+ id: pluginId,
786
+ dir: `${pluginDir}/${entry}`,
787
+ displayName: entry,
788
+ script: "init.sh",
789
+ aliases: [],
790
+ skills: 0,
791
+ agents: 0,
792
+ description: ""
793
+ });
794
+ existingIds.add(pluginId);
795
+ }
115
796
  }
116
- ];
797
+ if (dynamic.length > 0) {
798
+ PLUGIN_REGISTRY = [...PLUGIN_REGISTRY, ...dynamic].sort(
799
+ (a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
800
+ );
801
+ }
802
+ }
117
803
  function findPlugin(query) {
118
804
  const normalized = query.toLowerCase().trim();
119
805
  const exactMatch = PLUGIN_REGISTRY.find(
@@ -137,19 +823,37 @@ function getPluginById(id) {
137
823
 
138
824
  // src/core/detector.ts
139
825
  import { execa } from "execa";
140
- import { existsSync as existsSync2 } from "fs";
826
+ import { existsSync as existsSync4 } from "fs";
141
827
  import { homedir as homedir2 } from "os";
142
- import { join as join2 } from "path";
828
+ import { join as join4 } from "path";
143
829
 
144
830
  // src/utils/paths.ts
145
831
  import { homedir } from "os";
146
- import { join } from "path";
147
- import { existsSync } from "fs";
832
+ import { join as join3 } from "path";
833
+ import { existsSync as existsSync3 } from "fs";
834
+ var VALID_TOOLS = ["opencode", "claude", "trae", "cursor", "copilot"];
835
+ var VALID_LEVELS = ["project", "global"];
836
+ function validateTool(tool) {
837
+ if (!VALID_TOOLS.includes(tool)) {
838
+ throw new Error(
839
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
840
+ );
841
+ }
842
+ return tool;
843
+ }
844
+ function validateLevel(level) {
845
+ if (!VALID_LEVELS.includes(level)) {
846
+ throw new Error(
847
+ t("error_invalid_level").replace("{level}", level).replace("{levels}", VALID_LEVELS.join(", "))
848
+ );
849
+ }
850
+ return level;
851
+ }
148
852
  function detectTraeVariant() {
149
853
  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";
854
+ if (existsSync3(join3(home, ".trae-cn"))) return "ide";
855
+ if (existsSync3(join3(home, ".marscode"))) return "plugin";
856
+ if (existsSync3(join3(home, ".traecli"))) return "cli";
153
857
  return "unknown";
154
858
  }
155
859
  function getConfigRoot(tool, level, base) {
@@ -157,66 +861,77 @@ function getConfigRoot(tool, level, base) {
157
861
  if (level === "global") {
158
862
  switch (tool) {
159
863
  case "opencode":
160
- return join(home, ".config", "opencode");
864
+ return join3(home, ".config", "opencode");
161
865
  case "claude":
162
- return join(home, ".claude");
866
+ return join3(home, ".claude");
163
867
  case "trae": {
164
868
  const variant = detectTraeVariant();
165
869
  switch (variant) {
166
870
  case "plugin":
167
- return join(home, ".marscode");
871
+ return join3(home, ".marscode");
168
872
  case "cli":
169
- return join(home, ".traecli");
873
+ return join3(home, ".traecli");
170
874
  default:
171
- return join(home, ".trae-cn");
875
+ return join3(home, ".trae-cn");
172
876
  }
173
877
  }
174
878
  case "cursor":
175
- return join(home, ".cursor");
879
+ return join3(home, ".cursor");
176
880
  case "copilot":
177
- return join(home, ".copilot");
881
+ return join3(home, ".copilot");
882
+ default:
883
+ throw new Error(
884
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
885
+ );
178
886
  }
179
887
  }
180
888
  const baseDir = base || process.cwd();
181
889
  switch (tool) {
182
890
  case "opencode":
183
- return join(baseDir, ".opencode");
891
+ return join3(baseDir, ".opencode");
184
892
  case "claude":
185
- return join(baseDir, ".claude");
893
+ return join3(baseDir, ".claude");
186
894
  case "trae": {
187
895
  const variant = detectTraeVariant();
188
896
  switch (variant) {
189
897
  case "plugin":
190
- return join(baseDir, ".marscode");
898
+ return join3(baseDir, ".marscode");
191
899
  case "cli":
192
- return join(baseDir, ".traecli");
900
+ return join3(baseDir, ".traecli");
193
901
  default:
194
- return join(baseDir, ".trae");
902
+ return join3(baseDir, ".trae");
195
903
  }
196
904
  }
197
905
  case "cursor":
198
- return join(baseDir, ".cursor");
906
+ return join3(baseDir, ".cursor");
199
907
  case "copilot":
200
- return join(baseDir, ".github");
908
+ return join3(baseDir, ".github");
909
+ default:
910
+ throw new Error(
911
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
912
+ );
201
913
  }
202
914
  }
203
915
  function getConfigFileName(tool) {
204
916
  return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
205
917
  }
918
+ function getAgentsFileName(tool) {
919
+ return getConfigFileName(tool);
920
+ }
206
921
  function getSkillsDir(configRoot) {
207
- return join(configRoot, "skills");
922
+ return join3(configRoot, "skills");
208
923
  }
209
924
  function getAgentsDir(configRoot) {
210
- return join(configRoot, "agents");
925
+ return join3(configRoot, "agents");
211
926
  }
212
927
  function getManifestPath(configRoot) {
213
- return join(configRoot, "cannbot-manifest.json");
928
+ return join3(configRoot, "cannbot-manifest.json");
214
929
  }
215
930
  function getCannbotConfigDir() {
216
- return join(homedir(), ".cannbot");
931
+ return join3(homedir(), ".cannbot");
217
932
  }
218
933
  function getCannbotRepoPath() {
219
- return join(getCannbotConfigDir(), "repo");
934
+ return join3(getCannbotConfigDir(), "repo");
220
935
  }
221
936
 
222
937
  // src/core/detector.ts
@@ -232,8 +947,9 @@ async function getCommandVersion(cmd, args = ["--version"]) {
232
947
  }
233
948
  async function getCommandPath(cmd) {
234
949
  try {
235
- const result = await execa("sh", ["-c", `command -v ${cmd}`], { timeout: 5e3 });
236
- return result.stdout.trim();
950
+ const whichCmd = process.platform === "win32" ? "where" : "which";
951
+ const result = await execa(whichCmd, [cmd], { timeout: 5e3 });
952
+ return result.stdout.trim().split("\n")[0];
237
953
  } catch {
238
954
  return void 0;
239
955
  }
@@ -254,12 +970,12 @@ async function detectTrae() {
254
970
  const home = homedir2();
255
971
  const variant = detectTraeVariant();
256
972
  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");
973
+ if (variant === "ide" && existsSync4(join4(home, ".trae-cn"))) {
974
+ path = join4(home, ".trae-cn");
975
+ } else if (variant === "plugin" && existsSync4(join4(home, ".marscode"))) {
976
+ path = join4(home, ".marscode");
977
+ } else if (variant === "cli" && existsSync4(join4(home, ".traecli"))) {
978
+ path = join4(home, ".traecli");
263
979
  } else if (variant === "unknown") {
264
980
  const cmdPath = await getCommandPath("trae");
265
981
  if (cmdPath) path = cmdPath;
@@ -275,7 +991,7 @@ async function detectCursor() {
275
991
  }
276
992
  if (process.platform === "darwin") {
277
993
  const appPath = "/Applications/Cursor.app";
278
- if (existsSync2(appPath)) {
994
+ if (existsSync4(appPath)) {
279
995
  return { name: "cursor", path: appPath };
280
996
  }
281
997
  }
@@ -319,346 +1035,148 @@ function getToolDisplayName(tool) {
319
1035
  return names[tool];
320
1036
  }
321
1037
  function getAllTools() {
322
- return ["opencode", "claude", "trae", "cursor", "copilot"];
1038
+ return VALID_TOOLS;
323
1039
  }
324
1040
 
325
1041
  // src/core/manifest.ts
326
- import { existsSync as existsSync3, readFileSync } from "fs";
327
- import { join as join3 } from "path";
1042
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
1043
+ import { join as join5 } from "path";
328
1044
  function readManifest(configRoot) {
329
1045
  const manifestPath = getManifestPath(configRoot);
330
- if (existsSync3(manifestPath)) {
331
- try {
332
- const content = readFileSync(manifestPath, "utf-8");
333
- return JSON.parse(content);
334
- } catch {
335
- }
336
- }
337
- return null;
338
- }
339
- function readAllManifests(configRoot) {
340
- const manifests = [];
341
- const plugins = getAllPlugins();
342
- const standardPath = getManifestPath(configRoot);
343
- if (existsSync3(standardPath)) {
344
- try {
345
- const content = readFileSync(standardPath, "utf-8");
346
- manifests.push(JSON.parse(content));
347
- } catch {
348
- }
349
- }
350
- for (const plugin of plugins) {
351
- const pluginManifestPath = join3(configRoot, `${plugin.id}-manifest.json`);
352
- if (existsSync3(pluginManifestPath)) {
353
- try {
354
- const content = readFileSync(pluginManifestPath, "utf-8");
355
- manifests.push(JSON.parse(content));
356
- } catch {
357
- }
358
- }
359
- }
360
- return manifests;
361
- }
362
- function scanInstalled() {
363
- const plugins = getAllPlugins();
364
- const installed = [];
365
- const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
366
- const levels = ["project", "global"];
367
- for (const tool of tools) {
368
- for (const level of levels) {
369
- const configRoot = getConfigRoot(tool, level);
370
- const manifests = readAllManifests(configRoot);
371
- for (const manifest of manifests) {
372
- const plugin = plugins.find((p) => p.id === manifest.team);
373
- if (!plugin) continue;
374
- const alreadyAdded = installed.some(
375
- (p) => p.id === plugin.id && p.tool === tool && p.level === level
376
- );
377
- if (alreadyAdded) continue;
378
- installed.push({
379
- id: plugin.id,
380
- displayName: plugin.displayName,
381
- tool,
382
- level,
383
- skillsCount: manifest.installed_skills?.length || 0,
384
- agentsCount: manifest.installed_agents?.length || 0,
385
- 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();
1046
+ if (existsSync5(manifestPath)) {
1047
+ try {
1048
+ const content = readFileSync3(manifestPath, "utf-8");
1049
+ return JSON.parse(content);
1050
+ } catch {
1051
+ }
604
1052
  }
1053
+ return null;
605
1054
  }
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;
1055
+ function readAllManifests(configRoot) {
1056
+ const manifests = [];
1057
+ const plugins = getAllPlugins();
1058
+ const standardPath = getManifestPath(configRoot);
1059
+ if (existsSync5(standardPath)) {
1060
+ try {
1061
+ const content = readFileSync3(standardPath, "utf-8");
1062
+ manifests.push(JSON.parse(content));
1063
+ } catch {
614
1064
  }
615
1065
  }
616
- return width;
1066
+ for (const plugin of plugins) {
1067
+ const pluginManifestPath = join5(configRoot, `${plugin.id}-manifest.json`);
1068
+ if (existsSync5(pluginManifestPath)) {
1069
+ try {
1070
+ const content = readFileSync3(pluginManifestPath, "utf-8");
1071
+ manifests.push(JSON.parse(content));
1072
+ } catch {
1073
+ }
1074
+ }
1075
+ }
1076
+ return manifests;
617
1077
  }
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();
1078
+ function scanInstalled() {
1079
+ const plugins = getAllPlugins();
1080
+ const installed = [];
1081
+ const tools = VALID_TOOLS;
1082
+ const levels = ["project", "global"];
1083
+ for (const tool of tools) {
1084
+ for (const level of levels) {
1085
+ const configRoot = getConfigRoot(tool, level);
1086
+ const manifests = readAllManifests(configRoot);
1087
+ for (const manifest of manifests) {
1088
+ const plugin = plugins.find((p) => p.id === manifest.team);
1089
+ if (!plugin) continue;
1090
+ if (!verifyPluginFilesExist(configRoot, manifest, tool, level)) {
1091
+ continue;
1092
+ }
1093
+ const alreadyAdded = installed.some(
1094
+ (p) => p.id === plugin.id && p.tool === tool && p.level === level
1095
+ );
1096
+ if (alreadyAdded) continue;
1097
+ installed.push({
1098
+ id: plugin.id,
1099
+ displayName: plugin.displayName,
1100
+ tool,
1101
+ level,
1102
+ skillsCount: manifest.installed_skills?.length || 0,
1103
+ agentsCount: manifest.installed_agents?.length || 0,
1104
+ installTime: manifest.install_time,
1105
+ configRoot
1106
+ });
1107
+ }
1108
+ }
1109
+ }
1110
+ return installed;
630
1111
  }
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 \u9009\u62E9 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4
634
- `);
1112
+ function verifyPluginFilesExist(configRoot, manifest, tool, level) {
1113
+ const skillsDir = join5(configRoot, "skills");
1114
+ const agentsDir = join5(configRoot, "agents");
1115
+ if (manifest.installed_skills && manifest.installed_skills.length > 0) {
1116
+ const hasAnySkill = manifest.installed_skills.some((skillId) => {
1117
+ const skillPath = join5(skillsDir, skillId);
1118
+ return existsSync5(skillPath);
1119
+ });
1120
+ if (!hasAnySkill) return false;
1121
+ }
1122
+ if (manifest.installed_agents && manifest.installed_agents.length > 0) {
1123
+ const hasAnyAgent = manifest.installed_agents.some((agentId) => {
1124
+ const agentPath = join5(agentsDir, agentId);
1125
+ return existsSync5(agentPath);
1126
+ });
1127
+ if (!hasAnyAgent) return false;
1128
+ }
1129
+ let agentsMdPath;
1130
+ if (tool === "opencode" && level === "project") {
1131
+ agentsMdPath = join5(configRoot, "..", "AGENTS.md");
635
1132
  } else {
636
- console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m \u79FB\u52A8 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4
637
- `);
1133
+ agentsMdPath = join5(configRoot, "AGENTS.md");
638
1134
  }
1135
+ if (!existsSync5(agentsMdPath)) return false;
1136
+ return true;
639
1137
  }
640
1138
 
641
1139
  // 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";
1140
+ import { existsSync as existsSync7, mkdirSync, readFileSync as readFileSync4 } from "fs";
1141
+ import { join as join6 } from "path";
645
1142
  import { parse, stringify } from "yaml";
1143
+
1144
+ // src/utils/fs.ts
1145
+ import { writeFileSync, renameSync, existsSync as existsSync6, unlinkSync } from "fs";
1146
+ import { dirname as dirname3 } from "path";
1147
+ function atomicWriteFileSync(filePath, data, encoding = "utf-8") {
1148
+ const dir = dirname3(filePath);
1149
+ if (!existsSync6(dir)) {
1150
+ throw new Error(`Directory does not exist: ${dir}`);
1151
+ }
1152
+ const tmpPath = `${filePath}.tmp`;
1153
+ writeFileSync(tmpPath, data, encoding);
1154
+ if (existsSync6(filePath)) {
1155
+ try {
1156
+ unlinkSync(filePath);
1157
+ } catch {
1158
+ }
1159
+ }
1160
+ renameSync(tmpPath, filePath);
1161
+ }
1162
+
1163
+ // src/utils/config.ts
646
1164
  function getConfigDir() {
647
- return join4(homedir3(), ".cannbot");
1165
+ return getCannbotConfigDir();
648
1166
  }
649
1167
  function getConfigPath() {
650
- return join4(getConfigDir(), "config.yaml");
1168
+ return join6(getConfigDir(), "config.yaml");
651
1169
  }
652
1170
  function readConfig() {
653
1171
  const configPath = getConfigPath();
654
- if (!existsSync4(configPath)) {
1172
+ if (!existsSync7(configPath)) {
655
1173
  return {
656
1174
  language: "zh_CN",
657
1175
  installedPlugins: []
658
1176
  };
659
1177
  }
660
1178
  try {
661
- const content = readFileSync2(configPath, "utf-8");
1179
+ const content = readFileSync4(configPath, "utf-8");
662
1180
  const parsed = parse(content);
663
1181
  return {
664
1182
  language: parsed.language || "zh_CN",
@@ -668,6 +1186,7 @@ function readConfig() {
668
1186
  installedPlugins: parsed.installedPlugins || []
669
1187
  };
670
1188
  } catch {
1189
+ logger.warn(t("config_corrupted"));
671
1190
  return {
672
1191
  language: "zh_CN",
673
1192
  installedPlugins: []
@@ -676,12 +1195,12 @@ function readConfig() {
676
1195
  }
677
1196
  function writeConfig(config) {
678
1197
  const configDir = getConfigDir();
679
- if (!existsSync4(configDir)) {
1198
+ if (!existsSync7(configDir)) {
680
1199
  mkdirSync(configDir, { recursive: true });
681
1200
  }
682
1201
  const configPath = getConfigPath();
683
1202
  const content = stringify(config);
684
- writeFileSync(configPath, content, "utf-8");
1203
+ atomicWriteFileSync(configPath, content);
685
1204
  }
686
1205
  function updateConfig(updates) {
687
1206
  const config = readConfig();
@@ -704,22 +1223,11 @@ function removeInstalledPlugin(pluginId) {
704
1223
  writeConfig(config);
705
1224
  }
706
1225
 
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
1226
+ // src/utils/constants.ts
721
1227
  var BACK = "__back__";
722
1228
  var CANCEL = "__cancel__";
1229
+
1230
+ // src/ui/wizard.ts
723
1231
  async function selectToolWithDetection() {
724
1232
  const config = readConfig();
725
1233
  logger.blank();
@@ -789,7 +1297,7 @@ async function runWizard() {
789
1297
  step = 2;
790
1298
  break;
791
1299
  }
792
- if (result === CANCEL || result === false) {
1300
+ if (result === CANCEL) {
793
1301
  return { language: config.language || "zh_CN", tool: selectedTool, level, plugins: [], confirmed: false };
794
1302
  }
795
1303
  updateConfig({ lastTool: selectedTool, lastLevel: level });
@@ -799,11 +1307,11 @@ async function runWizard() {
799
1307
  }
800
1308
  }
801
1309
  async function stepTool(detectedTools, lastTool) {
802
- printBoxTitle("[1/4] \u9009\u62E9 AI \u7F16\u7A0B\u5DE5\u5177");
1310
+ printBoxTitle(t("wizard_step_1_title"));
803
1311
  if (detectedTools.length === 0) {
804
1312
  logger.error(t("error_tool_not_found"));
805
1313
  const choices2 = [
806
- ...["opencode", "claude", "trae", "cursor", "copilot"].map((tool) => ({
1314
+ ...VALID_TOOLS.map((tool) => ({
807
1315
  name: `> ${getToolDisplayName(tool)}`,
808
1316
  value: tool
809
1317
  })),
@@ -874,10 +1382,10 @@ async function stepToolManual(lastTool) {
874
1382
  return result;
875
1383
  }
876
1384
  async function stepLevel(lastLevel) {
877
- printBoxTitle("[2/4] \u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E");
1385
+ printBoxTitle(t("wizard_step_2_title"));
878
1386
  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" },
1387
+ { name: `> project \u2014 ${t("install_level_project")}`, value: "project" },
1388
+ { name: `> global \u2014 ${t("install_level_global")}`, value: "global" },
881
1389
  new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
882
1390
  { name: "<- " + t("wizard_back"), value: BACK },
883
1391
  { name: "x " + t("wizard_cancel"), value: CANCEL }
@@ -895,7 +1403,7 @@ async function stepLevel(lastLevel) {
895
1403
  return result;
896
1404
  }
897
1405
  async function stepPlugins(tool, level) {
898
- printBoxTitle("[3/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6");
1406
+ printBoxTitle(t("wizard_step_3_title"));
899
1407
  const plugins = getAllPlugins();
900
1408
  const configRoot = getConfigRoot(tool, level);
901
1409
  const manifests = readAllManifests(configRoot);
@@ -904,14 +1412,12 @@ async function stepPlugins(tool, level) {
904
1412
  const isInstalled = installedSet.has(p.id);
905
1413
  const suffix = isInstalled ? ` [${t("wizard_already_installed")}]` : "";
906
1414
  return {
907
- name: `${p.displayName}${suffix} \u2014 ${p.description}`,
1415
+ name: `${chalk3.cyan(p.displayName)}${suffix}`,
908
1416
  value: p.id,
909
- checked: false
1417
+ checked: false,
1418
+ description: p.description
910
1419
  };
911
1420
  });
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
1421
  showOperationHints(true);
916
1422
  const selected = await checkbox({
917
1423
  message: t("wizard_select_plugins"),
@@ -921,9 +1427,7 @@ async function stepPlugins(tool, level) {
921
1427
  theme: checkboxTheme,
922
1428
  pageSize: 15
923
1429
  });
924
- if (selected.includes(BACK)) return BACK;
925
- if (selected.includes(CANCEL)) return CANCEL;
926
- const pluginIds = selected.filter((v) => v !== BACK && v !== CANCEL);
1430
+ const pluginIds = selected;
927
1431
  if (pluginIds.length === 0) {
928
1432
  const action = await select({
929
1433
  message: t("wizard_no_selection"),
@@ -941,17 +1445,17 @@ async function stepPlugins(tool, level) {
941
1445
  return pluginIds;
942
1446
  }
943
1447
  async function stepConfirm(tool, level, plugins) {
944
- printBoxTitle("[4/4] \u786E\u8BA4\u5B89\u88C5");
1448
+ printBoxTitle(t("wizard_step_4_title"));
945
1449
  const allPlugins = getAllPlugins();
946
1450
  const selectedPlugins = plugins.map((id) => allPlugins.find((p) => p.id === id));
947
1451
  const displayPath = getConfigRoot(tool, level);
948
1452
  const toolName = getToolDisplayName(tool);
949
1453
  const levelText = level === "project" ? "project" : "global";
950
- 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`);
951
- logger.info(`\u5B89\u88C5\u8DEF\u5F84: ${chalk2.cyan(displayPath)}`);
1454
+ logger.info(t("wizard_confirm_install_format").replace("{count}", chalk3.bold(String(plugins.length))).replace("{tool}", chalk3.green(toolName)).replace("{level}", chalk3.cyan(levelText)));
1455
+ logger.info(`${t("wizard_install_path")}: ${chalk3.cyan(displayPath)}`);
952
1456
  for (const plugin of selectedPlugins) {
953
1457
  if (plugin) {
954
- logger.step(` \u2022 ${chalk2.bold(plugin.displayName)}`);
1458
+ logger.step(` \u2022 ${chalk3.bold(plugin.displayName)}`);
955
1459
  }
956
1460
  }
957
1461
  logger.blank();
@@ -969,14 +1473,19 @@ async function stepConfirm(tool, level, plugins) {
969
1473
  }
970
1474
 
971
1475
  // src/core/repository.ts
972
- import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
973
- import { join as join6 } from "path";
1476
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
1477
+ import { join as join8, dirname as dirname5 } from "path";
1478
+ import { fileURLToPath as fileURLToPath4 } from "url";
974
1479
  import { execa as execa2 } from "execa";
1480
+ import { parse as parseYaml3 } from "yaml";
975
1481
 
976
1482
  // src/core/scanner.ts
977
- import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
978
- import { join as join5, dirname } from "path";
979
- import { parse as parseYaml } from "yaml";
1483
+ import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync5, mkdirSync as mkdirSync2 } from "fs";
1484
+ import { join as join7, dirname as dirname4 } from "path";
1485
+ import { fileURLToPath as fileURLToPath3 } from "url";
1486
+ import { parse as parseYaml2 } from "yaml";
1487
+ var __filename3 = fileURLToPath3(import.meta.url);
1488
+ var __dirname3 = dirname4(__filename3);
980
1489
  var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
981
1490
  "node_modules",
982
1491
  ".git",
@@ -997,46 +1506,70 @@ var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
997
1506
  "docs",
998
1507
  "scripts"
999
1508
  ]);
1000
- var SKILL_SCAN_DIRS = [
1001
- "ops",
1002
- "model",
1003
- "graph",
1004
- "infra",
1005
- "ops-lab"
1006
- ];
1007
- var PLUGIN_SCAN_DIRS = [
1008
- "plugins-official",
1009
- "plugins-community"
1010
- ];
1509
+ function loadScanConfig() {
1510
+ const configPath = join7(__dirname3, "config", "repository.yaml");
1511
+ try {
1512
+ const config = parseYaml2(readFileSync5(configPath, "utf-8"));
1513
+ const ttlHours = config.scanCacheTtlHours || 24;
1514
+ return {
1515
+ skillDirs: config.scanDirs || ["ops", "model", "graph", "infra", "ops-lab", "runtime"],
1516
+ pluginDirs: config.pluginDirs || ["plugins-official", "plugins-community"],
1517
+ cacheTtlMs: ttlHours * 60 * 60 * 1e3
1518
+ };
1519
+ } catch {
1520
+ return {
1521
+ skillDirs: ["ops", "model", "graph", "infra", "ops-lab", "runtime"],
1522
+ pluginDirs: ["plugins-official", "plugins-community"],
1523
+ cacheTtlMs: 24 * 60 * 60 * 1e3
1524
+ };
1525
+ }
1526
+ }
1527
+ var scanConfig = loadScanConfig();
1528
+ var SKILL_SCAN_DIRS = scanConfig.skillDirs;
1529
+ var PLUGIN_SCAN_DIRS = scanConfig.pluginDirs;
1011
1530
  function scanSkills(repoPath) {
1012
1531
  const skills = [];
1013
1532
  const seen = /* @__PURE__ */ new Set();
1014
1533
  for (const dir of SKILL_SCAN_DIRS) {
1015
- const fullPath = join5(repoPath, dir);
1016
- if (!existsSync5(fullPath)) continue;
1534
+ const fullPath = join7(repoPath, dir);
1535
+ if (!existsSync8(fullPath)) continue;
1017
1536
  scanDirectory(fullPath, repoPath, dir, skills, seen);
1018
1537
  }
1019
1538
  for (const pluginDir of PLUGIN_SCAN_DIRS) {
1020
- const pluginParent = join5(repoPath, pluginDir);
1021
- if (!existsSync5(pluginParent)) continue;
1539
+ const pluginParent = join7(repoPath, pluginDir);
1540
+ if (!existsSync8(pluginParent)) continue;
1022
1541
  for (const plugin of safeReaddir(pluginParent)) {
1023
- const pluginPath = join5(pluginParent, plugin);
1542
+ const pluginPath = join7(pluginParent, plugin);
1024
1543
  if (!isDirectory(pluginPath)) continue;
1025
- const skillsDir = join5(pluginPath, "skills");
1026
- if (existsSync5(skillsDir)) {
1544
+ const skillsDir = join7(pluginPath, "skills");
1545
+ if (existsSync8(skillsDir)) {
1027
1546
  scanDirectory(skillsDir, repoPath, `${pluginDir}/${plugin}/skills`, skills, seen);
1028
1547
  }
1029
- const skillDir = join5(pluginPath, "skill");
1030
- if (existsSync5(skillDir)) {
1548
+ const skillDir = join7(pluginPath, "skill");
1549
+ if (existsSync8(skillDir)) {
1031
1550
  scanDirectory(skillDir, repoPath, `${pluginDir}/${plugin}/skill`, skills, seen);
1032
1551
  }
1552
+ const rootSkillMd = join7(pluginPath, "SKILL.md");
1553
+ const initSh = join7(pluginPath, "init.sh");
1554
+ if (existsSync8(rootSkillMd) && !existsSync8(initSh)) {
1555
+ const frontmatter = parseFrontmatter(rootSkillMd);
1556
+ if (frontmatter && !seen.has(frontmatter.name)) {
1557
+ seen.add(frontmatter.name);
1558
+ skills.push({
1559
+ id: frontmatter.name,
1560
+ description: frontmatter.description,
1561
+ source: `${pluginDir}/${plugin}`,
1562
+ filePath: rootSkillMd
1563
+ });
1564
+ }
1565
+ }
1033
1566
  }
1034
1567
  }
1035
1568
  return skills;
1036
1569
  }
1037
1570
  function parseFrontmatter(filePath) {
1038
1571
  try {
1039
- const content = readFileSync3(filePath, "utf-8");
1572
+ const content = readFileSync5(filePath, "utf-8");
1040
1573
  const lines = content.split("\n");
1041
1574
  if (lines.length < 3) return null;
1042
1575
  if (lines[0].trim() !== "---") return null;
@@ -1049,11 +1582,15 @@ function parseFrontmatter(filePath) {
1049
1582
  }
1050
1583
  if (endIndex === -1) return null;
1051
1584
  const yamlBlock = lines.slice(1, endIndex).map((l) => l.replace(/\r$/, "")).join("\n");
1052
- const parsed = parseYaml(yamlBlock);
1585
+ const parsed = parseYaml2(yamlBlock);
1053
1586
  if (!parsed || typeof parsed !== "object") return null;
1054
1587
  if (!parsed.name || typeof parsed.name !== "string") return null;
1588
+ const trimmedName = parsed.name.trim();
1589
+ if (!trimmedName || /[\/\\]|\.\./.test(trimmedName) || !/^[a-zA-Z0-9._-]+$/.test(trimmedName)) {
1590
+ return null;
1591
+ }
1055
1592
  return {
1056
- name: parsed.name.trim(),
1593
+ name: trimmedName,
1057
1594
  description: (parsed.description || "").trim()
1058
1595
  };
1059
1596
  } catch {
@@ -1062,17 +1599,17 @@ function parseFrontmatter(filePath) {
1062
1599
  }
1063
1600
  function getCurrentCommit(repoPath) {
1064
1601
  try {
1065
- const headFile = join5(repoPath, ".git", "HEAD");
1066
- if (!existsSync5(headFile)) return "unknown";
1067
- const headContent = readFileSync3(headFile, "utf-8").trim();
1602
+ const headFile = join7(repoPath, ".git", "HEAD");
1603
+ if (!existsSync8(headFile)) return "unknown";
1604
+ const headContent = readFileSync5(headFile, "utf-8").trim();
1068
1605
  if (headContent.startsWith("ref: ")) {
1069
- const refPath = join5(repoPath, ".git", headContent.slice(5));
1070
- if (existsSync5(refPath)) {
1071
- return readFileSync3(refPath, "utf-8").trim();
1606
+ const refPath = join7(repoPath, ".git", headContent.slice(5));
1607
+ if (existsSync8(refPath)) {
1608
+ return readFileSync5(refPath, "utf-8").trim();
1072
1609
  }
1073
- const packedRefsPath = join5(repoPath, ".git", "packed-refs");
1074
- if (existsSync5(packedRefsPath)) {
1075
- const packedRefs = readFileSync3(packedRefsPath, "utf-8");
1610
+ const packedRefsPath = join7(repoPath, ".git", "packed-refs");
1611
+ if (existsSync8(packedRefsPath)) {
1612
+ const packedRefs = readFileSync5(packedRefsPath, "utf-8");
1076
1613
  const refName = headContent.slice(5);
1077
1614
  const lines = packedRefs.split("\n");
1078
1615
  for (const line of lines) {
@@ -1090,12 +1627,12 @@ function getCurrentCommit(repoPath) {
1090
1627
  function readScanCache() {
1091
1628
  try {
1092
1629
  const cachePath = getCachePath();
1093
- if (!existsSync5(cachePath)) return null;
1094
- const content = readFileSync3(cachePath, "utf-8");
1630
+ if (!existsSync8(cachePath)) return null;
1631
+ const content = readFileSync5(cachePath, "utf-8");
1095
1632
  const cache = JSON.parse(content);
1096
1633
  if (!cache.skills || !cache.timestamp) return null;
1097
1634
  const age = Date.now() - cache.timestamp;
1098
- if (age > 24 * 60 * 60 * 1e3) return null;
1635
+ if (age > scanConfig.cacheTtlMs) return null;
1099
1636
  return cache;
1100
1637
  } catch {
1101
1638
  return null;
@@ -1104,39 +1641,24 @@ function readScanCache() {
1104
1641
  function writeScanCache(cache) {
1105
1642
  try {
1106
1643
  const cachePath = getCachePath();
1107
- const cacheDir = dirname(cachePath);
1108
- if (!existsSync5(cacheDir)) {
1644
+ const cacheDir = dirname4(cachePath);
1645
+ if (!existsSync8(cacheDir)) {
1109
1646
  mkdirSync2(cacheDir, { recursive: true });
1110
1647
  }
1111
- writeFileSync2(cachePath, JSON.stringify(cache, null, 2));
1648
+ atomicWriteFileSync(cachePath, JSON.stringify(cache, null, 2));
1112
1649
  } catch {
1113
1650
  }
1114
1651
  }
1115
1652
  function getCachePath() {
1116
- const homeDir = process.env.HOME || process.env.USERPROFILE || "/tmp";
1117
- return join5(homeDir, ".cannbot", "scan-cache.json");
1653
+ return join7(getCannbotConfigDir(), "scan-cache.json");
1118
1654
  }
1119
1655
  function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1120
- const selfSkillMd = join5(dirPath, "SKILL.md");
1121
- if (existsSync5(selfSkillMd)) {
1122
- const frontmatter = parseFrontmatter(selfSkillMd);
1123
- if (frontmatter && !seen.has(frontmatter.name)) {
1124
- seen.add(frontmatter.name);
1125
- skills.push({
1126
- id: frontmatter.name,
1127
- description: frontmatter.description,
1128
- source: sourcePrefix,
1129
- filePath: selfSkillMd
1130
- });
1131
- }
1132
- return;
1133
- }
1134
1656
  for (const entry of safeReaddir(dirPath)) {
1135
- const fullPath = join5(dirPath, entry);
1657
+ const fullPath = join7(dirPath, entry);
1136
1658
  if (EXCLUDE_DIRS.has(entry)) continue;
1137
1659
  if (!isDirectory(fullPath)) continue;
1138
- const skillMd = join5(fullPath, "SKILL.md");
1139
- if (existsSync5(skillMd)) {
1660
+ const skillMd = join7(fullPath, "SKILL.md");
1661
+ if (existsSync8(skillMd)) {
1140
1662
  const frontmatter = parseFrontmatter(skillMd);
1141
1663
  if (frontmatter && !seen.has(frontmatter.name)) {
1142
1664
  seen.add(frontmatter.name);
@@ -1149,38 +1671,32 @@ function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1149
1671
  }
1150
1672
  continue;
1151
1673
  }
1152
- scanDirectory(fullPath, repoPath, sourcePrefix, skills, seen);
1674
+ scanDirectory(fullPath, repoPath, sourcePrefix + "/" + entry, skills, seen);
1153
1675
  }
1154
1676
  }
1155
1677
  function safeReaddir(dir) {
1156
1678
  try {
1157
- return readdirSync(dir);
1679
+ return readdirSync2(dir);
1158
1680
  } catch {
1159
1681
  return [];
1160
1682
  }
1161
1683
  }
1162
- function isDirectory(path) {
1163
- try {
1164
- return statSync(path).isDirectory();
1165
- } catch {
1166
- return false;
1167
- }
1168
- }
1169
1684
 
1170
1685
  // src/core/skill-registry.ts
1171
1686
  var CATEGORY_DEFS = [
1172
- { id: "knowledge", name: "\u77E5\u8BC6\u4E0E\u53C2\u8003" },
1173
- { id: "env-tools", name: "\u73AF\u5883\u4E0E\u5DE5\u5177" },
1174
- { id: "debug", name: "\u8C03\u8BD5\u4E0E\u8BCA\u65AD" },
1175
- { id: "testing", name: "\u6D4B\u8BD5\u4E0E\u8D28\u91CF" },
1176
- { id: "ascendc", name: "AscendC \u5F00\u53D1" },
1177
- { id: "pypto", name: "PyPTO \u5F00\u53D1" },
1178
- { id: "tilelang", name: "TileLang \u5F00\u53D1" },
1179
- { id: "triton", name: "Triton \u5F00\u53D1" },
1180
- { id: "model", name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316" },
1181
- { id: "graph", name: "\u56FE\u6A21\u5F0F" },
1182
- { id: "platform", name: "\u5E73\u53F0\u5DE5\u5177" },
1183
- { id: "other", name: "\u5176\u4ED6 Skills" }
1687
+ { id: "knowledge", name: "\u77E5\u8BC6\u4E0E\u53C2\u8003", nameEn: "Knowledge & Reference" },
1688
+ { id: "env-tools", name: "\u73AF\u5883\u4E0E\u5DE5\u5177", nameEn: "Environment & Tools" },
1689
+ { id: "debug", name: "\u8C03\u8BD5\u4E0E\u8BCA\u65AD", nameEn: "Debug & Diagnostics" },
1690
+ { id: "testing", name: "\u6D4B\u8BD5\u4E0E\u8D28\u91CF", nameEn: "Testing & Quality" },
1691
+ { id: "ascendc", name: "AscendC \u5F00\u53D1", nameEn: "AscendC Development" },
1692
+ { id: "pypto", name: "PyPTO \u5F00\u53D1", nameEn: "PyPTO Development" },
1693
+ { id: "tilelang", name: "TileLang \u5F00\u53D1", nameEn: "TileLang Development" },
1694
+ { id: "triton", name: "Triton \u5F00\u53D1", nameEn: "Triton Development" },
1695
+ { id: "model", name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316", nameEn: "Model Inference Optimization" },
1696
+ { id: "runtime", name: "Runtime \u8FC1\u79FB", nameEn: "Runtime Migration" },
1697
+ { id: "graph", name: "\u56FE\u6A21\u5F0F", nameEn: "Graph Mode" },
1698
+ { id: "platform", name: "\u5E73\u53F0\u5DE5\u5177", nameEn: "Platform Tools" },
1699
+ { id: "other", name: "\u5176\u4ED6 Skills", nameEn: "Other Skills" }
1184
1700
  ];
1185
1701
  var dynamicSkills = [];
1186
1702
  var initialized = false;
@@ -1188,7 +1704,8 @@ function initFromScan(scanned) {
1188
1704
  dynamicSkills = scanned.map((s) => ({
1189
1705
  id: s.id,
1190
1706
  description: s.description,
1191
- source: s.source
1707
+ source: s.source,
1708
+ filePath: s.filePath
1192
1709
  }));
1193
1710
  initialized = true;
1194
1711
  }
@@ -1264,9 +1781,7 @@ var STATIC_SKILL_CATEGORIES = [
1264
1781
  { id: "catlass-op-develop", description: "Catlass \u7B97\u5B50\u5F00\u53D1", source: "ops" },
1265
1782
  { id: "catlass-op-perf-tune", description: "Catlass \u6027\u80FD\u8C03\u4F18", source: "ops" },
1266
1783
  { id: "cuda2ascend-simt", description: "CUDA \u8FC1\u79FB\u5230 Ascend C SIMT", source: "ops-lab" },
1267
- { id: "ops-direct-invoke-flash", description: "\u4ECE\u96F6\u6784\u5EFA Ascend C \u6838\u51FD\u6570", source: "plugins-official/ops-direct-invoke-flash/skills" },
1268
- { id: "ops-registry-invoke-workflow", description: "\u6CE8\u518C\u8C03\u7528\u5DE5\u4F5C\u6D41", source: "plugins-official/ops-registry-invoke" },
1269
- { id: "ops-easyasc-dsl", description: "EasyASC DSL \u7B97\u5B50\u5F00\u53D1", source: "plugins-community/ops-easyasc-dsl/skill" }
1784
+ { id: "ops-direct-invoke-flash", description: "\u4ECE\u96F6\u6784\u5EFA Ascend C \u6838\u51FD\u6570", source: "plugins-official/ops-direct-invoke-flash/skills" }
1270
1785
  ]
1271
1786
  },
1272
1787
  {
@@ -1323,7 +1838,9 @@ var STATIC_SKILL_CATEGORIES = [
1323
1838
  { id: "model-infer-prefetch", description: "\u6743\u91CD\u9884\u53D6\u9002\u914D", source: "model" },
1324
1839
  { id: "model-infer-superkernel", description: "SuperKernel \u9002\u914D", source: "model" },
1325
1840
  { id: "model-infer-precision-debug", description: "NPU \u63A8\u7406\u7CBE\u5EA6\u8BCA\u65AD", source: "model" },
1326
- { id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" }
1841
+ { id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" },
1842
+ { id: "model-infer-harmony", description: "\u7AEF\u4FA7\u9E3F\u8499 ASR \u91CF\u5316\u8F6C\u6362\u4E0E\u6253\u5305", source: "model" },
1843
+ { 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" }
1327
1844
  ]
1328
1845
  },
1329
1846
  {
@@ -1339,6 +1856,13 @@ var STATIC_SKILL_CATEGORIES = [
1339
1856
  { id: "torch-custom-ops-guide", description: "\u81EA\u5B9A\u4E49\u7B97\u5B50\u5165\u56FE\u6307\u5357", source: "graph" }
1340
1857
  ]
1341
1858
  },
1859
+ {
1860
+ id: "runtime",
1861
+ name: "Runtime \u8FC1\u79FB",
1862
+ skills: [
1863
+ { id: "runtime_migration", description: "CUDA \u5E94\u7528\u8FC1\u79FB\u5230 CANN \u5E73\u53F0\u6307\u5357", source: "runtime" }
1864
+ ]
1865
+ },
1342
1866
  {
1343
1867
  id: "platform",
1344
1868
  name: "\u5E73\u53F0\u5DE5\u5177",
@@ -1382,9 +1906,10 @@ function getAllSkills() {
1382
1906
  }
1383
1907
  function getAllCategories() {
1384
1908
  const skills = getActiveSkills();
1909
+ const isEn = t("list_name") === "Name";
1385
1910
  return CATEGORY_DEFS.map((def) => ({
1386
1911
  id: def.id,
1387
- name: def.name,
1912
+ name: isEn ? def.nameEn : def.name,
1388
1913
  skills: skills.filter((s) => {
1389
1914
  const catId = CATEGORY_MAP[s.id];
1390
1915
  if (catId) return catId === def.id;
@@ -1394,7 +1919,21 @@ function getAllCategories() {
1394
1919
  }
1395
1920
 
1396
1921
  // src/core/repository.ts
1397
- var REPO_URL = "https://gitcode.com/cann/cannbot-skills.git";
1922
+ var __filename4 = fileURLToPath4(import.meta.url);
1923
+ var __dirname4 = dirname5(__filename4);
1924
+ function loadRepoUrl() {
1925
+ if (process.env.CANNBOT_REPO_URL) {
1926
+ return process.env.CANNBOT_REPO_URL;
1927
+ }
1928
+ const configPath = join8(__dirname4, "config", "repository.yaml");
1929
+ try {
1930
+ const config = parseYaml3(readFileSync6(configPath, "utf-8"));
1931
+ return config.repository?.url || "https://gitcode.com/cann/cannbot-skills.git";
1932
+ } catch {
1933
+ return "https://gitcode.com/cann/cannbot-skills.git";
1934
+ }
1935
+ }
1936
+ var REPO_URL = loadRepoUrl();
1398
1937
  var RepositoryManager = class {
1399
1938
  repoPath;
1400
1939
  constructor(customPath) {
@@ -1419,10 +1958,13 @@ var RepositoryManager = class {
1419
1958
  this.repoPath = cachedPath;
1420
1959
  return cachedPath;
1421
1960
  }
1422
- return await this.cloneRepo();
1961
+ const clonedPath = await this.cloneRepo();
1962
+ return clonedPath;
1423
1963
  }
1424
1964
  async ensureRepoAndScan() {
1425
1965
  const repoPath = await this.ensureRepo();
1966
+ await this.updateRepo();
1967
+ mergeDynamicPlugins(repoPath);
1426
1968
  const cache = readScanCache();
1427
1969
  if (cache && cache.repoCommit === getCurrentCommit(repoPath)) {
1428
1970
  initFromScan(cache.skills);
@@ -1442,7 +1984,9 @@ var RepositoryManager = class {
1442
1984
  try {
1443
1985
  await execa2("git", ["pull", "--quiet"], { cwd: repoPath, timeout: 3e4 });
1444
1986
  } catch {
1987
+ logger.warn(t("repo_pull_failed"));
1445
1988
  }
1989
+ mergeDynamicPlugins(repoPath);
1446
1990
  }
1447
1991
  getRepoPath() {
1448
1992
  if (!this.repoPath) {
@@ -1451,18 +1995,18 @@ var RepositoryManager = class {
1451
1995
  return this.repoPath;
1452
1996
  }
1453
1997
  isValidRepo(path) {
1454
- if (!existsSync6(path)) return false;
1455
- const gitDir = join6(path, ".git");
1456
- const pluginsDir = join6(path, "plugins-official");
1457
- return existsSync6(gitDir) && existsSync6(pluginsDir);
1998
+ if (!existsSync9(path)) return false;
1999
+ const gitDir = join8(path, ".git");
2000
+ const pluginsDir = join8(path, "plugins-official");
2001
+ return existsSync9(gitDir) && existsSync9(pluginsDir);
1458
2002
  }
1459
2003
  async cloneRepo() {
1460
2004
  const configDir = getCannbotConfigDir();
1461
- if (!existsSync6(configDir)) {
2005
+ if (!existsSync9(configDir)) {
1462
2006
  mkdirSync3(configDir, { recursive: true });
1463
2007
  }
1464
2008
  const targetPath = getCannbotRepoPath();
1465
- if (existsSync6(targetPath)) {
2009
+ if (existsSync9(targetPath)) {
1466
2010
  if (this.isValidRepo(targetPath)) {
1467
2011
  this.repoPath = targetPath;
1468
2012
  return targetPath;
@@ -1488,102 +2032,102 @@ function createRepositoryManager(customPath) {
1488
2032
  }
1489
2033
 
1490
2034
  // src/core/installer.ts
1491
- import { existsSync as existsSync8 } from "fs";
1492
- import { join as join8 } from "path";
1493
- import { execa as execa3 } from "execa";
2035
+ import { existsSync as existsSync13 } from "fs";
2036
+ import { join as join12 } from "path";
2037
+ import { execa as execa4 } from "execa";
1494
2038
 
1495
2039
  // src/core/record.ts
1496
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, lstatSync, unlinkSync } from "fs";
1497
- import { join as join7 } from "path";
2040
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
2041
+ import { join as join9 } from "path";
1498
2042
  function getInstallsDir() {
1499
- return join7(getCannbotConfigDir(), "installs");
2043
+ return join9(getCannbotConfigDir(), "installs");
1500
2044
  }
1501
2045
  function getRecordPath(pluginId) {
1502
- return join7(getInstallsDir(), `${pluginId}.json`);
2046
+ return join9(getInstallsDir(), `${pluginId}.json`);
1503
2047
  }
1504
2048
  function readRecord(pluginId) {
1505
2049
  const recordPath = getRecordPath(pluginId);
1506
- if (!existsSync7(recordPath)) {
2050
+ if (!existsSync10(recordPath)) {
1507
2051
  return null;
1508
2052
  }
1509
2053
  try {
1510
- const content = readFileSync4(recordPath, "utf-8");
2054
+ const content = readFileSync7(recordPath, "utf-8");
1511
2055
  return JSON.parse(content);
1512
2056
  } catch {
2057
+ logger.warn(t("record_corrupted").replace("{file}", recordPath));
1513
2058
  return null;
1514
2059
  }
1515
2060
  }
1516
2061
  function writeRecord(record) {
1517
2062
  const installsDir = getInstallsDir();
1518
- if (!existsSync7(installsDir)) {
2063
+ if (!existsSync10(installsDir)) {
1519
2064
  mkdirSync4(installsDir, { recursive: true });
1520
2065
  }
1521
2066
  const recordPath = getRecordPath(record.pluginId);
1522
- writeFileSync4(recordPath, JSON.stringify(record, null, 2), "utf-8");
2067
+ atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
1523
2068
  }
1524
2069
  function deleteRecord(pluginId) {
1525
2070
  const recordPath = getRecordPath(pluginId);
1526
- if (existsSync7(recordPath)) {
1527
- unlinkSync(recordPath);
2071
+ if (existsSync10(recordPath)) {
2072
+ unlinkSync2(recordPath);
1528
2073
  }
1529
2074
  }
1530
2075
  function scanInstalledFiles(pluginId, displayName, tool, level, installPath, configRoot, manifest) {
1531
2076
  const files = [];
1532
2077
  const directories = [];
1533
2078
  if (manifest) {
1534
- const skillsDir = join7(configRoot, "skills");
1535
- if (existsSync7(skillsDir)) {
2079
+ const skillsDir = join9(configRoot, "skills");
2080
+ if (existsSync10(skillsDir)) {
1536
2081
  directories.push(skillsDir);
1537
2082
  for (const skillName of manifest.installed_skills || []) {
1538
- const skillPath = join7(skillsDir, skillName);
1539
- if (existsSync7(skillPath) || isSymlink(skillPath)) {
2083
+ const skillPath = join9(skillsDir, skillName);
2084
+ if (existsSync10(skillPath) || isSymlink(skillPath)) {
1540
2085
  files.push(skillPath);
1541
2086
  }
1542
2087
  }
1543
2088
  }
1544
- const agentsDir = join7(configRoot, "agents");
1545
- if (existsSync7(agentsDir)) {
2089
+ const agentsDir = join9(configRoot, "agents");
2090
+ if (existsSync10(agentsDir)) {
1546
2091
  directories.push(agentsDir);
1547
2092
  for (const agentName of manifest.installed_agents || []) {
1548
- const agentPath = join7(agentsDir, agentName);
1549
- const agentPathMd = join7(agentsDir, agentName + ".md");
1550
- if (existsSync7(agentPath) || isSymlink(agentPath)) {
2093
+ const agentPath = join9(agentsDir, agentName);
2094
+ const agentPathMd = join9(agentsDir, agentName + ".md");
2095
+ if (existsSync10(agentPath) || isSymlink(agentPath)) {
1551
2096
  files.push(agentPath);
1552
- } else if (existsSync7(agentPathMd) || isSymlink(agentPathMd)) {
2097
+ } else if (existsSync10(agentPathMd) || isSymlink(agentPathMd)) {
1553
2098
  files.push(agentPathMd);
1554
2099
  }
1555
2100
  }
1556
2101
  }
1557
2102
  }
1558
- const workflowsLink = join7(configRoot, "workflows");
2103
+ const workflowsLink = join9(configRoot, "workflows");
1559
2104
  if (isSymlink(workflowsLink)) {
1560
2105
  files.push(workflowsLink);
1561
2106
  }
1562
- const manifestPath = join7(configRoot, "cannbot-manifest.json");
1563
- if (existsSync7(manifestPath)) {
2107
+ const manifestPath = join9(configRoot, "cannbot-manifest.json");
2108
+ if (existsSync10(manifestPath)) {
1564
2109
  files.push(manifestPath);
1565
2110
  }
1566
- const pluginManifestPath = join7(configRoot, `${pluginId}-manifest.json`);
1567
- if (existsSync7(pluginManifestPath)) {
2111
+ const pluginManifestPath = join9(configRoot, `${pluginId}-manifest.json`);
2112
+ if (existsSync10(pluginManifestPath)) {
1568
2113
  files.push(pluginManifestPath);
1569
2114
  }
1570
2115
  const configFileName = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
1571
- const configFilePath = level === "project" ? join7(installPath, configFileName) : join7(configRoot, configFileName);
1572
- if (existsSync7(configFilePath)) {
2116
+ const configFilePath = level === "project" ? join9(installPath, configFileName) : join9(configRoot, configFileName);
2117
+ if (existsSync10(configFilePath)) {
1573
2118
  files.push(configFilePath);
1574
2119
  }
1575
2120
  const repoLinks = ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples"];
1576
2121
  for (const repoName of repoLinks) {
1577
- const repoLinkPath = join7(installPath, repoName);
2122
+ const repoLinkPath = join9(installPath, repoName);
1578
2123
  if (isSymlink(repoLinkPath)) {
1579
2124
  files.push(repoLinkPath);
1580
2125
  }
1581
- const repoLinkInConfig = join7(configRoot, repoName);
2126
+ const repoLinkInConfig = join9(configRoot, repoName);
1582
2127
  if (isSymlink(repoLinkInConfig)) {
1583
2128
  files.push(repoLinkInConfig);
1584
2129
  }
1585
2130
  }
1586
- directories.push(configRoot);
1587
2131
  return {
1588
2132
  pluginId,
1589
2133
  displayName,
@@ -1596,71 +2140,418 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
1596
2140
  directories
1597
2141
  };
1598
2142
  }
1599
- function isSymlink(path) {
2143
+ function getSkillRecordPath() {
2144
+ return join9(getInstallsDir(), "skills.json");
2145
+ }
2146
+ function readSkillRecord() {
2147
+ const recordPath = getSkillRecordPath();
2148
+ if (!existsSync10(recordPath)) {
2149
+ return {};
2150
+ }
2151
+ try {
2152
+ const content = readFileSync7(recordPath, "utf-8");
2153
+ return JSON.parse(content);
2154
+ } catch {
2155
+ logger.warn(t("skill_record_corrupted"));
2156
+ return {};
2157
+ }
2158
+ }
2159
+ function writeSkillRecord(record) {
2160
+ const installsDir = getInstallsDir();
2161
+ if (!existsSync10(installsDir)) {
2162
+ mkdirSync4(installsDir, { recursive: true });
2163
+ }
2164
+ const recordPath = getSkillRecordPath();
2165
+ atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
2166
+ }
2167
+ function addSkillsToRecord(skillIds, tool, level, installPath) {
2168
+ const record = readSkillRecord();
2169
+ if (!record[tool]) record[tool] = {};
2170
+ if (!record[tool][level]) record[tool][level] = {};
2171
+ if (!record[tool][level][installPath]) {
2172
+ record[tool][level][installPath] = { skills: [], installTime: "" };
2173
+ }
2174
+ const entry = record[tool][level][installPath];
2175
+ for (const id of skillIds) {
2176
+ if (!entry.skills.includes(id)) {
2177
+ entry.skills.push(id);
2178
+ }
2179
+ }
2180
+ entry.installTime = (/* @__PURE__ */ new Date()).toISOString();
2181
+ writeSkillRecord(record);
2182
+ }
2183
+ function removeSkillsFromRecord(skillIds, tool, level, installPath) {
2184
+ const record = readSkillRecord();
2185
+ if (!record[tool]?.[level]?.[installPath]) return;
2186
+ const entry = record[tool][level][installPath];
2187
+ entry.skills = entry.skills.filter((id) => !skillIds.includes(id));
2188
+ if (entry.skills.length === 0) {
2189
+ delete record[tool][level][installPath];
2190
+ if (Object.keys(record[tool][level]).length === 0) {
2191
+ delete record[tool][level];
2192
+ if (Object.keys(record[tool]).length === 0) {
2193
+ delete record[tool];
2194
+ }
2195
+ }
2196
+ }
2197
+ writeSkillRecord(record);
2198
+ }
2199
+ function getInstalledSkills(tool, level, installPath) {
2200
+ const record = readSkillRecord();
2201
+ const recordedSkills = record[tool]?.[level]?.[installPath]?.skills || [];
2202
+ const configRoot = getConfigRoot(tool, level);
2203
+ const skillsDir = join9(configRoot, "skills");
2204
+ return recordedSkills.filter((skillId) => {
2205
+ const skillPath = join9(skillsDir, skillId);
2206
+ return existsSync10(skillPath) || isSymlink(skillPath);
2207
+ });
2208
+ }
2209
+
2210
+ // src/core/backup.ts
2211
+ import { existsSync as existsSync11, copyFileSync, readdirSync as readdirSync3, unlinkSync as unlinkSync3 } from "fs";
2212
+ import { join as join10 } from "path";
2213
+ function detectCurrentPlugin(configRoot, tool) {
2214
+ const manifest = readManifest(configRoot);
2215
+ if (manifest?.team) {
2216
+ const plugin = findPlugin(manifest.team);
2217
+ if (plugin) {
2218
+ return {
2219
+ pluginId: plugin.id,
2220
+ pluginName: plugin.displayName
2221
+ };
2222
+ }
2223
+ }
2224
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
2225
+ if (!existsSync11(agentsFile)) {
2226
+ return null;
2227
+ }
2228
+ const allRecords = readAllRecords();
2229
+ for (const record of allRecords) {
2230
+ if (record.configRoot === configRoot && record.tool === tool) {
2231
+ const plugin = findPlugin(record.pluginId);
2232
+ if (plugin) {
2233
+ return {
2234
+ pluginId: plugin.id,
2235
+ pluginName: plugin.displayName
2236
+ };
2237
+ }
2238
+ }
2239
+ }
2240
+ return null;
2241
+ }
2242
+ function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
2243
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
2244
+ if (!existsSync11(agentsFile)) {
2245
+ return null;
2246
+ }
2247
+ const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
2248
+ const backupFileName = `${getAgentsFileName(tool)}.cannbot-backup.${fromPluginId}.${timestamp}`;
2249
+ const backupPath = join10(configRoot, backupFileName);
2250
+ try {
2251
+ copyFileSync(agentsFile, backupPath);
2252
+ return {
2253
+ filePath: backupPath,
2254
+ pluginId: fromPluginId,
2255
+ pluginName: fromPluginName,
2256
+ backupTime: timestamp
2257
+ };
2258
+ } catch {
2259
+ return null;
2260
+ }
2261
+ }
2262
+ function findBackups(configRoot) {
2263
+ if (!existsSync11(configRoot)) {
2264
+ return [];
2265
+ }
2266
+ const files = readdirSync3(configRoot);
2267
+ const backups = [];
2268
+ for (const file of files) {
2269
+ if (file.includes(".cannbot-backup.")) {
2270
+ const parts = file.split(".cannbot-backup.");
2271
+ if (parts.length === 2) {
2272
+ const pluginIdAndTime = parts[1];
2273
+ const lastDotIndex = pluginIdAndTime.lastIndexOf(".");
2274
+ if (lastDotIndex > 0) {
2275
+ const pluginId = pluginIdAndTime.substring(0, lastDotIndex);
2276
+ const timestamp = pluginIdAndTime.substring(lastDotIndex + 1);
2277
+ const plugin = findPlugin(pluginId);
2278
+ backups.push({
2279
+ filePath: join10(configRoot, file),
2280
+ pluginId,
2281
+ pluginName: plugin?.displayName || pluginId,
2282
+ backupTime: timestamp
2283
+ });
2284
+ }
2285
+ }
2286
+ }
2287
+ }
2288
+ return backups.sort((a, b) => b.backupTime.localeCompare(a.backupTime));
2289
+ }
2290
+ function restoreBackup(backupPath, configRoot, tool) {
2291
+ try {
2292
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
2293
+ copyFileSync(backupPath, agentsFile);
2294
+ return true;
2295
+ } catch {
2296
+ return false;
2297
+ }
2298
+ }
2299
+ function deleteBackup(backupPath) {
2300
+ try {
2301
+ if (existsSync11(backupPath)) {
2302
+ unlinkSync3(backupPath);
2303
+ return true;
2304
+ }
2305
+ return false;
2306
+ } catch {
2307
+ return false;
2308
+ }
2309
+ }
2310
+ function formatTimestamp(date) {
2311
+ const year = date.getFullYear();
2312
+ const month = String(date.getMonth() + 1).padStart(2, "0");
2313
+ const day = String(date.getDate()).padStart(2, "0");
2314
+ const hours = String(date.getHours()).padStart(2, "0");
2315
+ const minutes = String(date.getMinutes()).padStart(2, "0");
2316
+ const seconds = String(date.getSeconds()).padStart(2, "0");
2317
+ return `${year}${month}${day}-${hours}${minutes}${seconds}`;
2318
+ }
2319
+ function readAllRecords() {
2320
+ const records = [];
2321
+ const plugins = getAllPlugins();
2322
+ for (const plugin of plugins) {
2323
+ const record = readRecord(plugin.id);
2324
+ if (record) {
2325
+ records.push(record);
2326
+ }
2327
+ }
2328
+ return records;
2329
+ }
2330
+
2331
+ // src/ui/backup-prompts.ts
2332
+ import { select as select2 } from "@inquirer/prompts";
2333
+ import chalk4 from "chalk";
2334
+ async function showOverwriteWarning(currentPluginName, newPluginName) {
2335
+ console.log();
2336
+ console.log(chalk4.yellow(` \u26A0\uFE0F ${t("backup_detected")}`));
2337
+ console.log();
2338
+ console.log(` ${t("backup_current")}: ${chalk4.cyan(currentPluginName)}`);
2339
+ console.log(` ${t("backup_installing")}: ${chalk4.cyan(newPluginName)}`);
2340
+ console.log();
2341
+ console.log(` ${t("backup_overwrite_warning")}`);
2342
+ console.log(` ${t("backup_auto_create")}`);
2343
+ console.log();
2344
+ const choice = await select2({
2345
+ message: t("backup_select_action"),
2346
+ choices: [
2347
+ { name: t("backup_overwrite"), value: "overwrite" },
2348
+ { name: t("backup_cancel"), value: "cancel" }
2349
+ ]
2350
+ });
2351
+ return choice;
2352
+ }
2353
+ async function showRestorePrompt(backups) {
2354
+ console.log();
2355
+ console.log(chalk4.cyan(` ${t("backup_restore_prompt")}`));
2356
+ console.log();
2357
+ const choices = backups.map((b) => ({
2358
+ name: `${t("backup_restore")} ${b.pluginName} (${b.backupTime})`,
2359
+ value: b.filePath
2360
+ }));
2361
+ choices.push({ name: t("backup_only_uninstall"), value: "none" });
2362
+ const choice = await select2({
2363
+ message: t("backup_select_action"),
2364
+ choices
2365
+ });
2366
+ return choice;
2367
+ }
2368
+
2369
+ // src/core/plugin-installer.ts
2370
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync4, realpathSync, copyFileSync as copyFileSync2, cpSync, writeFileSync as writeFileSync3, readFileSync as readFileSync8 } from "fs";
2371
+ import { join as join11 } from "path";
2372
+ import { execa as execa3 } from "execa";
2373
+ function readPluginManifest(pluginDir) {
2374
+ const manifestPath = join11(pluginDir, "plugin-manifest.json");
2375
+ if (!existsSync12(manifestPath)) return null;
1600
2376
  try {
1601
- return lstatSync(path).isSymbolicLink();
2377
+ const content = JSON.parse(readFileSync8(manifestPath, "utf-8"));
2378
+ if (!content || !content.id) return null;
2379
+ return content;
1602
2380
  } catch {
1603
- return false;
2381
+ return null;
1604
2382
  }
1605
2383
  }
1606
- function getSkillRecordPath() {
1607
- return join7(getInstallsDir(), "skills.json");
1608
- }
1609
- function readSkillRecord() {
1610
- const recordPath = getSkillRecordPath();
1611
- if (!existsSync7(recordPath)) {
1612
- return {};
1613
- }
2384
+ function installLink(source, target) {
1614
2385
  try {
1615
- const content = readFileSync4(recordPath, "utf-8");
1616
- return JSON.parse(content);
2386
+ const resolved = realpathSync(source);
2387
+ if (existsSync12(target) || isSymlink(target)) {
2388
+ unlinkSync4(target);
2389
+ }
2390
+ try {
2391
+ symlinkSync(resolved, target);
2392
+ return "symlink";
2393
+ } catch (e) {
2394
+ if (e.code === "EPERM") {
2395
+ cpSync(resolved, target, { recursive: true });
2396
+ return "copy";
2397
+ }
2398
+ throw e;
2399
+ }
1617
2400
  } catch {
1618
- return {};
2401
+ return "failed";
1619
2402
  }
1620
2403
  }
1621
- function writeSkillRecord(record) {
1622
- const installsDir = getInstallsDir();
1623
- if (!existsSync7(installsDir)) {
1624
- mkdirSync4(installsDir, { recursive: true });
2404
+ function installConfigFile(pluginDir, configFile, tool, level, configRoot, installPath) {
2405
+ const sourcePath = join11(pluginDir, configFile);
2406
+ if (!existsSync12(sourcePath)) return;
2407
+ const agentsFileName = getAgentsFileName(tool);
2408
+ const isProjectLevel = level === "project";
2409
+ let primaryTarget;
2410
+ if (isProjectLevel) {
2411
+ primaryTarget = join11(installPath, agentsFileName);
2412
+ } else {
2413
+ primaryTarget = join11(configRoot, agentsFileName);
1625
2414
  }
1626
- const recordPath = getSkillRecordPath();
1627
- writeFileSync4(recordPath, JSON.stringify(record, null, 2), "utf-8");
1628
- }
1629
- function addSkillsToRecord(skillIds, tool, level, installPath) {
1630
- const record = readSkillRecord();
1631
- if (!record[tool]) record[tool] = {};
1632
- if (!record[tool][level]) record[tool][level] = {};
1633
- if (!record[tool][level][installPath]) {
1634
- record[tool][level][installPath] = { skills: [], installTime: "" };
2415
+ if (existsSync12(primaryTarget) || isSymlink(primaryTarget)) {
2416
+ unlinkSync4(primaryTarget);
1635
2417
  }
1636
- const entry = record[tool][level][installPath];
1637
- for (const id of skillIds) {
1638
- if (!entry.skills.includes(id)) {
1639
- entry.skills.push(id);
2418
+ try {
2419
+ symlinkSync(realpathSync(sourcePath), primaryTarget);
2420
+ } catch (e) {
2421
+ if (e.code === "EPERM") {
2422
+ copyFileSync2(sourcePath, primaryTarget);
2423
+ } else {
2424
+ throw e;
1640
2425
  }
1641
2426
  }
1642
- entry.installTime = (/* @__PURE__ */ new Date()).toISOString();
1643
- writeSkillRecord(record);
1644
- }
1645
- function removeSkillsFromRecord(skillIds, tool, level, installPath) {
1646
- const record = readSkillRecord();
1647
- if (!record[tool]?.[level]?.[installPath]) return;
1648
- const entry = record[tool][level][installPath];
1649
- entry.skills = entry.skills.filter((id) => !skillIds.includes(id));
1650
- if (entry.skills.length === 0) {
1651
- delete record[tool][level][installPath];
1652
- if (Object.keys(record[tool][level]).length === 0) {
1653
- delete record[tool][level];
1654
- if (Object.keys(record[tool]).length === 0) {
1655
- delete record[tool];
2427
+ if (isProjectLevel && primaryTarget !== join11(configRoot, agentsFileName)) {
2428
+ const configRootTarget = join11(configRoot, agentsFileName);
2429
+ if (existsSync12(configRootTarget) || isSymlink(configRootTarget)) {
2430
+ unlinkSync4(configRootTarget);
2431
+ }
2432
+ try {
2433
+ symlinkSync(realpathSync(sourcePath), configRootTarget);
2434
+ } catch (e) {
2435
+ if (e.code === "EPERM") {
2436
+ copyFileSync2(sourcePath, configRootTarget);
1656
2437
  }
1657
2438
  }
1658
2439
  }
1659
- writeSkillRecord(record);
1660
2440
  }
1661
- function getInstalledSkills(tool, level, installPath) {
1662
- const record = readSkillRecord();
1663
- return record[tool]?.[level]?.[installPath]?.skills || [];
2441
+ async function installExternalRepos(repos, repoPath) {
2442
+ if (!repos || repos.length === 0) return;
2443
+ for (const repo of repos) {
2444
+ const targetDir = join11(repoPath, repo.dir);
2445
+ if (existsSync12(targetDir)) {
2446
+ logger.info(` ${repo.dir} already exists, skipping clone`);
2447
+ continue;
2448
+ }
2449
+ const args = ["clone"];
2450
+ if (repo.depth) {
2451
+ args.push("--depth", String(repo.depth));
2452
+ }
2453
+ if (repo.recursive) {
2454
+ args.push("--recursive");
2455
+ }
2456
+ args.push(repo.url, targetDir);
2457
+ try {
2458
+ await execa3("git", args, { timeout: 12e4 });
2459
+ } catch {
2460
+ logger.warn(` git clone failed: ${repo.url}, clone manually: git clone ${repo.url} ${repo.dir}`);
2461
+ }
2462
+ }
2463
+ }
2464
+ function writeCannbotManifest(configRoot, pluginId, version, level, tool, skills, agents) {
2465
+ const manifest = {
2466
+ brand: "CANNBot",
2467
+ version,
2468
+ team: pluginId,
2469
+ level,
2470
+ tool,
2471
+ installed_skills: skills,
2472
+ installed_agents: agents,
2473
+ brand_dir: configRoot,
2474
+ install_time: (/* @__PURE__ */ new Date()).toISOString()
2475
+ };
2476
+ const manifestPath = join11(configRoot, "cannbot-manifest.json");
2477
+ writeFileSync3(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
2478
+ return manifest;
2479
+ }
2480
+ async function installViaManifest(manifest, pluginDir, repoPath, tool, level, installPath) {
2481
+ const cwd = installPath || process.cwd();
2482
+ const configRoot = getConfigRoot(tool, level, installPath);
2483
+ try {
2484
+ const skillsDir = join11(configRoot, "skills");
2485
+ const agentsDir = join11(configRoot, "agents");
2486
+ mkdirSync5(skillsDir, { recursive: true });
2487
+ mkdirSync5(agentsDir, { recursive: true });
2488
+ const installedSkills = [];
2489
+ const installedAgents = [];
2490
+ for (const source of manifest.skills) {
2491
+ const sourceDir = join11(repoPath, source.dir);
2492
+ if (!existsSync12(sourceDir)) {
2493
+ logger.warn(` Skill source not found: ${source.dir}`);
2494
+ continue;
2495
+ }
2496
+ for (const skillEntry of source.skills) {
2497
+ const skillName = typeof skillEntry === "string" ? skillEntry : skillEntry.name;
2498
+ const skillAlias = typeof skillEntry === "string" ? skillName : skillEntry.as || skillName;
2499
+ const skillPath = join11(sourceDir, skillName);
2500
+ if (!existsSync12(skillPath)) {
2501
+ logger.warn(` Skill not found: ${skillName} in ${source.dir}`);
2502
+ continue;
2503
+ }
2504
+ const targetPath = join11(skillsDir, skillAlias);
2505
+ const result = installLink(skillPath, targetPath);
2506
+ if (result !== "failed") {
2507
+ installedSkills.push(skillAlias);
2508
+ } else {
2509
+ logger.warn(` Failed to install skill: ${skillAlias}`);
2510
+ }
2511
+ }
2512
+ }
2513
+ for (const agentName of manifest.agents) {
2514
+ const agentFile = agentName.endsWith(".md") ? agentName : `${agentName}.md`;
2515
+ const agentPath = join11(pluginDir, "agents", agentFile);
2516
+ if (!existsSync12(agentPath)) {
2517
+ logger.warn(` Agent not found: ${agentFile}`);
2518
+ continue;
2519
+ }
2520
+ const targetPath = join11(agentsDir, agentFile);
2521
+ const result = installLink(agentPath, targetPath);
2522
+ if (result !== "failed") {
2523
+ installedAgents.push(agentFile);
2524
+ } else {
2525
+ logger.warn(` Failed to install agent: ${agentFile}`);
2526
+ }
2527
+ }
2528
+ installConfigFile(pluginDir, manifest.configFile, tool, level, configRoot, cwd);
2529
+ await installExternalRepos(manifest.externalRepos, repoPath);
2530
+ const cannbotManifest = writeCannbotManifest(
2531
+ configRoot,
2532
+ manifest.id,
2533
+ manifest.version || "1.0.0",
2534
+ level,
2535
+ tool,
2536
+ installedSkills,
2537
+ installedAgents
2538
+ );
2539
+ return {
2540
+ success: true,
2541
+ skillsCount: installedSkills.length,
2542
+ agentsCount: installedAgents.length,
2543
+ errors: [],
2544
+ manifest: cannbotManifest
2545
+ };
2546
+ } catch (error) {
2547
+ return {
2548
+ success: false,
2549
+ skillsCount: 0,
2550
+ agentsCount: 0,
2551
+ errors: [error instanceof Error ? error.message : t("error_unknown")],
2552
+ manifest: null
2553
+ };
2554
+ }
1664
2555
  }
1665
2556
 
1666
2557
  // src/core/installer.ts
@@ -1672,18 +2563,116 @@ async function installPlugin(opts) {
1672
2563
  pluginId: opts.pluginId,
1673
2564
  skillsCount: 0,
1674
2565
  agentsCount: 0,
1675
- errors: [`Plugin not found: ${opts.pluginId}`],
2566
+ errors: [t("error_plugin_not_found") + ": " + opts.pluginId],
1676
2567
  warnings: []
1677
2568
  };
1678
2569
  }
1679
- const scriptPath = join8(opts.repoPath, plugin.dir, plugin.script);
1680
- if (!existsSync8(scriptPath)) {
2570
+ const pluginDir = join12(opts.repoPath, plugin.dir);
2571
+ const cwd = opts.installPath || process.cwd();
2572
+ const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
2573
+ const agentsFile = join12(configRoot, getAgentsFileName(opts.tool));
2574
+ let backupInfo = null;
2575
+ if (existsSync13(agentsFile)) {
2576
+ const currentPlugin = detectCurrentPlugin(configRoot, opts.tool);
2577
+ if (currentPlugin && currentPlugin.pluginId !== opts.pluginId) {
2578
+ let choice = "overwrite";
2579
+ if (!opts.yes) {
2580
+ choice = await showOverwriteWarning(
2581
+ currentPlugin.pluginName,
2582
+ plugin.displayName
2583
+ );
2584
+ }
2585
+ if (choice === "cancel") {
2586
+ logger.info(t("backup_cancel"));
2587
+ return {
2588
+ success: false,
2589
+ pluginId: opts.pluginId,
2590
+ skillsCount: 0,
2591
+ agentsCount: 0,
2592
+ errors: [t("backup_cancel")],
2593
+ warnings: []
2594
+ };
2595
+ }
2596
+ backupInfo = createBackup(
2597
+ configRoot,
2598
+ opts.tool,
2599
+ currentPlugin.pluginId,
2600
+ currentPlugin.pluginName
2601
+ );
2602
+ if (!backupInfo) {
2603
+ logger.error(t("backup_failed"));
2604
+ return {
2605
+ success: false,
2606
+ pluginId: opts.pluginId,
2607
+ skillsCount: 0,
2608
+ agentsCount: 0,
2609
+ errors: [t("backup_failed")],
2610
+ warnings: []
2611
+ };
2612
+ }
2613
+ logger.success(`${t("backup_created")}: ${backupInfo.filePath}`);
2614
+ }
2615
+ }
2616
+ const pluginManifest = readPluginManifest(pluginDir);
2617
+ if (pluginManifest) {
2618
+ const result = await installViaManifest(
2619
+ pluginManifest,
2620
+ pluginDir,
2621
+ opts.repoPath,
2622
+ opts.tool,
2623
+ opts.level,
2624
+ opts.installPath
2625
+ );
2626
+ if (result.success) {
2627
+ try {
2628
+ const record = scanInstalledFiles(
2629
+ opts.pluginId,
2630
+ plugin.displayName,
2631
+ opts.tool,
2632
+ opts.level,
2633
+ cwd,
2634
+ configRoot,
2635
+ result.manifest
2636
+ );
2637
+ if (backupInfo) {
2638
+ record.backup = {
2639
+ filePath: backupInfo.filePath,
2640
+ fromPluginId: backupInfo.pluginId,
2641
+ fromPluginName: backupInfo.pluginName,
2642
+ backupTime: backupInfo.backupTime
2643
+ };
2644
+ }
2645
+ writeRecord(record);
2646
+ } catch {
2647
+ logger.warn(t("record_write_failed"));
2648
+ }
2649
+ return {
2650
+ success: true,
2651
+ pluginId: opts.pluginId,
2652
+ skillsCount: result.skillsCount,
2653
+ agentsCount: result.agentsCount,
2654
+ errors: result.errors,
2655
+ warnings: []
2656
+ };
2657
+ } else {
2658
+ return {
2659
+ success: false,
2660
+ pluginId: opts.pluginId,
2661
+ skillsCount: 0,
2662
+ agentsCount: 0,
2663
+ errors: result.errors,
2664
+ warnings: []
2665
+ };
2666
+ }
2667
+ }
2668
+ const scriptPath = join12(pluginDir, plugin.script);
2669
+ if (!existsSync13(scriptPath)) {
1681
2670
  return {
1682
2671
  success: false,
1683
2672
  pluginId: opts.pluginId,
1684
2673
  skillsCount: 0,
1685
2674
  agentsCount: 0,
1686
- errors: [`Script not found: ${scriptPath}`],
2675
+ errors: [t("error_script_not_found").replace("{path}", scriptPath)],
1687
2676
  warnings: []
1688
2677
  };
1689
2678
  }
@@ -1691,14 +2680,15 @@ async function installPlugin(opts) {
1691
2680
  if (opts.installPath) {
1692
2681
  args.push(opts.installPath);
1693
2682
  }
1694
- const cwd = opts.installPath || process.cwd();
1695
2683
  try {
1696
- await execa3("bash", [scriptPath, ...args], {
2684
+ const shell = process.platform === "win32" ? "sh" : "bash";
2685
+ const execOptions = {
1697
2686
  cwd,
1698
2687
  timeout: 3e5,
1699
- stdio: "pipe"
1700
- });
1701
- const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
2688
+ stdio: "pipe",
2689
+ input: opts.yes ? "y\n" : void 0
2690
+ };
2691
+ await execa4(shell, [scriptPath, ...args], execOptions);
1702
2692
  const manifest = readManifest(configRoot);
1703
2693
  let skillsCount = 0;
1704
2694
  let agentsCount = 0;
@@ -1719,8 +2709,17 @@ async function installPlugin(opts) {
1719
2709
  configRoot,
1720
2710
  manifest
1721
2711
  );
2712
+ if (backupInfo) {
2713
+ record.backup = {
2714
+ filePath: backupInfo.filePath,
2715
+ fromPluginId: backupInfo.pluginId,
2716
+ fromPluginName: backupInfo.pluginName,
2717
+ backupTime: backupInfo.backupTime
2718
+ };
2719
+ }
1722
2720
  writeRecord(record);
1723
2721
  } catch {
2722
+ logger.warn(t("record_write_failed"));
1724
2723
  }
1725
2724
  return {
1726
2725
  success: true,
@@ -1731,7 +2730,7 @@ async function installPlugin(opts) {
1731
2730
  warnings: []
1732
2731
  };
1733
2732
  } catch (error) {
1734
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
2733
+ const errorMessage = error instanceof Error ? error.message : t("error_unknown");
1735
2734
  return {
1736
2735
  success: false,
1737
2736
  pluginId: opts.pluginId,
@@ -1744,41 +2743,46 @@ async function installPlugin(opts) {
1744
2743
  }
1745
2744
 
1746
2745
  // src/core/skill-installer.ts
1747
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync2, readdirSync as readdirSync3, rmdirSync, realpathSync, lstatSync as lstatSync2 } from "fs";
1748
- import { join as join9 } from "path";
1749
- import { select as select2, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
1750
- import chalk3 from "chalk";
2746
+ import { existsSync as existsSync14, mkdirSync as mkdirSync6, symlinkSync as symlinkSync2, unlinkSync as unlinkSync5, readdirSync as readdirSync4, rmdirSync, realpathSync as realpathSync2 } from "fs";
2747
+ import { join as join13, dirname as dirname7 } from "path";
2748
+ import { select as select3, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
2749
+ import chalk5 from "chalk";
1751
2750
  import Table from "cli-table3";
1752
2751
  async function installSkills(skillIds, tool, level, repoPath) {
1753
2752
  const configRoot = getConfigRoot(tool, level);
1754
- const skillsDir = join9(configRoot, "skills");
2753
+ const skillsDir = join13(configRoot, "skills");
1755
2754
  const installPath = level === "project" ? process.cwd() : configRoot;
1756
- if (!existsSync9(skillsDir)) {
1757
- mkdirSync5(skillsDir, { recursive: true });
2755
+ if (!existsSync14(skillsDir)) {
2756
+ mkdirSync6(skillsDir, { recursive: true });
1758
2757
  }
1759
2758
  const results = [];
1760
2759
  const installedIds = [];
1761
2760
  for (const skillId of skillIds) {
1762
2761
  const skill = findSkill(skillId);
1763
2762
  if (!skill) {
1764
- results.push({ skillId, success: false, error: "Skill not found in registry" });
2763
+ results.push({ skillId, success: false, error: t("error_skill_not_found") });
1765
2764
  continue;
1766
2765
  }
1767
- const sourcePath = join9(repoPath, skill.source, skill.id);
1768
- const targetPath = join9(skillsDir, skillId);
1769
- if (!existsSync9(sourcePath)) {
1770
- results.push({ skillId, success: false, error: `Source not found: ${sourcePath}` });
2766
+ const sourcePath = resolveSkillSourcePath(skill, repoPath);
2767
+ const targetPath = join13(skillsDir, skillId);
2768
+ if (!existsSync14(sourcePath)) {
2769
+ results.push({ skillId, success: false, error: t("error_source_not_found").replace("{path}", sourcePath) });
1771
2770
  continue;
1772
2771
  }
1773
2772
  try {
1774
- if (existsSync9(targetPath) || isSymlink2(targetPath)) {
1775
- unlinkSync2(targetPath);
2773
+ const resolvedSource = realpathSync2(sourcePath);
2774
+ if (existsSync14(targetPath) || isSymlink(targetPath)) {
2775
+ unlinkSync5(targetPath);
1776
2776
  }
1777
- symlinkSync(realpathSync(sourcePath), targetPath);
2777
+ symlinkSync2(resolvedSource, targetPath);
1778
2778
  results.push({ skillId, success: true });
1779
2779
  installedIds.push(skillId);
1780
2780
  } catch (error) {
1781
- results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
2781
+ let msg = error instanceof Error ? error.message : t("error_unknown");
2782
+ if (process.platform === "win32" && msg.includes("EPERM")) {
2783
+ msg = t("error_symlink_windows");
2784
+ }
2785
+ results.push({ skillId, success: false, error: msg });
1782
2786
  }
1783
2787
  }
1784
2788
  if (installedIds.length > 0) {
@@ -1788,109 +2792,118 @@ async function installSkills(skillIds, tool, level, repoPath) {
1788
2792
  }
1789
2793
  async function uninstallSkills(skillIds, tool, level) {
1790
2794
  const configRoot = getConfigRoot(tool, level);
1791
- const skillsDir = join9(configRoot, "skills");
2795
+ const skillsDir = join13(configRoot, "skills");
1792
2796
  const installPath = level === "project" ? process.cwd() : configRoot;
1793
2797
  const results = [];
1794
2798
  const removedIds = [];
1795
2799
  for (const skillId of skillIds) {
1796
- const targetPath = join9(skillsDir, skillId);
2800
+ const targetPath = join13(skillsDir, skillId);
1797
2801
  try {
1798
- if (existsSync9(targetPath) || isSymlink2(targetPath)) {
1799
- unlinkSync2(targetPath);
2802
+ if (existsSync14(targetPath) || isSymlink(targetPath)) {
2803
+ unlinkSync5(targetPath);
1800
2804
  results.push({ skillId, success: true });
1801
2805
  removedIds.push(skillId);
1802
- logger.step(` \u79FB\u9664: ${skillId}`);
2806
+ logger.step(` ${t("uninstall_remove_label")}: ${skillId}`);
1803
2807
  } else {
1804
- results.push({ skillId, success: false, error: "Not installed" });
1805
- logger.warn(`${skillId} \u672A\u5B89\u88C5`);
2808
+ results.push({ skillId, success: false, error: t("error_not_installed") });
2809
+ logger.warn(`${skillId} ${t("uninstall_skill_not_installed")}`);
1806
2810
  }
1807
2811
  } catch (error) {
1808
- results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
2812
+ results.push({ skillId, success: false, error: error instanceof Error ? error.message : t("error_unknown") });
1809
2813
  }
1810
2814
  }
1811
2815
  if (removedIds.length > 0) {
1812
2816
  removeSkillsFromRecord(removedIds, tool, level, installPath);
1813
2817
  }
1814
- if (existsSync9(skillsDir)) {
2818
+ if (existsSync14(skillsDir)) {
1815
2819
  try {
1816
- const entries = readdirSync3(skillsDir);
2820
+ const entries = readdirSync4(skillsDir);
1817
2821
  if (entries.length === 0) {
1818
2822
  rmdirSync(skillsDir);
1819
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: skills/`);
1820
- }
1821
- } catch {
1822
- }
1823
- }
1824
- if (existsSync9(configRoot)) {
1825
- try {
1826
- const entries = readdirSync3(configRoot);
1827
- if (entries.length === 0) {
1828
- rmdirSync(configRoot);
1829
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${configRoot.split("/").pop()}/`);
2823
+ logger.step(` ${t("uninstall_clean_empty_dir")}: skills/`);
1830
2824
  }
1831
2825
  } catch {
1832
2826
  }
1833
2827
  }
1834
2828
  return results;
1835
2829
  }
1836
- async function interactiveSkillSelect() {
1837
- const BACK3 = "__back__";
1838
- const CANCEL3 = "__cancel__";
2830
+ var SELECT_ALL = "__select_all__";
2831
+ var SELECT_ALL_CATEGORY = "__select_all_category__";
2832
+ var CONTINUE_SELECT = "__continue_select__";
2833
+ async function interactiveSkillSelect(tool, level) {
1839
2834
  const categories = getAllCategories();
2835
+ const allSkillIds = getAllSkills().map((s) => s.id);
2836
+ let accumulated = [];
1840
2837
  let step = 0;
1841
2838
  let selectedCategoryId = "";
1842
2839
  while (true) {
1843
2840
  switch (step) {
1844
2841
  case 0: {
1845
- printBoxTitle("[3/4] \u9009\u62E9 Skill \u7C7B\u522B");
2842
+ 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");
2843
+ printBoxTitle(titleSuffix);
1846
2844
  const categoryChoices = [
2845
+ {
2846
+ name: `>> ${t("skill_select_all").replace("{count}", String(allSkillIds.length))}`,
2847
+ value: SELECT_ALL
2848
+ },
2849
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1847
2850
  ...categories.map((cat) => ({
1848
2851
  name: `> ${cat.name} (${cat.skills.length} skills)`,
1849
2852
  value: cat.id
1850
2853
  })),
1851
2854
  new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1852
- { name: "<- " + t("wizard_back"), value: BACK3 },
1853
- { name: "x " + t("wizard_cancel"), value: CANCEL3 }
2855
+ ...accumulated.length > 0 ? [{ name: `>> ${t("skill_confirm_install_count").replace("{count}", String(accumulated.length))}`, value: "install" }] : [],
2856
+ { name: "<- " + t("wizard_back"), value: BACK },
2857
+ { name: "x " + t("wizard_cancel"), value: CANCEL }
1854
2858
  ];
1855
2859
  showOperationHints();
1856
- selectedCategoryId = await select2({
2860
+ selectedCategoryId = await select3({
1857
2861
  message: t("skill_select_category"),
1858
2862
  choices: categoryChoices,
1859
2863
  loop: false,
1860
2864
  theme: selectTheme,
1861
2865
  pageSize: 15
1862
2866
  });
1863
- if (selectedCategoryId === BACK3) return "back";
1864
- if (selectedCategoryId === CANCEL3) return "cancel";
2867
+ if (selectedCategoryId === BACK) return "back";
2868
+ if (selectedCategoryId === CANCEL) return "cancel";
2869
+ if (selectedCategoryId === SELECT_ALL) {
2870
+ return allSkillIds;
2871
+ }
2872
+ if (selectedCategoryId === "install") {
2873
+ return accumulated;
2874
+ }
1865
2875
  step = 1;
1866
2876
  break;
1867
2877
  }
1868
2878
  case 1: {
1869
2879
  const category = categories.find((c) => c.id === selectedCategoryId);
1870
2880
  if (!category) return [];
1871
- printBoxTitle(`[4/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills \u2014 ${category.name}`);
1872
- const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
1873
- const levels = ["project", "global"];
1874
- const installedSkills = [];
1875
- for (const tk of tools) {
1876
- for (const l of levels) {
1877
- const path = l === "project" ? process.cwd() : getConfigRoot(tk, l);
1878
- installedSkills.push(...getInstalledSkills(tk, l, path));
1879
- }
1880
- }
2881
+ printBoxTitle(t("wizard_step_4_skill_title").replace("{category}", category.name));
2882
+ const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
2883
+ const installedSkills = getInstalledSkills(tool, level, installPath);
1881
2884
  const installedSet = new Set(installedSkills);
1882
- const skillChoices = category.skills.map((skill) => {
1883
- const isInstalled = installedSet.has(skill.id);
1884
- const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : "";
1885
- return {
1886
- name: `${skill.id}${suffix} \u2014 ${skill.description}`,
1887
- value: skill.id,
1888
- checked: false
1889
- };
1890
- });
1891
- skillChoices.push(new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1892
- skillChoices.push({ name: "<- " + t("wizard_back"), value: BACK3, checked: false });
1893
- skillChoices.push({ name: "x " + t("wizard_cancel"), value: CANCEL3, checked: false });
2885
+ const categorySkillIds = new Set(category.skills.map((s) => s.id));
2886
+ const accumulatedInCategory = new Set(accumulated.filter((id) => categorySkillIds.has(id)));
2887
+ const skillChoices = [
2888
+ {
2889
+ name: `>> ${t("skill_select_all_category")}`,
2890
+ value: SELECT_ALL_CATEGORY,
2891
+ checked: accumulatedInCategory.size === category.skills.length,
2892
+ description: t("skill_select_all_category")
2893
+ },
2894
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2895
+ ...category.skills.map((skill) => {
2896
+ const isInstalled = installedSet.has(skill.id);
2897
+ const isAccumulated = accumulatedInCategory.has(skill.id);
2898
+ const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : isAccumulated ? " [+]" : "";
2899
+ return {
2900
+ name: `${chalk5.cyan(skill.id)}${suffix}`,
2901
+ value: skill.id,
2902
+ checked: isAccumulated,
2903
+ description: skill.description
2904
+ };
2905
+ })
2906
+ ];
1894
2907
  showOperationHints(true);
1895
2908
  const selectedSkills = await checkbox2({
1896
2909
  message: t("skill_select_items"),
@@ -1900,16 +2913,21 @@ async function interactiveSkillSelect() {
1900
2913
  theme: checkboxTheme,
1901
2914
  pageSize: 15
1902
2915
  });
1903
- if (selectedSkills.includes(BACK3)) {
1904
- step = 0;
1905
- break;
2916
+ let skillIds;
2917
+ if (selectedSkills.includes(SELECT_ALL_CATEGORY)) {
2918
+ skillIds = category.skills.map((s) => s.id);
2919
+ } else {
2920
+ skillIds = selectedSkills.filter((id) => id !== SELECT_ALL_CATEGORY);
1906
2921
  }
1907
- if (selectedSkills.includes(CANCEL3)) return "cancel";
1908
- const skillIds = selectedSkills.filter((v) => v !== BACK3 && v !== CANCEL3);
1909
2922
  if (skillIds.length === 0) {
1910
- const action = await select2({
1911
- message: t("wizard_no_selection"),
1912
- choices: [
2923
+ accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
2924
+ const action2 = await select3({
2925
+ message: accumulated.length > 0 ? t("skill_continue_or_install").replace("{count}", String(accumulated.length)) : t("wizard_no_selection"),
2926
+ choices: accumulated.length > 0 ? [
2927
+ { name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
2928
+ { name: "<- " + t("wizard_back_to_reselect"), value: "back" },
2929
+ { name: "x " + t("wizard_cancel"), value: "cancel" }
2930
+ ] : [
1913
2931
  new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1914
2932
  { name: "<- " + t("wizard_back_to_reselect"), value: "back" },
1915
2933
  { name: "x " + t("wizard_cancel"), value: "cancel" }
@@ -1917,13 +2935,38 @@ async function interactiveSkillSelect() {
1917
2935
  loop: false,
1918
2936
  theme: selectTheme
1919
2937
  });
1920
- if (action === "back") {
2938
+ if (action2 === "install") return accumulated;
2939
+ if (action2 === "back") {
1921
2940
  step = 0;
1922
2941
  break;
1923
2942
  }
1924
2943
  return "cancel";
1925
2944
  }
1926
- return skillIds;
2945
+ accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
2946
+ for (const id of skillIds) {
2947
+ if (!accumulated.includes(id)) {
2948
+ accumulated.push(id);
2949
+ }
2950
+ }
2951
+ const action = await select3({
2952
+ message: t("skill_continue_or_install").replace("{count}", String(accumulated.length)),
2953
+ choices: [
2954
+ { name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
2955
+ { name: "> " + t("skill_continue_select"), value: CONTINUE_SELECT },
2956
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2957
+ { name: "x " + t("wizard_cancel"), value: "cancel" }
2958
+ ],
2959
+ loop: false,
2960
+ theme: selectTheme
2961
+ });
2962
+ if (action === "install") {
2963
+ return accumulated;
2964
+ }
2965
+ if (action === CONTINUE_SELECT) {
2966
+ step = 0;
2967
+ break;
2968
+ }
2969
+ return "cancel";
1927
2970
  }
1928
2971
  }
1929
2972
  }
@@ -1931,11 +2974,11 @@ async function interactiveSkillSelect() {
1931
2974
  function listAllSkills() {
1932
2975
  const categories = getAllCategories();
1933
2976
  console.log();
1934
- console.log(chalk3.bold(` ${t("skill_list_title")}`));
1935
- console.log(chalk3.dim(" " + "\u2500".repeat(60)));
2977
+ console.log(chalk5.bold(` ${t("skill_list_title")}`));
2978
+ console.log(chalk5.dim(" " + "\u2500".repeat(60)));
1936
2979
  for (const category of categories) {
1937
2980
  console.log();
1938
- console.log(chalk3.bold(` ${category.name}`) + chalk3.dim(` (${category.skills.length} skills)`));
2981
+ console.log(chalk5.bold(` ${category.name}`) + chalk5.dim(` (${category.skills.length} skills)`));
1939
2982
  const table = new Table({
1940
2983
  style: { head: [], border: [] },
1941
2984
  colWidths: [35, 45],
@@ -1943,51 +2986,54 @@ function listAllSkills() {
1943
2986
  });
1944
2987
  for (const skill of category.skills) {
1945
2988
  table.push([
1946
- chalk3.cyan(skill.id),
2989
+ chalk5.cyan(skill.id),
1947
2990
  skill.description
1948
2991
  ]);
1949
2992
  }
1950
2993
  console.log(table.toString());
1951
2994
  }
1952
2995
  console.log();
1953
- console.log(chalk3.dim(` \u5171 ${getAllSkills().length} \u4E2A Skills`));
2996
+ console.log(chalk5.dim(` ${t("skill_total_count").replace("{count}", String(getAllSkills().length))}`));
1954
2997
  console.log();
1955
2998
  }
1956
- function isSymlink2(path) {
1957
- try {
1958
- return lstatSync2(path).isSymbolicLink();
1959
- } catch {
1960
- return false;
2999
+ function resolveSkillSourcePath(skill, repoPath) {
3000
+ if (skill.filePath) {
3001
+ const absPath = skill.filePath.startsWith("/") ? skill.filePath : join13(repoPath, skill.filePath);
3002
+ if (existsSync14(absPath)) {
3003
+ return dirname7(absPath);
3004
+ }
1961
3005
  }
3006
+ return join13(repoPath, skill.source, skill.id);
1962
3007
  }
1963
3008
 
1964
3009
  // src/ui/display.ts
1965
- import chalk4 from "chalk";
3010
+ import chalk6 from "chalk";
1966
3011
  import Table2 from "cli-table3";
1967
3012
  function printPluginList(plugins, installed) {
1968
3013
  const table = new Table2({
1969
3014
  head: [
1970
- chalk4.cyan(t("list_id")),
1971
- chalk4.cyan(t("list_name")),
1972
- chalk4.cyan(t("list_status")),
1973
- chalk4.cyan(t("list_skills")),
1974
- chalk4.cyan(t("list_agents"))
3015
+ chalk6.cyan(t("list_id")),
3016
+ chalk6.cyan(t("list_name")),
3017
+ chalk6.cyan(t("list_status")),
3018
+ chalk6.cyan(t("list_description"))
1975
3019
  ],
1976
- style: { head: [], border: [] }
3020
+ style: { head: [], border: [] },
3021
+ colWidths: [6, 32, 8, 50],
3022
+ wordWrap: true
1977
3023
  });
1978
3024
  plugins.forEach((plugin, index) => {
1979
3025
  const isInstalled = installed.has(plugin.id);
1980
- const status = isInstalled ? chalk4.green(`\u2713 ${t("status_installed")}`) : chalk4.dim(`\u2014`);
3026
+ const status = isInstalled ? chalk6.green(`\u2713 ${t("status_installed")}`) : chalk6.dim(`\u2014`);
1981
3027
  table.push([
1982
3028
  String(index + 1),
1983
3029
  plugin.displayName,
1984
3030
  status,
1985
- String(plugin.skills),
1986
- String(plugin.agents)
3031
+ plugin.description || chalk6.dim("\u2014")
1987
3032
  ]);
1988
3033
  });
1989
3034
  console.log();
1990
- console.log(chalk4.bold(` ${t("list_title")} (${plugins.length} \u4E2A)`));
3035
+ const countSuffix = t("count_suffix");
3036
+ console.log(chalk6.bold(` ${t("list_title")} (${plugins.length}${countSuffix ? " " + countSuffix : ""})`));
1991
3037
  console.log(table.toString());
1992
3038
  console.log();
1993
3039
  }
@@ -1996,7 +3042,7 @@ function printInstallSummary(results) {
1996
3042
  const totalCount = results.length;
1997
3043
  console.log();
1998
3044
  console.log(
1999
- chalk4.bold(
3045
+ chalk6.bold(
2000
3046
  ` ${t("install_done")}! ${successCount}/${totalCount} ${t("install_success")}`
2001
3047
  )
2002
3048
  );
@@ -2004,10 +3050,10 @@ function printInstallSummary(results) {
2004
3050
  for (const result of results) {
2005
3051
  if (result.success) {
2006
3052
  console.log(
2007
- chalk4.green(" \u2713") + ` ${result.displayName} (${result.skillsCount} skills, ${result.agentsCount} agents)`
3053
+ chalk6.green(" \u2713") + ` ${result.displayName} (${result.skillsCount} skills, ${result.agentsCount} agents)`
2008
3054
  );
2009
3055
  } else {
2010
- console.log(chalk4.red(" \u2717") + ` ${result.displayName}`);
3056
+ console.log(chalk6.red(" \u2717") + ` ${result.displayName}`);
2011
3057
  }
2012
3058
  }
2013
3059
  console.log();
@@ -2020,22 +3066,25 @@ function printEnhancedSummary(results, tool, configRoot) {
2020
3066
  return;
2021
3067
  }
2022
3068
  const toolName = getToolDisplayName(tool);
3069
+ const countSuffix = t("count_suffix");
2023
3070
  const lines = [
2024
- chalk4.bold(` ${t("install_done")}!`),
3071
+ chalk6.bold(` ${t("install_done")}!`),
2025
3072
  "",
2026
- ` ${chalk4.dim(t("install_installed_to") + ":")} ${configRoot}`,
2027
- ` ${chalk4.dim(t("install_skills_count") + ":")} ${totalSkills} \u4E2A ${chalk4.dim("|")} ${chalk4.dim(t("install_agents_count") + ":")} ${totalAgents} \u4E2A`,
3073
+ ` ${chalk6.dim(t("install_installed_to") + ":")} ${configRoot}`,
3074
+ ` ${chalk6.dim(t("install_skills_count") + ":")} ${totalSkills}${countSuffix ? " " + countSuffix : ""} ${chalk6.dim("|")} ${chalk6.dim(t("install_agents_count") + ":")} ${totalAgents}${countSuffix ? " " + countSuffix : ""}`,
2028
3075
  "",
2029
- chalk4.bold(` ${t("install_enhanced_next_steps")}:`),
2030
- ` ${chalk4.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk4.green(toolName.toLowerCase())}`,
2031
- ` ${chalk4.cyan("2.")} ${t("install_enhanced_try")}: ${chalk4.green("\u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50")}`,
2032
- ` ${chalk4.cyan("3.")} ${t("install_enhanced_more")}: ${chalk4.green("install-helper list")}`,
2033
- ` ${chalk4.cyan("4.")} ${t("install_enhanced_check")}: ${chalk4.green("install-helper doctor")}`,
3076
+ chalk6.bold(` ${t("install_enhanced_next_steps")}:`),
3077
+ ` ${chalk6.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk6.green(toolName.toLowerCase())}`,
3078
+ ` ${chalk6.cyan("2.")} ${t("install_enhanced_try")}: ${chalk6.green(t("try_prompt"))}`,
3079
+ ` ${chalk6.cyan("3.")} ${t("install_enhanced_more")}: ${chalk6.green("install-helper list")}`,
3080
+ ` ${chalk6.cyan("4.")} ${t("install_enhanced_check")}: ${chalk6.green("install-helper doctor")}`,
2034
3081
  ""
2035
3082
  ];
2036
3083
  if (successResults.length === 1) {
3084
+ const plugin = findPlugin(successResults[0].pluginId);
3085
+ const docsPath = plugin ? `${plugin.dir}/quickstart.md` : `${successResults[0].pluginId}/quickstart.md`;
2037
3086
  lines.push(
2038
- ` ${chalk4.dim(t("install_enhanced_docs"))}: ${chalk4.dim(`plugins-official/${successResults[0].pluginId}/quickstart.md`)}`
3087
+ ` ${chalk6.dim(t("install_enhanced_docs"))}: ${chalk6.dim(docsPath)}`
2039
3088
  );
2040
3089
  }
2041
3090
  console.log();
@@ -2046,34 +3095,32 @@ function printEnhancedSummary(results, tool, configRoot) {
2046
3095
  }
2047
3096
 
2048
3097
  // src/commands/init.ts
2049
- import { existsSync as existsSync10, readFileSync as readFileSync5 } from "fs";
2050
- import { join as join10 } from "path";
2051
- var BACK2 = "__back__";
2052
- var CANCEL2 = "__cancel__";
3098
+ import { existsSync as existsSync15, readFileSync as readFileSync9 } from "fs";
3099
+ import { join as join14 } from "path";
2053
3100
  async function initCommand() {
2054
3101
  const cwd = process.cwd();
2055
- if (existsSync10(join10(cwd, "package.json")) && (() => {
3102
+ if (existsSync15(join14(cwd, "package.json")) && (() => {
2056
3103
  try {
2057
- const pkg = JSON.parse(readFileSync5(join10(cwd, "package.json"), "utf-8"));
3104
+ const pkg = JSON.parse(readFileSync9(join14(cwd, "package.json"), "utf-8"));
2058
3105
  return pkg.name === "@cannbot-ai/install-helper";
2059
3106
  } catch {
2060
3107
  return false;
2061
3108
  }
2062
3109
  })()) {
2063
- logger.warn("\u5F53\u524D\u76EE\u5F55\u770B\u8D77\u6765\u662F install-helper \u5305\u76EE\u5F55");
2064
- 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");
2065
- logger.info("\u793A\u4F8B\uFF1Acd ~/my-project && install-helper");
3110
+ logger.warn(t("init_pkg_dir_warn"));
3111
+ logger.info(t("init_pkg_dir_hint"));
3112
+ logger.info(t("init_pkg_dir_example"));
2066
3113
  return;
2067
3114
  }
2068
3115
  printBanner(t("wizard_title"));
2069
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
3116
+ const spinner = createSpinner(t("loading_skills_list"));
2070
3117
  spinner.start();
2071
3118
  try {
2072
3119
  const repoManager = createRepositoryManager();
2073
3120
  await repoManager.ensureRepoAndScan();
2074
- spinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
3121
+ spinner.succeed(t("loading_skills_list_complete"));
2075
3122
  } catch {
2076
- spinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
3123
+ spinner.warn(t("loading_skills_list_failed"));
2077
3124
  }
2078
3125
  let step = 0;
2079
3126
  let mode = "plugin";
@@ -2107,8 +3154,7 @@ async function initCommand() {
2107
3154
  }
2108
3155
  }
2109
3156
  async function selectMode() {
2110
- printBoxTitle("\u9009\u62E9\u5B89\u88C5\u7C7B\u578B");
2111
- const CANCEL3 = "__cancel__";
3157
+ printBoxTitle(t("wizard_select_mode_title"));
2112
3158
  const choices = [
2113
3159
  {
2114
3160
  name: `> ${t("wizard_mode_plugin")} \u2014 ${t("wizard_mode_plugin_desc")}`,
@@ -2119,30 +3165,30 @@ async function selectMode() {
2119
3165
  value: "skill"
2120
3166
  },
2121
3167
  new Separator3("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2122
- { name: "x " + t("wizard_cancel"), value: CANCEL3 }
3168
+ { name: "x " + t("wizard_cancel"), value: CANCEL }
2123
3169
  ];
2124
3170
  showOperationHints();
2125
- const result = await select3({
3171
+ const result = await select4({
2126
3172
  message: t("wizard_select_mode"),
2127
3173
  choices,
2128
3174
  loop: false,
2129
3175
  theme: selectTheme
2130
3176
  });
2131
- if (result === CANCEL3) return null;
3177
+ if (result === CANCEL) return null;
2132
3178
  return result;
2133
3179
  }
2134
3180
  async function pluginInstallFlow() {
2135
3181
  const answers = await runWizard();
2136
3182
  if (!answers.confirmed) {
2137
3183
  if (answers.back) return "back";
2138
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
3184
+ logger.info(t("init_cancelled"));
2139
3185
  return "done";
2140
3186
  }
2141
3187
  const repoManager = createRepositoryManager();
2142
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...");
3188
+ const spinner = createSpinner(t("loading_plugin_data"));
2143
3189
  spinner.start();
2144
3190
  const repoPath = await repoManager.ensureRepo();
2145
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
3191
+ spinner.succeed(t("loading_plugin_data_complete"));
2146
3192
  const allPlugins = getAllPlugins();
2147
3193
  const selectedPlugins = answers.plugins.map(
2148
3194
  (id) => allPlugins.find((p) => p.id === id)
@@ -2196,7 +3242,7 @@ async function skillInstallFlow() {
2196
3242
  const result = await selectToolWithDetection();
2197
3243
  if (result === "back") return "back";
2198
3244
  if (result === "cancel") {
2199
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
3245
+ logger.info(t("init_cancelled"));
2200
3246
  return "done";
2201
3247
  }
2202
3248
  tool = result;
@@ -2205,12 +3251,12 @@ async function skillInstallFlow() {
2205
3251
  }
2206
3252
  case 1: {
2207
3253
  const result = await stepLevel();
2208
- if (result === BACK2) {
3254
+ if (result === BACK) {
2209
3255
  step = 0;
2210
3256
  break;
2211
3257
  }
2212
- if (result === CANCEL2) {
2213
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
3258
+ if (result === CANCEL) {
3259
+ logger.info(t("init_cancelled"));
2214
3260
  return "done";
2215
3261
  }
2216
3262
  level = result;
@@ -2218,25 +3264,25 @@ async function skillInstallFlow() {
2218
3264
  break;
2219
3265
  }
2220
3266
  case 2: {
2221
- const selectedSkills = await interactiveSkillSelect();
3267
+ const selectedSkills = await interactiveSkillSelect(tool, level);
2222
3268
  if (selectedSkills === "back") {
2223
3269
  step = 1;
2224
3270
  break;
2225
3271
  }
2226
3272
  if (selectedSkills === "cancel") {
2227
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
3273
+ logger.info(t("init_cancelled"));
2228
3274
  return "done";
2229
3275
  }
2230
3276
  if (selectedSkills.length === 0) {
2231
- logger.info("\u672A\u9009\u62E9\u4EFB\u4F55 Skill");
3277
+ logger.info(t("install_no_skill_selected"));
2232
3278
  return "done";
2233
3279
  }
2234
3280
  const repoManager = createRepositoryManager();
2235
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...");
3281
+ const spinner = createSpinner(t("loading_plugin_data"));
2236
3282
  spinner.start();
2237
3283
  const repoPath = await repoManager.ensureRepo();
2238
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
2239
- logger.info(`${t("skill_install_progress")} ${chalk5.bold(selectedSkills.length)} \u4E2A Skills...`);
3284
+ spinner.succeed(t("loading_plugin_data_complete"));
3285
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk7.bold(String(selectedSkills.length)))}...`);
2240
3286
  const results = await installSkills(selectedSkills, tool, level, repoPath);
2241
3287
  let successCount = 0;
2242
3288
  let failCount = 0;
@@ -2254,10 +3300,10 @@ async function skillInstallFlow() {
2254
3300
  }
2255
3301
  const configRoot = getConfigRoot(tool, level);
2256
3302
  logger.blank();
2257
- logger.success(`${t("skill_install_done")}: ${chalk5.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk5.red(failCount + " \u5931\u8D25") : chalk5.dim(failCount + " \u5931\u8D25")}`);
3303
+ 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)))}`);
2258
3304
  logger.blank();
2259
- logger.info(`\u5B89\u88C5\u5230: ${chalk5.cyan(configRoot + "/skills/")}`);
2260
- logger.info(`\u542F\u52A8 ${chalk5.green(tool)} \u5373\u53EF\u4F7F\u7528`);
3305
+ logger.info(`${t("install_to")}: ${chalk7.cyan(configRoot + "/skills/")}`);
3306
+ logger.info(`${t("start_to_use").replace("{tool}", chalk7.green(tool))}`);
2261
3307
  logger.blank();
2262
3308
  return "done";
2263
3309
  }
@@ -2279,32 +3325,31 @@ async function listCommand() {
2279
3325
  }
2280
3326
 
2281
3327
  // src/commands/doctor.ts
2282
- import chalk6 from "chalk";
2283
- import { existsSync as existsSync11, readdirSync as readdirSync4, lstatSync as lstatSync3, unlinkSync as unlinkSync3, mkdirSync as mkdirSync6 } from "fs";
2284
- import { join as join11 } from "path";
3328
+ import chalk8 from "chalk";
3329
+ import { existsSync as existsSync16, readdirSync as readdirSync5, lstatSync as lstatSync2, unlinkSync as unlinkSync6, mkdirSync as mkdirSync7 } from "fs";
3330
+ import { join as join15 } from "path";
2285
3331
  async function doctorCommand(options = {}) {
2286
3332
  console.log();
2287
- console.log(chalk6.bold(` ${t("doctor_title")}`));
2288
- console.log(chalk6.dim(" " + "\u2500".repeat(46)));
2289
- let errors = 0;
3333
+ console.log(chalk8.bold(` ${t("doctor_title")}`));
3334
+ console.log(chalk8.dim(" " + "\u2500".repeat(46)));
2290
3335
  let warnings = 0;
2291
3336
  let fixes = 0;
2292
3337
  console.log();
2293
- console.log(chalk6.bold(` ${t("doctor_tools")}`));
3338
+ console.log(chalk8.bold(` ${t("doctor_tools")}`));
2294
3339
  const detectedTools = await detectTools();
2295
3340
  const allTools = getAllTools();
2296
3341
  for (const tool of allTools) {
2297
3342
  const detected = detectedTools.find((d) => d.name === tool);
2298
3343
  if (detected) {
2299
3344
  console.log(
2300
- chalk6.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
3345
+ chalk8.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
2301
3346
  );
2302
3347
  } else {
2303
- console.log(chalk6.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 \u672A\u5B89\u88C5`);
3348
+ console.log(chalk8.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 ${t("doctor_not_installed")}`);
2304
3349
  }
2305
3350
  }
2306
3351
  console.log();
2307
- console.log(chalk6.bold(` ${t("doctor_plugins")}`));
3352
+ console.log(chalk8.bold(` ${t("doctor_plugins")}`));
2308
3353
  const plugins = getAllPlugins();
2309
3354
  const installed = scanInstalled();
2310
3355
  const installedMap = new Map(installed.map((p) => [p.id, p]));
@@ -2312,80 +3357,80 @@ async function doctorCommand(options = {}) {
2312
3357
  const inst = installedMap.get(plugin.id);
2313
3358
  if (inst) {
2314
3359
  console.log(
2315
- chalk6.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
3360
+ chalk8.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
2316
3361
  );
2317
3362
  } else {
2318
- console.log(chalk6.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 \u672A\u5B89\u88C5`);
3363
+ console.log(chalk8.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 ${t("doctor_not_installed")}`);
2319
3364
  }
2320
3365
  }
2321
3366
  console.log();
2322
- console.log(chalk6.bold(` ${t("doctor_links")}`));
3367
+ console.log(chalk8.bold(` ${t("doctor_links")}`));
2323
3368
  const primaryTool = detectedTools[0]?.name || "opencode";
2324
3369
  const configRoot = getConfigRoot(primaryTool, "project");
2325
3370
  const skillsDir = getSkillsDir(configRoot);
2326
3371
  const agentsDir = getAgentsDir(configRoot);
2327
- if (existsSync11(skillsDir)) {
3372
+ if (existsSync16(skillsDir)) {
2328
3373
  const brokenLinks = checkBrokenLinks(skillsDir);
2329
3374
  if (brokenLinks === 0) {
2330
- const count = readdirSync4(skillsDir).length;
2331
- console.log(chalk6.green(" \u2713") + ` ${skillsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
3375
+ const count = readdirSync5(skillsDir).length;
3376
+ console.log(chalk8.green(" \u2713") + ` ${skillsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2332
3377
  } else {
2333
- console.log(chalk6.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
3378
+ console.log(chalk8.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2334
3379
  warnings++;
2335
3380
  if (options.fix) {
2336
3381
  const fixed = fixBrokenLinks(skillsDir);
2337
3382
  fixes += fixed;
2338
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
3383
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2339
3384
  }
2340
3385
  }
2341
3386
  } else {
2342
- console.log(chalk6.dim(" \u2014") + ` ${skillsDir} \u2014 \u4E0D\u5B58\u5728`);
3387
+ console.log(chalk8.dim(" \u2014") + ` ${skillsDir} \u2014 ${t("doctor_not_exist")}`);
2343
3388
  if (options.fix) {
2344
- mkdirSync6(skillsDir, { recursive: true });
3389
+ mkdirSync7(skillsDir, { recursive: true });
2345
3390
  fixes++;
2346
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${skillsDir}`);
3391
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", skillsDir)}`);
2347
3392
  }
2348
3393
  }
2349
- if (existsSync11(agentsDir)) {
3394
+ if (existsSync16(agentsDir)) {
2350
3395
  const brokenLinks = checkBrokenLinks(agentsDir);
2351
3396
  if (brokenLinks === 0) {
2352
- const count = readdirSync4(agentsDir).length;
2353
- console.log(chalk6.green(" \u2713") + ` ${agentsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
3397
+ const count = readdirSync5(agentsDir).length;
3398
+ console.log(chalk8.green(" \u2713") + ` ${agentsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2354
3399
  } else {
2355
- console.log(chalk6.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
3400
+ console.log(chalk8.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2356
3401
  warnings++;
2357
3402
  if (options.fix) {
2358
3403
  const fixed = fixBrokenLinks(agentsDir);
2359
3404
  fixes += fixed;
2360
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
3405
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2361
3406
  }
2362
3407
  }
2363
3408
  } else {
2364
- console.log(chalk6.dim(" \u2014") + ` ${agentsDir} \u2014 \u4E0D\u5B58\u5728`);
3409
+ console.log(chalk8.dim(" \u2014") + ` ${agentsDir} \u2014 ${t("doctor_not_exist")}`);
2365
3410
  if (options.fix) {
2366
- mkdirSync6(agentsDir, { recursive: true });
3411
+ mkdirSync7(agentsDir, { recursive: true });
2367
3412
  fixes++;
2368
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${agentsDir}`);
3413
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", agentsDir)}`);
2369
3414
  }
2370
3415
  }
2371
3416
  console.log();
2372
- console.log(chalk6.bold(` ${t("doctor_config")}`));
3417
+ console.log(chalk8.bold(` ${t("doctor_config")}`));
2373
3418
  const configFile = getConfigFileName(primaryTool);
2374
- const configPath = join11(process.cwd(), configFile);
2375
- if (existsSync11(configPath)) {
2376
- console.log(chalk6.green(" \u2713") + ` ${configFile} \u5B58\u5728`);
3419
+ const configPath = join15(process.cwd(), configFile);
3420
+ if (existsSync16(configPath)) {
3421
+ console.log(chalk8.green(" \u2713") + ` ${configFile} ${t("doctor_config_exists")}`);
2377
3422
  } else {
2378
- console.log(chalk6.dim(" \u2014") + ` ${configFile} \u4E0D\u5B58\u5728`);
3423
+ console.log(chalk8.dim(" \u2014") + ` ${configFile} ${t("doctor_config_not_exist")}`);
2379
3424
  }
2380
3425
  console.log();
2381
- console.log(chalk6.dim(" " + "\u2500".repeat(46)));
3426
+ console.log(chalk8.dim(" " + "\u2500".repeat(46)));
2382
3427
  if (options.fix && fixes > 0) {
2383
3428
  console.log(
2384
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF, ${fixes} \u9879\u5DF2\u4FEE\u590D`
3429
+ ` ${t("doctor_result")}: ${t("doctor_result_with_fix").replace("{warnings}", String(warnings)).replace("{fixes}", String(fixes))}`
2385
3430
  );
2386
3431
  } else {
2387
3432
  console.log(
2388
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF`
3433
+ ` ${t("doctor_result")}: ${t("doctor_result_without_fix").replace("{warnings}", String(warnings))}`
2389
3434
  );
2390
3435
  }
2391
3436
  console.log();
@@ -2393,13 +3438,13 @@ async function doctorCommand(options = {}) {
2393
3438
  function checkBrokenLinks(dir) {
2394
3439
  let broken = 0;
2395
3440
  try {
2396
- const entries = readdirSync4(dir);
3441
+ const entries = readdirSync5(dir);
2397
3442
  for (const entry of entries) {
2398
- const fullPath = join11(dir, entry);
3443
+ const fullPath = join15(dir, entry);
2399
3444
  try {
2400
- const stats = lstatSync3(fullPath);
3445
+ const stats = lstatSync2(fullPath);
2401
3446
  if (stats.isSymbolicLink()) {
2402
- if (!existsSync11(fullPath)) {
3447
+ if (!existsSync16(fullPath)) {
2403
3448
  broken++;
2404
3449
  }
2405
3450
  }
@@ -2414,14 +3459,14 @@ function checkBrokenLinks(dir) {
2414
3459
  function fixBrokenLinks(dir) {
2415
3460
  let fixed = 0;
2416
3461
  try {
2417
- const entries = readdirSync4(dir);
3462
+ const entries = readdirSync5(dir);
2418
3463
  for (const entry of entries) {
2419
- const fullPath = join11(dir, entry);
3464
+ const fullPath = join15(dir, entry);
2420
3465
  try {
2421
- const stats = lstatSync3(fullPath);
3466
+ const stats = lstatSync2(fullPath);
2422
3467
  if (stats.isSymbolicLink()) {
2423
- if (!existsSync11(fullPath)) {
2424
- unlinkSync3(fullPath);
3468
+ if (!existsSync16(fullPath)) {
3469
+ unlinkSync6(fullPath);
2425
3470
  fixed++;
2426
3471
  }
2427
3472
  }
@@ -2434,36 +3479,35 @@ function fixBrokenLinks(dir) {
2434
3479
  }
2435
3480
 
2436
3481
  // src/commands/status.ts
2437
- import chalk7 from "chalk";
3482
+ import chalk9 from "chalk";
2438
3483
  async function statusCommand() {
2439
- const plugins = getAllPlugins();
2440
3484
  const installed = scanInstalled();
2441
3485
  const installedMap = new Map(installed.map((p) => [p.id, p]));
2442
3486
  console.log();
2443
- console.log(chalk7.bold(` ${t("status_title")}`));
3487
+ console.log(chalk9.bold(` ${t("status_title")}`));
2444
3488
  console.log();
2445
3489
  if (installed.length === 0) {
2446
- console.log(chalk7.dim(` ${t("status_none")}`));
2447
- console.log(chalk7.dim(` ${t("status_hint")}`));
3490
+ console.log(chalk9.dim(` ${t("status_none")}`));
3491
+ console.log(chalk9.dim(` ${t("status_hint")}`));
2448
3492
  console.log();
2449
3493
  return;
2450
3494
  }
2451
3495
  for (const inst of installed) {
2452
3496
  console.log(
2453
- chalk7.green(" \u2713") + ` ${inst.displayName}` + chalk7.dim(` (${inst.tool}, ${inst.level})`)
3497
+ chalk9.green(" \u2713") + ` ${inst.displayName}` + chalk9.dim(` (${inst.tool}, ${inst.level})`)
2454
3498
  );
2455
3499
  console.log(
2456
- chalk7.dim(` ${inst.skillsCount} skills, ${inst.agentsCount} agents`)
3500
+ chalk9.dim(` ${inst.skillsCount} skills, ${inst.agentsCount} agents`)
2457
3501
  );
2458
- console.log(chalk7.dim(` ${inst.configRoot}`));
2459
- console.log(chalk7.dim(` ${t("status_install_time")}: ${inst.installTime}`));
3502
+ console.log(chalk9.dim(` ${inst.configRoot}`));
3503
+ console.log(chalk9.dim(` ${t("status_install_time")}: ${inst.installTime}`));
2460
3504
  console.log();
2461
3505
  }
2462
3506
  }
2463
3507
 
2464
3508
  // src/commands/install.ts
2465
3509
  import { confirm } from "@inquirer/prompts";
2466
- import chalk8 from "chalk";
3510
+ import chalk10 from "chalk";
2467
3511
  async function installCommand(names, options) {
2468
3512
  if (options.list) {
2469
3513
  try {
@@ -2474,79 +3518,150 @@ async function installCommand(names, options) {
2474
3518
  listAllSkills();
2475
3519
  return;
2476
3520
  }
3521
+ if (options.all) {
3522
+ const scanSpinner2 = createSpinner(t("loading_skills_list"));
3523
+ scanSpinner2.start();
3524
+ try {
3525
+ const scanRepoManager = createRepositoryManager();
3526
+ await scanRepoManager.ensureRepoAndScan();
3527
+ scanSpinner2.succeed(t("loading_skills_list_complete"));
3528
+ } catch {
3529
+ scanSpinner2.warn(t("loading_skills_list_failed"));
3530
+ }
3531
+ const allSkills = getAllSkills();
3532
+ const skillIds = allSkills.map((s) => s.id);
3533
+ if (skillIds.length === 0) {
3534
+ logger.error(t("install_no_skill_selected"));
3535
+ return;
3536
+ }
3537
+ let tool2;
3538
+ if (options.tool) {
3539
+ tool2 = validateTool(options.tool);
3540
+ } else {
3541
+ const detected = await selectToolWithDetection();
3542
+ if (detected === "back" || detected === "cancel") {
3543
+ logger.info(t("init_cancelled"));
3544
+ return;
3545
+ }
3546
+ tool2 = detected;
3547
+ }
3548
+ const level2 = options.level ? validateLevel(options.level) : "project";
3549
+ const repoManager = createRepositoryManager();
3550
+ const spinner = createSpinner(t("loading_plugin_data"));
3551
+ spinner.start();
3552
+ const repoPath = await repoManager.ensureRepo();
3553
+ spinner.succeed(t("loading_plugin_data_complete"));
3554
+ logger.info(`${t("skill_all_install_progress").replace("{count}", chalk10.bold(String(skillIds.length)))}`);
3555
+ const results = await installSkills(skillIds, tool2, level2, repoPath);
3556
+ let successCount = 0;
3557
+ let failCount = 0;
3558
+ const total = results.length;
3559
+ for (let i = 0; i < results.length; i++) {
3560
+ const result = results[i];
3561
+ const progress = `[${i + 1}/${total}]`;
3562
+ if (result.success) {
3563
+ logger.success(`${progress} ${result.skillId}`);
3564
+ successCount++;
3565
+ } else {
3566
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
3567
+ failCount++;
3568
+ }
3569
+ }
3570
+ const configRoot = getConfigRoot(tool2, level2);
3571
+ logger.blank();
3572
+ 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)))}`);
3573
+ logger.blank();
3574
+ logger.info(`${t("install_to")}: ${chalk10.cyan(configRoot + "/skills/")}`);
3575
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
3576
+ logger.blank();
3577
+ return;
3578
+ }
2477
3579
  if (names.length === 0) {
2478
- const scanSpinner2 = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
3580
+ const scanSpinner2 = createSpinner(t("loading_skills_list"));
2479
3581
  scanSpinner2.start();
2480
3582
  try {
2481
3583
  const scanRepoManager = createRepositoryManager();
2482
3584
  await scanRepoManager.ensureRepoAndScan();
2483
- scanSpinner2.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
3585
+ scanSpinner2.succeed(t("loading_skills_list_complete"));
2484
3586
  } catch {
2485
- scanSpinner2.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
3587
+ scanSpinner2.warn(t("loading_skills_list_failed"));
2486
3588
  }
2487
3589
  let tool2;
2488
3590
  if (options.tool) {
2489
- tool2 = options.tool;
3591
+ tool2 = validateTool(options.tool);
2490
3592
  } else {
2491
3593
  const detected = await selectToolWithDetection();
2492
- if (detected === "back") return;
3594
+ if (detected === "back" || detected === "cancel") {
3595
+ logger.info(t("init_cancelled"));
3596
+ return;
3597
+ }
2493
3598
  tool2 = detected;
2494
3599
  }
3600
+ const level2 = options.level ? validateLevel(options.level) : "project";
2495
3601
  let step = 1;
2496
3602
  while (true) {
2497
3603
  switch (step) {
2498
3604
  case 1: {
2499
- const selectedSkills = await interactiveSkillSelect();
3605
+ const selectedSkills = await interactiveSkillSelect(tool2, level2);
2500
3606
  if (selectedSkills === "back") {
2501
3607
  const redetected = await selectToolWithDetection();
2502
- if (redetected === "back") return;
3608
+ if (redetected === "back" || redetected === "cancel") {
3609
+ logger.info(t("init_cancelled"));
3610
+ return;
3611
+ }
2503
3612
  tool2 = redetected;
2504
3613
  break;
2505
3614
  }
3615
+ if (selectedSkills === "cancel") {
3616
+ logger.info(t("init_cancelled"));
3617
+ return;
3618
+ }
2506
3619
  if (selectedSkills.length === 0) {
2507
- logger.info("\u672A\u9009\u62E9\u4EFB\u4F55 Skill");
3620
+ logger.info(t("install_no_skill_selected"));
2508
3621
  return;
2509
3622
  }
2510
3623
  names = selectedSkills;
2511
3624
  const repoManager = createRepositoryManager();
2512
- const spinner = createSpinner(t("install_cloning"));
3625
+ const spinner = createSpinner(t("loading_plugin_data"));
2513
3626
  spinner.start();
2514
3627
  const repoPath = await repoManager.ensureRepo();
2515
- spinner.succeed(t("install_repo_ready"));
2516
- logger.info(`${t("skill_install_progress")} ${chalk8.bold(names.length)} \u4E2A Skills...`);
2517
- const level2 = options.level || "project";
3628
+ spinner.succeed(t("loading_plugin_data_complete"));
3629
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk10.bold(String(names.length)))}...`);
2518
3630
  const results = await installSkills(names, tool2, level2, repoPath);
2519
3631
  let successCount = 0;
2520
3632
  let failCount = 0;
2521
- for (const result of results) {
3633
+ const total = results.length;
3634
+ for (let i = 0; i < results.length; i++) {
3635
+ const result = results[i];
3636
+ const progress = `[${i + 1}/${total}]`;
2522
3637
  if (result.success) {
2523
- logger.success(`${result.skillId}`);
3638
+ logger.success(`${progress} ${result.skillId}`);
2524
3639
  successCount++;
2525
3640
  } else {
2526
- logger.error(`${result.skillId}: ${result.error}`);
3641
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
2527
3642
  failCount++;
2528
3643
  }
2529
3644
  }
2530
3645
  const configRoot = getConfigRoot(tool2, level2);
2531
3646
  logger.blank();
2532
- logger.success(`${t("skill_install_done")}: ${chalk8.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk8.red(failCount + " \u5931\u8D25") : chalk8.dim(failCount + " \u5931\u8D25")}`);
3647
+ 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)))}`);
2533
3648
  logger.blank();
2534
- logger.info(`\u5B89\u88C5\u5230: ${chalk8.cyan(configRoot + "/skills/")}`);
2535
- logger.info(`\u542F\u52A8 ${chalk8.green(tool2)} \u5373\u53EF\u4F7F\u7528`);
3649
+ logger.info(`${t("install_to")}: ${chalk10.cyan(configRoot + "/skills/")}`);
3650
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
2536
3651
  logger.blank();
2537
3652
  return;
2538
3653
  }
2539
3654
  }
2540
3655
  }
2541
3656
  }
2542
- const scanSpinner = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
3657
+ const scanSpinner = createSpinner(t("loading_skills_list"));
2543
3658
  scanSpinner.start();
2544
3659
  try {
2545
3660
  const scanRepoManager = createRepositoryManager();
2546
3661
  await scanRepoManager.ensureRepoAndScan();
2547
- scanSpinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
3662
+ scanSpinner.succeed(t("loading_skills_list_complete"));
2548
3663
  } catch {
2549
- scanSpinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
3664
+ scanSpinner.warn(t("loading_skills_list_failed"));
2550
3665
  }
2551
3666
  const plugins = [];
2552
3667
  const skills = [];
@@ -2563,22 +3678,25 @@ async function installCommand(names, options) {
2563
3678
  }
2564
3679
  logger.error(`${t("error_plugin_not_found")}: ${name}`);
2565
3680
  const allPlugins = getAllPlugins();
2566
- logger.info("\u53EF\u7528\u63D2\u4EF6:");
3681
+ logger.info(t("available_plugins") + ":");
2567
3682
  for (const p of allPlugins) {
2568
3683
  logger.step(` ${p.id} (${p.aliases.join(", ")})`);
2569
3684
  }
2570
- logger.info(`\u53EF\u7528 Skills: ${chalk8.cyan("install-helper install --list")}`);
3685
+ logger.info(`${t("available_skills")}: ${chalk10.cyan("install-helper install --list")}`);
2571
3686
  return;
2572
3687
  }
2573
3688
  let tool;
2574
3689
  if (options.tool) {
2575
- tool = options.tool;
3690
+ tool = validateTool(options.tool);
2576
3691
  } else {
2577
3692
  const detected = await selectToolWithDetection();
2578
- if (detected === "back") return;
3693
+ if (detected === "back" || detected === "cancel") {
3694
+ logger.info(t("init_cancelled"));
3695
+ return;
3696
+ }
2579
3697
  tool = detected;
2580
3698
  }
2581
- const level = options.level || "project";
3699
+ const level = options.level ? validateLevel(options.level) : "project";
2582
3700
  if (plugins.length > 0) {
2583
3701
  const configRoot = getConfigRoot(tool, level);
2584
3702
  const manifests = readAllManifests(configRoot);
@@ -2606,16 +3724,18 @@ async function installCommand(names, options) {
2606
3724
  }
2607
3725
  if (pluginsToInstall.length > 0) {
2608
3726
  const repoManager = createRepositoryManager();
2609
- const spinner = createSpinner(t("install_cloning"));
3727
+ const spinner = createSpinner(t("loading_plugin_data"));
2610
3728
  spinner.start();
2611
3729
  const repoPath = await repoManager.ensureRepo();
2612
- spinner.succeed(t("install_repo_ready"));
3730
+ spinner.succeed(t("loading_plugin_data_complete"));
2613
3731
  const allPlugins = getAllPlugins();
2614
3732
  const results = await installPlugins(
2615
3733
  pluginsToInstall,
2616
3734
  tool,
2617
3735
  level,
2618
- repoPath
3736
+ repoPath,
3737
+ void 0,
3738
+ options.yes
2619
3739
  );
2620
3740
  const summary = results.map((result) => {
2621
3741
  const plugin = allPlugins.find((p) => p.id === result.pluginId);
@@ -2633,32 +3753,35 @@ async function installCommand(names, options) {
2633
3753
  }
2634
3754
  if (skills.length > 0) {
2635
3755
  const repoManager = createRepositoryManager();
2636
- const spinner = createSpinner(t("install_cloning"));
3756
+ const spinner = createSpinner(t("loading_plugin_data"));
2637
3757
  spinner.start();
2638
3758
  const repoPath = await repoManager.ensureRepo();
2639
- spinner.succeed(t("install_repo_ready"));
2640
- logger.info(`${t("skill_install_progress")} ${chalk8.bold(skills.length)} \u4E2A Skills...`);
3759
+ spinner.succeed(t("loading_plugin_data_complete"));
3760
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk10.bold(String(skills.length)))}...`);
2641
3761
  const results = await installSkills(skills, tool, level, repoPath);
2642
3762
  let successCount = 0;
2643
3763
  let failCount = 0;
2644
- for (const result of results) {
3764
+ const total = results.length;
3765
+ for (let i = 0; i < results.length; i++) {
3766
+ const result = results[i];
3767
+ const progress = `[${i + 1}/${total}]`;
2645
3768
  if (result.success) {
2646
- logger.success(`${result.skillId}`);
3769
+ logger.success(`${progress} ${result.skillId}`);
2647
3770
  successCount++;
2648
3771
  } else {
2649
- logger.error(`${result.skillId}: ${result.error}`);
3772
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
2650
3773
  failCount++;
2651
3774
  }
2652
3775
  }
2653
3776
  logger.blank();
2654
- logger.success(`${t("skill_install_done")}: ${chalk8.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk8.red(failCount + " \u5931\u8D25") : chalk8.dim(failCount + " \u5931\u8D25")}`);
3777
+ 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)))}`);
2655
3778
  logger.blank();
2656
- logger.info(`\u5B89\u88C5\u5230: ${chalk8.cyan(getConfigRoot(tool, level) + "/skills/")}`);
2657
- logger.info(`\u542F\u52A8 ${chalk8.green(tool)} \u5373\u53EF\u4F7F\u7528`);
3779
+ logger.info(`${t("install_to")}: ${chalk10.cyan(getConfigRoot(tool, level) + "/skills/")}`);
3780
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool))}`);
2658
3781
  logger.blank();
2659
3782
  }
2660
3783
  }
2661
- async function installPlugins(pluginIds, tool, level, repoPath) {
3784
+ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes) {
2662
3785
  const results = [];
2663
3786
  const total = pluginIds.length;
2664
3787
  for (let i = 0; i < pluginIds.length; i++) {
@@ -2670,7 +3793,9 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
2670
3793
  pluginId,
2671
3794
  tool,
2672
3795
  level,
2673
- repoPath
3796
+ repoPath,
3797
+ installPath,
3798
+ yes
2674
3799
  });
2675
3800
  if (result.success) {
2676
3801
  spinner.succeed(
@@ -2686,11 +3811,12 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
2686
3811
  }
2687
3812
 
2688
3813
  // src/commands/uninstall.ts
2689
- import { existsSync as existsSync12, unlinkSync as unlinkSync4, rmdirSync as rmdirSync2, readdirSync as readdirSync5, lstatSync as lstatSync4 } from "fs";
2690
- import chalk9 from "chalk";
3814
+ import { existsSync as existsSync17, unlinkSync as unlinkSync7, rmdirSync as rmdirSync2, readdirSync as readdirSync6 } from "fs";
3815
+ import { resolve, basename as basename2 } from "path";
3816
+ import chalk11 from "chalk";
2691
3817
  async function uninstallCommand(names, options) {
2692
- const tool = options.tool || "opencode";
2693
- const level = options.level || "project";
3818
+ const tool = options.tool ? validateTool(options.tool) : "opencode";
3819
+ const level = options.level ? validateLevel(options.level) : "project";
2694
3820
  const plugins = [];
2695
3821
  const skills = [];
2696
3822
  for (const name of names) {
@@ -2704,40 +3830,50 @@ async function uninstallCommand(names, options) {
2704
3830
  skills.push(skill.id);
2705
3831
  continue;
2706
3832
  }
2707
- logger.error(`\u672A\u627E\u5230: ${name}`);
3833
+ logger.error(`${t("uninstall_not_found")}: ${name}`);
2708
3834
  }
2709
3835
  for (const pluginId of plugins) {
2710
3836
  const plugin = findPlugin(pluginId);
2711
3837
  if (!plugin) continue;
2712
3838
  const record = readRecord(plugin.id);
2713
3839
  if (!record) {
2714
- 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`);
2715
- 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`);
3840
+ logger.warn(`${plugin.displayName} ${t("uninstall_no_record")}`);
3841
+ logger.info(`${t("uninstall_no_record_hint")}`);
2716
3842
  continue;
2717
3843
  }
2718
- logger.info(`\u6B63\u5728\u5378\u8F7D ${plugin.displayName}...`);
3844
+ logger.info(`${t("uninstall_in_progress")} ${plugin.displayName}...`);
3845
+ const configRoot = getConfigRoot(record.tool, record.level);
3846
+ const allowedBases = [resolve(configRoot), resolve(record.installPath)];
3847
+ const isSafePath = (p) => {
3848
+ const resolved = resolve(p);
3849
+ return allowedBases.some((base) => resolved === base || resolved.startsWith(base + "/"));
3850
+ };
2719
3851
  let removedFiles = 0;
2720
3852
  let removedDirs = 0;
2721
3853
  for (const filePath of record.files) {
3854
+ if (!isSafePath(filePath)) continue;
2722
3855
  try {
2723
- if (existsSync12(filePath) || isSymlink3(filePath)) {
2724
- unlinkSync4(filePath);
2725
- const name = filePath.split("/").pop() || filePath;
2726
- logger.step(` \u79FB\u9664: ${name}`);
3856
+ if (existsSync17(filePath) || isSymlink(filePath)) {
3857
+ unlinkSync7(filePath);
3858
+ const name = basename2(filePath);
3859
+ logger.step(` ${t("uninstall_remove_label")}: ${name}`);
2727
3860
  removedFiles++;
2728
3861
  }
2729
3862
  } catch {
2730
3863
  }
2731
3864
  }
2732
- const sortedDirs = [...record.directories].sort((a, b) => b.length - a.length);
3865
+ const sortedDirs = [...record.directories].filter(isSafePath).sort((a, b) => b.length - a.length);
2733
3866
  for (const dirPath of sortedDirs) {
2734
3867
  try {
2735
- if (existsSync12(dirPath)) {
2736
- const entries = readdirSync5(dirPath);
3868
+ if (resolve(dirPath) === resolve(configRoot)) {
3869
+ continue;
3870
+ }
3871
+ if (existsSync17(dirPath)) {
3872
+ const entries = readdirSync6(dirPath);
2737
3873
  if (entries.length === 0) {
2738
3874
  rmdirSync2(dirPath);
2739
- const name = dirPath.split("/").pop() || dirPath;
2740
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${name}/`);
3875
+ const name = basename2(dirPath);
3876
+ logger.step(` ${t("uninstall_clean_empty_dir")}: ${name}/`);
2741
3877
  removedDirs++;
2742
3878
  }
2743
3879
  }
@@ -2746,15 +3882,32 @@ async function uninstallCommand(names, options) {
2746
3882
  }
2747
3883
  deleteRecord(plugin.id);
2748
3884
  removeInstalledPlugin(plugin.id);
3885
+ const backups = findBackups(configRoot);
3886
+ const otherBackups = backups.filter((b) => b.pluginId !== pluginId);
3887
+ if (otherBackups.length > 0) {
3888
+ const choice = await showRestorePrompt(otherBackups);
3889
+ if (choice !== "none") {
3890
+ const restored = restoreBackup(choice, configRoot, record.tool);
3891
+ if (restored) {
3892
+ const backupInfo = otherBackups.find((b) => b.filePath === choice);
3893
+ logger.success(`${t("backup_restore_success")}: ${backupInfo?.pluginName || ""}`);
3894
+ deleteBackup(choice);
3895
+ }
3896
+ }
3897
+ }
3898
+ const currentBackups = backups.filter((b) => b.pluginId === pluginId);
3899
+ for (const backup of currentBackups) {
3900
+ deleteBackup(backup.filePath);
3901
+ }
2749
3902
  const totalRemoved = removedFiles + removedDirs;
2750
3903
  if (totalRemoved > 0) {
2751
- logger.success(`\u5DF2\u5378\u8F7D ${plugin.displayName}\uFF08\u79FB\u9664 ${removedFiles} \u4E2A\u6587\u4EF6\uFF0C${removedDirs} \u4E2A\u76EE\u5F55\uFF09`);
3904
+ logger.success(t("uninstall_summary_format").replace("{name}", plugin.displayName).replace("{files}", String(removedFiles)).replace("{dirs}", String(removedDirs)));
2752
3905
  } else {
2753
- logger.warn(`${plugin.displayName} \u7684\u6587\u4EF6\u5DF2\u4E0D\u5B58\u5728\uFF0C\u5DF2\u6E05\u7406\u5B89\u88C5\u8BB0\u5F55`);
3906
+ logger.warn(`${plugin.displayName}${t("uninstall_files_gone")}`);
2754
3907
  }
2755
3908
  }
2756
3909
  if (skills.length > 0) {
2757
- logger.info(`\u6B63\u5728\u5378\u8F7D ${skills.length} \u4E2A Skills...`);
3910
+ logger.info(`${t("uninstall_skills_in_progress")} ${t("skill_count_format").replace("{count}", String(skills.length))}...`);
2758
3911
  const results = await uninstallSkills(skills, tool, level);
2759
3912
  let successCount = 0;
2760
3913
  let failCount = 0;
@@ -2766,30 +3919,26 @@ async function uninstallCommand(names, options) {
2766
3919
  }
2767
3920
  }
2768
3921
  logger.blank();
2769
- logger.success(`${t("skill_uninstall_done")}: ${chalk9.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk9.red(failCount + " \u5931\u8D25") : chalk9.dim(failCount + " \u5931\u8D25")}`);
3922
+ 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)))}`);
2770
3923
  logger.blank();
2771
3924
  }
2772
3925
  }
2773
- function isSymlink3(path) {
2774
- try {
2775
- return lstatSync4(path).isSymbolicLink();
2776
- } catch {
2777
- return false;
2778
- }
2779
- }
2780
3926
 
2781
3927
  // src/commands/update.ts
2782
- import chalk10 from "chalk";
3928
+ import chalk12 from "chalk";
2783
3929
  async function updateCommand(pluginNames, options) {
2784
3930
  let tool;
2785
3931
  if (options.tool) {
2786
- tool = options.tool;
3932
+ tool = validateTool(options.tool);
2787
3933
  } else {
2788
3934
  const detected = await selectToolWithDetection();
2789
- if (detected === "back") return;
3935
+ if (detected === "back" || detected === "cancel") {
3936
+ logger.info(t("init_cancelled"));
3937
+ return;
3938
+ }
2790
3939
  tool = detected;
2791
3940
  }
2792
- const level = options.level || "project";
3941
+ const level = options.level ? validateLevel(options.level) : "project";
2793
3942
  let pluginsToUpdate = [];
2794
3943
  if (pluginNames.length === 0) {
2795
3944
  const configRoot = getConfigRoot(tool, level);
@@ -2797,7 +3946,7 @@ async function updateCommand(pluginNames, options) {
2797
3946
  pluginsToUpdate = manifests.map((m) => m.team);
2798
3947
  if (pluginsToUpdate.length === 0) {
2799
3948
  logger.info(t("update_no_plugins"));
2800
- logger.info(`\u4F7F\u7528 ${chalk10.cyan("install-helper install <plugin>")} \u5B89\u88C5\u63D2\u4EF6`);
3949
+ logger.info(t("update_install_hint").replace("{cmd}", chalk12.cyan("install-helper install <plugin>")));
2801
3950
  return;
2802
3951
  }
2803
3952
  } else {
@@ -2813,9 +3962,7 @@ async function updateCommand(pluginNames, options) {
2813
3962
  const repoManager = createRepositoryManager();
2814
3963
  const updateSpinner = createSpinner(t("update_updating") + "...");
2815
3964
  updateSpinner.start();
2816
- await repoManager.updateRepo();
2817
- const repoPath = repoManager.getRepoPath();
2818
- await repoManager.ensureRepoAndScan();
3965
+ const repoPath = await repoManager.ensureRepoAndScan();
2819
3966
  updateSpinner.succeed(t("install_repo_ready"));
2820
3967
  const allPlugins = getAllPlugins();
2821
3968
  const results = [];
@@ -2859,42 +4006,43 @@ async function updateCommand(pluginNames, options) {
2859
4006
  }
2860
4007
 
2861
4008
  // src/commands/info.ts
2862
- import chalk11 from "chalk";
2863
- import { existsSync as existsSync13, readdirSync as readdirSync6 } from "fs";
2864
- import { join as join12 } from "path";
4009
+ import chalk13 from "chalk";
4010
+ import { existsSync as existsSync18, readdirSync as readdirSync7 } from "fs";
4011
+ import { join as join16 } from "path";
2865
4012
  async function infoCommand(pluginName) {
2866
4013
  const plugin = findPlugin(pluginName);
2867
4014
  if (!plugin) {
2868
- console.log(chalk11.red(` ${t("info_not_found")}: ${pluginName}`));
4015
+ console.log(chalk13.red(` ${t("info_not_found")}: ${pluginName}`));
2869
4016
  return;
2870
4017
  }
2871
4018
  const installed = scanInstalled();
2872
- const isInstalled = installed.some((p) => p.id === plugin.id);
2873
- const statusText = isInstalled ? chalk11.green(`\u2713 ${t("status_installed")}`) : chalk11.dim(`\u2014 ${t("status_not_installed")}`);
4019
+ const inst = installed.find((p) => p.id === plugin.id);
4020
+ const isInstalled = !!inst;
4021
+ const statusText = isInstalled ? chalk13.green(`\u2713 ${t("status_installed")}`) : chalk13.dim(`\u2014 ${t("status_not_installed")}`);
2874
4022
  console.log();
2875
- console.log(chalk11.bold(` ${plugin.displayName}`));
2876
- console.log(chalk11.dim(" " + "\u2500".repeat(40)));
4023
+ console.log(chalk13.bold(` ${plugin.displayName}`));
4024
+ console.log(chalk13.dim(" " + "\u2500".repeat(40)));
2877
4025
  console.log();
2878
- console.log(` ${chalk11.dim(t("info_description") + ":")} ${plugin.description}`);
2879
- console.log(` ${chalk11.dim("ID:")} ${plugin.id}`);
2880
- console.log(` ${chalk11.dim(t("info_status") + ":")} ${statusText}`);
2881
- console.log(` ${chalk11.dim(t("info_skills") + ":")} ${plugin.skills}`);
2882
- console.log(` ${chalk11.dim(t("info_agents") + ":")} ${plugin.agents}`);
2883
- console.log(` ${chalk11.dim("\u522B\u540D:")} ${plugin.aliases.join(", ")}`);
4026
+ console.log(` ${chalk13.dim(t("info_description") + ":")} ${plugin.description}`);
4027
+ console.log(` ${chalk13.dim("ID:")} ${plugin.id}`);
4028
+ console.log(` ${chalk13.dim(t("info_status") + ":")} ${statusText}`);
4029
+ console.log(` ${chalk13.dim(t("info_skills") + ":")} ${inst ? inst.skillsCount : plugin.skills}`);
4030
+ console.log(` ${chalk13.dim(t("info_agents") + ":")} ${inst ? inst.agentsCount : plugin.agents}`);
4031
+ console.log(` ${chalk13.dim(t("info_aliases") + ":")} ${plugin.aliases.join(", ")}`);
2884
4032
  console.log();
2885
4033
  try {
2886
4034
  const repoManager = createRepositoryManager();
2887
4035
  const repoPath = await repoManager.ensureRepo();
2888
- const quickstartPath = join12(repoPath, plugin.dir, "quickstart.md");
2889
- if (existsSync13(quickstartPath)) {
2890
- console.log(` ${chalk11.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
4036
+ const quickstartPath = join16(repoPath, plugin.dir, "quickstart.md");
4037
+ if (existsSync18(quickstartPath)) {
4038
+ console.log(` ${chalk13.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
2891
4039
  }
2892
- const agentsDir = join12(repoPath, plugin.dir, "agents");
2893
- if (existsSync13(agentsDir)) {
2894
- const agentFiles = readdirSync6(agentsDir).filter((f) => f.endsWith(".md"));
4040
+ const agentsDir = join16(repoPath, plugin.dir, "agents");
4041
+ if (existsSync18(agentsDir)) {
4042
+ const agentFiles = readdirSync7(agentsDir).filter((f) => f.endsWith(".md"));
2895
4043
  if (agentFiles.length > 0) {
2896
4044
  console.log();
2897
- console.log(` ${chalk11.bold("Agents:")}`);
4045
+ console.log(` ${chalk13.bold("Agents:")}`);
2898
4046
  for (const agent of agentFiles) {
2899
4047
  console.log(` \u2022 ${agent.replace(".md", "")}`);
2900
4048
  }
@@ -2906,49 +4054,74 @@ async function infoCommand(pluginName) {
2906
4054
  }
2907
4055
 
2908
4056
  // src/commands/lang.ts
2909
- import chalk12 from "chalk";
4057
+ import chalk14 from "chalk";
2910
4058
  async function langCommand(action, value) {
2911
4059
  const config = readConfig();
2912
4060
  if (!action || action === "show") {
2913
4061
  console.log();
2914
- console.log(chalk12.bold(` ${t("lang_title")}`));
2915
- console.log(chalk12.dim(" " + "\u2500".repeat(40)));
4062
+ console.log(chalk14.bold(` ${t("lang_title")}`));
4063
+ console.log(chalk14.dim(" " + "\u2500".repeat(40)));
2916
4064
  console.log();
2917
- console.log(` ${chalk12.dim(t("lang_current") + ":")} ${config.language}`);
4065
+ console.log(` ${chalk14.dim(t("lang_current") + ":")} ${config.language}`);
2918
4066
  console.log();
2919
- console.log(chalk12.dim(" \u652F\u6301\u7684\u8BED\u8A00: zh_CN, en_US"));
2920
- console.log(chalk12.dim(" \u7528\u6CD5: install-helper lang set <language>"));
4067
+ console.log(chalk14.dim(` ${t("lang_supported")}`));
4068
+ console.log(chalk14.dim(` ${t("lang_usage")}`));
2921
4069
  console.log();
2922
4070
  return;
2923
4071
  }
2924
4072
  if (action === "set") {
2925
4073
  if (!value) {
2926
- console.log(chalk12.red(" \u8BF7\u6307\u5B9A\u8BED\u8A00: install-helper lang set zh_CN"));
4074
+ console.log(chalk14.red(` ${t("lang_specify")}`));
2927
4075
  return;
2928
4076
  }
2929
4077
  if (value !== "zh_CN" && value !== "en_US") {
2930
- console.log(chalk12.red(` ${t("lang_invalid")}`));
4078
+ console.log(chalk14.red(` ${t("lang_invalid")}`));
2931
4079
  return;
2932
4080
  }
2933
4081
  updateConfig({ language: value });
2934
4082
  setLanguage(value);
2935
4083
  console.log();
2936
- console.log(chalk12.green(` \u2713 ${t("lang_set")} ${value}`));
4084
+ console.log(chalk14.green(` \u2713 ${t("lang_set")} ${value}`));
2937
4085
  console.log();
2938
4086
  return;
2939
4087
  }
2940
- console.log(chalk12.red(` \u672A\u77E5\u64CD\u4F5C: ${action}`));
2941
- console.log(chalk12.dim(" \u652F\u6301\u7684\u64CD\u4F5C: show, set"));
4088
+ console.log(chalk14.red(` ${t("lang_unknown_action")}: ${action}`));
4089
+ console.log(chalk14.dim(` ${t("lang_supported_actions")}`));
2942
4090
  }
2943
4091
 
2944
4092
  // src/cli.ts
4093
+ import { readFileSync as readFileSync10, existsSync as existsSync19 } from "fs";
4094
+ import { fileURLToPath as fileURLToPath5 } from "url";
4095
+ import { dirname as dirname8, join as join17 } from "path";
4096
+ function readVersion() {
4097
+ try {
4098
+ const __filename5 = fileURLToPath5(import.meta.url);
4099
+ const __dirname5 = dirname8(__filename5);
4100
+ const candidates = [
4101
+ join17(__dirname5, "..", "package.json"),
4102
+ join17(__dirname5, "package.json"),
4103
+ join17(process.cwd(), "package.json")
4104
+ ];
4105
+ for (const pkgPath of candidates) {
4106
+ if (existsSync19(pkgPath)) {
4107
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
4108
+ if (pkg.name === "@cannbot-ai/install-helper" && pkg.version) {
4109
+ return pkg.version;
4110
+ }
4111
+ }
4112
+ }
4113
+ return "0.0.0";
4114
+ } catch {
4115
+ return "0.0.0";
4116
+ }
4117
+ }
2945
4118
  function createCLI() {
2946
4119
  const config = readConfig();
2947
4120
  if (config.language) {
2948
4121
  setLanguage(config.language);
2949
4122
  }
2950
4123
  const program2 = new Command();
2951
- program2.name("install-helper").description("Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.4");
4124
+ program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version(readVersion());
2952
4125
  program2.command("init", { isDefault: false }).description("Run interactive installation wizard").action(async () => {
2953
4126
  await initCommand();
2954
4127
  });
@@ -2961,7 +4134,7 @@ function createCLI() {
2961
4134
  program2.command("status").description("Show installed plugins").action(async () => {
2962
4135
  await statusCommand();
2963
4136
  });
2964
- 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) => {
4137
+ 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) => {
2965
4138
  await installCommand(names, options);
2966
4139
  });
2967
4140
  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) => {
@@ -2985,7 +4158,7 @@ function createCLI() {
2985
4158
  // src/index.ts
2986
4159
  process.on("uncaughtException", (error) => {
2987
4160
  if (error.name === "ExitPromptError") {
2988
- console.log("\n\u5DF2\u53D6\u6D88\u5B89\u88C5");
4161
+ console.log("\n" + t("init_cancelled"));
2989
4162
  process.exit(0);
2990
4163
  }
2991
4164
  throw error;