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

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