@cannbot-ai/install-helper 1.0.8 → 1.1.3

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
@@ -8,22 +8,1064 @@ import { select as select4, Separator as Separator3 } from "@inquirer/prompts";
8
8
  import chalk7 from "chalk";
9
9
 
10
10
  // src/ui/wizard.ts
11
- import { select, checkbox, Separator } from "@inquirer/prompts";
11
+ import { select, Separator } from "@inquirer/prompts";
12
12
  import chalk3 from "chalk";
13
13
 
14
14
  // src/core/registry.ts
15
- import { readdirSync, readFileSync } from "fs";
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";
16
26
  import { join, dirname } from "path";
17
27
  import { fileURLToPath } from "url";
18
- import { parse as parseYaml } from "yaml";
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_plugin: "\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
+ skill_select_category: "\u9009\u62E9 Skill \u7C7B\u522B",
94
+ skill_select_items: "\u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills",
95
+ skill_already_installed: "\u5DF2\u5B89\u88C5",
96
+ skill_select_all: "\u5168\u9009\u6240\u6709 Skills\uFF08\u5171 {count} \u4E2A\uFF09",
97
+ skill_select_all_category: "\u5168\u9009\u5F53\u524D\u7C7B\u522B",
98
+ skill_continue_or_install: "\u5DF2\u9009 {count} \u4E2A Skills\uFF0C\u8BF7\u9009\u62E9\u64CD\u4F5C",
99
+ skill_continue_select: "\u7EE7\u7EED\u9009\u62E9\u5176\u4ED6\u7C7B\u522B",
100
+ skill_confirm_install_count: "\u786E\u8BA4\u5B89\u88C5\uFF08\u5DF2\u9009 {count} \u4E2A Skills\uFF09",
101
+ skill_all_install_progress: "\u6B63\u5728\u5B89\u88C5\u5168\u90E8 {count} \u4E2A Skills...",
102
+ skill_install_progress: "\u6B63\u5728\u5B89\u88C5",
103
+ skill_install_done: "Skill \u5B89\u88C5\u5B8C\u6210",
104
+ skill_uninstall_done: "Skill \u5378\u8F7D\u5B8C\u6210",
105
+ skill_list_title: "\u53EF\u7528 Skills",
106
+ wizard_select_mode: "\u9009\u62E9\u5B89\u88C5\u7C7B\u578B",
107
+ wizard_mode_plugin: "\u5B89\u88C5 Plugin",
108
+ wizard_mode_plugin_desc: "\u5B8C\u6574\u5F00\u53D1\u5DE5\u4F5C\u6D41\uFF08Skills + Agents + \u914D\u7F6E\u6587\u4EF6\uFF09",
109
+ wizard_mode_skill: "\u5B89\u88C5 Skill",
110
+ wizard_mode_skill_desc: "\u4EC5\u5B89\u88C5\u9886\u57DF\u77E5\u8BC6\u6280\u80FD\uFF0C\u8BA9 AI \u638C\u63E1\u7279\u5B9A\u80FD\u529B",
111
+ wizard_back: "\u8FD4\u56DE",
112
+ wizard_cancel: "\u9000\u51FA",
113
+ wizard_no_selection: "\u672A\u9009\u62E9\u4EFB\u4F55\u9879\uFF0C\u8BF7\u9009\u62E9\u64CD\u4F5C",
114
+ wizard_back_to_reselect: "\u8FD4\u56DE",
115
+ status_title: "\u5DF2\u5B89\u88C5\u63D2\u4EF6",
116
+ status_none: "\u6682\u65E0\u5DF2\u5B89\u88C5\u7684\u63D2\u4EF6",
117
+ status_hint: "\u8FD0\u884C install-helper init \u5F00\u59CB\u5B89\u88C5",
118
+ status_install_time: "\u5B89\u88C5\u65F6\u95F4",
119
+ install_installed_to: "\u5B89\u88C5\u5230",
120
+ install_skills_count: "Skills",
121
+ install_agents_count: "Agents",
122
+ lang_supported: "\u652F\u6301\u7684\u8BED\u8A00: zh_CN, en_US",
123
+ lang_usage: "\u7528\u6CD5: install-helper lang set <language>",
124
+ lang_specify: "\u8BF7\u6307\u5B9A\u8BED\u8A00: install-helper lang set zh_CN",
125
+ lang_unknown_action: "\u672A\u77E5\u64CD\u4F5C",
126
+ lang_supported_actions: "\u652F\u6301\u7684\u64CD\u4F5C: show, set",
127
+ info_aliases: "\u522B\u540D",
128
+ uninstall_not_found: "\u672A\u627E\u5230",
129
+ uninstall_no_record: "\u672A\u627E\u5230\u5B89\u88C5\u8BB0\u5F55\uFF0C\u53EF\u80FD\u672A\u5B89\u88C5\u6216\u5B89\u88C5\u8BB0\u5F55\u5DF2\u635F\u574F",
130
+ 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",
131
+ uninstall_in_progress: "\u6B63\u5728\u5378\u8F7D",
132
+ uninstall_summary_format: "{name}\uFF08\u79FB\u9664 {files} \u4E2A\u6587\u4EF6\uFF0C{dirs} \u4E2A\u76EE\u5F55\uFF09",
133
+ uninstall_files_gone: "\u7684\u6587\u4EF6\u5DF2\u4E0D\u5B58\u5728\uFF0C\u5DF2\u6E05\u7406\u5B89\u88C5\u8BB0\u5F55",
134
+ uninstall_skills_in_progress: "\u6B63\u5728\u5378\u8F7D",
135
+ init_pkg_dir_warn: "\u5F53\u524D\u76EE\u5F55\u770B\u8D77\u6765\u662F install-helper \u5305\u76EE\u5F55",
136
+ 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",
137
+ init_pkg_dir_example: "\u793A\u4F8B\uFF1Acd ~/my-project && install-helper",
138
+ init_cancelled: "\u5DF2\u53D6\u6D88\u5B89\u88C5",
139
+ backup_detected: "\u68C0\u6D4B\u5230\u5F53\u524D\u76EE\u5F55\u5DF2\u5B89\u88C5\u63D2\u4EF6",
140
+ backup_current: "\u5F53\u524D\u5B89\u88C5",
141
+ backup_installing: "\u5373\u5C06\u5B89\u88C5",
142
+ backup_overwrite_warning: "\u7EE7\u7EED\u5B89\u88C5\u5C06\u8986\u76D6\u73B0\u6709\u7684 AGENTS.md \u914D\u7F6E\u6587\u4EF6\u3002",
143
+ backup_auto_create: "\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u521B\u5EFA\u5907\u4EFD\uFF0C\u4EE5\u4FBF\u540E\u7EED\u6062\u590D\u3002",
144
+ backup_overwrite: "\u8986\u76D6\u5B89\u88C5\uFF08\u81EA\u52A8\u5907\u4EFD\uFF09",
145
+ backup_cancel: "\u53D6\u6D88\u5B89\u88C5",
146
+ backup_restore_prompt: "\u68C0\u6D4B\u5230\u5907\u4EFD\u914D\u7F6E\uFF0C\u662F\u5426\u6062\u590D\uFF1F",
147
+ backup_restore: "\u6062\u590D",
148
+ backup_only_uninstall: "\u4EC5\u5378\u8F7D\uFF08\u4E0D\u6062\u590D\uFF09",
149
+ backup_select_action: "\u8BF7\u9009\u62E9\u64CD\u4F5C",
150
+ backup_restore_success: "\u5DF2\u6062\u590D\u914D\u7F6E",
151
+ backup_created: "\u5DF2\u521B\u5EFA\u5907\u4EFD",
152
+ backup_failed: "\u5907\u4EFD\u521B\u5EFA\u5931\u8D25\uFF0C\u5B89\u88C5\u5DF2\u4E2D\u6B62\u4EE5\u4FDD\u62A4\u73B0\u6709\u914D\u7F6E",
153
+ loading_skills_list: "\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...",
154
+ loading_skills_list_complete: "Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210",
155
+ loading_plugin_data: "\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...",
156
+ loading_plugin_data_complete: "\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210",
157
+ loading_skills_list_failed: "Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E",
158
+ repo_pull_failed: "\u65E0\u6CD5\u62C9\u53D6\u6700\u65B0\u5185\u5BB9\uFF0C\u4F7F\u7528\u7F13\u5B58\u6570\u636E",
159
+ repo_cloning: "\u9996\u6B21\u4F7F\u7528\uFF0C\u6B63\u5728\u514B\u9686 cannbot-skills \u4ED3\u5E93...",
160
+ config_corrupted: "\u914D\u7F6E\u6587\u4EF6\u5DF2\u635F\u574F\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u914D\u7F6E",
161
+ record_corrupted: "\u5B89\u88C5\u8BB0\u5F55\u5DF2\u635F\u574F\uFF1A{file}",
162
+ skill_record_corrupted: "Skill \u5B89\u88C5\u8BB0\u5F55\u5DF2\u635F\u574F",
163
+ plugin_yaml_error: "\u63D2\u4EF6\u914D\u7F6E\u89E3\u6790\u5931\u8D25\uFF1A{file}",
164
+ record_write_failed: "\u5B89\u88C5\u8BB0\u5F55\u5199\u5165\u5931\u8D25\uFF0C\u5378\u8F7D\u65F6\u53EF\u80FD\u65E0\u6CD5\u81EA\u52A8\u6E05\u7406",
165
+ install_config_failed: "\u914D\u7F6E\u6587\u4EF6\u5B89\u88C5\u5931\u8D25: {error}",
166
+ install_clone_failed: "git clone \u5931\u8D25: {url}\uFF0C\u8BF7\u624B\u52A8\u514B\u9686: git clone {url} {dir}",
167
+ install_link_config_failed: "\u94FE\u63A5 {name} \u5230\u914D\u7F6E\u76EE\u5F55\u5931\u8D25",
168
+ install_link_project_failed: "\u94FE\u63A5 {name} \u5230\u9879\u76EE\u76EE\u5F55\u5931\u8D25",
169
+ install_link_workflows_failed: "\u94FE\u63A5 workflows \u5230\u914D\u7F6E\u76EE\u5F55\u5931\u8D25",
170
+ install_skill_source_not_found: "Skill \u6E90\u76EE\u5F55\u672A\u627E\u5230: {dir}",
171
+ install_skill_not_found: "Skill \u672A\u627E\u5230: {name} (\u5728 {dir})",
172
+ install_skill_failed: "Skill \u5B89\u88C5\u5931\u8D25: {name}",
173
+ install_agent_not_found: "Agent \u672A\u627E\u5230: {name}",
174
+ install_agent_failed: "Agent \u5B89\u88C5\u5931\u8D25: {name}",
175
+ install_no_skill_selected: "\u672A\u9009\u62E9\u4EFB\u4F55 Skill",
176
+ install_to: "\u5B89\u88C5\u5230",
177
+ start_to_use: "\u542F\u52A8 {tool} \u5373\u53EF\u4F7F\u7528",
178
+ wizard_step_1_title: "[1/4] \u9009\u62E9 AI \u7F16\u7A0B\u5DE5\u5177",
179
+ wizard_step_2_title: "[2/4] \u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E",
180
+ wizard_step_3_title: "[3/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6",
181
+ wizard_step_4_title: "[4/4] \u786E\u8BA4\u5B89\u88C5",
182
+ wizard_step_3_skill_title: "[3/4] \u9009\u62E9 Skill \u7C7B\u522B",
183
+ wizard_step_4_skill_title: "[4/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills \u2014 {category}",
184
+ install_level_project: "\u5B89\u88C5\u5230\u5F53\u524D\u76EE\u5F55\uFF08\u63A8\u8350\uFF0C\u4EC5\u5F53\u524D\u9879\u76EE\u53EF\u7528\uFF09",
185
+ install_level_global: "\u5B89\u88C5\u5230\u5168\u5C40\uFF08\u6240\u6709\u9879\u76EE\u5171\u4EAB\uFF09",
186
+ wizard_select_mode_title: "\u9009\u62E9\u5B89\u88C5\u7C7B\u578B",
187
+ skill_count_format: "{count} \u4E2A Skills",
188
+ result_success_format: "{count} \u6210\u529F",
189
+ result_failed_format: "{count} \u5931\u8D25",
190
+ uninstall_remove_label: "\u79FB\u9664",
191
+ uninstall_clean_empty_dir: "\u6E05\u7406\u7A7A\u76EE\u5F55",
192
+ uninstall_skill_not_installed: "\u672A\u5B89\u88C5",
193
+ available_plugins: "\u53EF\u7528\u63D2\u4EF6",
194
+ available_skills: "\u53EF\u7528 Skills",
195
+ skill_total_count: "\u5171 {count} \u4E2A Skills",
196
+ wizard_confirm_install_format: "\u5373\u5C06\u5B89\u88C5 {count} \u4E2A\u63D2\u4EF6\u5230 {tool}\uFF08{level} \u7EA7\u522B\uFF09\uFF1A",
197
+ wizard_install_path: "\u5B89\u88C5\u8DEF\u5F84",
198
+ update_install_hint: "\u4F7F\u7528 {cmd} \u5B89\u88C5\u63D2\u4EF6",
199
+ hint_select_move: "\u79FB\u52A8",
200
+ hint_select_space: "\u7A7A\u683C",
201
+ hint_select_check: "\u52FE\u9009",
202
+ hint_select_confirm: "\u786E\u8BA4",
203
+ hint_checkbox_suffix: "\u4E0D\u9009\u6309\u56DE\u8F66\u53EF\u8FD4\u56DE",
204
+ doctor_not_installed: "\u672A\u5B89\u88C5",
205
+ doctor_links_valid: "{count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548",
206
+ doctor_broken_links: "{count} \u4E2A\u5931\u6548\u94FE\u63A5",
207
+ doctor_fixed: "\u5DF2\u6E05\u7406 {count} \u4E2A\u5931\u6548\u94FE\u63A5",
208
+ doctor_not_exist: "\u4E0D\u5B58\u5728",
209
+ doctor_created: "\u5DF2\u521B\u5EFA {path}",
210
+ doctor_config_exists: "\u5B58\u5728",
211
+ doctor_config_not_exist: "\u4E0D\u5B58\u5728",
212
+ doctor_result_with_fix: "{warnings} \u4E2A\u8B66\u544A, {fixes} \u9879\u5DF2\u4FEE\u590D",
213
+ doctor_result_without_fix: "{warnings} \u4E2A\u8B66\u544A",
214
+ count_suffix: "\u4E2A",
215
+ try_prompt: "\u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50"
216
+ };
217
+
218
+ // src/locales/en_US.json
219
+ var en_US_default = {
220
+ wizard_title: "CANNBot Install Helper",
221
+ wizard_detect: "Detecting installed AI coding tools...",
222
+ wizard_select_tool: "Select AI coding tool",
223
+ wizard_select_level: "Select install level",
224
+ wizard_select_plugin: "Select a scenario to install",
225
+ wizard_confirm: "Confirm installation",
226
+ wizard_detected_tool: "Detected",
227
+ wizard_confirm_tool: "Confirm using",
228
+ wizard_last_used: "last used",
229
+ wizard_already_installed: "installed",
230
+ install_repo_ready: "Repository ready",
231
+ install_progress: "Installing",
232
+ install_done: "Installation complete",
233
+ install_success: "success",
234
+ install_already_installed: "already installed",
235
+ install_reinstall_confirm: "Reinstall (overwrite)",
236
+ install_skip: "skipped",
237
+ install_enhanced_next_steps: "Next steps",
238
+ install_enhanced_launch: "Launch",
239
+ install_enhanced_try: "Try",
240
+ install_enhanced_more: "More",
241
+ install_enhanced_check: "Check",
242
+ install_enhanced_docs: "Docs",
243
+ update_updating: "Updating",
244
+ update_done: "Update complete",
245
+ update_no_plugins: "No installed plugins",
246
+ doctor_title: "CANNBot Doctor",
247
+ doctor_tools: "AI Tools",
248
+ doctor_plugins: "Installed Plugins",
249
+ doctor_links: "Link Integrity",
250
+ doctor_config: "Configuration",
251
+ doctor_result: "Result",
252
+ doctor_fix_cleaning: "Cleaning broken links",
253
+ doctor_fix_rebuilding: "Rebuilding directories",
254
+ info_description: "Description",
255
+ info_skills: "Skills",
256
+ info_agents: "Agents",
257
+ info_status: "Status",
258
+ info_quickstart: "Quick Start",
259
+ info_not_found: "Plugin not found",
260
+ lang_title: "Language Settings",
261
+ lang_current: "Current language",
262
+ lang_set: "Language set to",
263
+ lang_invalid: "Invalid language, supported: zh_CN, en_US",
264
+ list_title: "Available Scenarios",
265
+ list_id: "#",
266
+ list_name: "Name",
267
+ list_status: "Status",
268
+ list_description: "Description",
269
+ status_installed: "installed",
270
+ status_not_installed: "not installed",
271
+ error_tool_not_found: "No AI coding tool detected",
272
+ error_plugin_not_found: "Plugin not found",
273
+ error_invalid_tool: "Unsupported AI tool: {tool}\nSupported tools: {tools}",
274
+ error_invalid_level: "Unsupported install level: {level}\nSupported levels: {levels}",
275
+ error_skill_not_found: "Skill not found in registry",
276
+ error_source_not_found: "Source not found: {path}",
277
+ error_script_not_found: "Script not found: {path}",
278
+ error_unknown: "Unknown error",
279
+ error_not_installed: "Not installed",
280
+ error_no_shell: "bash or sh not found, cannot execute install script. Please install Git Bash or WSL",
281
+ error_symlink_windows: "Failed to create symlink, please run as administrator or enable Windows Developer Mode",
282
+ skill_select_category: "Select Skill category",
283
+ skill_select_items: "Select Skills to install (space to toggle, enter to confirm)",
284
+ skill_already_installed: "installed",
285
+ skill_select_all: "Select ALL Skills ({count} total)",
286
+ skill_select_all_category: "Select all in this category",
287
+ skill_continue_or_install: "{count} Skills selected, choose an action",
288
+ skill_continue_select: "Continue selecting from other categories",
289
+ skill_confirm_install_count: "Install now ({count} Skills selected)",
290
+ skill_all_install_progress: "Installing all {count} Skills...",
291
+ skill_install_progress: "Installing",
292
+ skill_install_done: "Skill installation complete",
293
+ skill_uninstall_done: "Skill uninstallation complete",
294
+ skill_list_title: "Available Skills",
295
+ wizard_select_mode: "Select installation mode",
296
+ wizard_mode_plugin: "Install Plugin",
297
+ wizard_mode_plugin_desc: "Full development workflow (Skills + Agents + config)",
298
+ wizard_mode_skill: "Install Skill",
299
+ wizard_mode_skill_desc: "Domain knowledge only, give AI specific capabilities",
300
+ wizard_back: "Go back",
301
+ wizard_cancel: "Cancel installation",
302
+ wizard_no_selection: "No items selected, please choose an action",
303
+ wizard_back_to_reselect: "Go back",
304
+ status_title: "Installed Plugins",
305
+ status_none: "No installed plugins",
306
+ status_hint: "Run install-helper init to start",
307
+ status_install_time: "Installed at",
308
+ install_installed_to: "Installed to",
309
+ install_skills_count: "Skills",
310
+ install_agents_count: "Agents",
311
+ lang_supported: "Supported languages: zh_CN, en_US",
312
+ lang_usage: "Usage: install-helper lang set <language>",
313
+ lang_specify: "Please specify language: install-helper lang set zh_CN",
314
+ lang_unknown_action: "Unknown action",
315
+ lang_supported_actions: "Supported actions: show, set",
316
+ info_aliases: "Aliases",
317
+ uninstall_not_found: "Not found",
318
+ uninstall_no_record: "No install record found, may not be installed or record is corrupted",
319
+ uninstall_no_record_hint: "For manual cleanup, delete related files in .opencode/ or .claude/ directory",
320
+ uninstall_in_progress: "Uninstalling",
321
+ uninstall_summary_format: "{name} (removed {files} files, {dirs} directories)",
322
+ uninstall_files_gone: " files no longer exist, install record cleaned",
323
+ uninstall_skills_in_progress: "Uninstalling",
324
+ init_pkg_dir_warn: "Current directory appears to be the install-helper package directory",
325
+ init_pkg_dir_hint: "Please run this command in your project directory, not in the install-helper package directory",
326
+ init_pkg_dir_example: "Example: cd ~/my-project && install-helper",
327
+ init_cancelled: "Installation cancelled",
328
+ backup_detected: "Plugin already installed in current directory",
329
+ backup_current: "Currently installed",
330
+ backup_installing: "About to install",
331
+ backup_overwrite_warning: "Continuing will overwrite existing AGENTS.md configuration.",
332
+ backup_auto_create: "System will automatically create backup for future restoration.",
333
+ backup_overwrite: "Overwrite (auto backup)",
334
+ backup_cancel: "Cancel installation",
335
+ backup_restore_prompt: "Backup configuration detected, restore?",
336
+ backup_restore: "Restore",
337
+ backup_only_uninstall: "Uninstall only (no restore)",
338
+ backup_select_action: "Please select an action",
339
+ backup_restore_success: "Configuration restored",
340
+ backup_created: "Backup created",
341
+ backup_failed: "Backup creation failed, installation aborted to protect existing config",
342
+ loading_skills_list: "Loading Skills list...",
343
+ loading_skills_list_complete: "Skills list loaded",
344
+ loading_plugin_data: "Loading plugin data...",
345
+ loading_plugin_data_complete: "Plugin data loaded",
346
+ loading_skills_list_failed: "Failed to load Skills list, using built-in data",
347
+ repo_pull_failed: "Failed to pull latest updates, using cached data",
348
+ repo_cloning: "First-time setup: cloning cannbot-skills repository...",
349
+ config_corrupted: "Config file corrupted, using defaults",
350
+ record_corrupted: "Install record corrupted: {file}",
351
+ skill_record_corrupted: "Skill install record corrupted",
352
+ plugin_yaml_error: "Plugin config parse error: {file}",
353
+ record_write_failed: "Install record write failed, uninstall may not work properly",
354
+ install_config_failed: "Failed to install config file: {error}",
355
+ install_clone_failed: "git clone failed: {url}, clone manually: git clone {url} {dir}",
356
+ install_link_config_failed: "Failed to link {name} to config directory",
357
+ install_link_project_failed: "Failed to link {name} to project directory",
358
+ install_link_workflows_failed: "Failed to link workflows to config directory",
359
+ install_skill_source_not_found: "Skill source not found: {dir}",
360
+ install_skill_not_found: "Skill not found: {name} in {dir}",
361
+ install_skill_failed: "Failed to install skill: {name}",
362
+ install_agent_not_found: "Agent not found: {name}",
363
+ install_agent_failed: "Failed to install agent: {name}",
364
+ install_no_skill_selected: "No Skill selected",
365
+ install_to: "Install to",
366
+ start_to_use: "Start {tool} to use",
367
+ wizard_step_1_title: "[1/4] Select AI coding tool",
368
+ wizard_step_2_title: "[2/4] Select installation location",
369
+ wizard_step_3_title: "[3/4] Select plugins to install",
370
+ wizard_step_4_title: "[4/4] Confirm installation",
371
+ wizard_step_3_skill_title: "[3/4] Select Skill category",
372
+ wizard_step_4_skill_title: "[4/4] Select Skills to install \u2014 {category}",
373
+ install_level_project: "Install to current directory (recommended, current project only)",
374
+ install_level_global: "Install globally (all projects)",
375
+ wizard_select_mode_title: "Select installation type",
376
+ skill_count_format: "{count} Skills",
377
+ result_success_format: "{count} succeeded",
378
+ result_failed_format: "{count} failed",
379
+ uninstall_remove_label: "Removed",
380
+ uninstall_clean_empty_dir: "Cleaned empty directory",
381
+ uninstall_skill_not_installed: "Not installed",
382
+ available_plugins: "Available plugins",
383
+ available_skills: "Available Skills",
384
+ skill_total_count: "Total {count} Skills",
385
+ wizard_confirm_install_format: "About to install {count} plugins to {tool} ({level} level):",
386
+ wizard_install_path: "Install path",
387
+ update_install_hint: "Use {cmd} to install plugins",
388
+ hint_select_move: "Navigate",
389
+ hint_select_space: "Space",
390
+ hint_select_check: "Toggle",
391
+ hint_select_confirm: "Confirm",
392
+ hint_checkbox_suffix: "Press enter without selection to go back",
393
+ doctor_not_installed: "Not installed",
394
+ doctor_links_valid: "{count} symlinks all valid",
395
+ doctor_broken_links: "{count} broken links",
396
+ doctor_fixed: "Cleaned {count} broken links",
397
+ doctor_not_exist: "Does not exist",
398
+ doctor_created: "Created {path}",
399
+ doctor_config_exists: "Exists",
400
+ doctor_config_not_exist: "Does not exist",
401
+ doctor_result_with_fix: "{warnings} warnings, {fixes} fixed",
402
+ doctor_result_without_fix: "{warnings} warnings",
403
+ count_suffix: "",
404
+ try_prompt: "help me develop an Abs operator"
405
+ };
406
+
407
+ // src/utils/i18n.ts
19
408
  var __filename = fileURLToPath(import.meta.url);
20
409
  var __dirname = dirname(__filename);
410
+ var EMBEDDED_LOCALES = {
411
+ zh_CN: zh_CN_default,
412
+ en_US: en_US_default
413
+ };
414
+ function loadLocale(locale) {
415
+ const embedded = EMBEDDED_LOCALES[locale];
416
+ if (embedded) return embedded;
417
+ const localePath = join(__dirname, "locales", `${locale}.json`);
418
+ if (existsSync(localePath)) {
419
+ try {
420
+ const content = readFileSync(localePath, "utf-8");
421
+ return JSON.parse(content);
422
+ } catch {
423
+ }
424
+ }
425
+ const fallbackPath = join(__dirname, "locales", "zh_CN.json");
426
+ if (existsSync(fallbackPath)) {
427
+ try {
428
+ const content = readFileSync(fallbackPath, "utf-8");
429
+ return JSON.parse(content);
430
+ } catch {
431
+ }
432
+ }
433
+ return EMBEDDED_LOCALES.zh_CN || {};
434
+ }
435
+ var currentLang = "zh_CN";
436
+ var messages = loadLocale("zh_CN");
437
+ function setLanguage(lang) {
438
+ currentLang = lang;
439
+ messages = loadLocale(lang);
440
+ }
441
+ function t(key) {
442
+ return messages[key] !== void 0 ? messages[key] : key;
443
+ }
444
+
445
+ // src/ui/theme.ts
446
+ import chalk from "chalk";
447
+ var selectTheme = {
448
+ helpMode: "never"
449
+ };
450
+ function isCJK(code) {
451
+ 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;
452
+ }
453
+ function isEmoji(code) {
454
+ return code >= 127744 && code <= 129791 || code >= 9728 && code <= 10175;
455
+ }
456
+ function getDisplayWidth(str) {
457
+ let width = 0;
458
+ for (const ch of str) {
459
+ const code = ch.codePointAt(0);
460
+ if (isCJK(code) || isEmoji(code)) {
461
+ width += 2;
462
+ } else {
463
+ width += 1;
464
+ }
465
+ }
466
+ return width;
467
+ }
468
+ function wrapDescription(text) {
469
+ const prefix = " \u{1F4A1} ";
470
+ const indent = " ";
471
+ const cols = process.stdout.columns || 120;
472
+ const maxLen = Math.max(20, cols - getDisplayWidth(prefix) - 2);
473
+ const tokens = [];
474
+ let i = 0;
475
+ while (i < text.length) {
476
+ const code = text[i].codePointAt(0);
477
+ if (isCJK(code)) {
478
+ tokens.push({ text: text[i], width: 2 });
479
+ i++;
480
+ } else {
481
+ let j = i;
482
+ while (j < text.length) {
483
+ const c = text[j].codePointAt(0);
484
+ if (isCJK(c)) break;
485
+ j++;
486
+ }
487
+ const word = text.slice(i, j);
488
+ tokens.push({ text: word, width: getDisplayWidth(word) });
489
+ i = j;
490
+ }
491
+ }
492
+ const lines = [];
493
+ let line = "";
494
+ let lineWidth = 0;
495
+ for (const token of tokens) {
496
+ if (lineWidth + token.width > maxLen && line) {
497
+ lines.push(line.trimEnd());
498
+ line = token.text;
499
+ lineWidth = token.width;
500
+ } else {
501
+ line += token.text;
502
+ lineWidth += token.width;
503
+ }
504
+ }
505
+ if (line) lines.push(line.trimEnd());
506
+ return lines.map((l, i2) => chalk.cyan(i2 === 0 ? prefix + l : indent + l)).join("\n");
507
+ }
508
+ var checkboxTheme = {
509
+ icon: {
510
+ checked: "\x1B[32m\u2611\x1B[22m",
511
+ unchecked: "\u2610",
512
+ cursor: "\u276F"
513
+ },
514
+ helpMode: "never",
515
+ style: {
516
+ description: (text) => wrapDescription(text)
517
+ }
518
+ };
519
+
520
+ // src/utils/logger.ts
521
+ var logger = {
522
+ success: (msg) => console.log(chalk2.green("\u2713") + " " + msg),
523
+ error: (msg) => console.log(chalk2.red("\u2717") + " " + msg),
524
+ warn: (msg) => console.log(chalk2.yellow("\u26A0") + " " + msg),
525
+ info: (msg) => console.log(chalk2.cyan("\u2192") + " " + msg),
526
+ step: (msg) => console.log(chalk2.dim(msg)),
527
+ blank: () => console.log()
528
+ };
529
+ function createSpinner(text) {
530
+ return ora({ text, color: "cyan" });
531
+ }
532
+ function printBanner(subtitle) {
533
+ console.log(chalk2.cyan(`
534
+ ____ _ _ _ _ _ ____ _
535
+ / ___| / \\ | \\ | | \\ | | __ ) ___ | |_
536
+ | | / _ \\ | \\| | \\| | _ \\ / _ \\| __|
537
+ | |___ / ___ \\| |\\ | |\\ | |_) | (_) | |_
538
+ \\____/_/ \\_\\_| \\_|_| \\_|____/ \\___/ \\__|
539
+ `));
540
+ if (subtitle) {
541
+ console.log(chalk2.bold(` ${subtitle}`));
542
+ console.log();
543
+ }
544
+ }
545
+ function printBoxTitle(title, width = 65) {
546
+ const border = "\u2550".repeat(width);
547
+ const displayWidth = getDisplayWidth(title);
548
+ const totalPadding = width - displayWidth;
549
+ const leftPadding = Math.floor(totalPadding / 2);
550
+ const rightPadding = totalPadding - leftPadding;
551
+ const paddedTitle = " ".repeat(Math.max(0, leftPadding)) + title + " ".repeat(Math.max(0, rightPadding));
552
+ console.log();
553
+ console.log(chalk2.cyan.bold(` \u2554${border}\u2557`));
554
+ console.log(chalk2.cyan.bold(` \u2551`) + chalk2.cyan.bold(paddedTitle) + chalk2.cyan.bold(`\u2551`));
555
+ console.log(chalk2.cyan.bold(` \u255A${border}\u255D`));
556
+ console.log();
557
+ }
558
+ function showOperationHints(isCheckbox = false) {
559
+ if (isCheckbox) {
560
+ 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
561
+ `);
562
+ } else {
563
+ console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}
564
+ `);
565
+ }
566
+ }
567
+
568
+ // src/utils/fs-helpers.ts
569
+ import { lstatSync, statSync } from "fs";
570
+ function isSymlink(path) {
571
+ try {
572
+ return lstatSync(path).isSymbolicLink();
573
+ } catch {
574
+ return false;
575
+ }
576
+ }
577
+ function isDirectory(path) {
578
+ try {
579
+ return statSync(path).isDirectory();
580
+ } catch {
581
+ return false;
582
+ }
583
+ }
584
+
585
+ // src/embedded-plugins.json
586
+ var embedded_plugins_default = [
587
+ {
588
+ id: "ops-direct-invoke",
589
+ dir: "plugins-official/ops-direct-invoke",
590
+ displayName: "AscendC Kernel \u76F4\u8C03",
591
+ script: "init.sh",
592
+ aliases: [
593
+ "ops-direct",
594
+ "ascendc-direct",
595
+ "direct",
596
+ "kernel"
597
+ ],
598
+ skills: 19,
599
+ agents: 4,
600
+ 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",
601
+ version: "1.2.2",
602
+ configFile: "AGENTS.md",
603
+ configRootConfigLink: false,
604
+ installSkills: [
605
+ {
606
+ dir: "ops",
607
+ skills: [
608
+ "ascendc-tiling-design",
609
+ "npu-arch",
610
+ "ascendc-api-best-practices",
611
+ "ascendc-regbase-best-practice",
612
+ "ascendc-blaze-best-practice",
613
+ "ops-precision-standard",
614
+ "ascendc-docs-search",
615
+ "ascendc-env-check",
616
+ "ascendc-precision-debug",
617
+ "ops-profiling",
618
+ "ascendc-direct-invoke-template",
619
+ "torch-ascendc-op-extension",
620
+ "ascendc-runtime-debug",
621
+ "ascendc-crash-debug",
622
+ "ascendc-code-review"
623
+ ]
624
+ },
625
+ {
626
+ dir: "infra",
627
+ skills: [
628
+ "gitcode-toolkit",
629
+ "gitcode-pr-handler",
630
+ "gitcode-issue-gen",
631
+ "gitcode-issue-handler"
632
+ ]
633
+ }
634
+ ],
635
+ installAgents: [
636
+ "ascendc-kernel-architect",
637
+ "ascendc-kernel-design-reviewer",
638
+ "ascendc-kernel-developer",
639
+ "ascendc-kernel-reviewer"
640
+ ],
641
+ externalRepos: [
642
+ {
643
+ url: "https://gitcode.com/cann/asc-devkit.git",
644
+ dir: "plugins-official/ops-direct-invoke/asc-devkit",
645
+ configRootLink: false
646
+ }
647
+ ]
648
+ },
649
+ {
650
+ id: "ops-direct-invoke-flash",
651
+ dir: "plugins-official/ops-direct-invoke-flash",
652
+ displayName: "AscendC Kernel \u76F4\u8C03\uFF08\u5FEB\u901F\u7248\uFF09",
653
+ script: "init.sh",
654
+ aliases: [
655
+ "flash",
656
+ "kernel-flash"
657
+ ],
658
+ skills: 7,
659
+ agents: 1,
660
+ description: "\u4ECE\u6570\u5B66\u516C\u5F0F\u6216\u4EE3\u7801\u7247\u6BB5\u5FEB\u901F\u6784\u5EFA\u5E76\u9A8C\u8BC1 Ascend NPU \u6838\u51FD\u6570",
661
+ version: "1.2.2",
662
+ configFile: "AGENTS.md",
663
+ configRootConfigLink: true,
664
+ installSkills: [
665
+ {
666
+ dir: "plugins-official/ops-direct-invoke-flash/skills",
667
+ skills: [
668
+ "ops-direct-invoke-flash"
669
+ ]
670
+ },
671
+ {
672
+ dir: "ops",
673
+ skills: [
674
+ "ascendc-st-design",
675
+ "ascendc-whitebox-design"
676
+ ]
677
+ },
678
+ {
679
+ dir: "infra",
680
+ skills: [
681
+ "gitcode-toolkit",
682
+ "gitcode-pr-handler",
683
+ "gitcode-issue-gen",
684
+ "gitcode-issue-handler"
685
+ ]
686
+ }
687
+ ],
688
+ installAgents: [
689
+ "ops-direct-invoke-flash-reviewer"
690
+ ]
691
+ },
692
+ {
693
+ id: "ops-registry-invoke",
694
+ dir: "plugins-official/ops-registry-invoke",
695
+ displayName: "AscendC \u7B97\u5B50\u6CE8\u518C\u8C03\u7528",
696
+ script: "init.sh",
697
+ aliases: [
698
+ "ops-registry",
699
+ "ascendc-registry",
700
+ "registry"
701
+ ],
702
+ skills: 26,
703
+ agents: 3,
704
+ description: "\u751F\u4EA7\u7EA7 ACLNN \u6CE8\u518C\u7B97\u5B50\u5168\u6D41\u7A0B\uFF1A\u4EE3\u7801\u2192UT/ST\u2192\u4E0A\u5E93",
705
+ version: "1.2.2",
706
+ configFile: "AGENTS.md",
707
+ configRootConfigLink: false,
708
+ installSkills: [
709
+ {
710
+ dir: "ops",
711
+ skills: [
712
+ "ascendc-blaze-best-practice",
713
+ "ascendc-api-best-practices",
714
+ "ascendc-code-review",
715
+ "ascendc-crash-debug",
716
+ "ascendc-direct-invoke-template",
717
+ "ascendc-docs-gen",
718
+ "ascendc-docs-search",
719
+ "ascendc-env-check",
720
+ "npu-arch",
721
+ "ascendc-performance-best-practices",
722
+ "ascendc-precision-debug",
723
+ "ascendc-regbase-best-practice",
724
+ "ascendc-registry-invoke-template",
725
+ "ascendc-runtime-debug",
726
+ "ascendc-st-design",
727
+ "ascendc-tiling-design",
728
+ "ascendc-ut-develop",
729
+ "ascendc-whitebox-design",
730
+ "ops-precision-standard",
731
+ "ops-profiling",
732
+ "ops-spec-gen"
733
+ ]
734
+ },
735
+ {
736
+ dir: "infra",
737
+ skills: [
738
+ "gitcode-toolkit",
739
+ "gitcode-pr-handler",
740
+ "gitcode-issue-gen",
741
+ "gitcode-issue-handler"
742
+ ]
743
+ },
744
+ {
745
+ dir: "plugins-official/ops-registry-invoke",
746
+ skills: [
747
+ {
748
+ name: "workflow",
749
+ as: "ops-registry-invoke-workflow"
750
+ }
751
+ ]
752
+ }
753
+ ],
754
+ installAgents: [
755
+ "ascendc-ops-architect",
756
+ "ascendc-ops-developer",
757
+ "ascendc-ops-tester"
758
+ ],
759
+ externalRepos: [
760
+ {
761
+ url: "https://gitcode.com/cann/asc-devkit.git",
762
+ dir: "plugins-official/ops-registry-invoke/asc-devkit",
763
+ configRootLink: false
764
+ }
765
+ ]
766
+ },
767
+ {
768
+ id: "catlass-op-generator",
769
+ dir: "plugins-official/catlass-op-generator",
770
+ displayName: "Catlass \u7B97\u5B50\u76F4\u8C03",
771
+ script: "init.sh",
772
+ aliases: [
773
+ "catlass"
774
+ ],
775
+ skills: 14,
776
+ agents: 3,
777
+ description: "Catlass \u6A21\u677F\u62FC\u88C5 GEMM/Matmul/Epilogue \u7B97\u5B50\u5F00\u53D1",
778
+ version: "1.2.2",
779
+ configFile: "AGENTS.md",
780
+ configRootConfigLink: true,
781
+ installSkills: [
782
+ {
783
+ dir: "ops",
784
+ skills: [
785
+ "catlass-op-design",
786
+ "catlass-op-develop",
787
+ "catlass-op-perf-tune",
788
+ "ascendc-tiling-design",
789
+ "npu-arch",
790
+ "ascendc-api-best-practices",
791
+ "ops-precision-standard",
792
+ "ascendc-docs-search",
793
+ "ascendc-env-check",
794
+ "ascendc-precision-debug",
795
+ "ops-profiling",
796
+ "ascendc-runtime-debug",
797
+ "ascendc-code-review",
798
+ "torch-ascendc-op-extension"
799
+ ]
800
+ }
801
+ ],
802
+ installAgents: [
803
+ "catlass-op-architect",
804
+ "catlass-op-generator",
805
+ "catlass-op-reviewer"
806
+ ],
807
+ externalRepos: [
808
+ {
809
+ url: "https://gitcode.com/cann/asc-devkit.git",
810
+ dir: "plugins-official/catlass-op-generator/asc-devkit",
811
+ configRootLink: false
812
+ }
813
+ ]
814
+ },
815
+ {
816
+ id: "model-infer-optimize",
817
+ dir: "plugins-official/model-infer-optimize",
818
+ displayName: "NPU \u63A8\u7406\u4F18\u5316",
819
+ script: "init.sh",
820
+ aliases: [
821
+ "model-infer",
822
+ "infer",
823
+ "inference"
824
+ ],
825
+ skills: 12,
826
+ agents: 3,
827
+ 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",
828
+ version: "1.2.2",
829
+ configFile: "AGENTS.md",
830
+ configRootConfigLink: true,
831
+ installSkills: [
832
+ {
833
+ dir: "model",
834
+ skills: [
835
+ "model-infer-migrator",
836
+ "model-infer-parallel-analysis",
837
+ "model-infer-parallel-impl",
838
+ "model-infer-kvcache",
839
+ "model-infer-fusion",
840
+ "model-infer-quantization",
841
+ "model-infer-graph-mode",
842
+ "model-infer-precision-debug",
843
+ "model-infer-runtime-debug",
844
+ "model-infer-multi-stream",
845
+ "model-infer-prefetch",
846
+ "model-infer-superkernel"
847
+ ]
848
+ }
849
+ ],
850
+ installAgents: [
851
+ "model-infer-analyzer",
852
+ "model-infer-implementer",
853
+ "model-infer-reviewer"
854
+ ],
855
+ externalRepos: [
856
+ {
857
+ url: "https://gitcode.com/cann/cann-recipes-infer.git",
858
+ dir: "plugins-official/model-infer-optimize/cann-recipes-infer",
859
+ depth: 1,
860
+ configRootLink: true
861
+ }
862
+ ]
863
+ },
864
+ {
865
+ id: "pypto-op-orchestrator",
866
+ dir: "plugins-official/pypto-op-orchestrator",
867
+ displayName: "PyPTO \u7B97\u5B50\u5F00\u53D1",
868
+ script: "init.sh",
869
+ aliases: [
870
+ "pypto",
871
+ "pytorch"
872
+ ],
873
+ skills: 8,
874
+ agents: 3,
875
+ 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",
876
+ version: "1.2.2",
877
+ configFile: "AGENTS.md",
878
+ configRootConfigLink: false,
879
+ installSkills: [
880
+ {
881
+ dir: "ops",
882
+ skills: [
883
+ "pypto-intent-understand",
884
+ "pypto-api-explore",
885
+ "pypto-op-design",
886
+ "pypto-op-develop",
887
+ "pypto-precision-debug",
888
+ "pypto-precision-compare",
889
+ "pypto-op-perf-tune",
890
+ "pypto-golden-generate"
891
+ ]
892
+ }
893
+ ],
894
+ installAgents: [
895
+ "pypto-op-analyst",
896
+ "pypto-op-developer",
897
+ "pypto-op-perf-tuner"
898
+ ],
899
+ externalRepos: [
900
+ {
901
+ url: "https://gitcode.com/cann/pypto.git",
902
+ dir: "plugins-official/pypto-op-orchestrator/pypto",
903
+ configRootLink: false
904
+ }
905
+ ]
906
+ },
907
+ {
908
+ id: "tilelang-op-orchestrator",
909
+ dir: "plugins-official/tilelang-op-orchestrator",
910
+ displayName: "TileLang \u7B97\u5B50\u5F00\u53D1",
911
+ script: "init.sh",
912
+ aliases: [
913
+ "tilelang"
914
+ ],
915
+ skills: 9,
916
+ agents: 3,
917
+ description: "TileLang Python DSL + @tilelang.jit \u7B97\u5B50\u5F00\u53D1\uFF08\u652F\u6301 Developer/Expert \u53CC\u6A21\u5F0F\uFF09",
918
+ version: "1.2.2",
919
+ configFile: "AGENTS.md",
920
+ configRootConfigLink: false,
921
+ installSkills: [
922
+ {
923
+ dir: "ops",
924
+ skills: [
925
+ "tilelang-api-best-practices",
926
+ "tilelang-env-check",
927
+ "tilelang-op-design",
928
+ "tilelang-op-develop",
929
+ "tilelang-op-test-design",
930
+ "tilelang-perf-optimization",
931
+ "tilelang-programming-model-guide",
932
+ "tilelang-review",
933
+ "tilelang-submodule-pull"
934
+ ]
935
+ }
936
+ ],
937
+ installAgents: [
938
+ "tilelang-op-analyst",
939
+ "tilelang-op-developer",
940
+ "tilelang-op-perf-tuner"
941
+ ],
942
+ externalRepos: [
943
+ {
944
+ url: "https://github.com/tile-ai/tilelang-ascend.git",
945
+ dir: "plugins-official/tilelang-op-orchestrator/tilelang-ascend",
946
+ depth: 1,
947
+ recursive: true,
948
+ configRootLink: false
949
+ }
950
+ ]
951
+ },
952
+ {
953
+ id: "triton-op-generator",
954
+ dir: "plugins-official/triton-op-generator",
955
+ displayName: "Triton \u7B97\u5B50\u5F00\u53D1",
956
+ script: "init.sh",
957
+ aliases: [
958
+ "triton"
959
+ ],
960
+ skills: 6,
961
+ agents: 0,
962
+ 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",
963
+ version: "1.2.2",
964
+ configFile: "AGENTS.md",
965
+ configRootConfigLink: false,
966
+ installSkills: [
967
+ {
968
+ dir: "ops",
969
+ skills: [
970
+ "triton-task-extractor",
971
+ "triton-op-designer",
972
+ "triton-op-coding",
973
+ "triton-op-verifier",
974
+ "triton-latency-optimizer",
975
+ "npu-arch"
976
+ ]
977
+ }
978
+ ],
979
+ installAgents: []
980
+ },
981
+ {
982
+ id: "torch-compile",
983
+ dir: "plugins-official/torch-compile",
984
+ displayName: "torch.compile \u56FE\u6A21\u5F0F",
985
+ script: "init.sh",
986
+ aliases: [
987
+ "torch",
988
+ "compile",
989
+ "graph"
990
+ ],
991
+ skills: 6,
992
+ agents: 1,
993
+ 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",
994
+ version: "1.2.2",
995
+ configFile: "AGENTS.md",
996
+ configRootConfigLink: true,
997
+ installSkills: [
998
+ {
999
+ dir: "graph",
1000
+ skills: [
1001
+ "torch-npugraph-ex-knowledge",
1002
+ "torch-npugraph-ex-template",
1003
+ "torch-npugraph-ex-dfx-triage",
1004
+ "torch-npugraph-ex-compile-error-diagnosis",
1005
+ "torch-npugraph-ex-runtime-error-diagnosis",
1006
+ "torch-custom-ops-guide"
1007
+ ]
1008
+ }
1009
+ ],
1010
+ installAgents: [
1011
+ "torch-npugraph-ex"
1012
+ ]
1013
+ },
1014
+ {
1015
+ id: "ops-code-reviewer",
1016
+ dir: "plugins-official/ops-code-reviewer",
1017
+ displayName: "\u4EE3\u7801\u68C0\u89C6",
1018
+ script: "init.sh",
1019
+ aliases: [
1020
+ "code-review",
1021
+ "reviewer",
1022
+ "review"
1023
+ ],
1024
+ skills: 5,
1025
+ agents: 2,
1026
+ 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",
1027
+ version: "1.2.2",
1028
+ configFile: "AGENTS.md",
1029
+ configRootConfigLink: false,
1030
+ installSkills: [
1031
+ {
1032
+ dir: "ops",
1033
+ skills: [
1034
+ "ascendc-code-review",
1035
+ "ascendc-docs-search",
1036
+ "ascendc-task-focus",
1037
+ "ascendc-api-best-practices",
1038
+ "npu-arch"
1039
+ ]
1040
+ }
1041
+ ],
1042
+ installAgents: [
1043
+ "ascendc-code-summarizer",
1044
+ "ascendc-ops-reviewer"
1045
+ ]
1046
+ }
1047
+ ];
1048
+
1049
+ // src/core/registry.ts
1050
+ var __filename2 = fileURLToPath2(import.meta.url);
1051
+ var __dirname2 = dirname2(__filename2);
1052
+ function findPluginsDir() {
1053
+ const candidates = [
1054
+ join2(__dirname2, "plugins.d"),
1055
+ join2(__dirname2, "..", "plugins.d"),
1056
+ join2(__dirname2, "..", "..", "plugins.d")
1057
+ ];
1058
+ for (const dir of candidates) {
1059
+ if (existsSync2(dir)) return dir;
1060
+ }
1061
+ return candidates[0];
1062
+ }
21
1063
  function loadPluginsFromYaml() {
22
- const pluginsDir = join(__dirname, "plugins.d");
23
- const defaultsPath = join(pluginsDir, "_defaults.yml");
1064
+ const pluginsDir = findPluginsDir();
1065
+ const defaultsPath = join2(pluginsDir, "_defaults.yml");
24
1066
  let defaults = {};
25
1067
  try {
26
- defaults = parseYaml(readFileSync(defaultsPath, "utf-8")) || {};
1068
+ defaults = parseYaml(readFileSync2(defaultsPath, "utf-8")) || {};
27
1069
  } catch {
28
1070
  }
29
1071
  const plugins = [];
@@ -31,7 +1073,7 @@ function loadPluginsFromYaml() {
31
1073
  for (const file of readdirSync(pluginsDir)) {
32
1074
  if (file.startsWith("_") || !file.endsWith(".yml")) continue;
33
1075
  try {
34
- const content = parseYaml(readFileSync(join(pluginsDir, file), "utf-8"));
1076
+ const content = parseYaml(readFileSync2(join2(pluginsDir, file), "utf-8"));
35
1077
  if (!content || !content.id) continue;
36
1078
  plugins.push({
37
1079
  id: content.id,
@@ -41,19 +1083,70 @@ function loadPluginsFromYaml() {
41
1083
  aliases: content.aliases || [],
42
1084
  skills: content.skills ?? defaults.skills ?? 0,
43
1085
  agents: content.agents ?? defaults.agents ?? 0,
44
- description: content.description || ""
1086
+ description: content.description || "",
1087
+ version: content.version,
1088
+ configFile: content.configFile,
1089
+ configRootConfigLink: content.configRootConfigLink,
1090
+ installSkills: content.installSkills,
1091
+ installAgents: content.installAgents,
1092
+ externalRepos: content.externalRepos
45
1093
  });
46
1094
  } catch {
1095
+ logger.warn(t("plugin_yaml_error").replace("{file}", file));
47
1096
  }
48
1097
  }
49
1098
  } catch {
50
1099
  }
1100
+ if (plugins.length === 0) {
1101
+ return [...embedded_plugins_default];
1102
+ }
51
1103
  plugins.sort(
52
1104
  (a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
53
1105
  );
54
1106
  return plugins;
55
1107
  }
56
1108
  var PLUGIN_REGISTRY = loadPluginsFromYaml();
1109
+ var PLUGIN_DIRS = ["plugins-official", "plugins-community"];
1110
+ function mergeDynamicPlugins(repoPath) {
1111
+ if (!existsSync2(repoPath)) return;
1112
+ const existingIds = new Set(PLUGIN_REGISTRY.map((p) => p.id));
1113
+ const dynamic = [];
1114
+ for (const pluginDir of PLUGIN_DIRS) {
1115
+ const parent = join2(repoPath, pluginDir);
1116
+ if (!existsSync2(parent)) continue;
1117
+ let entries = [];
1118
+ try {
1119
+ entries = readdirSync(parent);
1120
+ } catch {
1121
+ continue;
1122
+ }
1123
+ for (const entry of entries) {
1124
+ const pluginPath = join2(parent, entry);
1125
+ if (!isDirectory(pluginPath)) continue;
1126
+ if (entry === "install-helper") continue;
1127
+ const initSh = join2(pluginPath, "init.sh");
1128
+ if (!existsSync2(initSh)) continue;
1129
+ const pluginId = entry;
1130
+ if (existingIds.has(pluginId)) continue;
1131
+ dynamic.push({
1132
+ id: pluginId,
1133
+ dir: `${pluginDir}/${entry}`,
1134
+ displayName: entry,
1135
+ script: "init.sh",
1136
+ aliases: [],
1137
+ skills: 0,
1138
+ agents: 0,
1139
+ description: ""
1140
+ });
1141
+ existingIds.add(pluginId);
1142
+ }
1143
+ }
1144
+ if (dynamic.length > 0) {
1145
+ PLUGIN_REGISTRY = [...PLUGIN_REGISTRY, ...dynamic].sort(
1146
+ (a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
1147
+ );
1148
+ }
1149
+ }
57
1150
  function findPlugin(query) {
58
1151
  const normalized = query.toLowerCase().trim();
59
1152
  const exactMatch = PLUGIN_REGISTRY.find(
@@ -77,47 +1170,14 @@ function getPluginById(id) {
77
1170
 
78
1171
  // src/core/detector.ts
79
1172
  import { execa } from "execa";
80
- import { existsSync as existsSync2 } from "fs";
1173
+ import { existsSync as existsSync4 } from "fs";
81
1174
  import { homedir as homedir2 } from "os";
82
1175
  import { join as join4 } from "path";
83
1176
 
84
1177
  // src/utils/paths.ts
85
1178
  import { homedir } from "os";
86
1179
  import { join as join3 } from "path";
87
- import { existsSync } from "fs";
88
-
89
- // src/utils/i18n.ts
90
- import { readFileSync as readFileSync2 } from "fs";
91
- import { join as join2, dirname as dirname2 } from "path";
92
- import { fileURLToPath as fileURLToPath2 } from "url";
93
- var __filename2 = fileURLToPath2(import.meta.url);
94
- var __dirname2 = dirname2(__filename2);
95
- function loadLocale(locale) {
96
- const localePath = join2(__dirname2, "locales", `${locale}.json`);
97
- try {
98
- const content = readFileSync2(localePath, "utf-8");
99
- return JSON.parse(content);
100
- } catch {
101
- const fallbackPath = join2(__dirname2, "locales", "zh_CN.json");
102
- try {
103
- const content = readFileSync2(fallbackPath, "utf-8");
104
- return JSON.parse(content);
105
- } catch {
106
- return {};
107
- }
108
- }
109
- }
110
- var currentLang = "zh_CN";
111
- var messages = loadLocale("zh_CN");
112
- function setLanguage(lang) {
113
- currentLang = lang;
114
- messages = loadLocale(lang);
115
- }
116
- function t(key) {
117
- return messages[key] !== void 0 ? messages[key] : key;
118
- }
119
-
120
- // src/utils/paths.ts
1180
+ import { existsSync as existsSync3 } from "fs";
121
1181
  var VALID_TOOLS = ["opencode", "claude", "trae", "cursor", "copilot"];
122
1182
  var VALID_LEVELS = ["project", "global"];
123
1183
  function validateTool(tool) {
@@ -138,9 +1198,9 @@ function validateLevel(level) {
138
1198
  }
139
1199
  function detectTraeVariant() {
140
1200
  const home = homedir();
141
- if (existsSync(join3(home, ".trae-cn"))) return "ide";
142
- if (existsSync(join3(home, ".marscode"))) return "plugin";
143
- if (existsSync(join3(home, ".traecli"))) return "cli";
1201
+ if (existsSync3(join3(home, ".trae-cn"))) return "ide";
1202
+ if (existsSync3(join3(home, ".marscode"))) return "plugin";
1203
+ if (existsSync3(join3(home, ".traecli"))) return "cli";
144
1204
  return "unknown";
145
1205
  }
146
1206
  function getConfigRoot(tool, level, base) {
@@ -202,6 +1262,9 @@ function getConfigRoot(tool, level, base) {
202
1262
  function getConfigFileName(tool) {
203
1263
  return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
204
1264
  }
1265
+ function getAgentsFileName(tool) {
1266
+ return getConfigFileName(tool);
1267
+ }
205
1268
  function getSkillsDir(configRoot) {
206
1269
  return join3(configRoot, "skills");
207
1270
  }
@@ -231,8 +1294,9 @@ async function getCommandVersion(cmd, args = ["--version"]) {
231
1294
  }
232
1295
  async function getCommandPath(cmd) {
233
1296
  try {
234
- const result = await execa("which", [cmd], { timeout: 5e3 });
235
- return result.stdout.trim();
1297
+ const whichCmd = process.platform === "win32" ? "where" : "which";
1298
+ const result = await execa(whichCmd, [cmd], { timeout: 5e3 });
1299
+ return result.stdout.trim().split("\n")[0];
236
1300
  } catch {
237
1301
  return void 0;
238
1302
  }
@@ -253,11 +1317,11 @@ async function detectTrae() {
253
1317
  const home = homedir2();
254
1318
  const variant = detectTraeVariant();
255
1319
  let path;
256
- if (variant === "ide" && existsSync2(join4(home, ".trae-cn"))) {
1320
+ if (variant === "ide" && existsSync4(join4(home, ".trae-cn"))) {
257
1321
  path = join4(home, ".trae-cn");
258
- } else if (variant === "plugin" && existsSync2(join4(home, ".marscode"))) {
1322
+ } else if (variant === "plugin" && existsSync4(join4(home, ".marscode"))) {
259
1323
  path = join4(home, ".marscode");
260
- } else if (variant === "cli" && existsSync2(join4(home, ".traecli"))) {
1324
+ } else if (variant === "cli" && existsSync4(join4(home, ".traecli"))) {
261
1325
  path = join4(home, ".traecli");
262
1326
  } else if (variant === "unknown") {
263
1327
  const cmdPath = await getCommandPath("trae");
@@ -274,7 +1338,7 @@ async function detectCursor() {
274
1338
  }
275
1339
  if (process.platform === "darwin") {
276
1340
  const appPath = "/Applications/Cursor.app";
277
- if (existsSync2(appPath)) {
1341
+ if (existsSync4(appPath)) {
278
1342
  return { name: "cursor", path: appPath };
279
1343
  }
280
1344
  }
@@ -318,15 +1382,15 @@ function getToolDisplayName(tool) {
318
1382
  return names[tool];
319
1383
  }
320
1384
  function getAllTools() {
321
- return ["opencode", "claude", "trae", "cursor", "copilot"];
1385
+ return VALID_TOOLS;
322
1386
  }
323
1387
 
324
1388
  // src/core/manifest.ts
325
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
1389
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
326
1390
  import { join as join5 } from "path";
327
1391
  function readManifest(configRoot) {
328
1392
  const manifestPath = getManifestPath(configRoot);
329
- if (existsSync3(manifestPath)) {
1393
+ if (existsSync5(manifestPath)) {
330
1394
  try {
331
1395
  const content = readFileSync3(manifestPath, "utf-8");
332
1396
  return JSON.parse(content);
@@ -339,7 +1403,7 @@ function readAllManifests(configRoot) {
339
1403
  const manifests = [];
340
1404
  const plugins = getAllPlugins();
341
1405
  const standardPath = getManifestPath(configRoot);
342
- if (existsSync3(standardPath)) {
1406
+ if (existsSync5(standardPath)) {
343
1407
  try {
344
1408
  const content = readFileSync3(standardPath, "utf-8");
345
1409
  manifests.push(JSON.parse(content));
@@ -348,7 +1412,7 @@ function readAllManifests(configRoot) {
348
1412
  }
349
1413
  for (const plugin of plugins) {
350
1414
  const pluginManifestPath = join5(configRoot, `${plugin.id}-manifest.json`);
351
- if (existsSync3(pluginManifestPath)) {
1415
+ if (existsSync5(pluginManifestPath)) {
352
1416
  try {
353
1417
  const content = readFileSync3(pluginManifestPath, "utf-8");
354
1418
  manifests.push(JSON.parse(content));
@@ -361,7 +1425,7 @@ function readAllManifests(configRoot) {
361
1425
  function scanInstalled() {
362
1426
  const plugins = getAllPlugins();
363
1427
  const installed = [];
364
- const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
1428
+ const tools = VALID_TOOLS;
365
1429
  const levels = ["project", "global"];
366
1430
  for (const tool of tools) {
367
1431
  for (const level of levels) {
@@ -398,103 +1462,57 @@ function verifyPluginFilesExist(configRoot, manifest, tool, level) {
398
1462
  if (manifest.installed_skills && manifest.installed_skills.length > 0) {
399
1463
  const hasAnySkill = manifest.installed_skills.some((skillId) => {
400
1464
  const skillPath = join5(skillsDir, skillId);
401
- return existsSync3(skillPath);
1465
+ return existsSync5(skillPath);
402
1466
  });
403
1467
  if (!hasAnySkill) return false;
404
1468
  }
405
1469
  if (manifest.installed_agents && manifest.installed_agents.length > 0) {
406
1470
  const hasAnyAgent = manifest.installed_agents.some((agentId) => {
407
1471
  const agentPath = join5(agentsDir, agentId);
408
- return existsSync3(agentPath);
1472
+ return existsSync5(agentPath);
409
1473
  });
410
1474
  if (!hasAnyAgent) return false;
411
1475
  }
412
- let agentsMdPath;
413
- if (tool === "opencode" && level === "project") {
414
- agentsMdPath = join5(configRoot, "..", "AGENTS.md");
415
- } else {
416
- agentsMdPath = join5(configRoot, "AGENTS.md");
417
- }
418
- if (!existsSync3(agentsMdPath)) return false;
1476
+ const agentsFileName = getAgentsFileName(tool);
1477
+ const configFilePath = level === "project" ? join5(configRoot, "..", agentsFileName) : join5(configRoot, agentsFileName);
1478
+ if (!existsSync5(configFilePath)) return false;
419
1479
  return true;
420
1480
  }
421
1481
 
422
- // src/utils/logger.ts
423
- import chalk from "chalk";
424
- import ora from "ora";
425
- var logger = {
426
- success: (msg) => console.log(chalk.green("\u2713") + " " + msg),
427
- error: (msg) => console.log(chalk.red("\u2717") + " " + msg),
428
- warn: (msg) => console.log(chalk.yellow("\u26A0") + " " + msg),
429
- info: (msg) => console.log(chalk.cyan("\u2192") + " " + msg),
430
- step: (msg) => console.log(chalk.dim(msg)),
431
- blank: () => console.log()
432
- };
433
- function createSpinner(text) {
434
- return ora({ text, color: "cyan" });
435
- }
436
- function printBanner(subtitle) {
437
- console.log(chalk.cyan(`
438
- ____ _ _ _ _ _ ____ _
439
- / ___| / \\ | \\ | | \\ | | __ ) ___ | |_
440
- | | / _ \\ | \\| | \\| | _ \\ / _ \\| __|
441
- | |___ / ___ \\| |\\ | |\\ | |_) | (_) | |_
442
- \\____/_/ \\_\\_| \\_|_| \\_|____/ \\___/ \\__|
443
- `));
444
- if (subtitle) {
445
- console.log(chalk.bold(` ${subtitle}`));
446
- console.log();
447
- }
448
- }
449
- function getDisplayWidth(str) {
450
- let width = 0;
451
- for (const ch of str) {
452
- const code = ch.codePointAt(0);
453
- 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) {
454
- width += 2;
455
- } else {
456
- width += 1;
1482
+ // src/utils/config.ts
1483
+ import { existsSync as existsSync7, mkdirSync, readFileSync as readFileSync4 } from "fs";
1484
+ import { join as join6 } from "path";
1485
+ import { parse, stringify } from "yaml";
1486
+
1487
+ // src/utils/fs.ts
1488
+ import { writeFileSync, renameSync, existsSync as existsSync6, unlinkSync } from "fs";
1489
+ import { dirname as dirname3 } from "path";
1490
+ function atomicWriteFileSync(filePath, data, encoding = "utf-8") {
1491
+ const dir = dirname3(filePath);
1492
+ if (!existsSync6(dir)) {
1493
+ throw new Error(`Directory does not exist: ${dir}`);
1494
+ }
1495
+ const tmpPath = `${filePath}.tmp`;
1496
+ writeFileSync(tmpPath, data, encoding);
1497
+ if (existsSync6(filePath)) {
1498
+ try {
1499
+ unlinkSync(filePath);
1500
+ } catch {
457
1501
  }
458
1502
  }
459
- return width;
460
- }
461
- function printBoxTitle(title, width = 65) {
462
- const border = "\u2550".repeat(width);
463
- const displayWidth = getDisplayWidth(title);
464
- const totalPadding = width - displayWidth;
465
- const leftPadding = Math.floor(totalPadding / 2);
466
- const rightPadding = totalPadding - leftPadding;
467
- const paddedTitle = " ".repeat(Math.max(0, leftPadding)) + title + " ".repeat(Math.max(0, rightPadding));
468
- console.log();
469
- console.log(chalk.cyan.bold(` \u2554${border}\u2557`));
470
- console.log(chalk.cyan.bold(` \u2551`) + chalk.cyan.bold(paddedTitle) + chalk.cyan.bold(`\u2551`));
471
- console.log(chalk.cyan.bold(` \u255A${border}\u255D`));
472
- console.log();
473
- }
474
- function showOperationHints(isCheckbox = false) {
475
- if (isCheckbox) {
476
- 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
477
- `);
478
- } else {
479
- console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}
480
- `);
481
- }
1503
+ renameSync(tmpPath, filePath);
482
1504
  }
483
1505
 
484
1506
  // src/utils/config.ts
485
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
486
- import { join as join6 } from "path";
487
- import { homedir as homedir3 } from "os";
488
- import { parse, stringify } from "yaml";
489
1507
  function getConfigDir() {
490
- return join6(homedir3(), ".cannbot");
1508
+ return getCannbotConfigDir();
491
1509
  }
492
1510
  function getConfigPath() {
493
1511
  return join6(getConfigDir(), "config.yaml");
494
1512
  }
495
1513
  function readConfig() {
496
1514
  const configPath = getConfigPath();
497
- if (!existsSync4(configPath)) {
1515
+ if (!existsSync7(configPath)) {
498
1516
  return {
499
1517
  language: "zh_CN",
500
1518
  installedPlugins: []
@@ -511,6 +1529,7 @@ function readConfig() {
511
1529
  installedPlugins: parsed.installedPlugins || []
512
1530
  };
513
1531
  } catch {
1532
+ logger.warn(t("config_corrupted"));
514
1533
  return {
515
1534
  language: "zh_CN",
516
1535
  installedPlugins: []
@@ -519,112 +1538,39 @@ function readConfig() {
519
1538
  }
520
1539
  function writeConfig(config) {
521
1540
  const configDir = getConfigDir();
522
- if (!existsSync4(configDir)) {
1541
+ if (!existsSync7(configDir)) {
523
1542
  mkdirSync(configDir, { recursive: true });
524
1543
  }
525
1544
  const configPath = getConfigPath();
526
1545
  const content = stringify(config);
527
- writeFileSync(configPath, content, "utf-8");
1546
+ atomicWriteFileSync(configPath, content);
528
1547
  }
529
1548
  function updateConfig(updates) {
530
1549
  const config = readConfig();
531
1550
  const updated = { ...config, ...updates };
532
1551
  writeConfig(updated);
533
1552
  return updated;
534
- }
535
- function addInstalledPlugin(pluginId) {
536
- const config = readConfig();
537
- if (!config.installedPlugins.includes(pluginId)) {
538
- config.installedPlugins.push(pluginId);
539
- writeConfig(config);
540
- }
541
- }
542
- function removeInstalledPlugin(pluginId) {
543
- const config = readConfig();
544
- config.installedPlugins = config.installedPlugins.filter(
545
- (id) => id !== pluginId
546
- );
547
- writeConfig(config);
548
- }
549
-
550
- // src/ui/theme.ts
551
- import chalk2 from "chalk";
552
- var selectTheme = {
553
- helpMode: "never"
554
- };
555
- function isCJK(code) {
556
- 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;
557
- }
558
- function isEmoji(code) {
559
- return code >= 127744 && code <= 129791 || code >= 9728 && code <= 10175;
560
- }
561
- function getDisplayWidth2(str) {
562
- let width = 0;
563
- for (const ch of str) {
564
- const code = ch.codePointAt(0);
565
- if (isCJK(code) || isEmoji(code)) {
566
- width += 2;
567
- } else {
568
- width += 1;
569
- }
570
- }
571
- return width;
572
- }
573
- function wrapDescription(text) {
574
- const prefix = " \u{1F4A1} ";
575
- const indent = " ";
576
- const cols = process.stdout.columns || 120;
577
- const maxLen = Math.max(20, cols - getDisplayWidth2(prefix) - 2);
578
- const tokens = [];
579
- let i = 0;
580
- while (i < text.length) {
581
- const code = text[i].codePointAt(0);
582
- if (isCJK(code)) {
583
- tokens.push({ text: text[i], width: 2 });
584
- i++;
585
- } else {
586
- let j = i;
587
- while (j < text.length) {
588
- const c = text[j].codePointAt(0);
589
- if (isCJK(c)) break;
590
- j++;
591
- }
592
- const word = text.slice(i, j);
593
- tokens.push({ text: word, width: getDisplayWidth2(word) });
594
- i = j;
595
- }
596
- }
597
- const lines = [];
598
- let line = "";
599
- let lineWidth = 0;
600
- for (const token of tokens) {
601
- if (lineWidth + token.width > maxLen && line) {
602
- lines.push(line.trimEnd());
603
- line = token.text;
604
- lineWidth = token.width;
605
- } else {
606
- line += token.text;
607
- lineWidth += token.width;
608
- }
609
- }
610
- if (line) lines.push(line.trimEnd());
611
- return lines.map((l, i2) => chalk2.cyan(i2 === 0 ? prefix + l : indent + l)).join("\n");
612
- }
613
- var checkboxTheme = {
614
- icon: {
615
- checked: "\x1B[32m\u2611\x1B[22m",
616
- unchecked: "\u2610",
617
- cursor: "\u276F"
618
- },
619
- helpMode: "never",
620
- style: {
621
- description: (text) => wrapDescription(text)
1553
+ }
1554
+ function addInstalledPlugin(pluginId) {
1555
+ const config = readConfig();
1556
+ if (!config.installedPlugins.includes(pluginId)) {
1557
+ config.installedPlugins.push(pluginId);
1558
+ writeConfig(config);
622
1559
  }
623
- };
1560
+ }
1561
+ function removeInstalledPlugin(pluginId) {
1562
+ const config = readConfig();
1563
+ config.installedPlugins = config.installedPlugins.filter(
1564
+ (id) => id !== pluginId
1565
+ );
1566
+ writeConfig(config);
1567
+ }
624
1568
 
625
- // src/ui/wizard.ts
1569
+ // src/utils/constants.ts
626
1570
  var BACK = "__back__";
627
1571
  var CANCEL = "__cancel__";
1572
+
1573
+ // src/ui/wizard.ts
628
1574
  async function selectToolWithDetection() {
629
1575
  const config = readConfig();
630
1576
  logger.blank();
@@ -708,7 +1654,7 @@ async function stepTool(detectedTools, lastTool) {
708
1654
  if (detectedTools.length === 0) {
709
1655
  logger.error(t("error_tool_not_found"));
710
1656
  const choices2 = [
711
- ...["opencode", "claude", "trae", "cursor", "copilot"].map((tool) => ({
1657
+ ...VALID_TOOLS.map((tool) => ({
712
1658
  name: `> ${getToolDisplayName(tool)}`,
713
1659
  value: tool
714
1660
  })),
@@ -809,37 +1755,25 @@ async function stepPlugins(tool, level) {
809
1755
  const isInstalled = installedSet.has(p.id);
810
1756
  const suffix = isInstalled ? ` [${t("wizard_already_installed")}]` : "";
811
1757
  return {
812
- name: `${chalk3.cyan(p.displayName)}${suffix}`,
1758
+ name: `> ${chalk3.cyan(p.displayName)}${suffix}`,
813
1759
  value: p.id,
814
- checked: false,
815
1760
  description: p.description
816
1761
  };
817
1762
  });
818
- showOperationHints(true);
819
- const selected = await checkbox({
820
- message: t("wizard_select_plugins"),
1763
+ choices.push(new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1764
+ choices.push({ name: "<- " + t("wizard_back"), value: BACK });
1765
+ choices.push({ name: "x " + t("wizard_cancel"), value: CANCEL });
1766
+ showOperationHints();
1767
+ const selected = await select({
1768
+ message: t("wizard_select_plugin"),
821
1769
  choices,
822
1770
  loop: false,
823
- instructions: false,
824
- theme: checkboxTheme,
1771
+ theme: selectTheme,
825
1772
  pageSize: 15
826
1773
  });
827
- const pluginIds = selected;
828
- if (pluginIds.length === 0) {
829
- const action = await select({
830
- message: t("wizard_no_selection"),
831
- choices: [
832
- new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
833
- { name: "<- " + t("wizard_back_to_reselect"), value: "back" },
834
- { name: "x " + t("wizard_cancel"), value: "cancel" }
835
- ],
836
- loop: false,
837
- theme: selectTheme
838
- });
839
- if (action === "back") return BACK;
840
- return CANCEL;
841
- }
842
- return pluginIds;
1774
+ if (selected === BACK) return BACK;
1775
+ if (selected === CANCEL) return CANCEL;
1776
+ return [selected];
843
1777
  }
844
1778
  async function stepConfirm(tool, level, plugins) {
845
1779
  printBoxTitle(t("wizard_step_4_title"));
@@ -870,19 +1804,19 @@ async function stepConfirm(tool, level, plugins) {
870
1804
  }
871
1805
 
872
1806
  // src/core/repository.ts
873
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
874
- import { join as join8, dirname as dirname4 } from "path";
1807
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
1808
+ import { join as join8, dirname as dirname5 } from "path";
875
1809
  import { fileURLToPath as fileURLToPath4 } from "url";
876
1810
  import { execa as execa2 } from "execa";
877
1811
  import { parse as parseYaml3 } from "yaml";
878
1812
 
879
1813
  // src/core/scanner.ts
880
- import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
881
- import { join as join7, dirname as dirname3 } from "path";
1814
+ import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync5, mkdirSync as mkdirSync2 } from "fs";
1815
+ import { join as join7, dirname as dirname4 } from "path";
882
1816
  import { fileURLToPath as fileURLToPath3 } from "url";
883
1817
  import { parse as parseYaml2 } from "yaml";
884
1818
  var __filename3 = fileURLToPath3(import.meta.url);
885
- var __dirname3 = dirname3(__filename3);
1819
+ var __dirname3 = dirname4(__filename3);
886
1820
  var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
887
1821
  "node_modules",
888
1822
  ".git",
@@ -907,14 +1841,17 @@ function loadScanConfig() {
907
1841
  const configPath = join7(__dirname3, "config", "repository.yaml");
908
1842
  try {
909
1843
  const config = parseYaml2(readFileSync5(configPath, "utf-8"));
1844
+ const ttlHours = config.scanCacheTtlHours || 24;
910
1845
  return {
911
- skillDirs: config.scanDirs || ["ops", "model", "graph", "infra", "ops-lab"],
912
- pluginDirs: config.pluginDirs || ["plugins-official", "plugins-community"]
1846
+ skillDirs: config.scanDirs || ["ops", "model", "graph", "infra", "ops-lab", "runtime"],
1847
+ pluginDirs: config.pluginDirs || ["plugins-official", "plugins-community"],
1848
+ cacheTtlMs: ttlHours * 60 * 60 * 1e3
913
1849
  };
914
1850
  } catch {
915
1851
  return {
916
- skillDirs: ["ops", "model", "graph", "infra", "ops-lab"],
917
- pluginDirs: ["plugins-official", "plugins-community"]
1852
+ skillDirs: ["ops", "model", "graph", "infra", "ops-lab", "runtime"],
1853
+ pluginDirs: ["plugins-official", "plugins-community"],
1854
+ cacheTtlMs: 24 * 60 * 60 * 1e3
918
1855
  };
919
1856
  }
920
1857
  }
@@ -926,23 +1863,37 @@ function scanSkills(repoPath) {
926
1863
  const seen = /* @__PURE__ */ new Set();
927
1864
  for (const dir of SKILL_SCAN_DIRS) {
928
1865
  const fullPath = join7(repoPath, dir);
929
- if (!existsSync5(fullPath)) continue;
1866
+ if (!existsSync8(fullPath)) continue;
930
1867
  scanDirectory(fullPath, repoPath, dir, skills, seen);
931
1868
  }
932
1869
  for (const pluginDir of PLUGIN_SCAN_DIRS) {
933
1870
  const pluginParent = join7(repoPath, pluginDir);
934
- if (!existsSync5(pluginParent)) continue;
1871
+ if (!existsSync8(pluginParent)) continue;
935
1872
  for (const plugin of safeReaddir(pluginParent)) {
936
1873
  const pluginPath = join7(pluginParent, plugin);
937
1874
  if (!isDirectory(pluginPath)) continue;
938
1875
  const skillsDir = join7(pluginPath, "skills");
939
- if (existsSync5(skillsDir)) {
1876
+ if (existsSync8(skillsDir)) {
940
1877
  scanDirectory(skillsDir, repoPath, `${pluginDir}/${plugin}/skills`, skills, seen);
941
1878
  }
942
1879
  const skillDir = join7(pluginPath, "skill");
943
- if (existsSync5(skillDir)) {
1880
+ if (existsSync8(skillDir)) {
944
1881
  scanDirectory(skillDir, repoPath, `${pluginDir}/${plugin}/skill`, skills, seen);
945
1882
  }
1883
+ const rootSkillMd = join7(pluginPath, "SKILL.md");
1884
+ const initSh = join7(pluginPath, "init.sh");
1885
+ if (existsSync8(rootSkillMd) && !existsSync8(initSh)) {
1886
+ const frontmatter = parseFrontmatter(rootSkillMd);
1887
+ if (frontmatter && !seen.has(frontmatter.name)) {
1888
+ seen.add(frontmatter.name);
1889
+ skills.push({
1890
+ id: frontmatter.name,
1891
+ description: frontmatter.description,
1892
+ source: `${pluginDir}/${plugin}`,
1893
+ filePath: rootSkillMd
1894
+ });
1895
+ }
1896
+ }
946
1897
  }
947
1898
  }
948
1899
  return skills;
@@ -965,8 +1916,12 @@ function parseFrontmatter(filePath) {
965
1916
  const parsed = parseYaml2(yamlBlock);
966
1917
  if (!parsed || typeof parsed !== "object") return null;
967
1918
  if (!parsed.name || typeof parsed.name !== "string") return null;
1919
+ const trimmedName = parsed.name.trim();
1920
+ if (!trimmedName || /[\/\\]|\.\./.test(trimmedName) || !/^[a-zA-Z0-9._-]+$/.test(trimmedName)) {
1921
+ return null;
1922
+ }
968
1923
  return {
969
- name: parsed.name.trim(),
1924
+ name: trimmedName,
970
1925
  description: (parsed.description || "").trim()
971
1926
  };
972
1927
  } catch {
@@ -976,15 +1931,15 @@ function parseFrontmatter(filePath) {
976
1931
  function getCurrentCommit(repoPath) {
977
1932
  try {
978
1933
  const headFile = join7(repoPath, ".git", "HEAD");
979
- if (!existsSync5(headFile)) return "unknown";
1934
+ if (!existsSync8(headFile)) return "unknown";
980
1935
  const headContent = readFileSync5(headFile, "utf-8").trim();
981
1936
  if (headContent.startsWith("ref: ")) {
982
1937
  const refPath = join7(repoPath, ".git", headContent.slice(5));
983
- if (existsSync5(refPath)) {
1938
+ if (existsSync8(refPath)) {
984
1939
  return readFileSync5(refPath, "utf-8").trim();
985
1940
  }
986
1941
  const packedRefsPath = join7(repoPath, ".git", "packed-refs");
987
- if (existsSync5(packedRefsPath)) {
1942
+ if (existsSync8(packedRefsPath)) {
988
1943
  const packedRefs = readFileSync5(packedRefsPath, "utf-8");
989
1944
  const refName = headContent.slice(5);
990
1945
  const lines = packedRefs.split("\n");
@@ -1003,12 +1958,12 @@ function getCurrentCommit(repoPath) {
1003
1958
  function readScanCache() {
1004
1959
  try {
1005
1960
  const cachePath = getCachePath();
1006
- if (!existsSync5(cachePath)) return null;
1961
+ if (!existsSync8(cachePath)) return null;
1007
1962
  const content = readFileSync5(cachePath, "utf-8");
1008
1963
  const cache = JSON.parse(content);
1009
1964
  if (!cache.skills || !cache.timestamp) return null;
1010
1965
  const age = Date.now() - cache.timestamp;
1011
- if (age > 24 * 60 * 60 * 1e3) return null;
1966
+ if (age > scanConfig.cacheTtlMs) return null;
1012
1967
  return cache;
1013
1968
  } catch {
1014
1969
  return null;
@@ -1017,11 +1972,11 @@ function readScanCache() {
1017
1972
  function writeScanCache(cache) {
1018
1973
  try {
1019
1974
  const cachePath = getCachePath();
1020
- const cacheDir = dirname3(cachePath);
1021
- if (!existsSync5(cacheDir)) {
1975
+ const cacheDir = dirname4(cachePath);
1976
+ if (!existsSync8(cacheDir)) {
1022
1977
  mkdirSync2(cacheDir, { recursive: true });
1023
1978
  }
1024
- writeFileSync2(cachePath, JSON.stringify(cache, null, 2));
1979
+ atomicWriteFileSync(cachePath, JSON.stringify(cache, null, 2));
1025
1980
  } catch {
1026
1981
  }
1027
1982
  }
@@ -1034,7 +1989,7 @@ function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1034
1989
  if (EXCLUDE_DIRS.has(entry)) continue;
1035
1990
  if (!isDirectory(fullPath)) continue;
1036
1991
  const skillMd = join7(fullPath, "SKILL.md");
1037
- if (existsSync5(skillMd)) {
1992
+ if (existsSync8(skillMd)) {
1038
1993
  const frontmatter = parseFrontmatter(skillMd);
1039
1994
  if (frontmatter && !seen.has(frontmatter.name)) {
1040
1995
  seen.add(frontmatter.name);
@@ -1057,28 +2012,22 @@ function safeReaddir(dir) {
1057
2012
  return [];
1058
2013
  }
1059
2014
  }
1060
- function isDirectory(path) {
1061
- try {
1062
- return statSync(path).isDirectory();
1063
- } catch {
1064
- return false;
1065
- }
1066
- }
1067
2015
 
1068
2016
  // src/core/skill-registry.ts
1069
2017
  var CATEGORY_DEFS = [
1070
- { id: "knowledge", name: "\u77E5\u8BC6\u4E0E\u53C2\u8003" },
1071
- { id: "env-tools", name: "\u73AF\u5883\u4E0E\u5DE5\u5177" },
1072
- { id: "debug", name: "\u8C03\u8BD5\u4E0E\u8BCA\u65AD" },
1073
- { id: "testing", name: "\u6D4B\u8BD5\u4E0E\u8D28\u91CF" },
1074
- { id: "ascendc", name: "AscendC \u5F00\u53D1" },
1075
- { id: "pypto", name: "PyPTO \u5F00\u53D1" },
1076
- { id: "tilelang", name: "TileLang \u5F00\u53D1" },
1077
- { id: "triton", name: "Triton \u5F00\u53D1" },
1078
- { id: "model", name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316" },
1079
- { id: "graph", name: "\u56FE\u6A21\u5F0F" },
1080
- { id: "platform", name: "\u5E73\u53F0\u5DE5\u5177" },
1081
- { id: "other", name: "\u5176\u4ED6 Skills" }
2018
+ { id: "knowledge", name: "\u77E5\u8BC6\u4E0E\u53C2\u8003", nameEn: "Knowledge & Reference" },
2019
+ { id: "env-tools", name: "\u73AF\u5883\u4E0E\u5DE5\u5177", nameEn: "Environment & Tools" },
2020
+ { id: "debug", name: "\u8C03\u8BD5\u4E0E\u8BCA\u65AD", nameEn: "Debug & Diagnostics" },
2021
+ { id: "testing", name: "\u6D4B\u8BD5\u4E0E\u8D28\u91CF", nameEn: "Testing & Quality" },
2022
+ { id: "ascendc", name: "AscendC \u5F00\u53D1", nameEn: "AscendC Development" },
2023
+ { id: "pypto", name: "PyPTO \u5F00\u53D1", nameEn: "PyPTO Development" },
2024
+ { id: "tilelang", name: "TileLang \u5F00\u53D1", nameEn: "TileLang Development" },
2025
+ { id: "triton", name: "Triton \u5F00\u53D1", nameEn: "Triton Development" },
2026
+ { id: "model", name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316", nameEn: "Model Inference Optimization" },
2027
+ { id: "runtime", name: "Runtime \u8FC1\u79FB", nameEn: "Runtime Migration" },
2028
+ { id: "graph", name: "\u56FE\u6A21\u5F0F", nameEn: "Graph Mode" },
2029
+ { id: "platform", name: "\u5E73\u53F0\u5DE5\u5177", nameEn: "Platform Tools" },
2030
+ { id: "other", name: "\u5176\u4ED6 Skills", nameEn: "Other Skills" }
1082
2031
  ];
1083
2032
  var dynamicSkills = [];
1084
2033
  var initialized = false;
@@ -1086,7 +2035,8 @@ function initFromScan(scanned) {
1086
2035
  dynamicSkills = scanned.map((s) => ({
1087
2036
  id: s.id,
1088
2037
  description: s.description,
1089
- source: s.source
2038
+ source: s.source,
2039
+ filePath: s.filePath
1090
2040
  }));
1091
2041
  initialized = true;
1092
2042
  }
@@ -1162,9 +2112,7 @@ var STATIC_SKILL_CATEGORIES = [
1162
2112
  { id: "catlass-op-develop", description: "Catlass \u7B97\u5B50\u5F00\u53D1", source: "ops" },
1163
2113
  { id: "catlass-op-perf-tune", description: "Catlass \u6027\u80FD\u8C03\u4F18", source: "ops" },
1164
2114
  { id: "cuda2ascend-simt", description: "CUDA \u8FC1\u79FB\u5230 Ascend C SIMT", source: "ops-lab" },
1165
- { id: "ops-direct-invoke-flash", description: "\u4ECE\u96F6\u6784\u5EFA Ascend C \u6838\u51FD\u6570", source: "plugins-official/ops-direct-invoke-flash/skills" },
1166
- { id: "ops-registry-invoke-workflow", description: "\u6CE8\u518C\u8C03\u7528\u5DE5\u4F5C\u6D41", source: "plugins-official/ops-registry-invoke" },
1167
- { id: "ops-easyasc-dsl", description: "EasyASC DSL \u7B97\u5B50\u5F00\u53D1", source: "plugins-community/ops-easyasc-dsl/skill" }
2115
+ { id: "ops-direct-invoke-flash", description: "\u4ECE\u96F6\u6784\u5EFA Ascend C \u6838\u51FD\u6570", source: "plugins-official/ops-direct-invoke-flash/skills" }
1168
2116
  ]
1169
2117
  },
1170
2118
  {
@@ -1221,7 +2169,9 @@ var STATIC_SKILL_CATEGORIES = [
1221
2169
  { id: "model-infer-prefetch", description: "\u6743\u91CD\u9884\u53D6\u9002\u914D", source: "model" },
1222
2170
  { id: "model-infer-superkernel", description: "SuperKernel \u9002\u914D", source: "model" },
1223
2171
  { id: "model-infer-precision-debug", description: "NPU \u63A8\u7406\u7CBE\u5EA6\u8BCA\u65AD", source: "model" },
1224
- { id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" }
2172
+ { id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" },
2173
+ { id: "model-infer-harmony", description: "\u7AEF\u4FA7\u9E3F\u8499 ASR \u91CF\u5316\u8F6C\u6362\u4E0E\u6253\u5305", source: "model" },
2174
+ { 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" }
1225
2175
  ]
1226
2176
  },
1227
2177
  {
@@ -1237,6 +2187,13 @@ var STATIC_SKILL_CATEGORIES = [
1237
2187
  { id: "torch-custom-ops-guide", description: "\u81EA\u5B9A\u4E49\u7B97\u5B50\u5165\u56FE\u6307\u5357", source: "graph" }
1238
2188
  ]
1239
2189
  },
2190
+ {
2191
+ id: "runtime",
2192
+ name: "Runtime \u8FC1\u79FB",
2193
+ skills: [
2194
+ { id: "runtime_migration", description: "CUDA \u5E94\u7528\u8FC1\u79FB\u5230 CANN \u5E73\u53F0\u6307\u5357", source: "runtime" }
2195
+ ]
2196
+ },
1240
2197
  {
1241
2198
  id: "platform",
1242
2199
  name: "\u5E73\u53F0\u5DE5\u5177",
@@ -1280,9 +2237,10 @@ function getAllSkills() {
1280
2237
  }
1281
2238
  function getAllCategories() {
1282
2239
  const skills = getActiveSkills();
2240
+ const isEn = t("list_name") === "Name";
1283
2241
  return CATEGORY_DEFS.map((def) => ({
1284
2242
  id: def.id,
1285
- name: def.name,
2243
+ name: isEn ? def.nameEn : def.name,
1286
2244
  skills: skills.filter((s) => {
1287
2245
  const catId = CATEGORY_MAP[s.id];
1288
2246
  if (catId) return catId === def.id;
@@ -1293,7 +2251,7 @@ function getAllCategories() {
1293
2251
 
1294
2252
  // src/core/repository.ts
1295
2253
  var __filename4 = fileURLToPath4(import.meta.url);
1296
- var __dirname4 = dirname4(__filename4);
2254
+ var __dirname4 = dirname5(__filename4);
1297
2255
  function loadRepoUrl() {
1298
2256
  if (process.env.CANNBOT_REPO_URL) {
1299
2257
  return process.env.CANNBOT_REPO_URL;
@@ -1309,8 +2267,10 @@ function loadRepoUrl() {
1309
2267
  var REPO_URL = loadRepoUrl();
1310
2268
  var RepositoryManager = class {
1311
2269
  repoPath;
2270
+ customPath;
1312
2271
  constructor(customPath) {
1313
2272
  this.repoPath = customPath;
2273
+ this.customPath = customPath;
1314
2274
  }
1315
2275
  async ensureRepo() {
1316
2276
  if (this.repoPath && this.isValidRepo(this.repoPath)) {
@@ -1331,10 +2291,16 @@ var RepositoryManager = class {
1331
2291
  this.repoPath = cachedPath;
1332
2292
  return cachedPath;
1333
2293
  }
1334
- return await this.cloneRepo();
2294
+ const clonedPath = await this.cloneRepo();
2295
+ return clonedPath;
1335
2296
  }
1336
2297
  async ensureRepoAndScan() {
1337
2298
  const repoPath = await this.ensureRepo();
2299
+ const isUserProvided = !!this.customPath || !!process.env.CANNBOT_REPO_PATH;
2300
+ if (!isUserProvided) {
2301
+ await this.updateRepo();
2302
+ }
2303
+ mergeDynamicPlugins(repoPath);
1338
2304
  const cache = readScanCache();
1339
2305
  if (cache && cache.repoCommit === getCurrentCommit(repoPath)) {
1340
2306
  initFromScan(cache.skills);
@@ -1354,7 +2320,9 @@ var RepositoryManager = class {
1354
2320
  try {
1355
2321
  await execa2("git", ["pull", "--quiet"], { cwd: repoPath, timeout: 3e4 });
1356
2322
  } catch {
2323
+ logger.warn(t("repo_pull_failed"));
1357
2324
  }
2325
+ mergeDynamicPlugins(repoPath);
1358
2326
  }
1359
2327
  getRepoPath() {
1360
2328
  if (!this.repoPath) {
@@ -1363,24 +2331,25 @@ var RepositoryManager = class {
1363
2331
  return this.repoPath;
1364
2332
  }
1365
2333
  isValidRepo(path) {
1366
- if (!existsSync6(path)) return false;
2334
+ if (!existsSync9(path)) return false;
1367
2335
  const gitDir = join8(path, ".git");
1368
2336
  const pluginsDir = join8(path, "plugins-official");
1369
- return existsSync6(gitDir) && existsSync6(pluginsDir);
2337
+ return existsSync9(gitDir) && existsSync9(pluginsDir);
1370
2338
  }
1371
2339
  async cloneRepo() {
1372
2340
  const configDir = getCannbotConfigDir();
1373
- if (!existsSync6(configDir)) {
2341
+ if (!existsSync9(configDir)) {
1374
2342
  mkdirSync3(configDir, { recursive: true });
1375
2343
  }
1376
2344
  const targetPath = getCannbotRepoPath();
1377
- if (existsSync6(targetPath)) {
2345
+ if (existsSync9(targetPath)) {
1378
2346
  if (this.isValidRepo(targetPath)) {
1379
2347
  this.repoPath = targetPath;
1380
2348
  return targetPath;
1381
2349
  }
1382
2350
  }
1383
2351
  try {
2352
+ logger.info(t("repo_cloning"));
1384
2353
  await execa2(
1385
2354
  "git",
1386
2355
  ["clone", "--depth", "1", REPO_URL, targetPath],
@@ -1390,7 +2359,7 @@ var RepositoryManager = class {
1390
2359
  return targetPath;
1391
2360
  } catch (error) {
1392
2361
  throw new Error(
1393
- `Failed to clone repository: ${error instanceof Error ? error.message : "Unknown error"}`
2362
+ `Failed to clone repository: ${error instanceof Error ? error.message : t("error_unknown")}`
1394
2363
  );
1395
2364
  }
1396
2365
  }
@@ -1400,12 +2369,12 @@ function createRepositoryManager(customPath) {
1400
2369
  }
1401
2370
 
1402
2371
  // src/core/installer.ts
1403
- import { existsSync as existsSync9 } from "fs";
1404
- import { join as join11 } from "path";
1405
- import { execa as execa3 } from "execa";
2372
+ import { existsSync as existsSync13 } from "fs";
2373
+ import { join as join12, basename as basename2 } from "path";
2374
+ import { execa as execa4, execaSync } from "execa";
1406
2375
 
1407
2376
  // src/core/record.ts
1408
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4, lstatSync, unlinkSync } from "fs";
2377
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
1409
2378
  import { join as join9 } from "path";
1410
2379
  function getInstallsDir() {
1411
2380
  return join9(getCannbotConfigDir(), "installs");
@@ -1415,53 +2384,54 @@ function getRecordPath(pluginId) {
1415
2384
  }
1416
2385
  function readRecord(pluginId) {
1417
2386
  const recordPath = getRecordPath(pluginId);
1418
- if (!existsSync7(recordPath)) {
2387
+ if (!existsSync10(recordPath)) {
1419
2388
  return null;
1420
2389
  }
1421
2390
  try {
1422
2391
  const content = readFileSync7(recordPath, "utf-8");
1423
2392
  return JSON.parse(content);
1424
2393
  } catch {
2394
+ logger.warn(t("record_corrupted").replace("{file}", recordPath));
1425
2395
  return null;
1426
2396
  }
1427
2397
  }
1428
2398
  function writeRecord(record) {
1429
2399
  const installsDir = getInstallsDir();
1430
- if (!existsSync7(installsDir)) {
2400
+ if (!existsSync10(installsDir)) {
1431
2401
  mkdirSync4(installsDir, { recursive: true });
1432
2402
  }
1433
2403
  const recordPath = getRecordPath(record.pluginId);
1434
- writeFileSync4(recordPath, JSON.stringify(record, null, 2), "utf-8");
2404
+ atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
1435
2405
  }
1436
2406
  function deleteRecord(pluginId) {
1437
2407
  const recordPath = getRecordPath(pluginId);
1438
- if (existsSync7(recordPath)) {
1439
- unlinkSync(recordPath);
2408
+ if (existsSync10(recordPath)) {
2409
+ unlinkSync2(recordPath);
1440
2410
  }
1441
2411
  }
1442
- function scanInstalledFiles(pluginId, displayName, tool, level, installPath, configRoot, manifest) {
2412
+ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, configRoot, manifest, externalRepoNames) {
1443
2413
  const files = [];
1444
2414
  const directories = [];
1445
2415
  if (manifest) {
1446
2416
  const skillsDir = join9(configRoot, "skills");
1447
- if (existsSync7(skillsDir)) {
2417
+ if (existsSync10(skillsDir)) {
1448
2418
  directories.push(skillsDir);
1449
2419
  for (const skillName of manifest.installed_skills || []) {
1450
2420
  const skillPath = join9(skillsDir, skillName);
1451
- if (existsSync7(skillPath) || isSymlink(skillPath)) {
2421
+ if (existsSync10(skillPath) || isSymlink(skillPath)) {
1452
2422
  files.push(skillPath);
1453
2423
  }
1454
2424
  }
1455
2425
  }
1456
2426
  const agentsDir = join9(configRoot, "agents");
1457
- if (existsSync7(agentsDir)) {
2427
+ if (existsSync10(agentsDir)) {
1458
2428
  directories.push(agentsDir);
1459
2429
  for (const agentName of manifest.installed_agents || []) {
1460
2430
  const agentPath = join9(agentsDir, agentName);
1461
2431
  const agentPathMd = join9(agentsDir, agentName + ".md");
1462
- if (existsSync7(agentPath) || isSymlink(agentPath)) {
2432
+ if (existsSync10(agentPath) || isSymlink(agentPath)) {
1463
2433
  files.push(agentPath);
1464
- } else if (existsSync7(agentPathMd) || isSymlink(agentPathMd)) {
2434
+ } else if (existsSync10(agentPathMd) || isSymlink(agentPathMd)) {
1465
2435
  files.push(agentPathMd);
1466
2436
  }
1467
2437
  }
@@ -1472,19 +2442,19 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
1472
2442
  files.push(workflowsLink);
1473
2443
  }
1474
2444
  const manifestPath = join9(configRoot, "cannbot-manifest.json");
1475
- if (existsSync7(manifestPath)) {
2445
+ if (existsSync10(manifestPath)) {
1476
2446
  files.push(manifestPath);
1477
2447
  }
1478
2448
  const pluginManifestPath = join9(configRoot, `${pluginId}-manifest.json`);
1479
- if (existsSync7(pluginManifestPath)) {
2449
+ if (existsSync10(pluginManifestPath)) {
1480
2450
  files.push(pluginManifestPath);
1481
2451
  }
1482
2452
  const configFileName = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
1483
2453
  const configFilePath = level === "project" ? join9(installPath, configFileName) : join9(configRoot, configFileName);
1484
- if (existsSync7(configFilePath)) {
2454
+ if (existsSync10(configFilePath)) {
1485
2455
  files.push(configFilePath);
1486
2456
  }
1487
- const repoLinks = ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples"];
2457
+ const repoLinks = externalRepoNames && externalRepoNames.length > 0 ? externalRepoNames : ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples"];
1488
2458
  for (const repoName of repoLinks) {
1489
2459
  const repoLinkPath = join9(installPath, repoName);
1490
2460
  if (isSymlink(repoLinkPath)) {
@@ -1507,35 +2477,29 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
1507
2477
  directories
1508
2478
  };
1509
2479
  }
1510
- function isSymlink(path) {
1511
- try {
1512
- return lstatSync(path).isSymbolicLink();
1513
- } catch {
1514
- return false;
1515
- }
1516
- }
1517
2480
  function getSkillRecordPath() {
1518
2481
  return join9(getInstallsDir(), "skills.json");
1519
2482
  }
1520
2483
  function readSkillRecord() {
1521
2484
  const recordPath = getSkillRecordPath();
1522
- if (!existsSync7(recordPath)) {
2485
+ if (!existsSync10(recordPath)) {
1523
2486
  return {};
1524
2487
  }
1525
2488
  try {
1526
2489
  const content = readFileSync7(recordPath, "utf-8");
1527
2490
  return JSON.parse(content);
1528
2491
  } catch {
2492
+ logger.warn(t("skill_record_corrupted"));
1529
2493
  return {};
1530
2494
  }
1531
2495
  }
1532
2496
  function writeSkillRecord(record) {
1533
2497
  const installsDir = getInstallsDir();
1534
- if (!existsSync7(installsDir)) {
2498
+ if (!existsSync10(installsDir)) {
1535
2499
  mkdirSync4(installsDir, { recursive: true });
1536
2500
  }
1537
2501
  const recordPath = getSkillRecordPath();
1538
- writeFileSync4(recordPath, JSON.stringify(record, null, 2), "utf-8");
2502
+ atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
1539
2503
  }
1540
2504
  function addSkillsToRecord(skillIds, tool, level, installPath) {
1541
2505
  const record = readSkillRecord();
@@ -1576,16 +2540,13 @@ function getInstalledSkills(tool, level, installPath) {
1576
2540
  const skillsDir = join9(configRoot, "skills");
1577
2541
  return recordedSkills.filter((skillId) => {
1578
2542
  const skillPath = join9(skillsDir, skillId);
1579
- return existsSync7(skillPath) || isSymlink(skillPath);
2543
+ return existsSync10(skillPath) || isSymlink(skillPath);
1580
2544
  });
1581
2545
  }
1582
2546
 
1583
2547
  // src/core/backup.ts
1584
- import { existsSync as existsSync8, copyFileSync, readdirSync as readdirSync3, unlinkSync as unlinkSync2 } from "fs";
2548
+ import { existsSync as existsSync11, copyFileSync, readdirSync as readdirSync3, unlinkSync as unlinkSync3 } from "fs";
1585
2549
  import { join as join10 } from "path";
1586
- function getAgentsFileName(tool) {
1587
- return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
1588
- }
1589
2550
  function detectCurrentPlugin(configRoot, tool) {
1590
2551
  const manifest = readManifest(configRoot);
1591
2552
  if (manifest?.team) {
@@ -1598,7 +2559,7 @@ function detectCurrentPlugin(configRoot, tool) {
1598
2559
  }
1599
2560
  }
1600
2561
  const agentsFile = join10(configRoot, getAgentsFileName(tool));
1601
- if (!existsSync8(agentsFile)) {
2562
+ if (!existsSync11(agentsFile)) {
1602
2563
  return null;
1603
2564
  }
1604
2565
  const allRecords = readAllRecords();
@@ -1617,7 +2578,7 @@ function detectCurrentPlugin(configRoot, tool) {
1617
2578
  }
1618
2579
  function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
1619
2580
  const agentsFile = join10(configRoot, getAgentsFileName(tool));
1620
- if (!existsSync8(agentsFile)) {
2581
+ if (!existsSync11(agentsFile)) {
1621
2582
  return null;
1622
2583
  }
1623
2584
  const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
@@ -1636,7 +2597,7 @@ function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
1636
2597
  }
1637
2598
  }
1638
2599
  function findBackups(configRoot) {
1639
- if (!existsSync8(configRoot)) {
2600
+ if (!existsSync11(configRoot)) {
1640
2601
  return [];
1641
2602
  }
1642
2603
  const files = readdirSync3(configRoot);
@@ -1674,8 +2635,8 @@ function restoreBackup(backupPath, configRoot, tool) {
1674
2635
  }
1675
2636
  function deleteBackup(backupPath) {
1676
2637
  try {
1677
- if (existsSync8(backupPath)) {
1678
- unlinkSync2(backupPath);
2638
+ if (existsSync11(backupPath)) {
2639
+ unlinkSync3(backupPath);
1679
2640
  return true;
1680
2641
  }
1681
2642
  return false;
@@ -1694,20 +2655,9 @@ function formatTimestamp(date) {
1694
2655
  }
1695
2656
  function readAllRecords() {
1696
2657
  const records = [];
1697
- const plugins = [
1698
- "ops-direct-invoke",
1699
- "ops-direct-invoke-flash",
1700
- "ops-registry-invoke",
1701
- "pypto-op-orchestrator",
1702
- "triton-op-generator",
1703
- "tilelang-op-orchestrator",
1704
- "model-infer-optimize",
1705
- "catlass-op-generator",
1706
- "ops-code-reviewer",
1707
- "torch-compile"
1708
- ];
1709
- for (const pluginId of plugins) {
1710
- const record = readRecord(pluginId);
2658
+ const plugins = getAllPlugins();
2659
+ for (const plugin of plugins) {
2660
+ const record = readRecord(plugin.id);
1711
2661
  if (record) {
1712
2662
  records.push(record);
1713
2663
  }
@@ -1753,39 +2703,294 @@ async function showRestorePrompt(backups) {
1753
2703
  return choice;
1754
2704
  }
1755
2705
 
1756
- // src/core/installer.ts
1757
- async function installPlugin(opts) {
1758
- const plugin = getPluginById(opts.pluginId);
1759
- if (!plugin) {
2706
+ // src/core/plugin-installer.ts
2707
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync4, realpathSync, readlinkSync, copyFileSync as copyFileSync2, cpSync, writeFileSync as writeFileSync2, rmSync } from "fs";
2708
+ import { join as join11, basename } from "path";
2709
+ import { execa as execa3 } from "execa";
2710
+ function installLink(source, target) {
2711
+ try {
2712
+ const resolved = realpathSync(source);
2713
+ if (isSymlink(target)) {
2714
+ try {
2715
+ const currentTarget = readlinkSync(target);
2716
+ const normCurrent = currentTarget.replace(/\\/g, "/").toLowerCase();
2717
+ const normSource = resolved.replace(/\\/g, "/").toLowerCase();
2718
+ if (normCurrent === normSource) {
2719
+ return "skipped";
2720
+ }
2721
+ } catch {
2722
+ return "skipped";
2723
+ }
2724
+ }
2725
+ if (existsSync12(target) || isSymlink(target)) {
2726
+ unlinkSync4(target);
2727
+ }
2728
+ try {
2729
+ symlinkSync(resolved, target);
2730
+ return "symlink";
2731
+ } catch (e) {
2732
+ if (e.code === "EPERM") {
2733
+ cpSync(resolved, target, { recursive: true });
2734
+ return "copy";
2735
+ }
2736
+ throw e;
2737
+ }
2738
+ } catch {
2739
+ return "failed";
2740
+ }
2741
+ }
2742
+ function installConfigFile(pluginDir, configFile, tool, level, configRoot, installPath, configRootConfigLink) {
2743
+ const sourcePath = join11(pluginDir, configFile);
2744
+ if (!existsSync12(sourcePath)) return;
2745
+ const agentsFileName = getAgentsFileName(tool);
2746
+ const isProjectLevel = level === "project";
2747
+ let primaryTarget;
2748
+ if (isProjectLevel) {
2749
+ primaryTarget = join11(installPath, agentsFileName);
2750
+ } else {
2751
+ primaryTarget = join11(configRoot, agentsFileName);
2752
+ }
2753
+ try {
2754
+ const resolvedSource = realpathSync(sourcePath);
2755
+ if (existsSync12(primaryTarget) || isSymlink(primaryTarget)) {
2756
+ unlinkSync4(primaryTarget);
2757
+ }
2758
+ try {
2759
+ symlinkSync(resolvedSource, primaryTarget);
2760
+ } catch (e) {
2761
+ if (e.code === "EPERM") {
2762
+ copyFileSync2(sourcePath, primaryTarget);
2763
+ } else {
2764
+ throw e;
2765
+ }
2766
+ }
2767
+ } catch (e) {
2768
+ logger.warn(t("install_config_failed").replace("{error}", e.message));
2769
+ }
2770
+ if (isProjectLevel && primaryTarget !== join11(configRoot, agentsFileName)) {
2771
+ if (configRootConfigLink === false) return;
2772
+ const configRootTarget = join11(configRoot, agentsFileName);
2773
+ if (existsSync12(configRootTarget) || isSymlink(configRootTarget)) {
2774
+ unlinkSync4(configRootTarget);
2775
+ }
2776
+ try {
2777
+ symlinkSync(realpathSync(sourcePath), configRootTarget);
2778
+ } catch (e) {
2779
+ if (e.code === "EPERM") {
2780
+ copyFileSync2(sourcePath, configRootTarget);
2781
+ }
2782
+ }
2783
+ }
2784
+ }
2785
+ async function installExternalRepos(repos, repoPath, configRoot, installPath, level) {
2786
+ if (!repos || repos.length === 0) return;
2787
+ for (const repo of repos) {
2788
+ const targetDir = join11(repoPath, repo.dir);
2789
+ if (!existsSync12(targetDir)) {
2790
+ const args = ["clone"];
2791
+ if (repo.depth) {
2792
+ args.push("--depth", String(repo.depth));
2793
+ }
2794
+ if (repo.recursive) {
2795
+ args.push("--recursive");
2796
+ }
2797
+ args.push(repo.url, targetDir);
2798
+ try {
2799
+ await execa3("git", args, { timeout: 12e4 });
2800
+ } catch {
2801
+ logger.warn(t("install_clone_failed").replace("{url}", repo.url).replace("{dir}", repo.dir));
2802
+ try {
2803
+ rmSync(targetDir, { recursive: true, force: true });
2804
+ } catch {
2805
+ }
2806
+ continue;
2807
+ }
2808
+ }
2809
+ const linkName = basename(repo.dir);
2810
+ const resolved = realpathSync(targetDir);
2811
+ const shouldConfigRootLink = repo.configRootLink !== false && (level === "global" || repo.configRootLink === true);
2812
+ if (shouldConfigRootLink) {
2813
+ const configRootLink = join11(configRoot, linkName);
2814
+ try {
2815
+ if (existsSync12(configRootLink) || isSymlink(configRootLink)) {
2816
+ unlinkSync4(configRootLink);
2817
+ }
2818
+ try {
2819
+ symlinkSync(resolved, configRootLink);
2820
+ } catch (e) {
2821
+ if (e.code === "EPERM") {
2822
+ cpSync(resolved, configRootLink, { recursive: true });
2823
+ }
2824
+ }
2825
+ } catch {
2826
+ logger.warn(t("install_link_config_failed").replace("{name}", linkName));
2827
+ }
2828
+ }
2829
+ if (level === "project") {
2830
+ const projectLink = join11(installPath, linkName);
2831
+ try {
2832
+ if (existsSync12(projectLink) || isSymlink(projectLink)) {
2833
+ unlinkSync4(projectLink);
2834
+ }
2835
+ try {
2836
+ symlinkSync(resolved, projectLink);
2837
+ } catch (e) {
2838
+ if (e.code === "EPERM") {
2839
+ cpSync(resolved, projectLink, { recursive: true });
2840
+ }
2841
+ }
2842
+ } catch {
2843
+ logger.warn(t("install_link_project_failed").replace("{name}", linkName));
2844
+ }
2845
+ }
2846
+ }
2847
+ }
2848
+ function installWorkflows(pluginDir, configRoot) {
2849
+ const workflowsDir = join11(pluginDir, "workflows");
2850
+ if (!existsSync12(workflowsDir)) return;
2851
+ const targetPath = join11(configRoot, "workflows");
2852
+ try {
2853
+ const resolved = realpathSync(workflowsDir);
2854
+ if (existsSync12(targetPath) || isSymlink(targetPath)) {
2855
+ unlinkSync4(targetPath);
2856
+ }
2857
+ try {
2858
+ symlinkSync(resolved, targetPath);
2859
+ } catch (e) {
2860
+ if (e.code === "EPERM") {
2861
+ cpSync(resolved, targetPath, { recursive: true });
2862
+ }
2863
+ }
2864
+ } catch {
2865
+ logger.warn(t("install_link_workflows_failed"));
2866
+ }
2867
+ }
2868
+ function writeCannbotManifest(configRoot, pluginId, version, level, tool, skills, agents) {
2869
+ const manifest = {
2870
+ brand: "CANNBot",
2871
+ version,
2872
+ team: pluginId,
2873
+ level,
2874
+ tool,
2875
+ installed_skills: skills,
2876
+ installed_agents: agents,
2877
+ brand_dir: configRoot,
2878
+ install_time: (/* @__PURE__ */ new Date()).toISOString()
2879
+ };
2880
+ const manifestPath = join11(configRoot, `${pluginId}-manifest.json`);
2881
+ writeFileSync2(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
2882
+ return manifest;
2883
+ }
2884
+ async function installViaManifest(plugin, repoPath, tool, level, installPath) {
2885
+ const cwd = installPath || process.cwd();
2886
+ const configRoot = getConfigRoot(tool, level, installPath);
2887
+ const pluginDir = join11(repoPath, plugin.dir);
2888
+ try {
2889
+ const skillsDir = join11(configRoot, "skills");
2890
+ const agentsDir = join11(configRoot, "agents");
2891
+ mkdirSync5(skillsDir, { recursive: true });
2892
+ mkdirSync5(agentsDir, { recursive: true });
2893
+ const installedSkills = [];
2894
+ const installedAgents = [];
2895
+ for (const source of plugin.installSkills || []) {
2896
+ const sourceDir = join11(repoPath, source.dir);
2897
+ if (!existsSync12(sourceDir)) {
2898
+ logger.warn(t("install_skill_source_not_found").replace("{dir}", source.dir));
2899
+ continue;
2900
+ }
2901
+ for (const skillEntry of source.skills) {
2902
+ const skillName = typeof skillEntry === "string" ? skillEntry : skillEntry.name;
2903
+ const skillAlias = typeof skillEntry === "string" ? skillName : skillEntry.as || skillName;
2904
+ const skillPath = join11(sourceDir, skillName);
2905
+ if (!existsSync12(skillPath)) {
2906
+ logger.warn(t("install_skill_not_found").replace("{name}", skillName).replace("{dir}", source.dir));
2907
+ continue;
2908
+ }
2909
+ const targetPath = join11(skillsDir, skillAlias);
2910
+ const result = installLink(skillPath, targetPath);
2911
+ if (result !== "failed") {
2912
+ installedSkills.push(skillAlias);
2913
+ } else {
2914
+ logger.warn(t("install_skill_failed").replace("{name}", skillAlias));
2915
+ }
2916
+ }
2917
+ }
2918
+ for (const agentName of plugin.installAgents || []) {
2919
+ const agentFile = agentName.endsWith(".md") ? agentName : `${agentName}.md`;
2920
+ const agentPath = join11(pluginDir, "agents", agentFile);
2921
+ if (!existsSync12(agentPath)) {
2922
+ logger.warn(t("install_agent_not_found").replace("{name}", agentFile));
2923
+ continue;
2924
+ }
2925
+ const targetPath = join11(agentsDir, agentFile);
2926
+ const result = installLink(agentPath, targetPath);
2927
+ if (result !== "failed") {
2928
+ installedAgents.push(agentFile);
2929
+ } else {
2930
+ logger.warn(t("install_agent_failed").replace("{name}", agentFile));
2931
+ }
2932
+ }
2933
+ installConfigFile(pluginDir, plugin.configFile || "AGENTS.md", tool, level, configRoot, cwd, plugin.configRootConfigLink);
2934
+ installWorkflows(pluginDir, configRoot);
2935
+ await installExternalRepos(plugin.externalRepos, repoPath, configRoot, cwd, level);
2936
+ const cannbotManifest = writeCannbotManifest(
2937
+ configRoot,
2938
+ plugin.id,
2939
+ plugin.version || "1.0.0",
2940
+ level,
2941
+ tool,
2942
+ installedSkills,
2943
+ installedAgents
2944
+ );
2945
+ return {
2946
+ success: true,
2947
+ skillsCount: installedSkills.length,
2948
+ agentsCount: installedAgents.length,
2949
+ errors: [],
2950
+ manifest: cannbotManifest
2951
+ };
2952
+ } catch (error) {
1760
2953
  return {
1761
2954
  success: false,
1762
- pluginId: opts.pluginId,
1763
2955
  skillsCount: 0,
1764
2956
  agentsCount: 0,
1765
- errors: [`Plugin not found: ${opts.pluginId}`],
1766
- warnings: []
2957
+ errors: [error instanceof Error ? error.message : t("error_unknown")],
2958
+ manifest: null
1767
2959
  };
1768
2960
  }
1769
- const scriptPath = join11(opts.repoPath, plugin.dir, plugin.script);
1770
- if (!existsSync9(scriptPath)) {
2961
+ }
2962
+
2963
+ // src/core/installer.ts
2964
+ function findShell() {
2965
+ const candidates = ["bash", "sh"];
2966
+ for (const cmd of candidates) {
2967
+ try {
2968
+ const detectCmd = process.platform === "win32" ? "where" : "which";
2969
+ execaSync(detectCmd, [cmd], { timeout: 3e3 });
2970
+ return cmd;
2971
+ } catch {
2972
+ }
2973
+ }
2974
+ return null;
2975
+ }
2976
+ async function installPlugin(opts) {
2977
+ const plugin = getPluginById(opts.pluginId);
2978
+ if (!plugin) {
1771
2979
  return {
1772
2980
  success: false,
1773
2981
  pluginId: opts.pluginId,
1774
2982
  skillsCount: 0,
1775
2983
  agentsCount: 0,
1776
- errors: [`Script not found: ${scriptPath}`],
2984
+ errors: [t("error_plugin_not_found") + ": " + opts.pluginId],
1777
2985
  warnings: []
1778
2986
  };
1779
2987
  }
1780
- const args = [opts.level, opts.tool];
1781
- if (opts.installPath) {
1782
- args.push(opts.installPath);
1783
- }
2988
+ const pluginDir = join12(opts.repoPath, plugin.dir);
1784
2989
  const cwd = opts.installPath || process.cwd();
1785
2990
  const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
1786
- const agentsFile = join11(configRoot, getAgentsFileName(opts.tool));
2991
+ const agentsFile = join12(configRoot, getAgentsFileName(opts.tool));
1787
2992
  let backupInfo = null;
1788
- if (existsSync9(agentsFile)) {
2993
+ if (existsSync13(agentsFile)) {
1789
2994
  const currentPlugin = detectCurrentPlugin(configRoot, opts.tool);
1790
2995
  if (currentPlugin && currentPlugin.pluginId !== opts.pluginId) {
1791
2996
  let choice = "overwrite";
@@ -1806,25 +3011,113 @@ async function installPlugin(opts) {
1806
3011
  warnings: []
1807
3012
  };
1808
3013
  }
3014
+ }
3015
+ if (currentPlugin) {
1809
3016
  backupInfo = createBackup(
1810
3017
  configRoot,
1811
3018
  opts.tool,
1812
3019
  currentPlugin.pluginId,
1813
3020
  currentPlugin.pluginName
1814
3021
  );
1815
- if (backupInfo) {
1816
- logger.success(`${t("backup_created")}: ${backupInfo.filePath}`);
3022
+ if (!backupInfo) {
3023
+ logger.error(t("backup_failed"));
3024
+ return {
3025
+ success: false,
3026
+ pluginId: opts.pluginId,
3027
+ skillsCount: 0,
3028
+ agentsCount: 0,
3029
+ errors: [t("backup_failed")],
3030
+ warnings: []
3031
+ };
3032
+ }
3033
+ logger.success(`${t("backup_created")}: ${backupInfo.filePath}`);
3034
+ }
3035
+ }
3036
+ if (plugin.installSkills && plugin.installSkills.length > 0) {
3037
+ const result = await installViaManifest(
3038
+ plugin,
3039
+ opts.repoPath,
3040
+ opts.tool,
3041
+ opts.level,
3042
+ opts.installPath
3043
+ );
3044
+ if (result.success) {
3045
+ try {
3046
+ const record = scanInstalledFiles(
3047
+ opts.pluginId,
3048
+ plugin.displayName,
3049
+ opts.tool,
3050
+ opts.level,
3051
+ cwd,
3052
+ configRoot,
3053
+ result.manifest,
3054
+ plugin.externalRepos?.map((r) => basename2(r.dir))
3055
+ );
3056
+ if (backupInfo) {
3057
+ record.backup = {
3058
+ filePath: backupInfo.filePath,
3059
+ fromPluginId: backupInfo.pluginId,
3060
+ fromPluginName: backupInfo.pluginName,
3061
+ backupTime: backupInfo.backupTime
3062
+ };
3063
+ }
3064
+ writeRecord(record);
3065
+ } catch {
3066
+ logger.warn(t("record_write_failed"));
1817
3067
  }
3068
+ return {
3069
+ success: true,
3070
+ pluginId: opts.pluginId,
3071
+ skillsCount: result.skillsCount,
3072
+ agentsCount: result.agentsCount,
3073
+ errors: result.errors,
3074
+ warnings: []
3075
+ };
3076
+ } else {
3077
+ return {
3078
+ success: false,
3079
+ pluginId: opts.pluginId,
3080
+ skillsCount: 0,
3081
+ agentsCount: 0,
3082
+ errors: result.errors,
3083
+ warnings: []
3084
+ };
1818
3085
  }
1819
3086
  }
3087
+ const scriptPath = join12(pluginDir, plugin.script);
3088
+ if (!existsSync13(scriptPath)) {
3089
+ return {
3090
+ success: false,
3091
+ pluginId: opts.pluginId,
3092
+ skillsCount: 0,
3093
+ agentsCount: 0,
3094
+ errors: [t("error_script_not_found").replace("{path}", scriptPath)],
3095
+ warnings: []
3096
+ };
3097
+ }
3098
+ const args = [opts.level, opts.tool];
3099
+ if (opts.installPath) {
3100
+ args.push(opts.installPath);
3101
+ }
1820
3102
  try {
3103
+ const shell = findShell();
3104
+ if (!shell) {
3105
+ return {
3106
+ success: false,
3107
+ pluginId: opts.pluginId,
3108
+ skillsCount: 0,
3109
+ agentsCount: 0,
3110
+ errors: [t("error_no_shell")],
3111
+ warnings: []
3112
+ };
3113
+ }
1821
3114
  const execOptions = {
1822
3115
  cwd,
1823
3116
  timeout: 3e5,
1824
3117
  stdio: "pipe",
1825
3118
  input: opts.yes ? "y\n" : void 0
1826
3119
  };
1827
- await execa3("bash", [scriptPath, ...args], execOptions);
3120
+ await execa4(shell, [scriptPath, ...args], execOptions);
1828
3121
  const manifest = readManifest(configRoot);
1829
3122
  let skillsCount = 0;
1830
3123
  let agentsCount = 0;
@@ -1843,7 +3136,8 @@ async function installPlugin(opts) {
1843
3136
  opts.level,
1844
3137
  cwd,
1845
3138
  configRoot,
1846
- manifest
3139
+ manifest,
3140
+ plugin.externalRepos?.map((r) => basename2(r.dir))
1847
3141
  );
1848
3142
  if (backupInfo) {
1849
3143
  record.backup = {
@@ -1855,6 +3149,7 @@ async function installPlugin(opts) {
1855
3149
  }
1856
3150
  writeRecord(record);
1857
3151
  } catch {
3152
+ logger.warn(t("record_write_failed"));
1858
3153
  }
1859
3154
  return {
1860
3155
  success: true,
@@ -1865,7 +3160,7 @@ async function installPlugin(opts) {
1865
3160
  warnings: []
1866
3161
  };
1867
3162
  } catch (error) {
1868
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3163
+ const errorMessage = error instanceof Error ? error.message : t("error_unknown");
1869
3164
  return {
1870
3165
  success: false,
1871
3166
  pluginId: opts.pluginId,
@@ -1878,41 +3173,62 @@ async function installPlugin(opts) {
1878
3173
  }
1879
3174
 
1880
3175
  // src/core/skill-installer.ts
1881
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync3, readdirSync as readdirSync4, rmdirSync, realpathSync, lstatSync as lstatSync2 } from "fs";
1882
- import { join as join12 } from "path";
1883
- import { select as select3, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
3176
+ import { existsSync as existsSync14, mkdirSync as mkdirSync6, symlinkSync as symlinkSync2, unlinkSync as unlinkSync5, readdirSync as readdirSync4, rmSync as rmSync2, realpathSync as realpathSync2, readlinkSync as readlinkSync2 } from "fs";
3177
+ import { join as join13, dirname as dirname6, isAbsolute } from "path";
3178
+ import { select as select3, checkbox, Separator as Separator2 } from "@inquirer/prompts";
1884
3179
  import chalk5 from "chalk";
1885
3180
  import Table from "cli-table3";
1886
3181
  async function installSkills(skillIds, tool, level, repoPath) {
1887
3182
  const configRoot = getConfigRoot(tool, level);
1888
- const skillsDir = join12(configRoot, "skills");
3183
+ const skillsDir = join13(configRoot, "skills");
1889
3184
  const installPath = level === "project" ? process.cwd() : configRoot;
1890
- if (!existsSync10(skillsDir)) {
1891
- mkdirSync5(skillsDir, { recursive: true });
3185
+ if (!existsSync14(skillsDir)) {
3186
+ mkdirSync6(skillsDir, { recursive: true });
1892
3187
  }
1893
3188
  const results = [];
1894
3189
  const installedIds = [];
1895
3190
  for (const skillId of skillIds) {
1896
3191
  const skill = findSkill(skillId);
1897
3192
  if (!skill) {
1898
- results.push({ skillId, success: false, error: "Skill not found in registry" });
3193
+ results.push({ skillId, success: false, error: t("error_skill_not_found") });
1899
3194
  continue;
1900
3195
  }
1901
- const sourcePath = join12(repoPath, skill.source, skill.id);
1902
- const targetPath = join12(skillsDir, skillId);
1903
- if (!existsSync10(sourcePath)) {
1904
- results.push({ skillId, success: false, error: `Source not found: ${sourcePath}` });
3196
+ const sourcePath = resolveSkillSourcePath(skill, repoPath);
3197
+ const targetPath = join13(skillsDir, skillId);
3198
+ if (!existsSync14(sourcePath)) {
3199
+ results.push({ skillId, success: false, error: t("error_source_not_found").replace("{path}", sourcePath) });
1905
3200
  continue;
1906
3201
  }
1907
3202
  try {
1908
- if (existsSync10(targetPath) || isSymlink2(targetPath)) {
1909
- unlinkSync3(targetPath);
3203
+ const resolvedSource = realpathSync2(sourcePath);
3204
+ if (isSymlink(targetPath)) {
3205
+ try {
3206
+ const currentTarget = readlinkSync2(targetPath);
3207
+ const normCurrent = currentTarget.replace(/\\/g, "/").toLowerCase();
3208
+ const normSource = resolvedSource.replace(/\\/g, "/").toLowerCase();
3209
+ if (normCurrent === normSource) {
3210
+ results.push({ skillId, success: true });
3211
+ installedIds.push(skillId);
3212
+ continue;
3213
+ }
3214
+ } catch {
3215
+ results.push({ skillId, success: true });
3216
+ installedIds.push(skillId);
3217
+ continue;
3218
+ }
3219
+ }
3220
+ if (existsSync14(targetPath) || isSymlink(targetPath)) {
3221
+ unlinkSync5(targetPath);
1910
3222
  }
1911
- symlinkSync(realpathSync(sourcePath), targetPath);
3223
+ symlinkSync2(resolvedSource, targetPath);
1912
3224
  results.push({ skillId, success: true });
1913
3225
  installedIds.push(skillId);
1914
3226
  } catch (error) {
1915
- results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
3227
+ let msg = error instanceof Error ? error.message : t("error_unknown");
3228
+ if (process.platform === "win32" && msg.includes("EPERM")) {
3229
+ msg = t("error_symlink_windows");
3230
+ }
3231
+ results.push({ skillId, success: false, error: msg });
1916
3232
  }
1917
3233
  }
1918
3234
  if (installedIds.length > 0) {
@@ -1922,34 +3238,34 @@ async function installSkills(skillIds, tool, level, repoPath) {
1922
3238
  }
1923
3239
  async function uninstallSkills(skillIds, tool, level) {
1924
3240
  const configRoot = getConfigRoot(tool, level);
1925
- const skillsDir = join12(configRoot, "skills");
3241
+ const skillsDir = join13(configRoot, "skills");
1926
3242
  const installPath = level === "project" ? process.cwd() : configRoot;
1927
3243
  const results = [];
1928
3244
  const removedIds = [];
1929
3245
  for (const skillId of skillIds) {
1930
- const targetPath = join12(skillsDir, skillId);
3246
+ const targetPath = join13(skillsDir, skillId);
1931
3247
  try {
1932
- if (existsSync10(targetPath) || isSymlink2(targetPath)) {
1933
- unlinkSync3(targetPath);
3248
+ if (existsSync14(targetPath) || isSymlink(targetPath)) {
3249
+ unlinkSync5(targetPath);
1934
3250
  results.push({ skillId, success: true });
1935
3251
  removedIds.push(skillId);
1936
3252
  logger.step(` ${t("uninstall_remove_label")}: ${skillId}`);
1937
3253
  } else {
1938
- results.push({ skillId, success: false, error: "Not installed" });
3254
+ results.push({ skillId, success: false, error: t("error_not_installed") });
1939
3255
  logger.warn(`${skillId} ${t("uninstall_skill_not_installed")}`);
1940
3256
  }
1941
3257
  } catch (error) {
1942
- results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
3258
+ results.push({ skillId, success: false, error: error instanceof Error ? error.message : t("error_unknown") });
1943
3259
  }
1944
3260
  }
1945
3261
  if (removedIds.length > 0) {
1946
3262
  removeSkillsFromRecord(removedIds, tool, level, installPath);
1947
3263
  }
1948
- if (existsSync10(skillsDir)) {
3264
+ if (existsSync14(skillsDir)) {
1949
3265
  try {
1950
3266
  const entries = readdirSync4(skillsDir);
1951
3267
  if (entries.length === 0) {
1952
- rmdirSync(skillsDir);
3268
+ rmSync2(skillsDir, { recursive: true });
1953
3269
  logger.step(` ${t("uninstall_clean_empty_dir")}: skills/`);
1954
3270
  }
1955
3271
  } catch {
@@ -1961,8 +3277,6 @@ var SELECT_ALL = "__select_all__";
1961
3277
  var SELECT_ALL_CATEGORY = "__select_all_category__";
1962
3278
  var CONTINUE_SELECT = "__continue_select__";
1963
3279
  async function interactiveSkillSelect(tool, level) {
1964
- const BACK3 = "__back__";
1965
- const CANCEL3 = "__cancel__";
1966
3280
  const categories = getAllCategories();
1967
3281
  const allSkillIds = getAllSkills().map((s) => s.id);
1968
3282
  let accumulated = [];
@@ -1985,8 +3299,8 @@ async function interactiveSkillSelect(tool, level) {
1985
3299
  })),
1986
3300
  new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1987
3301
  ...accumulated.length > 0 ? [{ name: `>> ${t("skill_confirm_install_count").replace("{count}", String(accumulated.length))}`, value: "install" }] : [],
1988
- { name: "<- " + t("wizard_back"), value: BACK3 },
1989
- { name: "x " + t("wizard_cancel"), value: CANCEL3 }
3302
+ { name: "<- " + t("wizard_back"), value: BACK },
3303
+ { name: "x " + t("wizard_cancel"), value: CANCEL }
1990
3304
  ];
1991
3305
  showOperationHints();
1992
3306
  selectedCategoryId = await select3({
@@ -1996,8 +3310,8 @@ async function interactiveSkillSelect(tool, level) {
1996
3310
  theme: selectTheme,
1997
3311
  pageSize: 15
1998
3312
  });
1999
- if (selectedCategoryId === BACK3) return "back";
2000
- if (selectedCategoryId === CANCEL3) return "cancel";
3313
+ if (selectedCategoryId === BACK) return "back";
3314
+ if (selectedCategoryId === CANCEL) return "cancel";
2001
3315
  if (selectedCategoryId === SELECT_ALL) {
2002
3316
  return allSkillIds;
2003
3317
  }
@@ -2037,7 +3351,7 @@ async function interactiveSkillSelect(tool, level) {
2037
3351
  })
2038
3352
  ];
2039
3353
  showOperationHints(true);
2040
- const selectedSkills = await checkbox2({
3354
+ const selectedSkills = await checkbox({
2041
3355
  message: t("skill_select_items"),
2042
3356
  choices: skillChoices,
2043
3357
  loop: false,
@@ -2128,12 +3442,14 @@ function listAllSkills() {
2128
3442
  console.log(chalk5.dim(` ${t("skill_total_count").replace("{count}", String(getAllSkills().length))}`));
2129
3443
  console.log();
2130
3444
  }
2131
- function isSymlink2(path) {
2132
- try {
2133
- return lstatSync2(path).isSymbolicLink();
2134
- } catch {
2135
- return false;
3445
+ function resolveSkillSourcePath(skill, repoPath) {
3446
+ if (skill.filePath) {
3447
+ const absPath = isAbsolute(skill.filePath) ? skill.filePath : join13(repoPath, skill.filePath);
3448
+ if (existsSync14(absPath)) {
3449
+ return dirname6(absPath);
3450
+ }
2136
3451
  }
3452
+ return join13(repoPath, skill.source, skill.id);
2137
3453
  }
2138
3454
 
2139
3455
  // src/ui/display.ts
@@ -2145,10 +3461,11 @@ function printPluginList(plugins, installed) {
2145
3461
  chalk6.cyan(t("list_id")),
2146
3462
  chalk6.cyan(t("list_name")),
2147
3463
  chalk6.cyan(t("list_status")),
2148
- chalk6.cyan(t("list_skills")),
2149
- chalk6.cyan(t("list_agents"))
3464
+ chalk6.cyan(t("list_description"))
2150
3465
  ],
2151
- style: { head: [], border: [] }
3466
+ style: { head: [], border: [] },
3467
+ colWidths: [6, 32, 8, 50],
3468
+ wordWrap: true
2152
3469
  });
2153
3470
  plugins.forEach((plugin, index) => {
2154
3471
  const isInstalled = installed.has(plugin.id);
@@ -2157,8 +3474,7 @@ function printPluginList(plugins, installed) {
2157
3474
  String(index + 1),
2158
3475
  plugin.displayName,
2159
3476
  status,
2160
- String(plugin.skills),
2161
- String(plugin.agents)
3477
+ plugin.description || chalk6.dim("\u2014")
2162
3478
  ]);
2163
3479
  });
2164
3480
  console.log();
@@ -2225,15 +3541,13 @@ function printEnhancedSummary(results, tool, configRoot) {
2225
3541
  }
2226
3542
 
2227
3543
  // src/commands/init.ts
2228
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
2229
- import { join as join13 } from "path";
2230
- var BACK2 = "__back__";
2231
- var CANCEL2 = "__cancel__";
3544
+ import { existsSync as existsSync15, readFileSync as readFileSync8 } from "fs";
3545
+ import { join as join14 } from "path";
2232
3546
  async function initCommand() {
2233
3547
  const cwd = process.cwd();
2234
- if (existsSync11(join13(cwd, "package.json")) && (() => {
3548
+ if (existsSync15(join14(cwd, "package.json")) && (() => {
2235
3549
  try {
2236
- const pkg = JSON.parse(readFileSync8(join13(cwd, "package.json"), "utf-8"));
3550
+ const pkg = JSON.parse(readFileSync8(join14(cwd, "package.json"), "utf-8"));
2237
3551
  return pkg.name === "@cannbot-ai/install-helper";
2238
3552
  } catch {
2239
3553
  return false;
@@ -2287,7 +3601,6 @@ async function initCommand() {
2287
3601
  }
2288
3602
  async function selectMode() {
2289
3603
  printBoxTitle(t("wizard_select_mode_title"));
2290
- const CANCEL3 = "__cancel__";
2291
3604
  const choices = [
2292
3605
  {
2293
3606
  name: `> ${t("wizard_mode_plugin")} \u2014 ${t("wizard_mode_plugin_desc")}`,
@@ -2298,7 +3611,7 @@ async function selectMode() {
2298
3611
  value: "skill"
2299
3612
  },
2300
3613
  new Separator3("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2301
- { name: "x " + t("wizard_cancel"), value: CANCEL3 }
3614
+ { name: "x " + t("wizard_cancel"), value: CANCEL }
2302
3615
  ];
2303
3616
  showOperationHints();
2304
3617
  const result = await select4({
@@ -2307,7 +3620,7 @@ async function selectMode() {
2307
3620
  loop: false,
2308
3621
  theme: selectTheme
2309
3622
  });
2310
- if (result === CANCEL3) return null;
3623
+ if (result === CANCEL) return null;
2311
3624
  return result;
2312
3625
  }
2313
3626
  async function pluginInstallFlow() {
@@ -2384,11 +3697,11 @@ async function skillInstallFlow() {
2384
3697
  }
2385
3698
  case 1: {
2386
3699
  const result = await stepLevel();
2387
- if (result === BACK2) {
3700
+ if (result === BACK) {
2388
3701
  step = 0;
2389
3702
  break;
2390
3703
  }
2391
- if (result === CANCEL2) {
3704
+ if (result === CANCEL) {
2392
3705
  logger.info(t("init_cancelled"));
2393
3706
  return "done";
2394
3707
  }
@@ -2435,7 +3748,7 @@ async function skillInstallFlow() {
2435
3748
  logger.blank();
2436
3749
  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)))}`);
2437
3750
  logger.blank();
2438
- logger.info(`${t("install_to")}: ${chalk7.cyan(configRoot + "/skills/")}`);
3751
+ logger.info(`${t("install_to")}: ${chalk7.cyan(join14(configRoot, "skills"))}`);
2439
3752
  logger.info(`${t("start_to_use").replace("{tool}", chalk7.green(tool))}`);
2440
3753
  logger.blank();
2441
3754
  return "done";
@@ -2459,13 +3772,12 @@ async function listCommand() {
2459
3772
 
2460
3773
  // src/commands/doctor.ts
2461
3774
  import chalk8 from "chalk";
2462
- import { existsSync as existsSync12, readdirSync as readdirSync5, lstatSync as lstatSync3, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6 } from "fs";
2463
- import { join as join14 } from "path";
3775
+ import { existsSync as existsSync16, readdirSync as readdirSync5, lstatSync as lstatSync2, unlinkSync as unlinkSync6, mkdirSync as mkdirSync7 } from "fs";
3776
+ import { join as join15 } from "path";
2464
3777
  async function doctorCommand(options = {}) {
2465
3778
  console.log();
2466
3779
  console.log(chalk8.bold(` ${t("doctor_title")}`));
2467
3780
  console.log(chalk8.dim(" " + "\u2500".repeat(46)));
2468
- let errors = 0;
2469
3781
  let warnings = 0;
2470
3782
  let fixes = 0;
2471
3783
  console.log();
@@ -2503,7 +3815,7 @@ async function doctorCommand(options = {}) {
2503
3815
  const configRoot = getConfigRoot(primaryTool, "project");
2504
3816
  const skillsDir = getSkillsDir(configRoot);
2505
3817
  const agentsDir = getAgentsDir(configRoot);
2506
- if (existsSync12(skillsDir)) {
3818
+ if (existsSync16(skillsDir)) {
2507
3819
  const brokenLinks = checkBrokenLinks(skillsDir);
2508
3820
  if (brokenLinks === 0) {
2509
3821
  const count = readdirSync5(skillsDir).length;
@@ -2520,12 +3832,12 @@ async function doctorCommand(options = {}) {
2520
3832
  } else {
2521
3833
  console.log(chalk8.dim(" \u2014") + ` ${skillsDir} \u2014 ${t("doctor_not_exist")}`);
2522
3834
  if (options.fix) {
2523
- mkdirSync6(skillsDir, { recursive: true });
3835
+ mkdirSync7(skillsDir, { recursive: true });
2524
3836
  fixes++;
2525
3837
  console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", skillsDir)}`);
2526
3838
  }
2527
3839
  }
2528
- if (existsSync12(agentsDir)) {
3840
+ if (existsSync16(agentsDir)) {
2529
3841
  const brokenLinks = checkBrokenLinks(agentsDir);
2530
3842
  if (brokenLinks === 0) {
2531
3843
  const count = readdirSync5(agentsDir).length;
@@ -2542,7 +3854,7 @@ async function doctorCommand(options = {}) {
2542
3854
  } else {
2543
3855
  console.log(chalk8.dim(" \u2014") + ` ${agentsDir} \u2014 ${t("doctor_not_exist")}`);
2544
3856
  if (options.fix) {
2545
- mkdirSync6(agentsDir, { recursive: true });
3857
+ mkdirSync7(agentsDir, { recursive: true });
2546
3858
  fixes++;
2547
3859
  console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", agentsDir)}`);
2548
3860
  }
@@ -2550,8 +3862,8 @@ async function doctorCommand(options = {}) {
2550
3862
  console.log();
2551
3863
  console.log(chalk8.bold(` ${t("doctor_config")}`));
2552
3864
  const configFile = getConfigFileName(primaryTool);
2553
- const configPath = join14(process.cwd(), configFile);
2554
- if (existsSync12(configPath)) {
3865
+ const configPath = join15(process.cwd(), configFile);
3866
+ if (existsSync16(configPath)) {
2555
3867
  console.log(chalk8.green(" \u2713") + ` ${configFile} ${t("doctor_config_exists")}`);
2556
3868
  } else {
2557
3869
  console.log(chalk8.dim(" \u2014") + ` ${configFile} ${t("doctor_config_not_exist")}`);
@@ -2560,11 +3872,11 @@ async function doctorCommand(options = {}) {
2560
3872
  console.log(chalk8.dim(" " + "\u2500".repeat(46)));
2561
3873
  if (options.fix && fixes > 0) {
2562
3874
  console.log(
2563
- ` ${t("doctor_result")}: ${t("doctor_result_with_fix").replace("{warnings}", String(warnings)).replace("{errors}", String(errors)).replace("{fixes}", String(fixes))}`
3875
+ ` ${t("doctor_result")}: ${t("doctor_result_with_fix").replace("{warnings}", String(warnings)).replace("{fixes}", String(fixes))}`
2564
3876
  );
2565
3877
  } else {
2566
3878
  console.log(
2567
- ` ${t("doctor_result")}: ${t("doctor_result_without_fix").replace("{warnings}", String(warnings)).replace("{errors}", String(errors))}`
3879
+ ` ${t("doctor_result")}: ${t("doctor_result_without_fix").replace("{warnings}", String(warnings))}`
2568
3880
  );
2569
3881
  }
2570
3882
  console.log();
@@ -2574,11 +3886,11 @@ function checkBrokenLinks(dir) {
2574
3886
  try {
2575
3887
  const entries = readdirSync5(dir);
2576
3888
  for (const entry of entries) {
2577
- const fullPath = join14(dir, entry);
3889
+ const fullPath = join15(dir, entry);
2578
3890
  try {
2579
- const stats = lstatSync3(fullPath);
3891
+ const stats = lstatSync2(fullPath);
2580
3892
  if (stats.isSymbolicLink()) {
2581
- if (!existsSync12(fullPath)) {
3893
+ if (!existsSync16(fullPath)) {
2582
3894
  broken++;
2583
3895
  }
2584
3896
  }
@@ -2595,12 +3907,12 @@ function fixBrokenLinks(dir) {
2595
3907
  try {
2596
3908
  const entries = readdirSync5(dir);
2597
3909
  for (const entry of entries) {
2598
- const fullPath = join14(dir, entry);
3910
+ const fullPath = join15(dir, entry);
2599
3911
  try {
2600
- const stats = lstatSync3(fullPath);
3912
+ const stats = lstatSync2(fullPath);
2601
3913
  if (stats.isSymbolicLink()) {
2602
- if (!existsSync12(fullPath)) {
2603
- unlinkSync4(fullPath);
3914
+ if (!existsSync16(fullPath)) {
3915
+ unlinkSync6(fullPath);
2604
3916
  fixed++;
2605
3917
  }
2606
3918
  }
@@ -2615,7 +3927,6 @@ function fixBrokenLinks(dir) {
2615
3927
  // src/commands/status.ts
2616
3928
  import chalk9 from "chalk";
2617
3929
  async function statusCommand() {
2618
- const plugins = getAllPlugins();
2619
3930
  const installed = scanInstalled();
2620
3931
  const installedMap = new Map(installed.map((p) => [p.id, p]));
2621
3932
  console.log();
@@ -2641,6 +3952,7 @@ async function statusCommand() {
2641
3952
  }
2642
3953
 
2643
3954
  // src/commands/install.ts
3955
+ import { join as join16 } from "path";
2644
3956
  import { confirm } from "@inquirer/prompts";
2645
3957
  import chalk10 from "chalk";
2646
3958
  async function installCommand(names, options) {
@@ -2674,7 +3986,10 @@ async function installCommand(names, options) {
2674
3986
  tool2 = validateTool(options.tool);
2675
3987
  } else {
2676
3988
  const detected = await selectToolWithDetection();
2677
- if (detected === "back" || detected === "cancel") return;
3989
+ if (detected === "back" || detected === "cancel") {
3990
+ logger.info(t("init_cancelled"));
3991
+ return;
3992
+ }
2678
3993
  tool2 = detected;
2679
3994
  }
2680
3995
  const level2 = options.level ? validateLevel(options.level) : "project";
@@ -2703,7 +4018,7 @@ async function installCommand(names, options) {
2703
4018
  logger.blank();
2704
4019
  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)))}`);
2705
4020
  logger.blank();
2706
- logger.info(`${t("install_to")}: ${chalk10.cyan(configRoot + "/skills/")}`);
4021
+ logger.info(`${t("install_to")}: ${chalk10.cyan(join16(configRoot, "skills"))}`);
2707
4022
  logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
2708
4023
  logger.blank();
2709
4024
  return;
@@ -2723,7 +4038,10 @@ async function installCommand(names, options) {
2723
4038
  tool2 = validateTool(options.tool);
2724
4039
  } else {
2725
4040
  const detected = await selectToolWithDetection();
2726
- if (detected === "back" || detected === "cancel") return;
4041
+ if (detected === "back" || detected === "cancel") {
4042
+ logger.info(t("init_cancelled"));
4043
+ return;
4044
+ }
2727
4045
  tool2 = detected;
2728
4046
  }
2729
4047
  const level2 = options.level ? validateLevel(options.level) : "project";
@@ -2734,11 +4052,17 @@ async function installCommand(names, options) {
2734
4052
  const selectedSkills = await interactiveSkillSelect(tool2, level2);
2735
4053
  if (selectedSkills === "back") {
2736
4054
  const redetected = await selectToolWithDetection();
2737
- if (redetected === "back" || redetected === "cancel") return;
4055
+ if (redetected === "back" || redetected === "cancel") {
4056
+ logger.info(t("init_cancelled"));
4057
+ return;
4058
+ }
2738
4059
  tool2 = redetected;
2739
4060
  break;
2740
4061
  }
2741
- if (selectedSkills === "cancel") return;
4062
+ if (selectedSkills === "cancel") {
4063
+ logger.info(t("init_cancelled"));
4064
+ return;
4065
+ }
2742
4066
  if (selectedSkills.length === 0) {
2743
4067
  logger.info(t("install_no_skill_selected"));
2744
4068
  return;
@@ -2769,7 +4093,7 @@ async function installCommand(names, options) {
2769
4093
  logger.blank();
2770
4094
  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)))}`);
2771
4095
  logger.blank();
2772
- logger.info(`${t("install_to")}: ${chalk10.cyan(configRoot + "/skills/")}`);
4096
+ logger.info(`${t("install_to")}: ${chalk10.cyan(join16(configRoot, "skills"))}`);
2773
4097
  logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
2774
4098
  logger.blank();
2775
4099
  return;
@@ -2813,7 +4137,10 @@ async function installCommand(names, options) {
2813
4137
  tool = validateTool(options.tool);
2814
4138
  } else {
2815
4139
  const detected = await selectToolWithDetection();
2816
- if (detected === "back" || detected === "cancel") return;
4140
+ if (detected === "back" || detected === "cancel") {
4141
+ logger.info(t("init_cancelled"));
4142
+ return;
4143
+ }
2817
4144
  tool = detected;
2818
4145
  }
2819
4146
  const level = options.level ? validateLevel(options.level) : "project";
@@ -2896,7 +4223,7 @@ async function installCommand(names, options) {
2896
4223
  logger.blank();
2897
4224
  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)))}`);
2898
4225
  logger.blank();
2899
- logger.info(`${t("install_to")}: ${chalk10.cyan(getConfigRoot(tool, level) + "/skills/")}`);
4226
+ logger.info(`${t("install_to")}: ${chalk10.cyan(join16(getConfigRoot(tool, level), "skills"))}`);
2900
4227
  logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool))}`);
2901
4228
  logger.blank();
2902
4229
  }
@@ -2931,7 +4258,8 @@ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes
2931
4258
  }
2932
4259
 
2933
4260
  // src/commands/uninstall.ts
2934
- import { existsSync as existsSync13, unlinkSync as unlinkSync5, rmdirSync as rmdirSync2, readdirSync as readdirSync6, lstatSync as lstatSync4 } from "fs";
4261
+ import { existsSync as existsSync17, unlinkSync as unlinkSync7, rmdirSync, readdirSync as readdirSync6 } from "fs";
4262
+ import { resolve, basename as basename3, sep } from "path";
2935
4263
  import chalk11 from "chalk";
2936
4264
  async function uninstallCommand(names, options) {
2937
4265
  const tool = options.tool ? validateTool(options.tool) : "opencode";
@@ -2961,31 +4289,37 @@ async function uninstallCommand(names, options) {
2961
4289
  continue;
2962
4290
  }
2963
4291
  logger.info(`${t("uninstall_in_progress")} ${plugin.displayName}...`);
4292
+ const configRoot = getConfigRoot(record.tool, record.level);
4293
+ const allowedBases = [resolve(configRoot), resolve(record.installPath)];
4294
+ const isSafePath = (p) => {
4295
+ const resolved = resolve(p);
4296
+ return allowedBases.some((base) => resolved === base || resolved.startsWith(base + sep));
4297
+ };
2964
4298
  let removedFiles = 0;
2965
4299
  let removedDirs = 0;
2966
4300
  for (const filePath of record.files) {
4301
+ if (!isSafePath(filePath)) continue;
2967
4302
  try {
2968
- if (existsSync13(filePath) || isSymlink3(filePath)) {
2969
- unlinkSync5(filePath);
2970
- const name = filePath.split("/").pop() || filePath;
4303
+ if (existsSync17(filePath) || isSymlink(filePath)) {
4304
+ unlinkSync7(filePath);
4305
+ const name = basename3(filePath);
2971
4306
  logger.step(` ${t("uninstall_remove_label")}: ${name}`);
2972
4307
  removedFiles++;
2973
4308
  }
2974
4309
  } catch {
2975
4310
  }
2976
4311
  }
2977
- const configRoot = getConfigRoot(record.tool, record.level);
2978
- const sortedDirs = [...record.directories].sort((a, b) => b.length - a.length);
4312
+ const sortedDirs = [...record.directories].filter(isSafePath).sort((a, b) => b.length - a.length);
2979
4313
  for (const dirPath of sortedDirs) {
2980
4314
  try {
2981
- if (dirPath === configRoot) {
4315
+ if (resolve(dirPath) === resolve(configRoot)) {
2982
4316
  continue;
2983
4317
  }
2984
- if (existsSync13(dirPath)) {
4318
+ if (existsSync17(dirPath)) {
2985
4319
  const entries = readdirSync6(dirPath);
2986
4320
  if (entries.length === 0) {
2987
- rmdirSync2(dirPath);
2988
- const name = dirPath.split("/").pop() || dirPath;
4321
+ rmdirSync(dirPath);
4322
+ const name = basename3(dirPath);
2989
4323
  logger.step(` ${t("uninstall_clean_empty_dir")}: ${name}/`);
2990
4324
  removedDirs++;
2991
4325
  }
@@ -3014,7 +4348,7 @@ async function uninstallCommand(names, options) {
3014
4348
  }
3015
4349
  const totalRemoved = removedFiles + removedDirs;
3016
4350
  if (totalRemoved > 0) {
3017
- logger.success(`${t("uninstall_removed")} ${plugin.displayName}\uFF08${t("uninstall_remove_file_format").replace("{count}", String(removedFiles))}\uFF0C${t("uninstall_remove_dir_format").replace("{count}", String(removedDirs))}\uFF09`);
4351
+ logger.success(t("uninstall_summary_format").replace("{name}", plugin.displayName).replace("{files}", String(removedFiles)).replace("{dirs}", String(removedDirs)));
3018
4352
  } else {
3019
4353
  logger.warn(`${plugin.displayName}${t("uninstall_files_gone")}`);
3020
4354
  }
@@ -3036,13 +4370,6 @@ async function uninstallCommand(names, options) {
3036
4370
  logger.blank();
3037
4371
  }
3038
4372
  }
3039
- function isSymlink3(path) {
3040
- try {
3041
- return lstatSync4(path).isSymbolicLink();
3042
- } catch {
3043
- return false;
3044
- }
3045
- }
3046
4373
 
3047
4374
  // src/commands/update.ts
3048
4375
  import chalk12 from "chalk";
@@ -3052,7 +4379,10 @@ async function updateCommand(pluginNames, options) {
3052
4379
  tool = validateTool(options.tool);
3053
4380
  } else {
3054
4381
  const detected = await selectToolWithDetection();
3055
- if (detected === "back" || detected === "cancel") return;
4382
+ if (detected === "back" || detected === "cancel") {
4383
+ logger.info(t("init_cancelled"));
4384
+ return;
4385
+ }
3056
4386
  tool = detected;
3057
4387
  }
3058
4388
  const level = options.level ? validateLevel(options.level) : "project";
@@ -3079,9 +4409,7 @@ async function updateCommand(pluginNames, options) {
3079
4409
  const repoManager = createRepositoryManager();
3080
4410
  const updateSpinner = createSpinner(t("update_updating") + "...");
3081
4411
  updateSpinner.start();
3082
- await repoManager.updateRepo();
3083
- const repoPath = repoManager.getRepoPath();
3084
- await repoManager.ensureRepoAndScan();
4412
+ const repoPath = await repoManager.ensureRepoAndScan();
3085
4413
  updateSpinner.succeed(t("install_repo_ready"));
3086
4414
  const allPlugins = getAllPlugins();
3087
4415
  const results = [];
@@ -3126,8 +4454,8 @@ async function updateCommand(pluginNames, options) {
3126
4454
 
3127
4455
  // src/commands/info.ts
3128
4456
  import chalk13 from "chalk";
3129
- import { existsSync as existsSync14, readdirSync as readdirSync7 } from "fs";
3130
- import { join as join15 } from "path";
4457
+ import { existsSync as existsSync18, readdirSync as readdirSync7 } from "fs";
4458
+ import { join as join17 } from "path";
3131
4459
  async function infoCommand(pluginName) {
3132
4460
  const plugin = findPlugin(pluginName);
3133
4461
  if (!plugin) {
@@ -3135,7 +4463,8 @@ async function infoCommand(pluginName) {
3135
4463
  return;
3136
4464
  }
3137
4465
  const installed = scanInstalled();
3138
- const isInstalled = installed.some((p) => p.id === plugin.id);
4466
+ const inst = installed.find((p) => p.id === plugin.id);
4467
+ const isInstalled = !!inst;
3139
4468
  const statusText = isInstalled ? chalk13.green(`\u2713 ${t("status_installed")}`) : chalk13.dim(`\u2014 ${t("status_not_installed")}`);
3140
4469
  console.log();
3141
4470
  console.log(chalk13.bold(` ${plugin.displayName}`));
@@ -3144,19 +4473,19 @@ async function infoCommand(pluginName) {
3144
4473
  console.log(` ${chalk13.dim(t("info_description") + ":")} ${plugin.description}`);
3145
4474
  console.log(` ${chalk13.dim("ID:")} ${plugin.id}`);
3146
4475
  console.log(` ${chalk13.dim(t("info_status") + ":")} ${statusText}`);
3147
- console.log(` ${chalk13.dim(t("info_skills") + ":")} ${plugin.skills}`);
3148
- console.log(` ${chalk13.dim(t("info_agents") + ":")} ${plugin.agents}`);
4476
+ console.log(` ${chalk13.dim(t("info_skills") + ":")} ${inst ? inst.skillsCount : plugin.skills}`);
4477
+ console.log(` ${chalk13.dim(t("info_agents") + ":")} ${inst ? inst.agentsCount : plugin.agents}`);
3149
4478
  console.log(` ${chalk13.dim(t("info_aliases") + ":")} ${plugin.aliases.join(", ")}`);
3150
4479
  console.log();
3151
4480
  try {
3152
4481
  const repoManager = createRepositoryManager();
3153
4482
  const repoPath = await repoManager.ensureRepo();
3154
- const quickstartPath = join15(repoPath, plugin.dir, "quickstart.md");
3155
- if (existsSync14(quickstartPath)) {
4483
+ const quickstartPath = join17(repoPath, plugin.dir, "quickstart.md");
4484
+ if (existsSync18(quickstartPath)) {
3156
4485
  console.log(` ${chalk13.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
3157
4486
  }
3158
- const agentsDir = join15(repoPath, plugin.dir, "agents");
3159
- if (existsSync14(agentsDir)) {
4487
+ const agentsDir = join17(repoPath, plugin.dir, "agents");
4488
+ if (existsSync18(agentsDir)) {
3160
4489
  const agentFiles = readdirSync7(agentsDir).filter((f) => f.endsWith(".md"));
3161
4490
  if (agentFiles.length > 0) {
3162
4491
  console.log();
@@ -3208,13 +4537,44 @@ async function langCommand(action, value) {
3208
4537
  }
3209
4538
 
3210
4539
  // src/cli.ts
4540
+ import { readFileSync as readFileSync9, existsSync as existsSync19 } from "fs";
4541
+ import { fileURLToPath as fileURLToPath5 } from "url";
4542
+ import { dirname as dirname7, join as join18 } from "path";
4543
+ function readVersion() {
4544
+ try {
4545
+ if (typeof INSTALL_HELPER_VERSION !== "undefined" && INSTALL_HELPER_VERSION) {
4546
+ return INSTALL_HELPER_VERSION;
4547
+ }
4548
+ } catch {
4549
+ }
4550
+ try {
4551
+ const __filename5 = fileURLToPath5(import.meta.url);
4552
+ const __dirname5 = dirname7(__filename5);
4553
+ const candidates = [
4554
+ join18(__dirname5, "..", "package.json"),
4555
+ join18(__dirname5, "package.json"),
4556
+ join18(process.cwd(), "package.json")
4557
+ ];
4558
+ for (const pkgPath of candidates) {
4559
+ if (existsSync19(pkgPath)) {
4560
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
4561
+ if (pkg.version && (pkg.name === "@cannbot-ai/install-helper" || pkg.name?.startsWith("@cannbot-ai/install-helper-"))) {
4562
+ return pkg.version;
4563
+ }
4564
+ }
4565
+ }
4566
+ return "0.0.0";
4567
+ } catch {
4568
+ return "0.0.0";
4569
+ }
4570
+ }
3211
4571
  function createCLI() {
3212
4572
  const config = readConfig();
3213
4573
  if (config.language) {
3214
4574
  setLanguage(config.language);
3215
4575
  }
3216
4576
  const program2 = new Command();
3217
- program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("1.0.8");
4577
+ program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version(readVersion());
3218
4578
  program2.command("init", { isDefault: false }).description("Run interactive installation wizard").action(async () => {
3219
4579
  await initCommand();
3220
4580
  });
@@ -3249,13 +4609,28 @@ function createCLI() {
3249
4609
  }
3250
4610
 
3251
4611
  // src/index.ts
3252
- process.on("uncaughtException", (error) => {
3253
- if (error.name === "ExitPromptError") {
4612
+ function isExitError(error) {
4613
+ if (!error) return false;
4614
+ return error.name === "ExitPromptError" || error.message?.includes("force closed") || error.message?.includes("SIGINT") || error.constructor?.name === "ExitPromptError";
4615
+ }
4616
+ function handleExit(error) {
4617
+ if (isExitError(error)) {
3254
4618
  console.log("\n" + t("init_cancelled"));
3255
4619
  process.exit(0);
3256
4620
  }
4621
+ }
4622
+ process.on("SIGINT", () => {
4623
+ console.log("\n" + t("init_cancelled"));
4624
+ process.exit(130);
4625
+ });
4626
+ process.on("uncaughtException", (error) => {
4627
+ handleExit(error);
3257
4628
  throw error;
3258
4629
  });
4630
+ process.on("unhandledRejection", (error) => {
4631
+ handleExit(error);
4632
+ process.exit(1);
4633
+ });
3259
4634
  var program = createCLI();
3260
4635
  program.parse();
3261
4636
  //# sourceMappingURL=index.js.map