@cannbot-ai/install-helper 1.2.1-beta.2 → 1.2.2-beta.0

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
@@ -177,6 +177,15 @@ var init_zh_CN = __esm({
177
177
  backup_restore_success: "\u5DF2\u6062\u590D\u914D\u7F6E",
178
178
  backup_created: "\u5DF2\u521B\u5EFA\u5907\u4EFD",
179
179
  backup_failed: "\u5907\u4EFD\u521B\u5EFA\u5931\u8D25\uFF0C\u5B89\u88C5\u5DF2\u4E2D\u6B62\u4EE5\u4FDD\u62A4\u73B0\u6709\u914D\u7F6E",
180
+ unowned_file_detected: "\u68C0\u6D4B\u5230\u975E CANNBot \u5B89\u88C5\u7684\u914D\u7F6E\u6587\u4EF6",
181
+ unowned_file_path: "\u6587\u4EF6",
182
+ unowned_file_warning: "\u8BE5\u6587\u4EF6\u4E0D\u662F\u7531 CANNBot \u5B89\u88C5\u7684\u3002\u66FF\u6362\u5C06\u8986\u76D6\u5176\u73B0\u6709\u5185\u5BB9\uFF08\u4F1A\u5728\u6587\u4EF6\u65C1\u521B\u5EFA\u5907\u4EFD\uFF09\u3002",
183
+ unowned_file_keep: "\u4FDD\u7559\u6211\u7684\u6587\u4EF6\uFF08\u8DF3\u8FC7\u66FF\u6362\uFF09",
184
+ unowned_file_replace: "\u66FF\u6362\uFF08\u81EA\u52A8\u5907\u4EFD\uFF09",
185
+ unowned_preserved: "\u5DF2\u4FDD\u7559\u73B0\u6709\u6587\u4EF6\uFF08\u672A\u66FF\u6362\uFF09: {file}",
186
+ unowned_preserved_yes: "\u975E\u4EA4\u4E92\u6A21\u5F0F\uFF1A\u5DF2\u4FDD\u7559\u975E CANNBot \u5B89\u88C5\u7684\u73B0\u6709\u6587\u4EF6: {file}",
187
+ unowned_backup_label: "\u7528\u6237\u6587\u4EF6",
188
+ backup_restore_user_file: "\u6062\u590D\u7528\u6237\u6587\u4EF6",
180
189
  loading_skills_list: "\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...",
181
190
  loading_skills_list_complete: "Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210",
182
191
  loading_plugin_data: "\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...",
@@ -191,6 +200,7 @@ var init_zh_CN = __esm({
191
200
  skill_record_corrupted: "Skill \u5B89\u88C5\u8BB0\u5F55\u5DF2\u635F\u574F",
192
201
  record_write_failed: "\u5B89\u88C5\u8BB0\u5F55\u5199\u5165\u5931\u8D25\uFF0C\u5378\u8F7D\u65F6\u53EF\u80FD\u65E0\u6CD5\u81EA\u52A8\u6E05\u7406",
193
202
  install_config_failed: "\u914D\u7F6E\u6587\u4EF6\u5B89\u88C5\u5931\u8D25: {error}",
203
+ install_config_preserved: "\u5DF2\u8DF3\u8FC7\u914D\u7F6E\u6587\u4EF6\uFF08\u4FDD\u7559\u73B0\u6709\u6587\u4EF6\uFF09: {file}",
194
204
  install_clone_failed: "git clone \u5931\u8D25: {url}\uFF0C\u8BF7\u624B\u52A8\u514B\u9686: git clone {url} {dir}",
195
205
  install_link_config_failed: "\u94FE\u63A5 {name} \u5230\u914D\u7F6E\u76EE\u5F55\u5931\u8D25",
196
206
  install_link_project_failed: "\u94FE\u63A5 {name} \u5230\u9879\u76EE\u76EE\u5F55\u5931\u8D25",
@@ -429,6 +439,15 @@ var init_en_US = __esm({
429
439
  backup_restore_success: "Configuration restored",
430
440
  backup_created: "Backup created",
431
441
  backup_failed: "Backup creation failed, installation aborted to protect existing config",
442
+ unowned_file_detected: "Configuration file not created by CANNBot detected",
443
+ unowned_file_path: "File",
444
+ unowned_file_warning: "This file was not installed by CANNBot. Replacing it overwrites its current content (a backup is created beside it).",
445
+ unowned_file_keep: "Keep my file (skip replacing it)",
446
+ unowned_file_replace: "Replace (auto backup)",
447
+ unowned_preserved: "Kept existing file (not replaced): {file}",
448
+ unowned_preserved_yes: "Non-interactive mode: kept existing file not created by CANNBot: {file}",
449
+ unowned_backup_label: "User file",
450
+ backup_restore_user_file: "Restore user file",
432
451
  loading_skills_list: "Loading Skills list...",
433
452
  loading_skills_list_complete: "Skills list loaded",
434
453
  loading_plugin_data: "Loading plugin data...",
@@ -443,6 +462,7 @@ var init_en_US = __esm({
443
462
  skill_record_corrupted: "Skill install record corrupted",
444
463
  record_write_failed: "Install record write failed, uninstall may not work properly",
445
464
  install_config_failed: "Failed to install config file: {error}",
465
+ install_config_preserved: "Skipped config file (kept existing file): {file}",
446
466
  install_clone_failed: "git clone failed: {url}, clone manually: git clone {url} {dir}",
447
467
  install_link_config_failed: "Failed to link {name} to config directory",
448
468
  install_link_project_failed: "Failed to link {name} to project directory",
@@ -1356,10 +1376,10 @@ var init_embedded_plugins = __esm({
1356
1376
  aliases: [
1357
1377
  "tilelang"
1358
1378
  ],
1359
- skills: 9,
1360
- agents: 3,
1379
+ skills: 13,
1380
+ agents: 6,
1361
1381
  description: "TileLang Python DSL + @tilelang.jit \u7B97\u5B50\u5F00\u53D1\uFF08\u652F\u6301 Developer/Expert \u53CC\u6A21\u5F0F\uFF09",
1362
- version: "1.1.0",
1382
+ version: "1.2.0",
1363
1383
  configFile: "AGENTS.md",
1364
1384
  configRootConfigLink: false,
1365
1385
  installSkills: [
@@ -1374,14 +1394,21 @@ var init_embedded_plugins = __esm({
1374
1394
  "tilelang-perf-optimization",
1375
1395
  "tilelang-programming-model-guide",
1376
1396
  "tilelang-review",
1377
- "tilelang-submodule-pull"
1397
+ "tilelang-submodule-pull",
1398
+ "tilelang-performance-best-practices",
1399
+ "ascendc-env-check",
1400
+ "npu-arch",
1401
+ "tilelang-op-profiling"
1378
1402
  ]
1379
1403
  }
1380
1404
  ],
1381
1405
  installAgents: [
1382
1406
  "tilelang-op-analyst",
1383
1407
  "tilelang-op-developer",
1384
- "tilelang-op-perf-tuner"
1408
+ "tilelang-op-perf-tuner",
1409
+ "tilelang-tuning",
1410
+ "tilelang-perf-analysis-expert",
1411
+ "tilelang-perf-impl-expert"
1385
1412
  ],
1386
1413
  externalRepos: [
1387
1414
  {
@@ -2913,7 +2940,7 @@ function createRepositoryManager(customPath) {
2913
2940
 
2914
2941
  // src/core/cannbot-delegate.ts
2915
2942
  import { existsSync as existsSync17, readFileSync as readFileSync12, writeFileSync as writeFileSync5, copyFileSync as copyFileSync3, unlinkSync as unlinkSync6, readdirSync as readdirSync8 } from "fs";
2916
- import { join as join16, resolve as resolve3 } from "path";
2943
+ import { join as join16, resolve as resolve4 } from "path";
2917
2944
  import { execa as execa6 } from "execa";
2918
2945
 
2919
2946
  // src/core/cannbot-installer.ts
@@ -3247,7 +3274,7 @@ function deleteRecord(pluginId, where) {
3247
3274
  writeStore(pluginId, store);
3248
3275
  }
3249
3276
  }
3250
- function scanInstalledFiles(pluginId, displayName, tool, level, installPath, configRoot, manifest, externalRepoNames, configRootConfigLink) {
3277
+ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, configRoot, manifest, externalRepoNames, configRootConfigLink, preservedTargets) {
3251
3278
  const files = [];
3252
3279
  const directories = [];
3253
3280
  if (manifest) {
@@ -3289,12 +3316,12 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
3289
3316
  }
3290
3317
  const configFileName = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
3291
3318
  const configFilePath = level === "project" ? join11(installPath, configFileName) : join11(configRoot, configFileName);
3292
- if (existsSync12(configFilePath) || isSymlink(configFilePath)) {
3319
+ if (!preservedTargets?.includes(configFilePath) && (existsSync12(configFilePath) || isSymlink(configFilePath))) {
3293
3320
  files.push(configFilePath);
3294
3321
  }
3295
3322
  if (level === "project" && configRootConfigLink !== false) {
3296
3323
  const configRootConfigPath = join11(configRoot, configFileName);
3297
- if (configRootConfigPath !== configFilePath && (existsSync12(configRootConfigPath) || isSymlink(configRootConfigPath))) {
3324
+ if (configRootConfigPath !== configFilePath && !preservedTargets?.includes(configRootConfigPath) && (existsSync12(configRootConfigPath) || isSymlink(configRootConfigPath))) {
3298
3325
  files.push(configRootConfigPath);
3299
3326
  }
3300
3327
  }
@@ -3813,16 +3840,17 @@ function findOrphanedDelegatedRecords(target) {
3813
3840
  init_registry();
3814
3841
  init_manifest();
3815
3842
  init_paths();
3816
- import { existsSync as existsSync16, readFileSync as readFileSync11 } from "fs";
3817
- import { join as join15, basename as basename2, isAbsolute } from "path";
3843
+ import { existsSync as existsSync16, readFileSync as readFileSync11, realpathSync as realpathSync2 } from "fs";
3844
+ import { join as join15, basename as basename3, isAbsolute } from "path";
3818
3845
  import { execa as execa5, execaSync as execaSync2 } from "execa";
3819
3846
 
3820
3847
  // src/core/backup.ts
3821
3848
  init_manifest();
3822
3849
  import { existsSync as existsSync14, copyFileSync, readdirSync as readdirSync7, unlinkSync as unlinkSync5 } from "fs";
3823
- import { join as join13 } from "path";
3850
+ import { join as join13, dirname as dirname7, basename, resolve as resolve3 } from "path";
3824
3851
  init_registry();
3825
3852
  init_paths();
3853
+ var UNOWNED_PLUGIN_ID = "unowned";
3826
3854
  function detectCurrentPlugin(configRoot, tool, installPath) {
3827
3855
  const manifests = readAllManifests(configRoot);
3828
3856
  if (manifests.length > 0) {
@@ -3860,18 +3888,18 @@ function detectCurrentPlugin(configRoot, tool, installPath) {
3860
3888
  }
3861
3889
  return null;
3862
3890
  }
3863
- function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
3864
- const agentsFile = join13(configRoot, getAgentsFileName(tool));
3865
- if (!existsSync14(agentsFile)) {
3891
+ function createBackup(targetPath, fromPluginId, fromPluginName) {
3892
+ if (!existsSync14(targetPath)) {
3866
3893
  return null;
3867
3894
  }
3868
3895
  const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
3869
- const backupFileName = `${getAgentsFileName(tool)}.cannbot-backup.${fromPluginId}.${timestamp}`;
3870
- const backupPath = join13(configRoot, backupFileName);
3896
+ const backupFileName = `${basename(targetPath)}.cannbot-backup.${fromPluginId}.${timestamp}`;
3897
+ const backupPath = join13(dirname7(targetPath), backupFileName);
3871
3898
  try {
3872
- copyFileSync(agentsFile, backupPath);
3899
+ copyFileSync(targetPath, backupPath);
3873
3900
  return {
3874
3901
  filePath: backupPath,
3902
+ originalPath: targetPath,
3875
3903
  pluginId: fromPluginId,
3876
3904
  pluginName: fromPluginName,
3877
3905
  backupTime: timestamp
@@ -3880,38 +3908,54 @@ function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
3880
3908
  return null;
3881
3909
  }
3882
3910
  }
3883
- function findBackups(configRoot) {
3884
- if (!existsSync14(configRoot)) {
3885
- return [];
3911
+ function findRecordOwner(targetPath) {
3912
+ const resolved = resolve3(targetPath);
3913
+ for (const record of readAllRecords()) {
3914
+ if ((record.files || []).some((f) => resolve3(f) === resolved)) {
3915
+ const plugin = findPlugin(record.pluginId);
3916
+ return {
3917
+ pluginId: record.pluginId,
3918
+ pluginName: plugin?.displayName || record.displayName || record.pluginId
3919
+ };
3920
+ }
3886
3921
  }
3887
- const files = readdirSync7(configRoot);
3922
+ return null;
3923
+ }
3924
+ function findBackups(configRoot) {
3925
+ const dirs = Array.isArray(configRoot) ? configRoot : [configRoot];
3888
3926
  const backups = [];
3889
- for (const file of files) {
3890
- if (file.includes(".cannbot-backup.")) {
3891
- const parts = file.split(".cannbot-backup.");
3892
- if (parts.length === 2) {
3893
- const pluginIdAndTime = parts[1];
3894
- const lastDotIndex = pluginIdAndTime.lastIndexOf(".");
3895
- if (lastDotIndex > 0) {
3896
- const pluginId = pluginIdAndTime.substring(0, lastDotIndex);
3897
- const timestamp = pluginIdAndTime.substring(lastDotIndex + 1);
3898
- const plugin = findPlugin(pluginId);
3899
- backups.push({
3900
- filePath: join13(configRoot, file),
3901
- pluginId,
3902
- pluginName: plugin?.displayName || pluginId,
3903
- backupTime: timestamp
3904
- });
3927
+ for (const dir of dirs) {
3928
+ if (!existsSync14(dir)) {
3929
+ continue;
3930
+ }
3931
+ const files = readdirSync7(dir);
3932
+ for (const file of files) {
3933
+ if (file.includes(".cannbot-backup.")) {
3934
+ const parts = file.split(".cannbot-backup.");
3935
+ if (parts.length === 2) {
3936
+ const pluginIdAndTime = parts[1];
3937
+ const lastDotIndex = pluginIdAndTime.lastIndexOf(".");
3938
+ if (lastDotIndex > 0) {
3939
+ const pluginId = pluginIdAndTime.substring(0, lastDotIndex);
3940
+ const timestamp = pluginIdAndTime.substring(lastDotIndex + 1);
3941
+ const plugin = findPlugin(pluginId);
3942
+ backups.push({
3943
+ filePath: join13(dir, file),
3944
+ originalPath: join13(dir, parts[0]),
3945
+ pluginId,
3946
+ pluginName: plugin?.displayName || pluginId,
3947
+ backupTime: timestamp
3948
+ });
3949
+ }
3905
3950
  }
3906
3951
  }
3907
3952
  }
3908
3953
  }
3909
3954
  return backups.sort((a, b) => b.backupTime.localeCompare(a.backupTime));
3910
3955
  }
3911
- function restoreBackup(backupPath, configRoot, tool) {
3956
+ function restoreBackup(backupPath, targetPath) {
3912
3957
  try {
3913
- const agentsFile = join13(configRoot, getAgentsFileName(tool));
3914
- copyFileSync(backupPath, agentsFile);
3958
+ copyFileSync(backupPath, targetPath);
3915
3959
  return true;
3916
3960
  } catch {
3917
3961
  return false;
@@ -3939,13 +3983,21 @@ function formatTimestamp(date) {
3939
3983
  }
3940
3984
  function readAllRecords() {
3941
3985
  const records = [];
3942
- const plugins = getAllPlugins();
3943
- for (const plugin of plugins) {
3944
- records.push(...readRecords(plugin.id));
3986
+ const installsDir = join13(getCannbotConfigDir(), "installs");
3987
+ if (!existsSync14(installsDir)) {
3988
+ return records;
3989
+ }
3990
+ for (const file of readdirSync7(installsDir)) {
3991
+ if (!file.endsWith(".json") || file === "skills.json") continue;
3992
+ const pluginId = file.replace(/\.json$/, "");
3993
+ records.push(...readRecords(pluginId));
3945
3994
  }
3946
3995
  return records;
3947
3996
  }
3948
3997
 
3998
+ // src/core/installer.ts
3999
+ init_fs_helpers();
4000
+
3949
4001
  // src/ui/backup-prompts.ts
3950
4002
  init_i18n();
3951
4003
  import { select as select2 } from "@inquirer/prompts";
@@ -3969,12 +4021,31 @@ async function showOverwriteWarning(currentPluginName, newPluginName) {
3969
4021
  });
3970
4022
  return choice;
3971
4023
  }
4024
+ async function showUnownedFileWarning(filePath, newPluginName) {
4025
+ console.log();
4026
+ console.log(chalk4.yellow(` \u26A0\uFE0F ${t("unowned_file_detected")}`));
4027
+ console.log();
4028
+ console.log(` ${t("unowned_file_path")}: ${chalk4.cyan(filePath)}`);
4029
+ console.log(` ${t("backup_installing")}: ${chalk4.cyan(newPluginName)}`);
4030
+ console.log();
4031
+ console.log(` ${t("unowned_file_warning")}`);
4032
+ console.log();
4033
+ const choice = await select2({
4034
+ message: t("backup_select_action"),
4035
+ choices: [
4036
+ { name: t("unowned_file_keep"), value: "keep" },
4037
+ { name: t("unowned_file_replace"), value: "replace" },
4038
+ { name: t("backup_cancel"), value: "cancel" }
4039
+ ]
4040
+ });
4041
+ return choice;
4042
+ }
3972
4043
  async function showRestorePrompt(backups) {
3973
4044
  console.log();
3974
4045
  console.log(chalk4.cyan(` ${t("backup_restore_prompt")}`));
3975
4046
  console.log();
3976
4047
  const choices = backups.map((b) => ({
3977
- name: `${t("backup_restore")} ${b.pluginName} (${b.backupTime})`,
4048
+ name: b.pluginId === "unowned" ? `${t("backup_restore_user_file")} ${b.originalPath} (${b.backupTime})` : `${t("backup_restore")} ${b.pluginName} (${b.backupTime})`,
3978
4049
  value: b.filePath
3979
4050
  }));
3980
4051
  choices.push({ name: t("backup_only_uninstall"), value: "none" });
@@ -3991,7 +4062,7 @@ init_fs_helpers();
3991
4062
  init_logger();
3992
4063
  init_i18n();
3993
4064
  import { existsSync as existsSync15, mkdirSync as mkdirSync6, symlinkSync, realpathSync, readlinkSync, copyFileSync as copyFileSync2, cpSync, writeFileSync as writeFileSync4, rmSync as rmSync4, readFileSync as readFileSync10, constants as fsConstants } from "fs";
3994
- import { join as join14, basename } from "path";
4065
+ import { join as join14, basename as basename2 } from "path";
3995
4066
  import { execa as execa4 } from "execa";
3996
4067
  var ISSUE_HANDLER_ID = "gitcode-issue-handler";
3997
4068
  var ISSUE_HANDLER_CONFIG_FILES = ["classify_config.yaml", "operator_owners.yaml"];
@@ -4058,7 +4129,25 @@ function installLink(source, target) {
4058
4129
  return "failed";
4059
4130
  }
4060
4131
  }
4061
- function installConfigFile(pluginDir, configFile, tool, level, configRoot, installPath, configRootConfigLink) {
4132
+ function configFileTargets(pluginDir, configFile, tool, level, configRoot, installPath, configRootConfigLink) {
4133
+ const sourcePath = join14(pluginDir, configFile);
4134
+ if (!existsSync15(sourcePath)) return [];
4135
+ const agentsFileName = getAgentsFileName(tool);
4136
+ const targets = [];
4137
+ if (level === "project") {
4138
+ targets.push(join14(installPath, agentsFileName));
4139
+ if (configRootConfigLink !== false) {
4140
+ const configRootTarget = join14(configRoot, agentsFileName);
4141
+ if (!targets.includes(configRootTarget)) {
4142
+ targets.push(configRootTarget);
4143
+ }
4144
+ }
4145
+ } else {
4146
+ targets.push(join14(configRoot, agentsFileName));
4147
+ }
4148
+ return targets;
4149
+ }
4150
+ function installConfigFile(pluginDir, configFile, tool, level, configRoot, installPath, configRootConfigLink, preservedTargets) {
4062
4151
  const sourcePath = join14(pluginDir, configFile);
4063
4152
  if (!existsSync15(sourcePath)) return;
4064
4153
  const agentsFileName = getAgentsFileName(tool);
@@ -4069,26 +4158,34 @@ function installConfigFile(pluginDir, configFile, tool, level, configRoot, insta
4069
4158
  } else {
4070
4159
  primaryTarget = join14(configRoot, agentsFileName);
4071
4160
  }
4072
- try {
4073
- const resolvedSource = realpathSync(sourcePath);
4074
- if (existsSync15(primaryTarget) || isSymlink(primaryTarget)) {
4075
- removePath(primaryTarget);
4076
- }
4161
+ if (preservedTargets?.includes(primaryTarget)) {
4162
+ logger.info(t("install_config_preserved").replace("{file}", primaryTarget));
4163
+ } else {
4077
4164
  try {
4078
- symlinkSync(resolvedSource, primaryTarget);
4079
- } catch (e) {
4080
- if (e.code === "EPERM") {
4081
- copyFileSync2(sourcePath, primaryTarget);
4082
- } else {
4083
- throw e;
4165
+ const resolvedSource = realpathSync(sourcePath);
4166
+ if (existsSync15(primaryTarget) || isSymlink(primaryTarget)) {
4167
+ removePath(primaryTarget);
4084
4168
  }
4169
+ try {
4170
+ symlinkSync(resolvedSource, primaryTarget);
4171
+ } catch (e) {
4172
+ if (e.code === "EPERM") {
4173
+ copyFileSync2(sourcePath, primaryTarget);
4174
+ } else {
4175
+ throw e;
4176
+ }
4177
+ }
4178
+ } catch (e) {
4179
+ logger.warn(t("install_config_failed").replace("{error}", e.message));
4085
4180
  }
4086
- } catch (e) {
4087
- logger.warn(t("install_config_failed").replace("{error}", e.message));
4088
4181
  }
4089
4182
  if (isProjectLevel && primaryTarget !== join14(configRoot, agentsFileName)) {
4090
4183
  if (configRootConfigLink === false) return;
4091
4184
  const configRootTarget = join14(configRoot, agentsFileName);
4185
+ if (preservedTargets?.includes(configRootTarget)) {
4186
+ logger.info(t("install_config_preserved").replace("{file}", configRootTarget));
4187
+ return;
4188
+ }
4092
4189
  if (existsSync15(configRootTarget) || isSymlink(configRootTarget)) {
4093
4190
  removePath(configRootTarget);
4094
4191
  }
@@ -4132,7 +4229,7 @@ async function installExternalRepos(repos, repoPath, configRoot, installPath, le
4132
4229
  continue;
4133
4230
  }
4134
4231
  }
4135
- const linkName = basename(repo.dir);
4232
+ const linkName = basename2(repo.dir);
4136
4233
  const resolved = realpathSync(targetDir);
4137
4234
  const shouldConfigRootLink = repo.configRootLink !== false && (level === "global" || repo.configRootLink === true);
4138
4235
  if (shouldConfigRootLink) {
@@ -4207,7 +4304,7 @@ function writeCannbotManifest(configRoot, pluginId, version, level, tool, skills
4207
4304
  writeFileSync4(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
4208
4305
  return manifest;
4209
4306
  }
4210
- async function installViaManifest(plugin, repoPath, tool, level, installPath) {
4307
+ async function installViaManifest(plugin, repoPath, tool, level, installPath, preservedTargets) {
4211
4308
  const cwd = installPath || process.cwd();
4212
4309
  const configRoot = getConfigRoot(tool, level, installPath);
4213
4310
  const pluginDir = join14(repoPath, plugin.dir);
@@ -4278,7 +4375,7 @@ async function installViaManifest(plugin, repoPath, tool, level, installPath) {
4278
4375
  logger.warn(t("install_agent_failed").replace("{name}", agentFile));
4279
4376
  }
4280
4377
  }
4281
- installConfigFile(pluginDir, plugin.configFile || "AGENTS.md", tool, level, configRoot, cwd, plugin.configRootConfigLink);
4378
+ installConfigFile(pluginDir, plugin.configFile || "AGENTS.md", tool, level, configRoot, cwd, plugin.configRootConfigLink, preservedTargets);
4282
4379
  installWorkflows(pluginDir, configRoot);
4283
4380
  await installExternalRepos(plugin.externalRepos, repoPath, configRoot, cwd, level);
4284
4381
  const cannbotManifest = writeCannbotManifest(
@@ -4334,6 +4431,25 @@ function scriptSupportsTool(scriptPath, tool) {
4334
4431
  return true;
4335
4432
  }
4336
4433
  }
4434
+ function classifyConfigTarget(targetPath, pluginConfigSource, pluginId, pluginName, configRoot, tool, installPath) {
4435
+ if (isSymlink(targetPath)) {
4436
+ try {
4437
+ if (realpathSync2(targetPath) === realpathSync2(pluginConfigSource)) {
4438
+ return { kind: "same-plugin", pluginId, pluginName };
4439
+ }
4440
+ } catch {
4441
+ }
4442
+ }
4443
+ const recordOwner = findRecordOwner(targetPath);
4444
+ if (recordOwner) {
4445
+ return recordOwner.pluginId === pluginId ? { kind: "same-plugin", pluginId, pluginName } : { kind: "other-plugin", ...recordOwner };
4446
+ }
4447
+ const currentPlugin = detectCurrentPlugin(configRoot, tool, installPath);
4448
+ if (currentPlugin) {
4449
+ return currentPlugin.pluginId === pluginId ? { kind: "same-plugin", pluginId, pluginName } : { kind: "other-plugin", ...currentPlugin };
4450
+ }
4451
+ return { kind: "unowned", pluginId: UNOWNED_PLUGIN_ID, pluginName: UNOWNED_PLUGIN_ID };
4452
+ }
4337
4453
  async function installPlugin(opts) {
4338
4454
  const plugin = opts.plugin ?? getPluginById(opts.pluginId);
4339
4455
  if (!plugin) {
@@ -4359,38 +4475,55 @@ async function installPlugin(opts) {
4359
4475
  const pluginDir = isAbsolute(plugin.dir) ? plugin.dir : join15(opts.repoPath, plugin.dir);
4360
4476
  const cwd = opts.installPath || process.cwd();
4361
4477
  const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
4362
- const agentsFile = join15(configRoot, getAgentsFileName(opts.tool));
4363
- let backupInfo = null;
4364
- if (existsSync16(agentsFile)) {
4365
- const currentPlugin = detectCurrentPlugin(configRoot, opts.tool, opts.installPath);
4366
- if (currentPlugin && currentPlugin.pluginId !== opts.pluginId) {
4367
- let choice = "overwrite";
4368
- if (!opts.yes) {
4369
- choice = await showOverwriteWarning(
4370
- currentPlugin.pluginName,
4371
- plugin.displayName
4372
- );
4373
- }
4374
- if (choice === "cancel") {
4375
- logger.info(t("backup_cancel"));
4376
- return {
4377
- success: false,
4378
- pluginId: opts.pluginId,
4379
- skillsCount: 0,
4380
- agentsCount: 0,
4381
- errors: [t("backup_cancel")],
4382
- warnings: []
4383
- };
4384
- }
4478
+ const pluginConfigSource = join15(pluginDir, plugin.configFile || "AGENTS.md");
4479
+ const existingTargets = configFileTargets(
4480
+ pluginDir,
4481
+ plugin.configFile || "AGENTS.md",
4482
+ opts.tool,
4483
+ opts.level,
4484
+ configRoot,
4485
+ cwd,
4486
+ plugin.configRootConfigLink
4487
+ ).filter((p) => existsSync16(p) || isSymlink(p));
4488
+ const backups = [];
4489
+ const preservedTargets = [];
4490
+ const promptedPlugins = /* @__PURE__ */ new Set();
4491
+ for (const target of existingTargets) {
4492
+ const owner = classifyConfigTarget(
4493
+ target,
4494
+ pluginConfigSource,
4495
+ opts.pluginId,
4496
+ plugin.displayName,
4497
+ configRoot,
4498
+ opts.tool,
4499
+ cwd
4500
+ );
4501
+ if (owner.kind === "same-plugin") {
4502
+ const backup2 = createBackup(target, owner.pluginId, owner.pluginName);
4503
+ if (backup2) backups.push(backup2);
4504
+ continue;
4385
4505
  }
4386
- if (currentPlugin) {
4387
- backupInfo = createBackup(
4388
- configRoot,
4389
- opts.tool,
4390
- currentPlugin.pluginId,
4391
- currentPlugin.pluginName
4392
- );
4393
- if (!backupInfo) {
4506
+ if (owner.kind === "other-plugin") {
4507
+ if (!promptedPlugins.has(owner.pluginId)) {
4508
+ promptedPlugins.add(owner.pluginId);
4509
+ let choice2 = "overwrite";
4510
+ if (!opts.yes) {
4511
+ choice2 = await showOverwriteWarning(owner.pluginName, plugin.displayName);
4512
+ }
4513
+ if (choice2 === "cancel") {
4514
+ logger.info(t("backup_cancel"));
4515
+ return {
4516
+ success: false,
4517
+ pluginId: opts.pluginId,
4518
+ skillsCount: 0,
4519
+ agentsCount: 0,
4520
+ errors: [t("backup_cancel")],
4521
+ warnings: []
4522
+ };
4523
+ }
4524
+ }
4525
+ const backup2 = createBackup(target, owner.pluginId, owner.pluginName);
4526
+ if (!backup2) {
4394
4527
  logger.error(t("backup_failed"));
4395
4528
  return {
4396
4529
  success: false,
@@ -4401,8 +4534,46 @@ async function installPlugin(opts) {
4401
4534
  warnings: []
4402
4535
  };
4403
4536
  }
4404
- logger.success(`${t("backup_created")}: ${backupInfo.filePath}`);
4537
+ backups.push(backup2);
4538
+ logger.success(`${t("backup_created")}: ${backup2.filePath}`);
4539
+ continue;
4540
+ }
4541
+ if (opts.yes) {
4542
+ preservedTargets.push(target);
4543
+ logger.warn(t("unowned_preserved_yes").replace("{file}", target));
4544
+ continue;
4545
+ }
4546
+ const choice = await showUnownedFileWarning(target, plugin.displayName);
4547
+ if (choice === "cancel") {
4548
+ logger.info(t("backup_cancel"));
4549
+ return {
4550
+ success: false,
4551
+ pluginId: opts.pluginId,
4552
+ skillsCount: 0,
4553
+ agentsCount: 0,
4554
+ errors: [t("backup_cancel")],
4555
+ warnings: []
4556
+ };
4405
4557
  }
4558
+ if (choice === "keep") {
4559
+ preservedTargets.push(target);
4560
+ logger.info(t("unowned_preserved").replace("{file}", target));
4561
+ continue;
4562
+ }
4563
+ const backup = createBackup(target, UNOWNED_PLUGIN_ID, t("unowned_backup_label"));
4564
+ if (!backup) {
4565
+ logger.error(t("backup_failed"));
4566
+ return {
4567
+ success: false,
4568
+ pluginId: opts.pluginId,
4569
+ skillsCount: 0,
4570
+ agentsCount: 0,
4571
+ errors: [t("backup_failed")],
4572
+ warnings: []
4573
+ };
4574
+ }
4575
+ backups.push(backup);
4576
+ logger.success(`${t("backup_created")}: ${backup.filePath}`);
4406
4577
  }
4407
4578
  if (plugin.installSkills && plugin.installSkills.length > 0) {
4408
4579
  const result = await installViaManifest(
@@ -4410,7 +4581,8 @@ async function installPlugin(opts) {
4410
4581
  opts.repoPath,
4411
4582
  opts.tool,
4412
4583
  opts.level,
4413
- opts.installPath
4584
+ opts.installPath,
4585
+ preservedTargets
4414
4586
  );
4415
4587
  if (result.success) {
4416
4588
  try {
@@ -4422,16 +4594,21 @@ async function installPlugin(opts) {
4422
4594
  cwd,
4423
4595
  configRoot,
4424
4596
  result.manifest,
4425
- plugin.externalRepos?.map((r) => basename2(r.dir)),
4426
- plugin.configRootConfigLink
4597
+ plugin.externalRepos?.map((r) => basename3(r.dir)),
4598
+ plugin.configRootConfigLink,
4599
+ preservedTargets
4427
4600
  );
4428
- if (backupInfo) {
4429
- record.backup = {
4430
- filePath: backupInfo.filePath,
4431
- fromPluginId: backupInfo.pluginId,
4432
- fromPluginName: backupInfo.pluginName,
4433
- backupTime: backupInfo.backupTime
4434
- };
4601
+ if (backups.length > 0) {
4602
+ record.backups = backups.map((b) => ({
4603
+ filePath: b.filePath,
4604
+ originalPath: b.originalPath,
4605
+ fromPluginId: b.pluginId,
4606
+ fromPluginName: b.pluginName,
4607
+ backupTime: b.backupTime
4608
+ }));
4609
+ }
4610
+ if (preservedTargets.length > 0) {
4611
+ record.preservedTargets = [...preservedTargets];
4435
4612
  }
4436
4613
  writeRecord(record);
4437
4614
  } catch {
@@ -4519,16 +4696,21 @@ async function installPlugin(opts) {
4519
4696
  cwd,
4520
4697
  configRoot,
4521
4698
  manifest,
4522
- plugin.externalRepos?.map((r) => basename2(r.dir)),
4523
- plugin.configRootConfigLink
4699
+ plugin.externalRepos?.map((r) => basename3(r.dir)),
4700
+ plugin.configRootConfigLink,
4701
+ preservedTargets
4524
4702
  );
4525
- if (backupInfo) {
4526
- record.backup = {
4527
- filePath: backupInfo.filePath,
4528
- fromPluginId: backupInfo.pluginId,
4529
- fromPluginName: backupInfo.pluginName,
4530
- backupTime: backupInfo.backupTime
4531
- };
4703
+ if (backups.length > 0) {
4704
+ record.backups = backups.map((b) => ({
4705
+ filePath: b.filePath,
4706
+ originalPath: b.originalPath,
4707
+ fromPluginId: b.pluginId,
4708
+ fromPluginName: b.pluginName,
4709
+ backupTime: b.backupTime
4710
+ }));
4711
+ }
4712
+ if (preservedTargets.length > 0) {
4713
+ record.preservedTargets = [...preservedTargets];
4532
4714
  }
4533
4715
  writeRecord(record);
4534
4716
  } catch {
@@ -4650,7 +4832,7 @@ function installerSupportsBundledFlag() {
4650
4832
  }
4651
4833
  async function delegateInstall(options) {
4652
4834
  const { pluginId, tool, level } = options;
4653
- const target = resolve3(options.target || process.cwd());
4835
+ const target = resolve4(options.target || process.cwd());
4654
4836
  const displayName = options.displayName || pluginId;
4655
4837
  const result = {
4656
4838
  success: false,
@@ -4776,8 +4958,8 @@ init_fs_helpers();
4776
4958
  init_constants();
4777
4959
  init_logger();
4778
4960
  init_i18n();
4779
- import { existsSync as existsSync18, mkdirSync as mkdirSync7, symlinkSync as symlinkSync2, readdirSync as readdirSync9, rmSync as rmSync5, realpathSync as realpathSync2, readlinkSync as readlinkSync2, cpSync as cpSync2 } from "fs";
4780
- import { join as join17, dirname as dirname7, basename as basename3, isAbsolute as isAbsolute2 } from "path";
4961
+ import { existsSync as existsSync18, mkdirSync as mkdirSync7, symlinkSync as symlinkSync2, readdirSync as readdirSync9, rmSync as rmSync5, realpathSync as realpathSync3, readlinkSync as readlinkSync2, cpSync as cpSync2 } from "fs";
4962
+ import { join as join17, dirname as dirname8, basename as basename4, isAbsolute as isAbsolute2 } from "path";
4781
4963
  import { select as select3, checkbox, Separator as Separator2 } from "@inquirer/prompts";
4782
4964
  import chalk5 from "chalk";
4783
4965
  import Table from "cli-table3";
@@ -4805,7 +4987,7 @@ async function installSkills(skillIds, tool, level, repoPath) {
4805
4987
  continue;
4806
4988
  }
4807
4989
  try {
4808
- const resolvedSource = realpathSync2(sourcePath);
4990
+ const resolvedSource = realpathSync3(sourcePath);
4809
4991
  if (isSymlink(targetPath)) {
4810
4992
  try {
4811
4993
  const currentTarget = readlinkSync2(targetPath);
@@ -4884,11 +5066,11 @@ async function uninstallSkills(skillIds, tool, level) {
4884
5066
  rmSync5(skillsDir, { recursive: true });
4885
5067
  logger.step(` ${t("uninstall_clean_empty_dir")}: skills/`);
4886
5068
  if (tool === "codex") {
4887
- const agentsRoot = dirname7(skillsDir);
5069
+ const agentsRoot = dirname8(skillsDir);
4888
5070
  try {
4889
5071
  if (existsSync18(agentsRoot) && readdirSync9(agentsRoot).length === 0) {
4890
5072
  rmSync5(agentsRoot, { recursive: true });
4891
- logger.step(` ${t("uninstall_clean_empty_dir")}: ${basename3(agentsRoot)}/`);
5073
+ logger.step(` ${t("uninstall_clean_empty_dir")}: ${basename4(agentsRoot)}/`);
4892
5074
  }
4893
5075
  } catch {
4894
5076
  }
@@ -5298,7 +5480,7 @@ function resolveSkillSourcePath(skill, repoPath) {
5298
5480
  if (skill.filePath) {
5299
5481
  const absPath = isAbsolute2(skill.filePath) ? skill.filePath : join17(repoPath, skill.filePath);
5300
5482
  if (existsSync18(absPath)) {
5301
- return dirname7(absPath);
5483
+ return dirname8(absPath);
5302
5484
  }
5303
5485
  }
5304
5486
  return join17(repoPath, skill.source, skill.id);
@@ -6354,7 +6536,7 @@ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes
6354
6536
  // src/commands/uninstall.ts
6355
6537
  init_registry();
6356
6538
  import { existsSync as existsSync21, unlinkSync as unlinkSync8, rmdirSync as rmdirSync2, readdirSync as readdirSync11, readFileSync as readFileSync15 } from "fs";
6357
- import { resolve as resolve4, basename as basename4, sep as sep3, join as join20 } from "path";
6539
+ import { resolve as resolve5, basename as basename5, sep as sep3, join as join20 } from "path";
6358
6540
  import chalk11 from "chalk";
6359
6541
  import { select as select5, checkbox as checkbox2, Separator as Separator4, confirm as confirm3 } from "@inquirer/prompts";
6360
6542
  init_config();
@@ -6740,7 +6922,7 @@ async function uninstallPluginById(pluginId, batchMode) {
6740
6922
  installPath,
6741
6923
  configRoot2,
6742
6924
  manifest,
6743
- plugin.externalRepos?.map((r) => basename4(r.dir)),
6925
+ plugin.externalRepos?.map((r) => basename5(r.dir)),
6744
6926
  plugin.configRootConfigLink
6745
6927
  );
6746
6928
  break;
@@ -6755,14 +6937,15 @@ async function uninstallPluginById(pluginId, batchMode) {
6755
6937
  }
6756
6938
  logger.info(`${t("uninstall_in_progress")} ${plugin.displayName}...`);
6757
6939
  const configRoot = record.configRoot;
6758
- const allowedBases = [resolve4(configRoot), resolve4(record.installPath)];
6940
+ const allowedBases = [resolve5(configRoot), resolve5(record.installPath)];
6759
6941
  if (record.tool === "codex") {
6760
- allowedBases.push(resolve4(getSkillsRoot(record.tool, record.level, record.installPath)));
6942
+ allowedBases.push(resolve5(getSkillsRoot(record.tool, record.level, record.installPath)));
6761
6943
  }
6762
6944
  const isSafePath = (p) => {
6763
- const resolved = resolve4(p);
6945
+ const resolved = resolve5(p);
6764
6946
  return allowedBases.some((base) => resolved === base || resolved.startsWith(base + sep3));
6765
6947
  };
6948
+ const preservedPaths = new Set((record.preservedTargets || []).map((p) => resolve5(p)));
6766
6949
  let removedFiles = 0;
6767
6950
  let removedDirs = 0;
6768
6951
  const configFileName = record.tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
@@ -6772,13 +6955,15 @@ async function uninstallPluginById(pluginId, batchMode) {
6772
6955
  if (record.level === "project" && configRootConfigPath !== installPathConfigPath && !record.files.includes(configRootConfigPath) && (existsSync21(configRootConfigPath) || isSymlink(configRootConfigPath))) {
6773
6956
  extraFiles.push(configRootConfigPath);
6774
6957
  }
6775
- const allFiles = [...record.files, ...extraFiles];
6958
+ const allFiles = [...record.files, ...extraFiles].filter(
6959
+ (filePath) => !preservedPaths.has(resolve5(filePath))
6960
+ );
6776
6961
  for (const filePath of allFiles) {
6777
6962
  if (!isSafePath(filePath)) continue;
6778
6963
  try {
6779
6964
  if (existsSync21(filePath) || isSymlink(filePath)) {
6780
6965
  unlinkSync8(filePath);
6781
- const name = basename4(filePath);
6966
+ const name = basename5(filePath);
6782
6967
  logger.step(` ${t("uninstall_remove_label")}: ${name}`);
6783
6968
  removedFiles++;
6784
6969
  }
@@ -6788,14 +6973,14 @@ async function uninstallPluginById(pluginId, batchMode) {
6788
6973
  const sortedDirs = [...record.directories].filter(isSafePath).sort((a, b) => b.length - a.length);
6789
6974
  for (const dirPath of sortedDirs) {
6790
6975
  try {
6791
- if (resolve4(dirPath) === resolve4(configRoot)) {
6976
+ if (resolve5(dirPath) === resolve5(configRoot)) {
6792
6977
  continue;
6793
6978
  }
6794
6979
  if (existsSync21(dirPath)) {
6795
6980
  const entries = readdirSync11(dirPath);
6796
6981
  if (entries.length === 0) {
6797
6982
  rmdirSync2(dirPath);
6798
- const name = basename4(dirPath);
6983
+ const name = basename5(dirPath);
6799
6984
  logger.step(` ${t("uninstall_clean_empty_dir")}: ${name}/`);
6800
6985
  removedDirs++;
6801
6986
  }
@@ -6805,10 +6990,10 @@ async function uninstallPluginById(pluginId, batchMode) {
6805
6990
  }
6806
6991
  if (record.tool === "codex") {
6807
6992
  try {
6808
- const agentsRoot = resolve4(getSkillsRoot(record.tool, record.level, record.installPath), "..");
6993
+ const agentsRoot = resolve5(getSkillsRoot(record.tool, record.level, record.installPath), "..");
6809
6994
  if (existsSync21(agentsRoot) && readdirSync11(agentsRoot).length === 0) {
6810
6995
  rmdirSync2(agentsRoot);
6811
- logger.step(` ${t("uninstall_clean_empty_dir")}: ${basename4(agentsRoot)}/`);
6996
+ logger.step(` ${t("uninstall_clean_empty_dir")}: ${basename5(agentsRoot)}/`);
6812
6997
  removedDirs++;
6813
6998
  }
6814
6999
  } catch {
@@ -6820,16 +7005,27 @@ async function uninstallPluginById(pluginId, batchMode) {
6820
7005
  installPath: record.installPath
6821
7006
  });
6822
7007
  removeInstalledPlugin(plugin.id);
6823
- const backups = findBackups(configRoot);
7008
+ const backupDirs = resolve5(record.installPath) === resolve5(configRoot) ? [configRoot] : [configRoot, record.installPath];
7009
+ const recordBackups = [
7010
+ ...record.backups || [],
7011
+ ...record.backup ? [{ ...record.backup, originalPath: join20(configRoot, configFileName) }] : []
7012
+ ];
7013
+ const backups = findBackups(backupDirs).map((b) => {
7014
+ const recorded = recordBackups.find((rb) => resolve5(rb.filePath) === resolve5(b.filePath));
7015
+ return recorded ? { ...b, originalPath: recorded.originalPath } : b;
7016
+ });
6824
7017
  const otherBackups = backups.filter((b) => b.pluginId !== pluginId);
6825
7018
  if (!batchMode && otherBackups.length > 0) {
6826
7019
  const choice = await showRestorePrompt(otherBackups);
6827
7020
  if (choice !== "none") {
6828
- const restored = restoreBackup(choice, configRoot, record.tool);
6829
- if (restored) {
6830
- const backupInfo = otherBackups.find((b) => b.filePath === choice);
6831
- logger.success(`${t("backup_restore_success")}: ${backupInfo?.pluginName || ""}`);
6832
- deleteBackup(choice);
7021
+ const backupInfo = otherBackups.find((b) => b.filePath === choice);
7022
+ const restoreTarget = backupInfo?.originalPath || join20(configRoot, configFileName);
7023
+ if (isSafePath(restoreTarget)) {
7024
+ const restored = restoreBackup(choice, restoreTarget);
7025
+ if (restored) {
7026
+ logger.success(`${t("backup_restore_success")}: ${backupInfo?.pluginName || ""} -> ${restoreTarget}`);
7027
+ deleteBackup(choice);
7028
+ }
6833
7029
  }
6834
7030
  }
6835
7031
  } else if (batchMode && otherBackups.length > 0) {
@@ -7101,7 +7297,7 @@ init_config();
7101
7297
  init_i18n();
7102
7298
  import { readFileSync as readFileSync16, existsSync as existsSync23 } from "fs";
7103
7299
  import { fileURLToPath as fileURLToPath5 } from "url";
7104
- import { dirname as dirname8, join as join22 } from "path";
7300
+ import { dirname as dirname9, join as join22 } from "path";
7105
7301
  function readVersion() {
7106
7302
  try {
7107
7303
  if (typeof INSTALL_HELPER_VERSION !== "undefined" && INSTALL_HELPER_VERSION) {
@@ -7111,7 +7307,7 @@ function readVersion() {
7111
7307
  }
7112
7308
  try {
7113
7309
  const __filename5 = fileURLToPath5(import.meta.url);
7114
- const __dirname5 = dirname8(__filename5);
7310
+ const __dirname5 = dirname9(__filename5);
7115
7311
  const candidates = [
7116
7312
  join22(__dirname5, "..", "package.json"),
7117
7313
  join22(__dirname5, "package.json"),