@baton-dx/cli 0.6.1 → 0.7.1

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.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { a as Ne, c as Ve, d as bt, f as je, g as runMain, h as defineCommand, i as Le, l as We, m as require_dist, o as R, p as Ct, r as Je, s as Re, t as findSourceRoot, u as Ze, y as __toESM } from "./context-detection-CGh_5f6N.mjs";
3
- import { $ as isKnownIdePlatform, A as isLockedProfile, B as generateLock, C as mergeMemoryWithWarnings, D as mergeSkills, E as mergeRulesWithWarnings, F as detectLegacyPaths, G as esm_default, H as writeLock, I as placeFile, J as removeGitignoreManagedSection, K as collectComprehensivePatterns, L as discoverProfilesInSourceRepo, M as mergeContentParts, N as resolveProfileSupport, O as mergeSkillsWithWarnings, P as resolveProfileChain, Q as idePlatformRegistry, R as findSourceManifest, S as mergeMemory, T as mergeRules, U as resolveVersion, V as readLock, W as cloneGitSource, X as getIdePlatformTargetDir, Y as updateGitignore, Z as getRegisteredIdePlatforms, _ as removeGlobalSource, a as resolvePreferences, at as loadProjectManifest, b as setGlobalIdePlatforms, c as computeIntersection, ct as SourceParseError, d as addGlobalSource, dt as getAllAIToolKeys, et as getAIToolAdaptersForKeys, f as getDefaultGlobalSource, g as loadGlobalConfig, h as getGlobalSources, i as formatInstallCommand, it as loadProfileManifest, j as sortProfilesByWeight, k as getProfileWeight, l as clearIdeCache, lt as getAIToolConfig, m as getGlobalIdePlatforms, n as isUpdateAvailable, nt as parseFrontmatter, o as readProjectPreferences, ot as KEBAB_CASE_REGEX, p as getGlobalAiTools, q as ensureBatonDirGitignored, r as detectInstallMethod, rt as parseSource, s as writeProjectPreferences, st as FileNotFoundError, t as checkLatestVersion, tt as getAllAIToolAdapters, u as detectInstalledIdes, ut as getAIToolPath, v as saveGlobalConfig, w as mergeAgentsWithWarnings, x as require_lib, y as setGlobalAiTools, z as removePlacedFiles } from "./src-B-Z49jsg.mjs";
4
- import { n as detectInstalledAITools, t as clearAIToolCache } from "./ai-tool-detection-DMnwwNBI.mjs";
2
+ import { i as __toESM, t as require_dist } from "./dist-BoZnMvNi.mjs";
3
+ import { a as Ne, c as Ve, d as bt, f as je, h as runMain, i as Le, l as We, m as defineCommand, o as R, p as Ct, r as Je, s as Re, t as findSourceRoot, u as Ze } from "./context-detection-DO0ZeRyQ.mjs";
4
+ import { $ as cloneGitSource, A as mergeRulesWithWarnings, At as loadProfileManifest, B as detectLegacyPaths, C as setGlobalAiTools, D as mergeMemoryWithWarnings, E as mergeMemory, F as isLockedProfile, H as discoverProfilesInSourceRepo, I as sortProfilesByWeight, J as removePlacedFiles, Jt as getAllAIToolKeys, K as readState, Kt as getAIToolConfig, L as mergeContentParts, Lt as KEBAB_CASE_REGEX, M as mergeSkillsWithWarnings, O as mergeAgentsWithWarnings, Ot as parseFrontmatter, P as getProfileWeight, Q as resolveVersion, R as resolveProfileSupport, S as saveGlobalConfig, T as require_lib, U as findSourceManifest, V as placeFile, Vt as FileNotFoundError, Wt as SourceParseError, X as readLock, Y as generateLock, Z as writeLock, a as resolvePreferences, at as updateGitignore, b as loadGlobalConfig, c as writeProjectPreferences, ct as idePlatformRegistry, d as clearIdeCache, dt as getAIToolAdaptersForKeys, f as detectInstalledIdes, ft as getAllAIToolAdapters, g as getGlobalAiTools, h as getDefaultGlobalSource, i as formatInstallCommand, it as removeGitignoreManagedSection, j as mergeSkills, jt as loadProjectManifest, k as mergeRules, kt as parseSource, lt as isKnownIdePlatform, n as isUpdateAvailable, nt as collectComprehensivePatterns, ot as getIdePlatformTargetDir, p as addGlobalSource, q as writeState, qt as getAIToolPath, r as detectInstallMethod, rt as ensureBatonDirGitignored, s as readProjectPreferences, st as getRegisteredIdePlatforms, t as checkLatestVersion, tt as esm_default, u as computeIntersection, v as getGlobalIdePlatforms, w as setGlobalIdePlatforms, x as removeGlobalSource, y as getGlobalSources, z as resolveProfileChain } from "./src-C0c228gb.mjs";
5
+ import { n as detectInstalledAITools, t as clearAIToolCache } from "./ai-tool-detection-CMsBNa9e.mjs";
5
6
  import { access, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
6
7
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
7
8
  import { fileURLToPath } from "node:url";
@@ -608,14 +609,46 @@ async function writeLockData(params) {
608
609
  spinner.stop("Lockfile updated");
609
610
  }
610
611
  /**
611
- * Detect and remove files that were in the previous lockfile but are no longer
612
- * part of the current sync. Cleans up empty parent directories.
612
+ * Write local placement state to `.baton/state.yaml`.
613
+ * Tracks tool-specific file paths placed on disk for orphan detection.
614
+ */
615
+ async function writeStateData(params) {
616
+ const { actualPlacedPaths, syncedAiTools, projectRoot, spinner } = params;
617
+ spinner.start("Writing local state...");
618
+ await writeState(projectRoot, {
619
+ synced_at: (/* @__PURE__ */ new Date()).toISOString(),
620
+ tools: syncedAiTools,
621
+ placed_files: [...actualPlacedPaths].sort()
622
+ });
623
+ spinner.stop("Local state updated");
624
+ }
625
+ /**
626
+ * Load previous tool-specific paths for orphan detection.
627
+ *
628
+ * Reads from `.baton/state.yaml` (preferred). Falls back to extracting paths
629
+ * from an old-format `baton.lock` (legacy tool-specific keys) for migration.
630
+ */
631
+ async function loadPreviousPlacedPaths(projectRoot) {
632
+ const state = await readState(projectRoot);
633
+ if (state) return new Set(state.placed_files);
634
+ try {
635
+ const { readLock } = await import("./src-C-rwSQpx.mjs");
636
+ const previousLock = await readLock(resolve(projectRoot, "baton.lock"));
637
+ const paths = /* @__PURE__ */ new Set();
638
+ for (const pkg of Object.values(previousLock.packages)) for (const filePath of Object.keys(pkg.integrity)) if (filePath.startsWith(".") || filePath.includes("/")) paths.add(filePath);
639
+ return paths;
640
+ } catch {
641
+ return /* @__PURE__ */ new Set();
642
+ }
643
+ }
644
+ /**
645
+ * Detect and remove files that were previously placed but are no longer
646
+ * part of the current sync. Compares tool-specific paths from state.yaml
647
+ * against currently placed paths. Cleans up empty parent directories.
613
648
  */
614
649
  async function cleanupOrphanedFiles(params) {
615
- const { previousPaths, placedFiles, projectRoot, dryRun, autoYes, spinner } = params;
650
+ const { previousPaths, currentPaths, projectRoot, dryRun, autoYes, spinner } = params;
616
651
  if (previousPaths.size === 0) return;
617
- const currentPaths = /* @__PURE__ */ new Set();
618
- for (const files of placedFiles.values()) for (const filePath of Object.keys(files)) currentPaths.add(filePath);
619
652
  const orphanedPaths = [...previousPaths].filter((prev) => !currentPaths.has(prev));
620
653
  if (orphanedPaths.length === 0) return;
621
654
  if (dryRun) {
@@ -729,8 +762,7 @@ const applyCommand = defineCommand({
729
762
  if (verbose) R.warn("No lockfile found. Falling back to manifest versions.");
730
763
  }
731
764
  const maxCacheAgeMs = fresh ? 0 : void 0;
732
- const previousPaths = /* @__PURE__ */ new Set();
733
- if (lockfile) for (const pkg of Object.values(lockfile.packages)) for (const filePath of Object.keys(pkg.integrity)) previousPaths.add(filePath);
765
+ const previousPaths = await loadPreviousPlacedPaths(projectRoot);
734
766
  const spinner = bt();
735
767
  spinner.start("Resolving profile chain...");
736
768
  const allProfiles = [];
@@ -974,6 +1006,7 @@ const applyCommand = defineCommand({
974
1006
  projectRoot
975
1007
  };
976
1008
  const placedFiles = /* @__PURE__ */ new Map();
1009
+ const actualPlacedPaths = /* @__PURE__ */ new Set();
977
1010
  const profileLocalPaths = /* @__PURE__ */ new Map();
978
1011
  for (const profileSource of projectManifest.profiles || []) {
979
1012
  const parsed = parseSource(profileSource.source);
@@ -1065,18 +1098,18 @@ const applyCommand = defineCommand({
1065
1098
  const absoluteTargetDir = targetSkillPath.startsWith("/") ? targetSkillPath : resolve(projectRoot, targetSkillPath);
1066
1099
  const placed = await copyDirectoryRecursive(skillSourceDir, absoluteTargetDir);
1067
1100
  stats.created += placed;
1101
+ actualPlacedPaths.add(targetSkillPath);
1102
+ const canonicalKey = `skills/${skillItem.name}`;
1068
1103
  const profileFiles = getOrCreatePlacedFiles(placedFiles, skillItem.profileName);
1069
- try {
1070
- profileFiles[targetSkillPath] = {
1104
+ if (!profileFiles[canonicalKey]) try {
1105
+ profileFiles[canonicalKey] = {
1071
1106
  content: await readFile(resolve(skillSourceDir, "index.md"), "utf-8"),
1072
- tool: adapter.key,
1073
- category: "ai"
1107
+ type: "skills"
1074
1108
  };
1075
1109
  } catch {
1076
- profileFiles[targetSkillPath] = {
1110
+ profileFiles[canonicalKey] = {
1077
1111
  content: skillItem.name,
1078
- tool: adapter.key,
1079
- category: "ai"
1112
+ type: "skills"
1080
1113
  };
1081
1114
  }
1082
1115
  if (verbose) {
@@ -1185,12 +1218,13 @@ const applyCommand = defineCommand({
1185
1218
  const result = await placeFile(combinedContent, entry.adapter, entry.type, "project", entry.name, placementConfig);
1186
1219
  if (result.action !== "skipped") stats.created++;
1187
1220
  const relPath = isAbsolute(result.path) ? relative(projectRoot, result.path) : result.path;
1221
+ actualPlacedPaths.add(relPath);
1222
+ const canonicalKey = `${entry.type}/${entry.name}`;
1188
1223
  for (const profileName of entry.profiles) {
1189
1224
  const pf = getOrCreatePlacedFiles(placedFiles, profileName);
1190
- pf[relPath] = {
1225
+ if (!pf[canonicalKey]) pf[canonicalKey] = {
1191
1226
  content: combinedContent,
1192
- tool: entry.adapter.key,
1193
- category: "ai"
1227
+ type: entry.type
1194
1228
  };
1195
1229
  }
1196
1230
  if (verbose) {
@@ -1218,11 +1252,12 @@ const applyCommand = defineCommand({
1218
1252
  const result = await placeFile(content, adapter, "commands", "project", commandName, placementConfig);
1219
1253
  if (result.action !== "skipped") stats.created++;
1220
1254
  const cmdRelPath = isAbsolute(result.path) ? relative(projectRoot, result.path) : result.path;
1255
+ actualPlacedPaths.add(cmdRelPath);
1256
+ const canonicalKey = `commands/${commandName}`;
1221
1257
  const pf = getOrCreatePlacedFiles(placedFiles, profile.name);
1222
- pf[cmdRelPath] = {
1258
+ if (!pf[canonicalKey]) pf[canonicalKey] = {
1223
1259
  content,
1224
- tool: adapter.key,
1225
- category: "ai"
1260
+ type: "commands"
1226
1261
  };
1227
1262
  if (verbose) {
1228
1263
  const label = result.action === "skipped" ? "unchanged, skipped" : result.action;
@@ -1251,10 +1286,12 @@ const applyCommand = defineCommand({
1251
1286
  stats.created++;
1252
1287
  if (verbose) R.info(` -> ${fileEntry.target} (created)`);
1253
1288
  } else if (verbose) R.info(` -> ${fileEntry.target} (unchanged, skipped)`);
1289
+ actualPlacedPaths.add(fileEntry.target);
1290
+ const canonicalKey = `files/${fileEntry.target}`;
1254
1291
  const fpf = getOrCreatePlacedFiles(placedFiles, fileEntry.profileName);
1255
- fpf[fileEntry.target] = {
1292
+ if (!fpf[canonicalKey]) fpf[canonicalKey] = {
1256
1293
  content,
1257
- category: "files"
1294
+ type: "files"
1258
1295
  };
1259
1296
  } catch (error) {
1260
1297
  spinner.message(`Error placing file ${fileEntry.source}: ${error}`);
@@ -1282,11 +1319,12 @@ const applyCommand = defineCommand({
1282
1319
  if (verbose) R.info(` -> ${ideEntry.targetDir}/${ideEntry.fileName} (created)`);
1283
1320
  } else if (verbose) R.info(` -> ${ideEntry.targetDir}/${ideEntry.fileName} (unchanged, skipped)`);
1284
1321
  const ideRelPath = `${ideEntry.targetDir}/${ideEntry.fileName}`;
1322
+ actualPlacedPaths.add(ideRelPath);
1323
+ const canonicalKey = `ide/${ideEntry.ideKey}/${ideEntry.fileName}`;
1285
1324
  const ipf = getOrCreatePlacedFiles(placedFiles, ideEntry.profileName);
1286
- ipf[ideRelPath] = {
1325
+ if (!ipf[canonicalKey]) ipf[canonicalKey] = {
1287
1326
  content,
1288
- tool: ideEntry.ideKey,
1289
- category: "ide"
1327
+ type: "ide"
1290
1328
  };
1291
1329
  } catch (error) {
1292
1330
  spinner.message(`Error placing IDE config ${ideEntry.fileName}: ${error}`);
@@ -1306,9 +1344,15 @@ const applyCommand = defineCommand({
1306
1344
  projectRoot,
1307
1345
  spinner
1308
1346
  });
1347
+ if (!dryRun) await writeStateData({
1348
+ actualPlacedPaths,
1349
+ syncedAiTools,
1350
+ projectRoot,
1351
+ spinner
1352
+ });
1309
1353
  await cleanupOrphanedFiles({
1310
1354
  previousPaths,
1311
- placedFiles,
1355
+ currentPaths: actualPlacedPaths,
1312
1356
  projectRoot,
1313
1357
  dryRun,
1314
1358
  autoYes,
@@ -3043,9 +3087,9 @@ const profileCommand = defineCommand({
3043
3087
  description: "Manage profiles (create, list, remove)"
3044
3088
  },
3045
3089
  subCommands: {
3046
- create: () => import("./create-Cq7dvT9C.mjs").then((m) => m.createCommand),
3047
- list: () => import("./list-xZTyCWwl.mjs").then((m) => m.profileListCommand),
3048
- remove: () => import("./remove-DSBMNbtP.mjs").then((m) => m.profileRemoveCommand)
3090
+ create: () => import("./create-BiE2_2Fo.mjs").then((m) => m.createCommand),
3091
+ list: () => import("./list-7GvmRp3q.mjs").then((m) => m.profileListCommand),
3092
+ remove: () => import("./remove-Cx8jC2vY.mjs").then((m) => m.profileRemoveCommand)
3049
3093
  }
3050
3094
  });
3051
3095
 
@@ -3115,9 +3159,9 @@ const selfUpdateCommand = defineCommand({
3115
3159
  R.warn("Could not detect installation method.");
3116
3160
  R.message([
3117
3161
  "Please update manually using one of:",
3118
- " npm update -g @baton-dx/cli",
3119
- " pnpm update -g @baton-dx/cli",
3120
- " bun update -g @baton-dx/cli",
3162
+ " npm install -g @baton-dx/cli@latest",
3163
+ " pnpm update -g @baton-dx/cli --latest",
3164
+ " bun update -g @baton-dx/cli --latest",
3121
3165
  " brew upgrade baton-dx"
3122
3166
  ].join("\n"));
3123
3167
  Le("Manual update required.");
@@ -3641,11 +3685,7 @@ const syncCommand = defineCommand({
3641
3685
  process.exit(1);
3642
3686
  }
3643
3687
  await promptFirstRunPreferences(projectRoot, !!args.yes);
3644
- const previousPaths = /* @__PURE__ */ new Set();
3645
- try {
3646
- const previousLock = await readLock(resolve(projectRoot, "baton.lock"));
3647
- for (const pkg of Object.values(previousLock.packages)) for (const filePath of Object.keys(pkg.integrity)) previousPaths.add(filePath);
3648
- } catch {}
3688
+ const previousPaths = await loadPreviousPlacedPaths(projectRoot);
3649
3689
  const spinner = bt();
3650
3690
  spinner.start("Resolving profile chain...");
3651
3691
  const allProfiles = [];
@@ -3887,6 +3927,7 @@ const syncCommand = defineCommand({
3887
3927
  projectRoot
3888
3928
  };
3889
3929
  const placedFiles = /* @__PURE__ */ new Map();
3930
+ const actualPlacedPaths = /* @__PURE__ */ new Set();
3890
3931
  const profileLocalPaths = /* @__PURE__ */ new Map();
3891
3932
  for (const profileSource of projectManifest.profiles || []) {
3892
3933
  const parsed = parseSource(profileSource.source);
@@ -3970,18 +4011,18 @@ const syncCommand = defineCommand({
3970
4011
  const absoluteTargetDir = targetSkillPath.startsWith("/") ? targetSkillPath : resolve(projectRoot, targetSkillPath);
3971
4012
  const placed = await copyDirectoryRecursive(skillSourceDir, absoluteTargetDir);
3972
4013
  stats.created += placed;
4014
+ actualPlacedPaths.add(targetSkillPath);
4015
+ const canonicalKey = `skills/${skillItem.name}`;
3973
4016
  const profileFiles = getOrCreatePlacedFiles(placedFiles, skillItem.profileName);
3974
- try {
3975
- profileFiles[targetSkillPath] = {
4017
+ if (!profileFiles[canonicalKey]) try {
4018
+ profileFiles[canonicalKey] = {
3976
4019
  content: await readFile(resolve(skillSourceDir, "index.md"), "utf-8"),
3977
- tool: adapter.key,
3978
- category: "ai"
4020
+ type: "skills"
3979
4021
  };
3980
4022
  } catch {
3981
- profileFiles[targetSkillPath] = {
4023
+ profileFiles[canonicalKey] = {
3982
4024
  content: skillItem.name,
3983
- tool: adapter.key,
3984
- category: "ai"
4025
+ type: "skills"
3985
4026
  };
3986
4027
  }
3987
4028
  if (verbose) {
@@ -4090,12 +4131,13 @@ const syncCommand = defineCommand({
4090
4131
  const result = await placeFile(combinedContent, entry.adapter, entry.type, "project", entry.name, placementConfig);
4091
4132
  if (result.action !== "skipped") stats.created++;
4092
4133
  const relPath = isAbsolute(result.path) ? relative(projectRoot, result.path) : result.path;
4134
+ actualPlacedPaths.add(relPath);
4135
+ const canonicalKey = `${entry.type}/${entry.name}`;
4093
4136
  for (const profileName of entry.profiles) {
4094
4137
  const pf = getOrCreatePlacedFiles(placedFiles, profileName);
4095
- pf[relPath] = {
4138
+ if (!pf[canonicalKey]) pf[canonicalKey] = {
4096
4139
  content: combinedContent,
4097
- tool: entry.adapter.key,
4098
- category: "ai"
4140
+ type: entry.type
4099
4141
  };
4100
4142
  }
4101
4143
  if (verbose) {
@@ -4123,11 +4165,12 @@ const syncCommand = defineCommand({
4123
4165
  const result = await placeFile(content, adapter, "commands", "project", commandName, placementConfig);
4124
4166
  if (result.action !== "skipped") stats.created++;
4125
4167
  const cmdRelPath = isAbsolute(result.path) ? relative(projectRoot, result.path) : result.path;
4168
+ actualPlacedPaths.add(cmdRelPath);
4169
+ const canonicalKey = `commands/${commandName}`;
4126
4170
  const pf = getOrCreatePlacedFiles(placedFiles, profile.name);
4127
- pf[cmdRelPath] = {
4171
+ if (!pf[canonicalKey]) pf[canonicalKey] = {
4128
4172
  content,
4129
- tool: adapter.key,
4130
- category: "ai"
4173
+ type: "commands"
4131
4174
  };
4132
4175
  if (verbose) {
4133
4176
  const label = result.action === "skipped" ? "unchanged, skipped" : result.action;
@@ -4156,10 +4199,12 @@ const syncCommand = defineCommand({
4156
4199
  stats.created++;
4157
4200
  if (verbose) R.info(` -> ${fileEntry.target} (created)`);
4158
4201
  } else if (verbose) R.info(` -> ${fileEntry.target} (unchanged, skipped)`);
4202
+ actualPlacedPaths.add(fileEntry.target);
4203
+ const canonicalKey = `files/${fileEntry.target}`;
4159
4204
  const fpf = getOrCreatePlacedFiles(placedFiles, fileEntry.profileName);
4160
- fpf[fileEntry.target] = {
4205
+ if (!fpf[canonicalKey]) fpf[canonicalKey] = {
4161
4206
  content,
4162
- category: "files"
4207
+ type: "files"
4163
4208
  };
4164
4209
  } catch (error) {
4165
4210
  spinner.message(`Error placing file ${fileEntry.source}: ${error}`);
@@ -4187,11 +4232,12 @@ const syncCommand = defineCommand({
4187
4232
  if (verbose) R.info(` -> ${ideEntry.targetDir}/${ideEntry.fileName} (created)`);
4188
4233
  } else if (verbose) R.info(` -> ${ideEntry.targetDir}/${ideEntry.fileName} (unchanged, skipped)`);
4189
4234
  const ideRelPath = `${ideEntry.targetDir}/${ideEntry.fileName}`;
4235
+ actualPlacedPaths.add(ideRelPath);
4236
+ const canonicalKey = `ide/${ideEntry.ideKey}/${ideEntry.fileName}`;
4190
4237
  const ipf = getOrCreatePlacedFiles(placedFiles, ideEntry.profileName);
4191
- ipf[ideRelPath] = {
4238
+ if (!ipf[canonicalKey]) ipf[canonicalKey] = {
4192
4239
  content,
4193
- tool: ideEntry.ideKey,
4194
- category: "ide"
4240
+ type: "ide"
4195
4241
  };
4196
4242
  } catch (error) {
4197
4243
  spinner.message(`Error placing IDE config ${ideEntry.fileName}: ${error}`);
@@ -4211,9 +4257,15 @@ const syncCommand = defineCommand({
4211
4257
  projectRoot,
4212
4258
  spinner
4213
4259
  });
4260
+ if (!dryRun) await writeStateData({
4261
+ actualPlacedPaths,
4262
+ syncedAiTools,
4263
+ projectRoot,
4264
+ spinner
4265
+ });
4214
4266
  await cleanupOrphanedFiles({
4215
4267
  previousPaths,
4216
- placedFiles,
4268
+ currentPaths: actualPlacedPaths,
4217
4269
  projectRoot,
4218
4270
  dryRun,
4219
4271
  autoYes,