@cannbot-ai/install-helper 0.0.2-beta.32 → 0.0.2-beta.34

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
@@ -12,18 +12,115 @@ import { select, checkbox, Separator } from "@inquirer/prompts";
12
12
  import chalk3 from "chalk";
13
13
 
14
14
  // src/core/registry.ts
15
- import { readdirSync, readFileSync, existsSync, statSync } from "fs";
15
+ import { readdirSync, readFileSync as readFileSync2, existsSync, statSync } 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 chalk from "chalk";
22
+ import ora from "ora";
23
+
24
+ // src/utils/i18n.ts
25
+ import { readFileSync } from "fs";
16
26
  import { join, dirname } from "path";
17
27
  import { fileURLToPath } from "url";
18
- import { parse as parseYaml } from "yaml";
19
28
  var __filename = fileURLToPath(import.meta.url);
20
29
  var __dirname = dirname(__filename);
30
+ function loadLocale(locale) {
31
+ const localePath = join(__dirname, "locales", `${locale}.json`);
32
+ try {
33
+ const content = readFileSync(localePath, "utf-8");
34
+ return JSON.parse(content);
35
+ } catch {
36
+ const fallbackPath = join(__dirname, "locales", "zh_CN.json");
37
+ try {
38
+ const content = readFileSync(fallbackPath, "utf-8");
39
+ return JSON.parse(content);
40
+ } catch {
41
+ return {};
42
+ }
43
+ }
44
+ }
45
+ var currentLang = "zh_CN";
46
+ var messages = loadLocale("zh_CN");
47
+ function setLanguage(lang) {
48
+ currentLang = lang;
49
+ messages = loadLocale(lang);
50
+ }
51
+ function t(key) {
52
+ return messages[key] !== void 0 ? messages[key] : key;
53
+ }
54
+
55
+ // src/utils/logger.ts
56
+ var logger = {
57
+ success: (msg) => console.log(chalk.green("\u2713") + " " + msg),
58
+ error: (msg) => console.log(chalk.red("\u2717") + " " + msg),
59
+ warn: (msg) => console.log(chalk.yellow("\u26A0") + " " + msg),
60
+ info: (msg) => console.log(chalk.cyan("\u2192") + " " + msg),
61
+ step: (msg) => console.log(chalk.dim(msg)),
62
+ blank: () => console.log()
63
+ };
64
+ function createSpinner(text) {
65
+ return ora({ text, color: "cyan" });
66
+ }
67
+ function printBanner(subtitle) {
68
+ console.log(chalk.cyan(`
69
+ ____ _ _ _ _ _ ____ _
70
+ / ___| / \\ | \\ | | \\ | | __ ) ___ | |_
71
+ | | / _ \\ | \\| | \\| | _ \\ / _ \\| __|
72
+ | |___ / ___ \\| |\\ | |\\ | |_) | (_) | |_
73
+ \\____/_/ \\_\\_| \\_|_| \\_|____/ \\___/ \\__|
74
+ `));
75
+ if (subtitle) {
76
+ console.log(chalk.bold(` ${subtitle}`));
77
+ console.log();
78
+ }
79
+ }
80
+ function getDisplayWidth(str) {
81
+ let width = 0;
82
+ for (const ch of str) {
83
+ const code = ch.codePointAt(0);
84
+ 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) {
85
+ width += 2;
86
+ } else {
87
+ width += 1;
88
+ }
89
+ }
90
+ return width;
91
+ }
92
+ function printBoxTitle(title, width = 65) {
93
+ const border = "\u2550".repeat(width);
94
+ const displayWidth = getDisplayWidth(title);
95
+ const totalPadding = width - displayWidth;
96
+ const leftPadding = Math.floor(totalPadding / 2);
97
+ const rightPadding = totalPadding - leftPadding;
98
+ const paddedTitle = " ".repeat(Math.max(0, leftPadding)) + title + " ".repeat(Math.max(0, rightPadding));
99
+ console.log();
100
+ console.log(chalk.cyan.bold(` \u2554${border}\u2557`));
101
+ console.log(chalk.cyan.bold(` \u2551`) + chalk.cyan.bold(paddedTitle) + chalk.cyan.bold(`\u2551`));
102
+ console.log(chalk.cyan.bold(` \u255A${border}\u255D`));
103
+ console.log();
104
+ }
105
+ function showOperationHints(isCheckbox = false) {
106
+ if (isCheckbox) {
107
+ 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
108
+ `);
109
+ } else {
110
+ console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}
111
+ `);
112
+ }
113
+ }
114
+
115
+ // src/core/registry.ts
116
+ var __filename2 = fileURLToPath2(import.meta.url);
117
+ var __dirname2 = dirname2(__filename2);
21
118
  function loadPluginsFromYaml() {
22
- const pluginsDir = join(__dirname, "plugins.d");
23
- const defaultsPath = join(pluginsDir, "_defaults.yml");
119
+ const pluginsDir = join2(__dirname2, "plugins.d");
120
+ const defaultsPath = join2(pluginsDir, "_defaults.yml");
24
121
  let defaults = {};
25
122
  try {
26
- defaults = parseYaml(readFileSync(defaultsPath, "utf-8")) || {};
123
+ defaults = parseYaml(readFileSync2(defaultsPath, "utf-8")) || {};
27
124
  } catch {
28
125
  }
29
126
  const plugins = [];
@@ -31,7 +128,7 @@ function loadPluginsFromYaml() {
31
128
  for (const file of readdirSync(pluginsDir)) {
32
129
  if (file.startsWith("_") || !file.endsWith(".yml")) continue;
33
130
  try {
34
- const content = parseYaml(readFileSync(join(pluginsDir, file), "utf-8"));
131
+ const content = parseYaml(readFileSync2(join2(pluginsDir, file), "utf-8"));
35
132
  if (!content || !content.id) continue;
36
133
  plugins.push({
37
134
  id: content.id,
@@ -44,6 +141,7 @@ function loadPluginsFromYaml() {
44
141
  description: content.description || ""
45
142
  });
46
143
  } catch {
144
+ logger.warn(t("plugin_yaml_error").replace("{file}", file));
47
145
  }
48
146
  }
49
147
  } catch {
@@ -60,7 +158,7 @@ function mergeDynamicPlugins(repoPath) {
60
158
  const existingIds = new Set(PLUGIN_REGISTRY.map((p) => p.id));
61
159
  const dynamic = [];
62
160
  for (const pluginDir of PLUGIN_DIRS) {
63
- const parent = join(repoPath, pluginDir);
161
+ const parent = join2(repoPath, pluginDir);
64
162
  if (!existsSync(parent)) continue;
65
163
  let entries = [];
66
164
  try {
@@ -69,10 +167,10 @@ function mergeDynamicPlugins(repoPath) {
69
167
  continue;
70
168
  }
71
169
  for (const entry of entries) {
72
- const pluginPath = join(parent, entry);
170
+ const pluginPath = join2(parent, entry);
73
171
  if (!isDirectory(pluginPath)) continue;
74
172
  if (entry === "install-helper") continue;
75
- const initSh = join(pluginPath, "init.sh");
173
+ const initSh = join2(pluginPath, "init.sh");
76
174
  if (!existsSync(initSh)) continue;
77
175
  const pluginId = entry;
78
176
  if (existingIds.has(pluginId)) continue;
@@ -133,39 +231,6 @@ import { join as join4 } from "path";
133
231
  import { homedir } from "os";
134
232
  import { join as join3 } from "path";
135
233
  import { existsSync as existsSync2 } from "fs";
136
-
137
- // src/utils/i18n.ts
138
- import { readFileSync as readFileSync2 } from "fs";
139
- import { join as join2, dirname as dirname2 } from "path";
140
- import { fileURLToPath as fileURLToPath2 } from "url";
141
- var __filename2 = fileURLToPath2(import.meta.url);
142
- var __dirname2 = dirname2(__filename2);
143
- function loadLocale(locale) {
144
- const localePath = join2(__dirname2, "locales", `${locale}.json`);
145
- try {
146
- const content = readFileSync2(localePath, "utf-8");
147
- return JSON.parse(content);
148
- } catch {
149
- const fallbackPath = join2(__dirname2, "locales", "zh_CN.json");
150
- try {
151
- const content = readFileSync2(fallbackPath, "utf-8");
152
- return JSON.parse(content);
153
- } catch {
154
- return {};
155
- }
156
- }
157
- }
158
- var currentLang = "zh_CN";
159
- var messages = loadLocale("zh_CN");
160
- function setLanguage(lang) {
161
- currentLang = lang;
162
- messages = loadLocale(lang);
163
- }
164
- function t(key) {
165
- return messages[key] !== void 0 ? messages[key] : key;
166
- }
167
-
168
- // src/utils/paths.ts
169
234
  var VALID_TOOLS = ["opencode", "claude", "trae", "cursor", "copilot"];
170
235
  var VALID_LEVELS = ["project", "global"];
171
236
  function validateTool(tool) {
@@ -467,73 +532,32 @@ function verifyPluginFilesExist(configRoot, manifest, tool, level) {
467
532
  return true;
468
533
  }
469
534
 
470
- // src/utils/logger.ts
471
- import chalk from "chalk";
472
- import ora from "ora";
473
- var logger = {
474
- success: (msg) => console.log(chalk.green("\u2713") + " " + msg),
475
- error: (msg) => console.log(chalk.red("\u2717") + " " + msg),
476
- warn: (msg) => console.log(chalk.yellow("\u26A0") + " " + msg),
477
- info: (msg) => console.log(chalk.cyan("\u2192") + " " + msg),
478
- step: (msg) => console.log(chalk.dim(msg)),
479
- blank: () => console.log()
480
- };
481
- function createSpinner(text) {
482
- return ora({ text, color: "cyan" });
483
- }
484
- function printBanner(subtitle) {
485
- console.log(chalk.cyan(`
486
- ____ _ _ _ _ _ ____ _
487
- / ___| / \\ | \\ | | \\ | | __ ) ___ | |_
488
- | | / _ \\ | \\| | \\| | _ \\ / _ \\| __|
489
- | |___ / ___ \\| |\\ | |\\ | |_) | (_) | |_
490
- \\____/_/ \\_\\_| \\_|_| \\_|____/ \\___/ \\__|
491
- `));
492
- if (subtitle) {
493
- console.log(chalk.bold(` ${subtitle}`));
494
- console.log();
495
- }
496
- }
497
- function getDisplayWidth(str) {
498
- let width = 0;
499
- for (const ch of str) {
500
- const code = ch.codePointAt(0);
501
- 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) {
502
- width += 2;
503
- } else {
504
- width += 1;
535
+ // src/utils/config.ts
536
+ import { existsSync as existsSync6, mkdirSync, readFileSync as readFileSync4 } from "fs";
537
+ import { join as join6 } from "path";
538
+ import { homedir as homedir3 } from "os";
539
+ import { parse, stringify } from "yaml";
540
+
541
+ // src/utils/fs.ts
542
+ import { writeFileSync, renameSync, existsSync as existsSync5, unlinkSync } from "fs";
543
+ import { dirname as dirname3 } from "path";
544
+ function atomicWriteFileSync(filePath, data, encoding = "utf-8") {
545
+ const dir = dirname3(filePath);
546
+ if (!existsSync5(dir)) {
547
+ throw new Error(`Directory does not exist: ${dir}`);
548
+ }
549
+ const tmpPath = `${filePath}.tmp`;
550
+ writeFileSync(tmpPath, data, encoding);
551
+ if (existsSync5(filePath)) {
552
+ try {
553
+ unlinkSync(filePath);
554
+ } catch {
505
555
  }
506
556
  }
507
- return width;
508
- }
509
- function printBoxTitle(title, width = 65) {
510
- const border = "\u2550".repeat(width);
511
- const displayWidth = getDisplayWidth(title);
512
- const totalPadding = width - displayWidth;
513
- const leftPadding = Math.floor(totalPadding / 2);
514
- const rightPadding = totalPadding - leftPadding;
515
- const paddedTitle = " ".repeat(Math.max(0, leftPadding)) + title + " ".repeat(Math.max(0, rightPadding));
516
- console.log();
517
- console.log(chalk.cyan.bold(` \u2554${border}\u2557`));
518
- console.log(chalk.cyan.bold(` \u2551`) + chalk.cyan.bold(paddedTitle) + chalk.cyan.bold(`\u2551`));
519
- console.log(chalk.cyan.bold(` \u255A${border}\u255D`));
520
- console.log();
521
- }
522
- function showOperationHints(isCheckbox = false) {
523
- if (isCheckbox) {
524
- 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
525
- `);
526
- } else {
527
- console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}
528
- `);
529
- }
557
+ renameSync(tmpPath, filePath);
530
558
  }
531
559
 
532
560
  // src/utils/config.ts
533
- import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
534
- import { join as join6 } from "path";
535
- import { homedir as homedir3 } from "os";
536
- import { parse, stringify } from "yaml";
537
561
  function getConfigDir() {
538
562
  return join6(homedir3(), ".cannbot");
539
563
  }
@@ -542,7 +566,7 @@ function getConfigPath() {
542
566
  }
543
567
  function readConfig() {
544
568
  const configPath = getConfigPath();
545
- if (!existsSync5(configPath)) {
569
+ if (!existsSync6(configPath)) {
546
570
  return {
547
571
  language: "zh_CN",
548
572
  installedPlugins: []
@@ -559,6 +583,7 @@ function readConfig() {
559
583
  installedPlugins: parsed.installedPlugins || []
560
584
  };
561
585
  } catch {
586
+ logger.warn(t("config_corrupted"));
562
587
  return {
563
588
  language: "zh_CN",
564
589
  installedPlugins: []
@@ -567,12 +592,12 @@ function readConfig() {
567
592
  }
568
593
  function writeConfig(config) {
569
594
  const configDir = getConfigDir();
570
- if (!existsSync5(configDir)) {
595
+ if (!existsSync6(configDir)) {
571
596
  mkdirSync(configDir, { recursive: true });
572
597
  }
573
598
  const configPath = getConfigPath();
574
599
  const content = stringify(config);
575
- writeFileSync(configPath, content, "utf-8");
600
+ atomicWriteFileSync(configPath, content);
576
601
  }
577
602
  function updateConfig(updates) {
578
603
  const config = readConfig();
@@ -918,19 +943,19 @@ async function stepConfirm(tool, level, plugins) {
918
943
  }
919
944
 
920
945
  // src/core/repository.ts
921
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
922
- import { join as join8, dirname as dirname4 } from "path";
946
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
947
+ import { join as join8, dirname as dirname5 } from "path";
923
948
  import { fileURLToPath as fileURLToPath4 } from "url";
924
949
  import { execa as execa2 } from "execa";
925
950
  import { parse as parseYaml3 } from "yaml";
926
951
 
927
952
  // src/core/scanner.ts
928
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
929
- import { join as join7, dirname as dirname3 } from "path";
953
+ import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync2, mkdirSync as mkdirSync2 } from "fs";
954
+ import { join as join7, dirname as dirname4 } from "path";
930
955
  import { fileURLToPath as fileURLToPath3 } from "url";
931
956
  import { parse as parseYaml2 } from "yaml";
932
957
  var __filename3 = fileURLToPath3(import.meta.url);
933
- var __dirname3 = dirname3(__filename3);
958
+ var __dirname3 = dirname4(__filename3);
934
959
  var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
935
960
  "node_modules",
936
961
  ".git",
@@ -974,26 +999,26 @@ function scanSkills(repoPath) {
974
999
  const seen = /* @__PURE__ */ new Set();
975
1000
  for (const dir of SKILL_SCAN_DIRS) {
976
1001
  const fullPath = join7(repoPath, dir);
977
- if (!existsSync6(fullPath)) continue;
1002
+ if (!existsSync7(fullPath)) continue;
978
1003
  scanDirectory(fullPath, repoPath, dir, skills, seen);
979
1004
  }
980
1005
  for (const pluginDir of PLUGIN_SCAN_DIRS) {
981
1006
  const pluginParent = join7(repoPath, pluginDir);
982
- if (!existsSync6(pluginParent)) continue;
1007
+ if (!existsSync7(pluginParent)) continue;
983
1008
  for (const plugin of safeReaddir(pluginParent)) {
984
1009
  const pluginPath = join7(pluginParent, plugin);
985
1010
  if (!isDirectory2(pluginPath)) continue;
986
1011
  const skillsDir = join7(pluginPath, "skills");
987
- if (existsSync6(skillsDir)) {
1012
+ if (existsSync7(skillsDir)) {
988
1013
  scanDirectory(skillsDir, repoPath, `${pluginDir}/${plugin}/skills`, skills, seen);
989
1014
  }
990
1015
  const skillDir = join7(pluginPath, "skill");
991
- if (existsSync6(skillDir)) {
1016
+ if (existsSync7(skillDir)) {
992
1017
  scanDirectory(skillDir, repoPath, `${pluginDir}/${plugin}/skill`, skills, seen);
993
1018
  }
994
1019
  const rootSkillMd = join7(pluginPath, "SKILL.md");
995
1020
  const initSh = join7(pluginPath, "init.sh");
996
- if (existsSync6(rootSkillMd) && !existsSync6(initSh)) {
1021
+ if (existsSync7(rootSkillMd) && !existsSync7(initSh)) {
997
1022
  const frontmatter = parseFrontmatter(rootSkillMd);
998
1023
  if (frontmatter && !seen.has(frontmatter.name)) {
999
1024
  seen.add(frontmatter.name);
@@ -1027,8 +1052,12 @@ function parseFrontmatter(filePath) {
1027
1052
  const parsed = parseYaml2(yamlBlock);
1028
1053
  if (!parsed || typeof parsed !== "object") return null;
1029
1054
  if (!parsed.name || typeof parsed.name !== "string") return null;
1055
+ const trimmedName = parsed.name.trim();
1056
+ if (!trimmedName || /[\/\\]|\.\./.test(trimmedName) || !/^[a-zA-Z0-9._-]+$/.test(trimmedName)) {
1057
+ return null;
1058
+ }
1030
1059
  return {
1031
- name: parsed.name.trim(),
1060
+ name: trimmedName,
1032
1061
  description: (parsed.description || "").trim()
1033
1062
  };
1034
1063
  } catch {
@@ -1038,15 +1067,15 @@ function parseFrontmatter(filePath) {
1038
1067
  function getCurrentCommit(repoPath) {
1039
1068
  try {
1040
1069
  const headFile = join7(repoPath, ".git", "HEAD");
1041
- if (!existsSync6(headFile)) return "unknown";
1070
+ if (!existsSync7(headFile)) return "unknown";
1042
1071
  const headContent = readFileSync5(headFile, "utf-8").trim();
1043
1072
  if (headContent.startsWith("ref: ")) {
1044
1073
  const refPath = join7(repoPath, ".git", headContent.slice(5));
1045
- if (existsSync6(refPath)) {
1074
+ if (existsSync7(refPath)) {
1046
1075
  return readFileSync5(refPath, "utf-8").trim();
1047
1076
  }
1048
1077
  const packedRefsPath = join7(repoPath, ".git", "packed-refs");
1049
- if (existsSync6(packedRefsPath)) {
1078
+ if (existsSync7(packedRefsPath)) {
1050
1079
  const packedRefs = readFileSync5(packedRefsPath, "utf-8");
1051
1080
  const refName = headContent.slice(5);
1052
1081
  const lines = packedRefs.split("\n");
@@ -1065,7 +1094,7 @@ function getCurrentCommit(repoPath) {
1065
1094
  function readScanCache() {
1066
1095
  try {
1067
1096
  const cachePath = getCachePath();
1068
- if (!existsSync6(cachePath)) return null;
1097
+ if (!existsSync7(cachePath)) return null;
1069
1098
  const content = readFileSync5(cachePath, "utf-8");
1070
1099
  const cache = JSON.parse(content);
1071
1100
  if (!cache.skills || !cache.timestamp) return null;
@@ -1079,11 +1108,11 @@ function readScanCache() {
1079
1108
  function writeScanCache(cache) {
1080
1109
  try {
1081
1110
  const cachePath = getCachePath();
1082
- const cacheDir = dirname3(cachePath);
1083
- if (!existsSync6(cacheDir)) {
1111
+ const cacheDir = dirname4(cachePath);
1112
+ if (!existsSync7(cacheDir)) {
1084
1113
  mkdirSync2(cacheDir, { recursive: true });
1085
1114
  }
1086
- writeFileSync2(cachePath, JSON.stringify(cache, null, 2));
1115
+ atomicWriteFileSync(cachePath, JSON.stringify(cache, null, 2));
1087
1116
  } catch {
1088
1117
  }
1089
1118
  }
@@ -1096,7 +1125,7 @@ function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1096
1125
  if (EXCLUDE_DIRS.has(entry)) continue;
1097
1126
  if (!isDirectory2(fullPath)) continue;
1098
1127
  const skillMd = join7(fullPath, "SKILL.md");
1099
- if (existsSync6(skillMd)) {
1128
+ if (existsSync7(skillMd)) {
1100
1129
  const frontmatter = parseFrontmatter(skillMd);
1101
1130
  if (frontmatter && !seen.has(frontmatter.name)) {
1102
1131
  seen.add(frontmatter.name);
@@ -1364,7 +1393,7 @@ function getAllCategories() {
1364
1393
 
1365
1394
  // src/core/repository.ts
1366
1395
  var __filename4 = fileURLToPath4(import.meta.url);
1367
- var __dirname4 = dirname4(__filename4);
1396
+ var __dirname4 = dirname5(__filename4);
1368
1397
  function loadRepoUrl() {
1369
1398
  if (process.env.CANNBOT_REPO_URL) {
1370
1399
  return process.env.CANNBOT_REPO_URL;
@@ -1428,6 +1457,7 @@ var RepositoryManager = class {
1428
1457
  try {
1429
1458
  await execa2("git", ["pull", "--quiet"], { cwd: repoPath, timeout: 3e4 });
1430
1459
  } catch {
1460
+ logger.warn(t("repo_pull_failed"));
1431
1461
  }
1432
1462
  mergeDynamicPlugins(repoPath);
1433
1463
  }
@@ -1438,18 +1468,18 @@ var RepositoryManager = class {
1438
1468
  return this.repoPath;
1439
1469
  }
1440
1470
  isValidRepo(path) {
1441
- if (!existsSync7(path)) return false;
1471
+ if (!existsSync8(path)) return false;
1442
1472
  const gitDir = join8(path, ".git");
1443
1473
  const pluginsDir = join8(path, "plugins-official");
1444
- return existsSync7(gitDir) && existsSync7(pluginsDir);
1474
+ return existsSync8(gitDir) && existsSync8(pluginsDir);
1445
1475
  }
1446
1476
  async cloneRepo() {
1447
1477
  const configDir = getCannbotConfigDir();
1448
- if (!existsSync7(configDir)) {
1478
+ if (!existsSync8(configDir)) {
1449
1479
  mkdirSync3(configDir, { recursive: true });
1450
1480
  }
1451
1481
  const targetPath = getCannbotRepoPath();
1452
- if (existsSync7(targetPath)) {
1482
+ if (existsSync8(targetPath)) {
1453
1483
  if (this.isValidRepo(targetPath)) {
1454
1484
  this.repoPath = targetPath;
1455
1485
  return targetPath;
@@ -1475,12 +1505,12 @@ function createRepositoryManager(customPath) {
1475
1505
  }
1476
1506
 
1477
1507
  // src/core/installer.ts
1478
- import { existsSync as existsSync10 } from "fs";
1508
+ import { existsSync as existsSync11 } from "fs";
1479
1509
  import { join as join11 } from "path";
1480
1510
  import { execa as execa3 } from "execa";
1481
1511
 
1482
1512
  // src/core/record.ts
1483
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4, lstatSync, unlinkSync } from "fs";
1513
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync7, lstatSync, unlinkSync as unlinkSync2 } from "fs";
1484
1514
  import { join as join9 } from "path";
1485
1515
  function getInstallsDir() {
1486
1516
  return join9(getCannbotConfigDir(), "installs");
@@ -1490,28 +1520,29 @@ function getRecordPath(pluginId) {
1490
1520
  }
1491
1521
  function readRecord(pluginId) {
1492
1522
  const recordPath = getRecordPath(pluginId);
1493
- if (!existsSync8(recordPath)) {
1523
+ if (!existsSync9(recordPath)) {
1494
1524
  return null;
1495
1525
  }
1496
1526
  try {
1497
1527
  const content = readFileSync7(recordPath, "utf-8");
1498
1528
  return JSON.parse(content);
1499
1529
  } catch {
1530
+ logger.warn(t("record_corrupted").replace("{file}", recordPath));
1500
1531
  return null;
1501
1532
  }
1502
1533
  }
1503
1534
  function writeRecord(record) {
1504
1535
  const installsDir = getInstallsDir();
1505
- if (!existsSync8(installsDir)) {
1536
+ if (!existsSync9(installsDir)) {
1506
1537
  mkdirSync4(installsDir, { recursive: true });
1507
1538
  }
1508
1539
  const recordPath = getRecordPath(record.pluginId);
1509
- writeFileSync4(recordPath, JSON.stringify(record, null, 2), "utf-8");
1540
+ atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
1510
1541
  }
1511
1542
  function deleteRecord(pluginId) {
1512
1543
  const recordPath = getRecordPath(pluginId);
1513
- if (existsSync8(recordPath)) {
1514
- unlinkSync(recordPath);
1544
+ if (existsSync9(recordPath)) {
1545
+ unlinkSync2(recordPath);
1515
1546
  }
1516
1547
  }
1517
1548
  function scanInstalledFiles(pluginId, displayName, tool, level, installPath, configRoot, manifest) {
@@ -1519,24 +1550,24 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
1519
1550
  const directories = [];
1520
1551
  if (manifest) {
1521
1552
  const skillsDir = join9(configRoot, "skills");
1522
- if (existsSync8(skillsDir)) {
1553
+ if (existsSync9(skillsDir)) {
1523
1554
  directories.push(skillsDir);
1524
1555
  for (const skillName of manifest.installed_skills || []) {
1525
1556
  const skillPath = join9(skillsDir, skillName);
1526
- if (existsSync8(skillPath) || isSymlink(skillPath)) {
1557
+ if (existsSync9(skillPath) || isSymlink(skillPath)) {
1527
1558
  files.push(skillPath);
1528
1559
  }
1529
1560
  }
1530
1561
  }
1531
1562
  const agentsDir = join9(configRoot, "agents");
1532
- if (existsSync8(agentsDir)) {
1563
+ if (existsSync9(agentsDir)) {
1533
1564
  directories.push(agentsDir);
1534
1565
  for (const agentName of manifest.installed_agents || []) {
1535
1566
  const agentPath = join9(agentsDir, agentName);
1536
1567
  const agentPathMd = join9(agentsDir, agentName + ".md");
1537
- if (existsSync8(agentPath) || isSymlink(agentPath)) {
1568
+ if (existsSync9(agentPath) || isSymlink(agentPath)) {
1538
1569
  files.push(agentPath);
1539
- } else if (existsSync8(agentPathMd) || isSymlink(agentPathMd)) {
1570
+ } else if (existsSync9(agentPathMd) || isSymlink(agentPathMd)) {
1540
1571
  files.push(agentPathMd);
1541
1572
  }
1542
1573
  }
@@ -1547,16 +1578,16 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
1547
1578
  files.push(workflowsLink);
1548
1579
  }
1549
1580
  const manifestPath = join9(configRoot, "cannbot-manifest.json");
1550
- if (existsSync8(manifestPath)) {
1581
+ if (existsSync9(manifestPath)) {
1551
1582
  files.push(manifestPath);
1552
1583
  }
1553
1584
  const pluginManifestPath = join9(configRoot, `${pluginId}-manifest.json`);
1554
- if (existsSync8(pluginManifestPath)) {
1585
+ if (existsSync9(pluginManifestPath)) {
1555
1586
  files.push(pluginManifestPath);
1556
1587
  }
1557
1588
  const configFileName = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
1558
1589
  const configFilePath = level === "project" ? join9(installPath, configFileName) : join9(configRoot, configFileName);
1559
- if (existsSync8(configFilePath)) {
1590
+ if (existsSync9(configFilePath)) {
1560
1591
  files.push(configFilePath);
1561
1592
  }
1562
1593
  const repoLinks = ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples"];
@@ -1594,23 +1625,24 @@ function getSkillRecordPath() {
1594
1625
  }
1595
1626
  function readSkillRecord() {
1596
1627
  const recordPath = getSkillRecordPath();
1597
- if (!existsSync8(recordPath)) {
1628
+ if (!existsSync9(recordPath)) {
1598
1629
  return {};
1599
1630
  }
1600
1631
  try {
1601
1632
  const content = readFileSync7(recordPath, "utf-8");
1602
1633
  return JSON.parse(content);
1603
1634
  } catch {
1635
+ logger.warn(t("skill_record_corrupted"));
1604
1636
  return {};
1605
1637
  }
1606
1638
  }
1607
1639
  function writeSkillRecord(record) {
1608
1640
  const installsDir = getInstallsDir();
1609
- if (!existsSync8(installsDir)) {
1641
+ if (!existsSync9(installsDir)) {
1610
1642
  mkdirSync4(installsDir, { recursive: true });
1611
1643
  }
1612
1644
  const recordPath = getSkillRecordPath();
1613
- writeFileSync4(recordPath, JSON.stringify(record, null, 2), "utf-8");
1645
+ atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
1614
1646
  }
1615
1647
  function addSkillsToRecord(skillIds, tool, level, installPath) {
1616
1648
  const record = readSkillRecord();
@@ -1651,12 +1683,12 @@ function getInstalledSkills(tool, level, installPath) {
1651
1683
  const skillsDir = join9(configRoot, "skills");
1652
1684
  return recordedSkills.filter((skillId) => {
1653
1685
  const skillPath = join9(skillsDir, skillId);
1654
- return existsSync8(skillPath) || isSymlink(skillPath);
1686
+ return existsSync9(skillPath) || isSymlink(skillPath);
1655
1687
  });
1656
1688
  }
1657
1689
 
1658
1690
  // src/core/backup.ts
1659
- import { existsSync as existsSync9, copyFileSync, readdirSync as readdirSync3, unlinkSync as unlinkSync2 } from "fs";
1691
+ import { existsSync as existsSync10, copyFileSync, readdirSync as readdirSync3, unlinkSync as unlinkSync3 } from "fs";
1660
1692
  import { join as join10 } from "path";
1661
1693
  function getAgentsFileName(tool) {
1662
1694
  return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
@@ -1673,7 +1705,7 @@ function detectCurrentPlugin(configRoot, tool) {
1673
1705
  }
1674
1706
  }
1675
1707
  const agentsFile = join10(configRoot, getAgentsFileName(tool));
1676
- if (!existsSync9(agentsFile)) {
1708
+ if (!existsSync10(agentsFile)) {
1677
1709
  return null;
1678
1710
  }
1679
1711
  const allRecords = readAllRecords();
@@ -1692,7 +1724,7 @@ function detectCurrentPlugin(configRoot, tool) {
1692
1724
  }
1693
1725
  function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
1694
1726
  const agentsFile = join10(configRoot, getAgentsFileName(tool));
1695
- if (!existsSync9(agentsFile)) {
1727
+ if (!existsSync10(agentsFile)) {
1696
1728
  return null;
1697
1729
  }
1698
1730
  const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
@@ -1711,7 +1743,7 @@ function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
1711
1743
  }
1712
1744
  }
1713
1745
  function findBackups(configRoot) {
1714
- if (!existsSync9(configRoot)) {
1746
+ if (!existsSync10(configRoot)) {
1715
1747
  return [];
1716
1748
  }
1717
1749
  const files = readdirSync3(configRoot);
@@ -1749,8 +1781,8 @@ function restoreBackup(backupPath, configRoot, tool) {
1749
1781
  }
1750
1782
  function deleteBackup(backupPath) {
1751
1783
  try {
1752
- if (existsSync9(backupPath)) {
1753
- unlinkSync2(backupPath);
1784
+ if (existsSync10(backupPath)) {
1785
+ unlinkSync3(backupPath);
1754
1786
  return true;
1755
1787
  }
1756
1788
  return false;
@@ -1842,7 +1874,7 @@ async function installPlugin(opts) {
1842
1874
  };
1843
1875
  }
1844
1876
  const scriptPath = join11(opts.repoPath, plugin.dir, plugin.script);
1845
- if (!existsSync10(scriptPath)) {
1877
+ if (!existsSync11(scriptPath)) {
1846
1878
  return {
1847
1879
  success: false,
1848
1880
  pluginId: opts.pluginId,
@@ -1860,7 +1892,7 @@ async function installPlugin(opts) {
1860
1892
  const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
1861
1893
  const agentsFile = join11(configRoot, getAgentsFileName(opts.tool));
1862
1894
  let backupInfo = null;
1863
- if (existsSync10(agentsFile)) {
1895
+ if (existsSync11(agentsFile)) {
1864
1896
  const currentPlugin = detectCurrentPlugin(configRoot, opts.tool);
1865
1897
  if (currentPlugin && currentPlugin.pluginId !== opts.pluginId) {
1866
1898
  let choice = "overwrite";
@@ -1887,9 +1919,18 @@ async function installPlugin(opts) {
1887
1919
  currentPlugin.pluginId,
1888
1920
  currentPlugin.pluginName
1889
1921
  );
1890
- if (backupInfo) {
1891
- logger.success(`${t("backup_created")}: ${backupInfo.filePath}`);
1922
+ if (!backupInfo) {
1923
+ logger.error(t("backup_failed"));
1924
+ return {
1925
+ success: false,
1926
+ pluginId: opts.pluginId,
1927
+ skillsCount: 0,
1928
+ agentsCount: 0,
1929
+ errors: [t("backup_failed")],
1930
+ warnings: []
1931
+ };
1892
1932
  }
1933
+ logger.success(`${t("backup_created")}: ${backupInfo.filePath}`);
1893
1934
  }
1894
1935
  }
1895
1936
  try {
@@ -1930,6 +1971,7 @@ async function installPlugin(opts) {
1930
1971
  }
1931
1972
  writeRecord(record);
1932
1973
  } catch {
1974
+ logger.warn(t("record_write_failed"));
1933
1975
  }
1934
1976
  return {
1935
1977
  success: true,
@@ -1953,8 +1995,8 @@ async function installPlugin(opts) {
1953
1995
  }
1954
1996
 
1955
1997
  // src/core/skill-installer.ts
1956
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync3, readdirSync as readdirSync4, rmdirSync, realpathSync, lstatSync as lstatSync2 } from "fs";
1957
- import { join as join12, dirname as dirname5 } from "path";
1998
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync4, readdirSync as readdirSync4, rmdirSync, realpathSync, lstatSync as lstatSync2 } from "fs";
1999
+ import { join as join12, dirname as dirname6 } from "path";
1958
2000
  import { select as select3, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
1959
2001
  import chalk5 from "chalk";
1960
2002
  import Table from "cli-table3";
@@ -1962,7 +2004,7 @@ async function installSkills(skillIds, tool, level, repoPath) {
1962
2004
  const configRoot = getConfigRoot(tool, level);
1963
2005
  const skillsDir = join12(configRoot, "skills");
1964
2006
  const installPath = level === "project" ? process.cwd() : configRoot;
1965
- if (!existsSync11(skillsDir)) {
2007
+ if (!existsSync12(skillsDir)) {
1966
2008
  mkdirSync5(skillsDir, { recursive: true });
1967
2009
  }
1968
2010
  const results = [];
@@ -1975,15 +2017,16 @@ async function installSkills(skillIds, tool, level, repoPath) {
1975
2017
  }
1976
2018
  const sourcePath = resolveSkillSourcePath(skill, repoPath);
1977
2019
  const targetPath = join12(skillsDir, skillId);
1978
- if (!existsSync11(sourcePath)) {
2020
+ if (!existsSync12(sourcePath)) {
1979
2021
  results.push({ skillId, success: false, error: `Source not found: ${sourcePath}` });
1980
2022
  continue;
1981
2023
  }
1982
2024
  try {
1983
- if (existsSync11(targetPath) || isSymlink2(targetPath)) {
1984
- unlinkSync3(targetPath);
2025
+ const resolvedSource = realpathSync(sourcePath);
2026
+ if (existsSync12(targetPath) || isSymlink2(targetPath)) {
2027
+ unlinkSync4(targetPath);
1985
2028
  }
1986
- symlinkSync(realpathSync(sourcePath), targetPath);
2029
+ symlinkSync(resolvedSource, targetPath);
1987
2030
  results.push({ skillId, success: true });
1988
2031
  installedIds.push(skillId);
1989
2032
  } catch (error) {
@@ -2004,8 +2047,8 @@ async function uninstallSkills(skillIds, tool, level) {
2004
2047
  for (const skillId of skillIds) {
2005
2048
  const targetPath = join12(skillsDir, skillId);
2006
2049
  try {
2007
- if (existsSync11(targetPath) || isSymlink2(targetPath)) {
2008
- unlinkSync3(targetPath);
2050
+ if (existsSync12(targetPath) || isSymlink2(targetPath)) {
2051
+ unlinkSync4(targetPath);
2009
2052
  results.push({ skillId, success: true });
2010
2053
  removedIds.push(skillId);
2011
2054
  logger.step(` ${t("uninstall_remove_label")}: ${skillId}`);
@@ -2020,7 +2063,7 @@ async function uninstallSkills(skillIds, tool, level) {
2020
2063
  if (removedIds.length > 0) {
2021
2064
  removeSkillsFromRecord(removedIds, tool, level, installPath);
2022
2065
  }
2023
- if (existsSync11(skillsDir)) {
2066
+ if (existsSync12(skillsDir)) {
2024
2067
  try {
2025
2068
  const entries = readdirSync4(skillsDir);
2026
2069
  if (entries.length === 0) {
@@ -2206,8 +2249,8 @@ function listAllSkills() {
2206
2249
  function resolveSkillSourcePath(skill, repoPath) {
2207
2250
  if (skill.filePath) {
2208
2251
  const absPath = skill.filePath.startsWith("/") ? skill.filePath : join12(repoPath, skill.filePath);
2209
- if (existsSync11(absPath)) {
2210
- return dirname5(absPath);
2252
+ if (existsSync12(absPath)) {
2253
+ return dirname6(absPath);
2211
2254
  }
2212
2255
  }
2213
2256
  return join12(repoPath, skill.source, skill.id);
@@ -2309,13 +2352,13 @@ function printEnhancedSummary(results, tool, configRoot) {
2309
2352
  }
2310
2353
 
2311
2354
  // src/commands/init.ts
2312
- import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
2355
+ import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
2313
2356
  import { join as join13 } from "path";
2314
2357
  var BACK2 = "__back__";
2315
2358
  var CANCEL2 = "__cancel__";
2316
2359
  async function initCommand() {
2317
2360
  const cwd = process.cwd();
2318
- if (existsSync12(join13(cwd, "package.json")) && (() => {
2361
+ if (existsSync13(join13(cwd, "package.json")) && (() => {
2319
2362
  try {
2320
2363
  const pkg = JSON.parse(readFileSync8(join13(cwd, "package.json"), "utf-8"));
2321
2364
  return pkg.name === "@cannbot-ai/install-helper";
@@ -2543,7 +2586,7 @@ async function listCommand() {
2543
2586
 
2544
2587
  // src/commands/doctor.ts
2545
2588
  import chalk8 from "chalk";
2546
- import { existsSync as existsSync13, readdirSync as readdirSync5, lstatSync as lstatSync3, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6 } from "fs";
2589
+ import { existsSync as existsSync14, readdirSync as readdirSync5, lstatSync as lstatSync3, unlinkSync as unlinkSync5, mkdirSync as mkdirSync6 } from "fs";
2547
2590
  import { join as join14 } from "path";
2548
2591
  async function doctorCommand(options = {}) {
2549
2592
  console.log();
@@ -2587,7 +2630,7 @@ async function doctorCommand(options = {}) {
2587
2630
  const configRoot = getConfigRoot(primaryTool, "project");
2588
2631
  const skillsDir = getSkillsDir(configRoot);
2589
2632
  const agentsDir = getAgentsDir(configRoot);
2590
- if (existsSync13(skillsDir)) {
2633
+ if (existsSync14(skillsDir)) {
2591
2634
  const brokenLinks = checkBrokenLinks(skillsDir);
2592
2635
  if (brokenLinks === 0) {
2593
2636
  const count = readdirSync5(skillsDir).length;
@@ -2609,7 +2652,7 @@ async function doctorCommand(options = {}) {
2609
2652
  console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", skillsDir)}`);
2610
2653
  }
2611
2654
  }
2612
- if (existsSync13(agentsDir)) {
2655
+ if (existsSync14(agentsDir)) {
2613
2656
  const brokenLinks = checkBrokenLinks(agentsDir);
2614
2657
  if (brokenLinks === 0) {
2615
2658
  const count = readdirSync5(agentsDir).length;
@@ -2635,7 +2678,7 @@ async function doctorCommand(options = {}) {
2635
2678
  console.log(chalk8.bold(` ${t("doctor_config")}`));
2636
2679
  const configFile = getConfigFileName(primaryTool);
2637
2680
  const configPath = join14(process.cwd(), configFile);
2638
- if (existsSync13(configPath)) {
2681
+ if (existsSync14(configPath)) {
2639
2682
  console.log(chalk8.green(" \u2713") + ` ${configFile} ${t("doctor_config_exists")}`);
2640
2683
  } else {
2641
2684
  console.log(chalk8.dim(" \u2014") + ` ${configFile} ${t("doctor_config_not_exist")}`);
@@ -2662,7 +2705,7 @@ function checkBrokenLinks(dir) {
2662
2705
  try {
2663
2706
  const stats = lstatSync3(fullPath);
2664
2707
  if (stats.isSymbolicLink()) {
2665
- if (!existsSync13(fullPath)) {
2708
+ if (!existsSync14(fullPath)) {
2666
2709
  broken++;
2667
2710
  }
2668
2711
  }
@@ -2683,8 +2726,8 @@ function fixBrokenLinks(dir) {
2683
2726
  try {
2684
2727
  const stats = lstatSync3(fullPath);
2685
2728
  if (stats.isSymbolicLink()) {
2686
- if (!existsSync13(fullPath)) {
2687
- unlinkSync4(fullPath);
2729
+ if (!existsSync14(fullPath)) {
2730
+ unlinkSync5(fullPath);
2688
2731
  fixed++;
2689
2732
  }
2690
2733
  }
@@ -2758,7 +2801,10 @@ async function installCommand(names, options) {
2758
2801
  tool2 = validateTool(options.tool);
2759
2802
  } else {
2760
2803
  const detected = await selectToolWithDetection();
2761
- if (detected === "back" || detected === "cancel") return;
2804
+ if (detected === "back" || detected === "cancel") {
2805
+ logger.info(t("init_cancelled"));
2806
+ return;
2807
+ }
2762
2808
  tool2 = detected;
2763
2809
  }
2764
2810
  const level2 = options.level ? validateLevel(options.level) : "project";
@@ -2807,7 +2853,10 @@ async function installCommand(names, options) {
2807
2853
  tool2 = validateTool(options.tool);
2808
2854
  } else {
2809
2855
  const detected = await selectToolWithDetection();
2810
- if (detected === "back" || detected === "cancel") return;
2856
+ if (detected === "back" || detected === "cancel") {
2857
+ logger.info(t("init_cancelled"));
2858
+ return;
2859
+ }
2811
2860
  tool2 = detected;
2812
2861
  }
2813
2862
  const level2 = options.level ? validateLevel(options.level) : "project";
@@ -2818,11 +2867,17 @@ async function installCommand(names, options) {
2818
2867
  const selectedSkills = await interactiveSkillSelect(tool2, level2);
2819
2868
  if (selectedSkills === "back") {
2820
2869
  const redetected = await selectToolWithDetection();
2821
- if (redetected === "back" || redetected === "cancel") return;
2870
+ if (redetected === "back" || redetected === "cancel") {
2871
+ logger.info(t("init_cancelled"));
2872
+ return;
2873
+ }
2822
2874
  tool2 = redetected;
2823
2875
  break;
2824
2876
  }
2825
- if (selectedSkills === "cancel") return;
2877
+ if (selectedSkills === "cancel") {
2878
+ logger.info(t("init_cancelled"));
2879
+ return;
2880
+ }
2826
2881
  if (selectedSkills.length === 0) {
2827
2882
  logger.info(t("install_no_skill_selected"));
2828
2883
  return;
@@ -2897,7 +2952,10 @@ async function installCommand(names, options) {
2897
2952
  tool = validateTool(options.tool);
2898
2953
  } else {
2899
2954
  const detected = await selectToolWithDetection();
2900
- if (detected === "back" || detected === "cancel") return;
2955
+ if (detected === "back" || detected === "cancel") {
2956
+ logger.info(t("init_cancelled"));
2957
+ return;
2958
+ }
2901
2959
  tool = detected;
2902
2960
  }
2903
2961
  const level = options.level ? validateLevel(options.level) : "project";
@@ -3015,7 +3073,8 @@ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes
3015
3073
  }
3016
3074
 
3017
3075
  // src/commands/uninstall.ts
3018
- import { existsSync as existsSync14, unlinkSync as unlinkSync5, rmdirSync as rmdirSync2, readdirSync as readdirSync6, lstatSync as lstatSync4 } from "fs";
3076
+ import { existsSync as existsSync15, unlinkSync as unlinkSync6, rmdirSync as rmdirSync2, readdirSync as readdirSync6, lstatSync as lstatSync4 } from "fs";
3077
+ import { resolve } from "path";
3019
3078
  import chalk11 from "chalk";
3020
3079
  async function uninstallCommand(names, options) {
3021
3080
  const tool = options.tool ? validateTool(options.tool) : "opencode";
@@ -3045,12 +3104,19 @@ async function uninstallCommand(names, options) {
3045
3104
  continue;
3046
3105
  }
3047
3106
  logger.info(`${t("uninstall_in_progress")} ${plugin.displayName}...`);
3107
+ const configRoot = getConfigRoot(record.tool, record.level);
3108
+ const allowedBases = [resolve(configRoot), resolve(record.installPath)];
3109
+ const isSafePath = (p) => {
3110
+ const resolved = resolve(p);
3111
+ return allowedBases.some((base) => resolved === base || resolved.startsWith(base + "/"));
3112
+ };
3048
3113
  let removedFiles = 0;
3049
3114
  let removedDirs = 0;
3050
3115
  for (const filePath of record.files) {
3116
+ if (!isSafePath(filePath)) continue;
3051
3117
  try {
3052
- if (existsSync14(filePath) || isSymlink3(filePath)) {
3053
- unlinkSync5(filePath);
3118
+ if (existsSync15(filePath) || isSymlink3(filePath)) {
3119
+ unlinkSync6(filePath);
3054
3120
  const name = filePath.split("/").pop() || filePath;
3055
3121
  logger.step(` ${t("uninstall_remove_label")}: ${name}`);
3056
3122
  removedFiles++;
@@ -3058,14 +3124,13 @@ async function uninstallCommand(names, options) {
3058
3124
  } catch {
3059
3125
  }
3060
3126
  }
3061
- const configRoot = getConfigRoot(record.tool, record.level);
3062
- const sortedDirs = [...record.directories].sort((a, b) => b.length - a.length);
3127
+ const sortedDirs = [...record.directories].filter(isSafePath).sort((a, b) => b.length - a.length);
3063
3128
  for (const dirPath of sortedDirs) {
3064
3129
  try {
3065
- if (dirPath === configRoot) {
3130
+ if (resolve(dirPath) === resolve(configRoot)) {
3066
3131
  continue;
3067
3132
  }
3068
- if (existsSync14(dirPath)) {
3133
+ if (existsSync15(dirPath)) {
3069
3134
  const entries = readdirSync6(dirPath);
3070
3135
  if (entries.length === 0) {
3071
3136
  rmdirSync2(dirPath);
@@ -3136,7 +3201,10 @@ async function updateCommand(pluginNames, options) {
3136
3201
  tool = validateTool(options.tool);
3137
3202
  } else {
3138
3203
  const detected = await selectToolWithDetection();
3139
- if (detected === "back" || detected === "cancel") return;
3204
+ if (detected === "back" || detected === "cancel") {
3205
+ logger.info(t("init_cancelled"));
3206
+ return;
3207
+ }
3140
3208
  tool = detected;
3141
3209
  }
3142
3210
  const level = options.level ? validateLevel(options.level) : "project";
@@ -3210,7 +3278,7 @@ async function updateCommand(pluginNames, options) {
3210
3278
 
3211
3279
  // src/commands/info.ts
3212
3280
  import chalk13 from "chalk";
3213
- import { existsSync as existsSync15, readdirSync as readdirSync7 } from "fs";
3281
+ import { existsSync as existsSync16, readdirSync as readdirSync7 } from "fs";
3214
3282
  import { join as join15 } from "path";
3215
3283
  async function infoCommand(pluginName) {
3216
3284
  const plugin = findPlugin(pluginName);
@@ -3236,11 +3304,11 @@ async function infoCommand(pluginName) {
3236
3304
  const repoManager = createRepositoryManager();
3237
3305
  const repoPath = await repoManager.ensureRepo();
3238
3306
  const quickstartPath = join15(repoPath, plugin.dir, "quickstart.md");
3239
- if (existsSync15(quickstartPath)) {
3307
+ if (existsSync16(quickstartPath)) {
3240
3308
  console.log(` ${chalk13.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
3241
3309
  }
3242
3310
  const agentsDir = join15(repoPath, plugin.dir, "agents");
3243
- if (existsSync15(agentsDir)) {
3311
+ if (existsSync16(agentsDir)) {
3244
3312
  const agentFiles = readdirSync7(agentsDir).filter((f) => f.endsWith(".md"));
3245
3313
  if (agentFiles.length > 0) {
3246
3314
  console.log();
@@ -3298,7 +3366,7 @@ function createCLI() {
3298
3366
  setLanguage(config.language);
3299
3367
  }
3300
3368
  const program2 = new Command();
3301
- program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.32");
3369
+ program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.34");
3302
3370
  program2.command("init", { isDefault: false }).description("Run interactive installation wizard").action(async () => {
3303
3371
  await initCommand();
3304
3372
  });