@baton-dx/cli 0.6.0 → 0.7.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.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-D9yccWot.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-BVo3M5-4.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-BI6xWv0H.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-B8_hp8ig.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-Y2IeW_fK.mjs").then((m) => m.createCommand),
3047
- list: () => import("./list-D3woEF2B.mjs").then((m) => m.profileListCommand),
3048
- remove: () => import("./remove-CsxkkNiu.mjs").then((m) => m.profileRemoveCommand)
3090
+ create: () => import("./create-y-ut18-j.mjs").then((m) => m.createCommand),
3091
+ list: () => import("./list-BHPDZfmU.mjs").then((m) => m.profileListCommand),
3092
+ remove: () => import("./remove-Cx8jC2vY.mjs").then((m) => m.profileRemoveCommand)
3049
3093
  }
3050
3094
  });
3051
3095
 
@@ -3210,7 +3254,10 @@ const connectCommand = defineCommand({
3210
3254
  });
3211
3255
  const displayName = args.name || url;
3212
3256
  R.success(`Connected source: ${displayName}`);
3213
- const shouldSync = await Re({ message: "Would you like to sync profiles from this source now?" });
3257
+ const shouldSync = await Re({
3258
+ message: "Would you like to sync profiles from this source now?",
3259
+ initialValue: false
3260
+ });
3214
3261
  if (Ct(shouldSync) || !shouldSync) {
3215
3262
  Le("Source connected. Run 'baton init' to set up profiles.");
3216
3263
  return;
@@ -3638,11 +3685,7 @@ const syncCommand = defineCommand({
3638
3685
  process.exit(1);
3639
3686
  }
3640
3687
  await promptFirstRunPreferences(projectRoot, !!args.yes);
3641
- const previousPaths = /* @__PURE__ */ new Set();
3642
- try {
3643
- const previousLock = await readLock(resolve(projectRoot, "baton.lock"));
3644
- for (const pkg of Object.values(previousLock.packages)) for (const filePath of Object.keys(pkg.integrity)) previousPaths.add(filePath);
3645
- } catch {}
3688
+ const previousPaths = await loadPreviousPlacedPaths(projectRoot);
3646
3689
  const spinner = bt();
3647
3690
  spinner.start("Resolving profile chain...");
3648
3691
  const allProfiles = [];
@@ -3884,6 +3927,7 @@ const syncCommand = defineCommand({
3884
3927
  projectRoot
3885
3928
  };
3886
3929
  const placedFiles = /* @__PURE__ */ new Map();
3930
+ const actualPlacedPaths = /* @__PURE__ */ new Set();
3887
3931
  const profileLocalPaths = /* @__PURE__ */ new Map();
3888
3932
  for (const profileSource of projectManifest.profiles || []) {
3889
3933
  const parsed = parseSource(profileSource.source);
@@ -3967,18 +4011,18 @@ const syncCommand = defineCommand({
3967
4011
  const absoluteTargetDir = targetSkillPath.startsWith("/") ? targetSkillPath : resolve(projectRoot, targetSkillPath);
3968
4012
  const placed = await copyDirectoryRecursive(skillSourceDir, absoluteTargetDir);
3969
4013
  stats.created += placed;
4014
+ actualPlacedPaths.add(targetSkillPath);
4015
+ const canonicalKey = `skills/${skillItem.name}`;
3970
4016
  const profileFiles = getOrCreatePlacedFiles(placedFiles, skillItem.profileName);
3971
- try {
3972
- profileFiles[targetSkillPath] = {
4017
+ if (!profileFiles[canonicalKey]) try {
4018
+ profileFiles[canonicalKey] = {
3973
4019
  content: await readFile(resolve(skillSourceDir, "index.md"), "utf-8"),
3974
- tool: adapter.key,
3975
- category: "ai"
4020
+ type: "skills"
3976
4021
  };
3977
4022
  } catch {
3978
- profileFiles[targetSkillPath] = {
4023
+ profileFiles[canonicalKey] = {
3979
4024
  content: skillItem.name,
3980
- tool: adapter.key,
3981
- category: "ai"
4025
+ type: "skills"
3982
4026
  };
3983
4027
  }
3984
4028
  if (verbose) {
@@ -4087,12 +4131,13 @@ const syncCommand = defineCommand({
4087
4131
  const result = await placeFile(combinedContent, entry.adapter, entry.type, "project", entry.name, placementConfig);
4088
4132
  if (result.action !== "skipped") stats.created++;
4089
4133
  const relPath = isAbsolute(result.path) ? relative(projectRoot, result.path) : result.path;
4134
+ actualPlacedPaths.add(relPath);
4135
+ const canonicalKey = `${entry.type}/${entry.name}`;
4090
4136
  for (const profileName of entry.profiles) {
4091
4137
  const pf = getOrCreatePlacedFiles(placedFiles, profileName);
4092
- pf[relPath] = {
4138
+ if (!pf[canonicalKey]) pf[canonicalKey] = {
4093
4139
  content: combinedContent,
4094
- tool: entry.adapter.key,
4095
- category: "ai"
4140
+ type: entry.type
4096
4141
  };
4097
4142
  }
4098
4143
  if (verbose) {
@@ -4120,11 +4165,12 @@ const syncCommand = defineCommand({
4120
4165
  const result = await placeFile(content, adapter, "commands", "project", commandName, placementConfig);
4121
4166
  if (result.action !== "skipped") stats.created++;
4122
4167
  const cmdRelPath = isAbsolute(result.path) ? relative(projectRoot, result.path) : result.path;
4168
+ actualPlacedPaths.add(cmdRelPath);
4169
+ const canonicalKey = `commands/${commandName}`;
4123
4170
  const pf = getOrCreatePlacedFiles(placedFiles, profile.name);
4124
- pf[cmdRelPath] = {
4171
+ if (!pf[canonicalKey]) pf[canonicalKey] = {
4125
4172
  content,
4126
- tool: adapter.key,
4127
- category: "ai"
4173
+ type: "commands"
4128
4174
  };
4129
4175
  if (verbose) {
4130
4176
  const label = result.action === "skipped" ? "unchanged, skipped" : result.action;
@@ -4153,10 +4199,12 @@ const syncCommand = defineCommand({
4153
4199
  stats.created++;
4154
4200
  if (verbose) R.info(` -> ${fileEntry.target} (created)`);
4155
4201
  } else if (verbose) R.info(` -> ${fileEntry.target} (unchanged, skipped)`);
4202
+ actualPlacedPaths.add(fileEntry.target);
4203
+ const canonicalKey = `files/${fileEntry.target}`;
4156
4204
  const fpf = getOrCreatePlacedFiles(placedFiles, fileEntry.profileName);
4157
- fpf[fileEntry.target] = {
4205
+ if (!fpf[canonicalKey]) fpf[canonicalKey] = {
4158
4206
  content,
4159
- category: "files"
4207
+ type: "files"
4160
4208
  };
4161
4209
  } catch (error) {
4162
4210
  spinner.message(`Error placing file ${fileEntry.source}: ${error}`);
@@ -4184,11 +4232,12 @@ const syncCommand = defineCommand({
4184
4232
  if (verbose) R.info(` -> ${ideEntry.targetDir}/${ideEntry.fileName} (created)`);
4185
4233
  } else if (verbose) R.info(` -> ${ideEntry.targetDir}/${ideEntry.fileName} (unchanged, skipped)`);
4186
4234
  const ideRelPath = `${ideEntry.targetDir}/${ideEntry.fileName}`;
4235
+ actualPlacedPaths.add(ideRelPath);
4236
+ const canonicalKey = `ide/${ideEntry.ideKey}/${ideEntry.fileName}`;
4187
4237
  const ipf = getOrCreatePlacedFiles(placedFiles, ideEntry.profileName);
4188
- ipf[ideRelPath] = {
4238
+ if (!ipf[canonicalKey]) ipf[canonicalKey] = {
4189
4239
  content,
4190
- tool: ideEntry.ideKey,
4191
- category: "ide"
4240
+ type: "ide"
4192
4241
  };
4193
4242
  } catch (error) {
4194
4243
  spinner.message(`Error placing IDE config ${ideEntry.fileName}: ${error}`);
@@ -4208,9 +4257,15 @@ const syncCommand = defineCommand({
4208
4257
  projectRoot,
4209
4258
  spinner
4210
4259
  });
4260
+ if (!dryRun) await writeStateData({
4261
+ actualPlacedPaths,
4262
+ syncedAiTools,
4263
+ projectRoot,
4264
+ spinner
4265
+ });
4211
4266
  await cleanupOrphanedFiles({
4212
4267
  previousPaths,
4213
- placedFiles,
4268
+ currentPaths: actualPlacedPaths,
4214
4269
  projectRoot,
4215
4270
  dryRun,
4216
4271
  autoYes,