@baton-dx/cli 0.2.0 → 0.3.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,14 +1,166 @@
1
1
  #!/usr/bin/env node
2
2
  import { r as __toESM } from "./chunk-BbwQpWto.mjs";
3
3
  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 } from "./context-detection-DqOTnD6_.mjs";
4
- import { A as readLock, B as getAdaptersForKeys, C as resolveProfileSupport, D as discoverProfilesInSourceRepo, E as placeFile, F as updateGitignore, G as loadProjectManifest, H as parseSource, I as getIdePlatformTargetDir, J as SourceParseError, K as KEBAB_CASE_REGEX, L as getRegisteredIdePlatforms, M as resolveVersion, N as cloneGitSource, O as findSourceManifest, P as collectProfileSupportPatterns, R as idePlatformRegistry, S as mergeContentParts, T as detectLegacyPaths, U as loadLockfile, V as parseFrontmatter, W as loadProfileManifest, X as getAgentPath, Y as getAgentConfig, Z as getAllAgentKeys, _ as mergeSkills, a as getDefaultGlobalSource, b as isLockedProfile, c as getGlobalSources, d as setGlobalIdePlatforms, f as require_lib, g as mergeRulesWithWarnings, h as mergeRules, i as addGlobalSource, j as writeLock, k as generateLock, l as removeGlobalSource, m as mergeMemoryWithWarnings, n as clearIdeCache, o as getGlobalAiTools, p as mergeMemory, q as FileNotFoundError, r as detectInstalledIdes, s as getGlobalIdePlatforms, t as computeIntersection, u as setGlobalAiTools, v as mergeSkillsWithWarnings, w as resolveProfileChain, x as sortProfilesByWeight, y as getProfileWeight, z as isKnownIdePlatform } from "./src-DBbk6iAs.mjs";
4
+ import { $ as SourceParseError, A as discoverProfilesInSourceRepo, B as getIdePlatformTargetDir, C as isLockedProfile, D as resolveProfileChain, E as resolveProfileSupport, F as writeLock, G as getAllAdapters, H as idePlatformRegistry, I as resolveVersion, J as loadLockfile, K as parseFrontmatter, L as cloneGitSource, M as removePlacedFiles, N as generateLock, O as detectLegacyPaths, P as readLock, Q as FileNotFoundError, R as collectProfileSupportPatterns, S as getProfileWeight, T as mergeContentParts, U as isKnownIdePlatform, V as getRegisteredIdePlatforms, W as getAdaptersForKeys, X as loadProjectManifest, Y as loadProfileManifest, Z as KEBAB_CASE_REGEX, _ as mergeMemoryWithWarnings, a as clearIdeCache, b as mergeSkills, c as getDefaultGlobalSource, d as getGlobalSources, et as getAgentConfig, f as removeGlobalSource, g as mergeMemory, h as require_lib, i as computeIntersection, j as findSourceManifest, k as placeFile, l as getGlobalAiTools, m as setGlobalIdePlatforms, n as readProjectPreferences, nt as getAllAgentKeys, o as detectInstalledIdes, p as setGlobalAiTools, q as parseSource, r as writeProjectPreferences, s as addGlobalSource, t as resolvePreferences, tt as getAgentPath, u as getGlobalIdePlatforms, v as mergeRules, w as sortProfilesByWeight, x as mergeSkillsWithWarnings, y as mergeRulesWithWarnings, z as updateGitignore } from "./src-C3-Vz-R7.mjs";
5
5
  import { n as detectInstalledAgents, t as clearAgentCache } from "./agent-detection-DTiVeO5W.mjs";
6
6
  import { d as esm_default } from "./esm-BagM-kVd.mjs";
7
- import { access, mkdir, readFile, readdir, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
8
- import { dirname, join, resolve } from "node:path";
7
+ import { access, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
8
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { homedir } from "node:os";
11
11
 
12
+ //#region src/commands/ai-tools/configure.ts
13
+ const aiToolsConfigureCommand = defineCommand({
14
+ meta: {
15
+ name: "configure",
16
+ description: "Manually configure which AI tools Baton manages"
17
+ },
18
+ args: {
19
+ yes: {
20
+ type: "boolean",
21
+ alias: "y",
22
+ description: "Keep current selection unchanged (no-op), or with --project write useGlobal: true"
23
+ },
24
+ project: {
25
+ type: "boolean",
26
+ description: "Configure AI tools for this project instead of globally"
27
+ }
28
+ },
29
+ async run({ args }) {
30
+ if (args.project) await runProjectMode$1(args.yes ?? false);
31
+ else await runGlobalMode$1(args.yes ?? false);
32
+ }
33
+ });
34
+ async function runGlobalMode$1(nonInteractive) {
35
+ We("Baton - Configure AI Tools");
36
+ const currentTools = await getGlobalAiTools();
37
+ if (nonInteractive) {
38
+ if (currentTools.length > 0) R.info(`Current AI tools: ${currentTools.join(", ")}`);
39
+ else R.info("No AI tools currently configured.");
40
+ Le("No changes made.");
41
+ return;
42
+ }
43
+ const options = getAllAdapters().map((adapter) => {
44
+ const isSaved = currentTools.includes(adapter.key);
45
+ return {
46
+ value: adapter.key,
47
+ label: isSaved ? `${adapter.name} (currently saved)` : adapter.name
48
+ };
49
+ });
50
+ const selected = await je({
51
+ message: "Select which AI tools to save:",
52
+ options,
53
+ initialValues: currentTools
54
+ });
55
+ if (Ct(selected)) {
56
+ Le("No changes made.");
57
+ return;
58
+ }
59
+ const selectedKeys = selected;
60
+ if (selectedKeys.length !== currentTools.length || selectedKeys.some((key) => !currentTools.includes(key))) {
61
+ await setGlobalAiTools(selectedKeys);
62
+ R.success(`Saved ${selectedKeys.length} tool(s) to global config.`);
63
+ } else R.info("No changes made.");
64
+ Le("Configuration complete.");
65
+ }
66
+ async function runProjectMode$1(nonInteractive) {
67
+ We("Baton - Configure AI Tools (Project)");
68
+ const projectRoot = process.cwd();
69
+ const manifestPath = resolve(projectRoot, "baton.yaml");
70
+ try {
71
+ await stat(manifestPath);
72
+ } catch {
73
+ Ne("No baton.yaml found in current directory. Run `baton init` first.");
74
+ process.exit(1);
75
+ }
76
+ if (nonInteractive) {
77
+ const existing = await readProjectPreferences(projectRoot);
78
+ await writeProjectPreferences(projectRoot, {
79
+ version: "1.0",
80
+ ai: {
81
+ useGlobal: true,
82
+ tools: existing?.ai.tools ?? []
83
+ },
84
+ ide: existing?.ide ?? {
85
+ useGlobal: true,
86
+ platforms: []
87
+ }
88
+ });
89
+ R.info("Set AI tools to use global config for this project.");
90
+ Le("Configuration complete.");
91
+ return;
92
+ }
93
+ const existing = await readProjectPreferences(projectRoot);
94
+ const globalTools = await getGlobalAiTools();
95
+ if (globalTools.length > 0) R.info(`Global AI tools: ${globalTools.join(", ")}`);
96
+ const mode = await Je({
97
+ message: "How should this project resolve AI tools?",
98
+ options: [{
99
+ value: "global",
100
+ label: "Use global config",
101
+ hint: "always follows your global AI tools setting"
102
+ }, {
103
+ value: "project",
104
+ label: "Customize for this project",
105
+ hint: "choose specific tools for this project"
106
+ }],
107
+ initialValue: existing?.ai.useGlobal === false ? "project" : "global"
108
+ });
109
+ if (Ct(mode)) {
110
+ Le("No changes made.");
111
+ return;
112
+ }
113
+ if (mode === "global") {
114
+ await writeProjectPreferences(projectRoot, {
115
+ version: "1.0",
116
+ ai: {
117
+ useGlobal: true,
118
+ tools: []
119
+ },
120
+ ide: existing?.ide ?? {
121
+ useGlobal: true,
122
+ platforms: []
123
+ }
124
+ });
125
+ R.success("Project configured to use global AI tools.");
126
+ Le("Configuration complete.");
127
+ return;
128
+ }
129
+ const allAdapters = getAllAdapters();
130
+ const currentProjectTools = existing?.ai.useGlobal === false ? existing.ai.tools : globalTools;
131
+ const options = allAdapters.map((adapter) => {
132
+ const isGlobal = globalTools.includes(adapter.key);
133
+ return {
134
+ value: adapter.key,
135
+ label: isGlobal ? `${adapter.name} (in global config)` : adapter.name
136
+ };
137
+ });
138
+ const selected = await je({
139
+ message: "Select AI tools for this project:",
140
+ options,
141
+ initialValues: currentProjectTools
142
+ });
143
+ if (Ct(selected)) {
144
+ Le("No changes made.");
145
+ return;
146
+ }
147
+ const selectedKeys = selected;
148
+ await writeProjectPreferences(projectRoot, {
149
+ version: "1.0",
150
+ ai: {
151
+ useGlobal: false,
152
+ tools: selectedKeys
153
+ },
154
+ ide: existing?.ide ?? {
155
+ useGlobal: true,
156
+ platforms: []
157
+ }
158
+ });
159
+ R.success(`Project configured with ${selectedKeys.length} AI tool(s).`);
160
+ Le("Configuration complete.");
161
+ }
162
+
163
+ //#endregion
12
164
  //#region src/commands/ai-tools/list.ts
13
165
  const aiToolsListCommand = defineCommand({
14
166
  meta: {
@@ -135,55 +287,41 @@ const aiToolsScanCommand = defineCommand({
135
287
  spinner.start("Scanning for AI tools...");
136
288
  clearAgentCache();
137
289
  const detectedAgents = await detectInstalledAgents();
138
- const allAgentKeys = getAllAgentKeys();
290
+ const allAdapters = getAllAdapters();
139
291
  const currentTools = await getGlobalAiTools();
140
292
  spinner.stop("Scan complete.");
141
- const installed = [];
142
- const notInstalled = [];
143
- for (const agentKey of allAgentKeys) {
144
- const entry = {
145
- key: agentKey,
146
- name: getAgentConfig(agentKey).name
147
- };
148
- if (detectedAgents.includes(agentKey)) installed.push(entry);
149
- else notInstalled.push(entry);
150
- }
151
- if (installed.length > 0) {
152
- R.success(`Found ${installed.length} AI tool${installed.length !== 1 ? "s" : ""}:`);
153
- for (const agent of installed) {
154
- const badge = currentTools.includes(agent.key) ? " (saved)" : " (new)";
155
- console.log(` \x1b[32m✓\x1b[0m ${agent.name}${badge}`);
156
- }
157
- } else {
158
- R.warn("No AI tools detected on your system.");
293
+ if (detectedAgents.length > 0) R.success(`Found ${detectedAgents.length} AI tool${detectedAgents.length !== 1 ? "s" : ""} on your system.`);
294
+ else R.warn("No AI tools detected on your system.");
295
+ if (args.yes) {
296
+ const detectedKeys = detectedAgents;
297
+ if (detectedKeys.length !== currentTools.length || detectedKeys.some((key) => !currentTools.includes(key))) {
298
+ await setGlobalAiTools(detectedKeys);
299
+ R.success(`Saved ${detectedKeys.length} detected tool(s) to global config.`);
300
+ } else R.info("Global config is already up to date.");
159
301
  Le("Scan finished.");
160
302
  return;
161
303
  }
162
- if (notInstalled.length > 0) {
163
- console.log("");
164
- R.info(`Not detected (${notInstalled.length}):`);
165
- for (const agent of notInstalled) console.log(` \x1b[90m✗ ${agent.name}\x1b[0m`);
166
- }
167
- const detectedKeys = installed.map((a) => a.key);
168
- if (detectedKeys.length !== currentTools.length || detectedKeys.some((key) => !currentTools.includes(key))) {
169
- console.log("");
170
- let shouldSave = args.yes;
171
- if (!shouldSave) {
172
- const confirm = await Re({ message: "Save detected tools to global config (~/.baton/config.yaml)?" });
173
- if (Ct(confirm)) {
174
- Le("Scan finished (not saved).");
175
- return;
176
- }
177
- shouldSave = confirm;
178
- }
179
- if (shouldSave) {
180
- await setGlobalAiTools(detectedKeys);
181
- R.success("Tools saved to global config.");
182
- }
183
- } else {
184
- console.log("");
185
- R.info("Global config is already up to date.");
304
+ const options = allAdapters.map((adapter) => {
305
+ const isDetected = detectedAgents.includes(adapter.key);
306
+ return {
307
+ value: adapter.key,
308
+ label: isDetected ? `${adapter.name} (detected)` : adapter.name
309
+ };
310
+ });
311
+ const selected = await je({
312
+ message: "Select which AI tools to save:",
313
+ options,
314
+ initialValues: detectedAgents
315
+ });
316
+ if (Ct(selected)) {
317
+ Le("Scan finished (not saved).");
318
+ return;
186
319
  }
320
+ const selectedKeys = selected;
321
+ if (selectedKeys.length !== currentTools.length || selectedKeys.some((key) => !currentTools.includes(key))) {
322
+ await setGlobalAiTools(selectedKeys);
323
+ R.success(`Saved ${selectedKeys.length} tool(s) to global config.`);
324
+ } else R.info("Global config is already up to date.");
187
325
  Le("Scan finished.");
188
326
  }
189
327
  });
@@ -196,6 +334,7 @@ const aiToolsCommand = defineCommand({
196
334
  description: "Manage AI tool detection and configuration"
197
335
  },
198
336
  subCommands: {
337
+ configure: aiToolsConfigureCommand,
199
338
  list: aiToolsListCommand,
200
339
  scan: aiToolsScanCommand
201
340
  }
@@ -319,7 +458,29 @@ async function showDashboard() {
319
458
  }
320
459
  console.log("");
321
460
  R.step("Developer Tools");
322
- if (aiTools.length === 0 && idePlatforms.length === 0) R.info(" No tools configured. Run: baton ai-tools scan && baton ides scan");
461
+ if (projectManifest) {
462
+ const prefs = await resolvePreferences(process.cwd());
463
+ const resolvedAiTools = prefs.ai.tools;
464
+ const resolvedIdePlatforms = prefs.ide.platforms;
465
+ if (resolvedAiTools.length === 0 && resolvedIdePlatforms.length === 0) R.info(" No tools configured. Run: baton ai-tools scan && baton ides scan");
466
+ else {
467
+ if (resolvedAiTools.length > 0) {
468
+ const toolNames = resolvedAiTools.map((key) => {
469
+ try {
470
+ return getAgentConfig(key).name;
471
+ } catch {
472
+ return key;
473
+ }
474
+ });
475
+ const aiSourceLabel = prefs.ai.source === "project" ? "project preferences" : "global config";
476
+ R.info(` AI Tools: ${toolNames.join(", ")} (from ${aiSourceLabel})`);
477
+ }
478
+ if (resolvedIdePlatforms.length > 0) {
479
+ const ideSourceLabel = prefs.ide.source === "project" ? "project preferences" : "global config";
480
+ R.info(` IDE Platforms: ${resolvedIdePlatforms.join(", ")} (from ${ideSourceLabel})`);
481
+ }
482
+ }
483
+ } else if (aiTools.length === 0 && idePlatforms.length === 0) R.info(" No tools configured. Run: baton ai-tools scan && baton ides scan");
323
484
  else {
324
485
  if (aiTools.length > 0) {
325
486
  const toolNames = aiTools.map((key) => {
@@ -329,9 +490,9 @@ async function showDashboard() {
329
490
  return key;
330
491
  }
331
492
  });
332
- R.info(` AI Tools: ${toolNames.join(", ")}`);
493
+ R.info(` AI Tools: ${toolNames.join(", ")} (from global config)`);
333
494
  }
334
- if (idePlatforms.length > 0) R.info(` IDE Platforms: ${idePlatforms.join(", ")}`);
495
+ if (idePlatforms.length > 0) R.info(` IDE Platforms: ${idePlatforms.join(", ")} (from global config)`);
335
496
  }
336
497
  console.log("");
337
498
  R.step("Current Project");
@@ -830,7 +991,7 @@ async function loadFilesFromDirectory(dirPath) {
830
991
  /**
831
992
  * Format an IDE platform key into a display name.
832
993
  */
833
- function formatIdeName(ideKey) {
994
+ function formatIdeName$2(ideKey) {
834
995
  return {
835
996
  vscode: "VS Code",
836
997
  jetbrains: "JetBrains",
@@ -841,6 +1002,156 @@ function formatIdeName(ideKey) {
841
1002
  }[ideKey] ?? ideKey;
842
1003
  }
843
1004
 
1005
+ //#endregion
1006
+ //#region src/commands/ides/configure.ts
1007
+ const idesConfigureCommand = defineCommand({
1008
+ meta: {
1009
+ name: "configure",
1010
+ description: "Manually configure which IDE platforms Baton manages"
1011
+ },
1012
+ args: {
1013
+ yes: {
1014
+ type: "boolean",
1015
+ alias: "y",
1016
+ description: "Keep current selection unchanged (no-op), or with --project write useGlobal: true"
1017
+ },
1018
+ project: {
1019
+ type: "boolean",
1020
+ description: "Configure IDE platforms for this project instead of globally"
1021
+ }
1022
+ },
1023
+ async run({ args }) {
1024
+ if (args.project) await runProjectMode(args.yes ?? false);
1025
+ else await runGlobalMode(args.yes ?? false);
1026
+ }
1027
+ });
1028
+ async function runGlobalMode(nonInteractive) {
1029
+ We("Baton - Configure IDE Platforms");
1030
+ const currentPlatforms = await getGlobalIdePlatforms();
1031
+ if (nonInteractive) {
1032
+ if (currentPlatforms.length > 0) R.info(`Current IDE platforms: ${currentPlatforms.join(", ")}`);
1033
+ else R.info("No IDE platforms currently configured.");
1034
+ Le("No changes made.");
1035
+ return;
1036
+ }
1037
+ const options = getRegisteredIdePlatforms().map((ideKey) => {
1038
+ return {
1039
+ value: ideKey,
1040
+ label: currentPlatforms.includes(ideKey) ? `${formatIdeName$2(ideKey)} (currently saved)` : formatIdeName$2(ideKey)
1041
+ };
1042
+ });
1043
+ const selected = await je({
1044
+ message: "Select which IDE platforms to save:",
1045
+ options,
1046
+ initialValues: currentPlatforms
1047
+ });
1048
+ if (Ct(selected)) {
1049
+ Le("No changes made.");
1050
+ return;
1051
+ }
1052
+ const selectedKeys = selected;
1053
+ if (selectedKeys.length !== currentPlatforms.length || selectedKeys.some((key) => !currentPlatforms.includes(key))) {
1054
+ await setGlobalIdePlatforms(selectedKeys);
1055
+ R.success(`Saved ${selectedKeys.length} platform(s) to global config.`);
1056
+ } else R.info("No changes made.");
1057
+ Le("Configuration complete.");
1058
+ }
1059
+ async function runProjectMode(nonInteractive) {
1060
+ We("Baton - Configure IDE Platforms (Project)");
1061
+ const projectRoot = process.cwd();
1062
+ const manifestPath = resolve(projectRoot, "baton.yaml");
1063
+ try {
1064
+ await stat(manifestPath);
1065
+ } catch {
1066
+ Ne("No baton.yaml found in current directory. Run `baton init` first.");
1067
+ process.exit(1);
1068
+ }
1069
+ if (nonInteractive) {
1070
+ const existing = await readProjectPreferences(projectRoot);
1071
+ await writeProjectPreferences(projectRoot, {
1072
+ version: "1.0",
1073
+ ai: existing?.ai ?? {
1074
+ useGlobal: true,
1075
+ tools: []
1076
+ },
1077
+ ide: {
1078
+ useGlobal: true,
1079
+ platforms: existing?.ide.platforms ?? []
1080
+ }
1081
+ });
1082
+ R.info("Set IDE platforms to use global config for this project.");
1083
+ Le("Configuration complete.");
1084
+ return;
1085
+ }
1086
+ const existing = await readProjectPreferences(projectRoot);
1087
+ const globalPlatforms = await getGlobalIdePlatforms();
1088
+ if (globalPlatforms.length > 0) R.info(`Global IDE platforms: ${globalPlatforms.join(", ")}`);
1089
+ const mode = await Je({
1090
+ message: "How should this project resolve IDE platforms?",
1091
+ options: [{
1092
+ value: "global",
1093
+ label: "Use global config",
1094
+ hint: "always follows your global IDE platforms setting"
1095
+ }, {
1096
+ value: "project",
1097
+ label: "Customize for this project",
1098
+ hint: "choose specific IDEs for this project"
1099
+ }],
1100
+ initialValue: existing?.ide.useGlobal === false ? "project" : "global"
1101
+ });
1102
+ if (Ct(mode)) {
1103
+ Le("No changes made.");
1104
+ return;
1105
+ }
1106
+ if (mode === "global") {
1107
+ await writeProjectPreferences(projectRoot, {
1108
+ version: "1.0",
1109
+ ai: existing?.ai ?? {
1110
+ useGlobal: true,
1111
+ tools: []
1112
+ },
1113
+ ide: {
1114
+ useGlobal: true,
1115
+ platforms: []
1116
+ }
1117
+ });
1118
+ R.success("Project configured to use global IDE platforms.");
1119
+ Le("Configuration complete.");
1120
+ return;
1121
+ }
1122
+ const allIdeKeys = getRegisteredIdePlatforms();
1123
+ const currentProjectPlatforms = existing?.ide.useGlobal === false ? existing.ide.platforms : globalPlatforms;
1124
+ const options = allIdeKeys.map((ideKey) => {
1125
+ return {
1126
+ value: ideKey,
1127
+ label: globalPlatforms.includes(ideKey) ? `${formatIdeName$2(ideKey)} (in global config)` : formatIdeName$2(ideKey)
1128
+ };
1129
+ });
1130
+ const selected = await je({
1131
+ message: "Select IDE platforms for this project:",
1132
+ options,
1133
+ initialValues: currentProjectPlatforms
1134
+ });
1135
+ if (Ct(selected)) {
1136
+ Le("No changes made.");
1137
+ return;
1138
+ }
1139
+ const selectedKeys = selected;
1140
+ await writeProjectPreferences(projectRoot, {
1141
+ version: "1.0",
1142
+ ai: existing?.ai ?? {
1143
+ useGlobal: true,
1144
+ tools: []
1145
+ },
1146
+ ide: {
1147
+ useGlobal: false,
1148
+ platforms: selectedKeys
1149
+ }
1150
+ });
1151
+ R.success(`Project configured with ${selectedKeys.length} IDE platform(s).`);
1152
+ Le("Configuration complete.");
1153
+ }
1154
+
844
1155
  //#endregion
845
1156
  //#region src/commands/ides/list.ts
846
1157
  const idesListCommand = defineCommand({
@@ -869,7 +1180,7 @@ const idesListCommand = defineCommand({
869
1180
  const entry = idePlatformRegistry[ideKey];
870
1181
  return {
871
1182
  key: ideKey,
872
- name: formatIdeName(ideKey),
1183
+ name: formatIdeName$2(ideKey),
873
1184
  saved: isSaved,
874
1185
  targetDir: entry?.targetDir ?? "unknown"
875
1186
  };
@@ -885,7 +1196,7 @@ const idesListCommand = defineCommand({
885
1196
  R.info(`All ${allIdeKeys.length} supported platforms:`);
886
1197
  for (const key of allIdeKeys) {
887
1198
  const entry = idePlatformRegistry[key];
888
- console.log(` \x1b[90m- ${formatIdeName(key)} (${entry?.targetDir ?? key})\x1b[0m`);
1199
+ console.log(` \x1b[90m- ${formatIdeName$2(key)} (${entry?.targetDir ?? key})\x1b[0m`);
889
1200
  }
890
1201
  Le("Run 'baton ides scan' to get started.");
891
1202
  return;
@@ -922,52 +1233,36 @@ const idesScanCommand = defineCommand({
922
1233
  const allIdeKeys = getRegisteredIdePlatforms();
923
1234
  const currentPlatforms = await getGlobalIdePlatforms();
924
1235
  spinner.stop("Scan complete.");
925
- const installed = [];
926
- const notInstalled = [];
927
- for (const ideKey of allIdeKeys) {
928
- const entry = {
929
- key: ideKey,
930
- name: formatIdeName(ideKey)
931
- };
932
- if (detectedIdes.includes(ideKey)) installed.push(entry);
933
- else notInstalled.push(entry);
934
- }
935
- if (installed.length > 0) {
936
- R.success(`Found ${installed.length} IDE platform${installed.length !== 1 ? "s" : ""}:`);
937
- for (const ide of installed) {
938
- const badge = currentPlatforms.includes(ide.key) ? " (saved)" : " (new)";
939
- console.log(` \x1b[32m✓\x1b[0m ${ide.name}${badge}`);
940
- }
941
- } else {
942
- R.warn("No IDE platforms detected on your system.");
1236
+ if (detectedIdes.length > 0) R.success(`Found ${detectedIdes.length} IDE platform${detectedIdes.length !== 1 ? "s" : ""} on your system.`);
1237
+ else R.warn("No IDE platforms detected on your system.");
1238
+ if (args.yes) {
1239
+ if (detectedIdes.length !== currentPlatforms.length || detectedIdes.some((key) => !currentPlatforms.includes(key))) {
1240
+ await setGlobalIdePlatforms(detectedIdes);
1241
+ R.success(`Saved ${detectedIdes.length} detected platform(s) to global config.`);
1242
+ } else R.info("Global config is already up to date.");
943
1243
  Le("Scan finished.");
944
1244
  return;
945
1245
  }
946
- if (notInstalled.length > 0) {
947
- console.log("");
948
- R.info(`Not detected (${notInstalled.length}):`);
949
- for (const ide of notInstalled) console.log(` \x1b[90m✗ ${ide.name}\x1b[0m`);
950
- }
951
- const detectedKeys = installed.map((i) => i.key);
952
- if (detectedKeys.length !== currentPlatforms.length || detectedKeys.some((key) => !currentPlatforms.includes(key))) {
953
- console.log("");
954
- let shouldSave = args.yes;
955
- if (!shouldSave) {
956
- const confirm = await Re({ message: "Save detected platforms to global config (~/.baton/config.yaml)?" });
957
- if (Ct(confirm)) {
958
- Le("Scan finished (not saved).");
959
- return;
960
- }
961
- shouldSave = confirm;
962
- }
963
- if (shouldSave) {
964
- await setGlobalIdePlatforms(detectedKeys);
965
- R.success("Platforms saved to global config.");
966
- }
967
- } else {
968
- console.log("");
969
- R.info("Global config is already up to date.");
1246
+ const options = allIdeKeys.map((ideKey) => {
1247
+ return {
1248
+ value: ideKey,
1249
+ label: detectedIdes.includes(ideKey) ? `${formatIdeName$2(ideKey)} (detected)` : formatIdeName$2(ideKey)
1250
+ };
1251
+ });
1252
+ const selected = await je({
1253
+ message: "Select which IDE platforms to save:",
1254
+ options,
1255
+ initialValues: detectedIdes
1256
+ });
1257
+ if (Ct(selected)) {
1258
+ Le("Scan finished (not saved).");
1259
+ return;
970
1260
  }
1261
+ const selectedKeys = selected;
1262
+ if (selectedKeys.length !== currentPlatforms.length || selectedKeys.some((key) => !currentPlatforms.includes(key))) {
1263
+ await setGlobalIdePlatforms(selectedKeys);
1264
+ R.success(`Saved ${selectedKeys.length} platform(s) to global config.`);
1265
+ } else R.info("Global config is already up to date.");
971
1266
  Le("Scan finished.");
972
1267
  }
973
1268
  });
@@ -980,11 +1275,126 @@ const idesCommand = defineCommand({
980
1275
  description: "Manage IDE platform detection and configuration"
981
1276
  },
982
1277
  subCommands: {
1278
+ configure: idesConfigureCommand,
983
1279
  list: idesListCommand,
984
1280
  scan: idesScanCommand
985
1281
  }
986
1282
  });
987
1283
 
1284
+ //#endregion
1285
+ //#region src/utils/first-run-preferences.ts
1286
+ /**
1287
+ * Format an IDE platform key into a display name.
1288
+ * Duplicated here to avoid circular dependency with ides/utils.
1289
+ */
1290
+ function formatIdeName$1(ideKey) {
1291
+ return {
1292
+ vscode: "VS Code",
1293
+ jetbrains: "JetBrains",
1294
+ cursor: "Cursor",
1295
+ windsurf: "Windsurf",
1296
+ antigravity: "Antigravity",
1297
+ zed: "Zed"
1298
+ }[ideKey] ?? ideKey;
1299
+ }
1300
+ /**
1301
+ * Shows the first-run preferences prompt if .baton/preferences.yaml doesn't exist.
1302
+ *
1303
+ * Asks the user whether to use global config or customize AI tools and IDEs
1304
+ * for this project, then writes the preferences file.
1305
+ *
1306
+ * @param projectRoot - Absolute path to the project root
1307
+ * @param nonInteractive - If true, writes useGlobal: true silently
1308
+ * @returns true if preferences were written, false if already existed
1309
+ */
1310
+ async function promptFirstRunPreferences(projectRoot, nonInteractive) {
1311
+ if (await readProjectPreferences(projectRoot)) return false;
1312
+ if (nonInteractive) {
1313
+ await writeProjectPreferences(projectRoot, {
1314
+ version: "1.0",
1315
+ ai: {
1316
+ useGlobal: true,
1317
+ tools: []
1318
+ },
1319
+ ide: {
1320
+ useGlobal: true,
1321
+ platforms: []
1322
+ }
1323
+ });
1324
+ return true;
1325
+ }
1326
+ const aiMode = await Je({
1327
+ message: "How do you want to configure AI tools for this project?",
1328
+ options: [{
1329
+ value: "global",
1330
+ label: "Use global config",
1331
+ hint: "recommended"
1332
+ }, {
1333
+ value: "customize",
1334
+ label: "Customize for this project"
1335
+ }]
1336
+ });
1337
+ if (Ct(aiMode)) return false;
1338
+ let aiUseGlobal = true;
1339
+ let aiTools = [];
1340
+ if (aiMode === "customize") {
1341
+ const globalTools = await getGlobalAiTools();
1342
+ const allAdapters = getAllAdapters();
1343
+ const selected = await je({
1344
+ message: "Select AI tools for this project:",
1345
+ options: allAdapters.map((adapter) => ({
1346
+ value: adapter.key,
1347
+ label: globalTools.includes(adapter.key) ? `${adapter.name} (in global config)` : adapter.name
1348
+ })),
1349
+ initialValues: globalTools
1350
+ });
1351
+ if (Ct(selected)) return false;
1352
+ aiUseGlobal = false;
1353
+ aiTools = selected;
1354
+ }
1355
+ const ideMode = await Je({
1356
+ message: "How do you want to configure IDE platforms for this project?",
1357
+ options: [{
1358
+ value: "global",
1359
+ label: "Use global config",
1360
+ hint: "recommended"
1361
+ }, {
1362
+ value: "customize",
1363
+ label: "Customize for this project"
1364
+ }]
1365
+ });
1366
+ if (Ct(ideMode)) return false;
1367
+ let ideUseGlobal = true;
1368
+ let idePlatforms = [];
1369
+ if (ideMode === "customize") {
1370
+ const globalPlatforms = await getGlobalIdePlatforms();
1371
+ const allIdeKeys = getRegisteredIdePlatforms();
1372
+ const selected = await je({
1373
+ message: "Select IDE platforms for this project:",
1374
+ options: allIdeKeys.map((ideKey) => ({
1375
+ value: ideKey,
1376
+ label: globalPlatforms.includes(ideKey) ? `${formatIdeName$1(ideKey)} (in global config)` : formatIdeName$1(ideKey)
1377
+ })),
1378
+ initialValues: globalPlatforms
1379
+ });
1380
+ if (Ct(selected)) return false;
1381
+ ideUseGlobal = false;
1382
+ idePlatforms = selected;
1383
+ }
1384
+ await writeProjectPreferences(projectRoot, {
1385
+ version: "1.0",
1386
+ ai: {
1387
+ useGlobal: aiUseGlobal,
1388
+ tools: aiTools
1389
+ },
1390
+ ide: {
1391
+ useGlobal: ideUseGlobal,
1392
+ platforms: idePlatforms
1393
+ }
1394
+ });
1395
+ return true;
1396
+ }
1397
+
988
1398
  //#endregion
989
1399
  //#region src/utils/profile-selection.ts
990
1400
  /**
@@ -1261,6 +1671,7 @@ const initCommand = defineCommand({
1261
1671
  } catch (_error) {
1262
1672
  spinner.stop("✅ .baton directory already exists");
1263
1673
  }
1674
+ await promptFirstRunPreferences(cwd, !isInteractive);
1264
1675
  if (profileSources.length > 0) {
1265
1676
  const shouldSync = isInteractive ? await Re({
1266
1677
  message: "Sync profiles now?",
@@ -1554,13 +1965,173 @@ async function handleRemoveBaton(cwd) {
1554
1965
  R.warn("Cancelled.");
1555
1966
  return false;
1556
1967
  }
1968
+ const lockPath = join(cwd, "baton.lock");
1969
+ await cleanupPlacedFilesFromLock(lockPath, cwd);
1557
1970
  await rm(join(cwd, "baton.yaml"), { force: true });
1558
- await rm(join(cwd, "baton.lock"), { force: true });
1971
+ await rm(lockPath, { force: true });
1559
1972
  R.success("Baton has been removed from this project.");
1560
- R.info("Note: Synced files (rules, skills, memory) were not removed.");
1561
- R.info("Run 'baton sync' before removing to clean up, or delete them manually.");
1562
1973
  return true;
1563
1974
  }
1975
+ async function cleanupPlacedFilesFromLock(lockPath, projectRoot) {
1976
+ let placedPaths;
1977
+ try {
1978
+ const lockfile = await readLock(lockPath);
1979
+ placedPaths = Object.values(lockfile.packages).flatMap((pkg) => Object.keys(pkg.integrity));
1980
+ } catch (error) {
1981
+ if (error instanceof FileNotFoundError) return;
1982
+ return;
1983
+ }
1984
+ if (placedPaths.length === 0) return;
1985
+ R.info(`Found ${placedPaths.length} placed file(s):`);
1986
+ for (const filePath of placedPaths) R.info(` ${filePath}`);
1987
+ const shouldClean = await Re({
1988
+ message: `Also remove ${placedPaths.length} placed file(s)?`,
1989
+ initialValue: false
1990
+ });
1991
+ if (Ct(shouldClean) || !shouldClean) return;
1992
+ const removedCount = await removePlacedFiles(placedPaths, projectRoot);
1993
+ R.success(`Removed ${removedCount} placed file(s).`);
1994
+ }
1995
+ function formatIdeName(ideKey) {
1996
+ return {
1997
+ vscode: "VS Code",
1998
+ jetbrains: "JetBrains",
1999
+ cursor: "Cursor",
2000
+ windsurf: "Windsurf",
2001
+ antigravity: "Antigravity",
2002
+ zed: "Zed"
2003
+ }[ideKey] ?? ideKey;
2004
+ }
2005
+ async function handleConfigureAiTools(cwd) {
2006
+ const existing = await readProjectPreferences(cwd);
2007
+ const globalTools = await getGlobalAiTools();
2008
+ if (globalTools.length > 0) R.info(`Global AI tools: ${globalTools.join(", ")}`);
2009
+ const mode = await Je({
2010
+ message: "How should this project resolve AI tools?",
2011
+ options: [{
2012
+ value: "global",
2013
+ label: "Use global config",
2014
+ hint: "always follows your global AI tools setting"
2015
+ }, {
2016
+ value: "project",
2017
+ label: "Customize for this project",
2018
+ hint: "choose specific tools for this project"
2019
+ }],
2020
+ initialValue: existing?.ai.useGlobal === false ? "project" : "global"
2021
+ });
2022
+ if (Ct(mode)) {
2023
+ R.warn("Cancelled.");
2024
+ return;
2025
+ }
2026
+ if (mode === "global") {
2027
+ await writeProjectPreferences(cwd, {
2028
+ version: "1.0",
2029
+ ai: {
2030
+ useGlobal: true,
2031
+ tools: []
2032
+ },
2033
+ ide: existing?.ide ?? {
2034
+ useGlobal: true,
2035
+ platforms: []
2036
+ }
2037
+ });
2038
+ R.success("Project configured to use global AI tools.");
2039
+ return;
2040
+ }
2041
+ const allAdapters = getAllAdapters();
2042
+ const currentProjectTools = existing?.ai.useGlobal === false ? existing.ai.tools : globalTools;
2043
+ const options = allAdapters.map((adapter) => ({
2044
+ value: adapter.key,
2045
+ label: globalTools.includes(adapter.key) ? `${adapter.name} (in global config)` : adapter.name
2046
+ }));
2047
+ const selected = await je({
2048
+ message: "Select AI tools for this project:",
2049
+ options,
2050
+ initialValues: currentProjectTools
2051
+ });
2052
+ if (Ct(selected)) {
2053
+ R.warn("Cancelled.");
2054
+ return;
2055
+ }
2056
+ const selectedKeys = selected;
2057
+ await writeProjectPreferences(cwd, {
2058
+ version: "1.0",
2059
+ ai: {
2060
+ useGlobal: false,
2061
+ tools: selectedKeys
2062
+ },
2063
+ ide: existing?.ide ?? {
2064
+ useGlobal: true,
2065
+ platforms: []
2066
+ }
2067
+ });
2068
+ R.success(`Project configured with ${selectedKeys.length} AI tool(s).`);
2069
+ }
2070
+ async function handleConfigureIdes(cwd) {
2071
+ const existing = await readProjectPreferences(cwd);
2072
+ const globalPlatforms = await getGlobalIdePlatforms();
2073
+ if (globalPlatforms.length > 0) R.info(`Global IDE platforms: ${globalPlatforms.join(", ")}`);
2074
+ const mode = await Je({
2075
+ message: "How should this project resolve IDE platforms?",
2076
+ options: [{
2077
+ value: "global",
2078
+ label: "Use global config",
2079
+ hint: "always follows your global IDE platforms setting"
2080
+ }, {
2081
+ value: "project",
2082
+ label: "Customize for this project",
2083
+ hint: "choose specific IDEs for this project"
2084
+ }],
2085
+ initialValue: existing?.ide.useGlobal === false ? "project" : "global"
2086
+ });
2087
+ if (Ct(mode)) {
2088
+ R.warn("Cancelled.");
2089
+ return;
2090
+ }
2091
+ if (mode === "global") {
2092
+ await writeProjectPreferences(cwd, {
2093
+ version: "1.0",
2094
+ ai: existing?.ai ?? {
2095
+ useGlobal: true,
2096
+ tools: []
2097
+ },
2098
+ ide: {
2099
+ useGlobal: true,
2100
+ platforms: []
2101
+ }
2102
+ });
2103
+ R.success("Project configured to use global IDE platforms.");
2104
+ return;
2105
+ }
2106
+ const allIdeKeys = getRegisteredIdePlatforms();
2107
+ const currentProjectPlatforms = existing?.ide.useGlobal === false ? existing.ide.platforms : globalPlatforms;
2108
+ const options = allIdeKeys.map((ideKey) => ({
2109
+ value: ideKey,
2110
+ label: globalPlatforms.includes(ideKey) ? `${formatIdeName(ideKey)} (in global config)` : formatIdeName(ideKey)
2111
+ }));
2112
+ const selected = await je({
2113
+ message: "Select IDE platforms for this project:",
2114
+ options,
2115
+ initialValues: currentProjectPlatforms
2116
+ });
2117
+ if (Ct(selected)) {
2118
+ R.warn("Cancelled.");
2119
+ return;
2120
+ }
2121
+ const selectedKeys = selected;
2122
+ await writeProjectPreferences(cwd, {
2123
+ version: "1.0",
2124
+ ai: existing?.ai ?? {
2125
+ useGlobal: true,
2126
+ tools: []
2127
+ },
2128
+ ide: {
2129
+ useGlobal: false,
2130
+ platforms: selectedKeys
2131
+ }
2132
+ });
2133
+ R.success(`Project configured with ${selectedKeys.length} IDE platform(s).`);
2134
+ }
1564
2135
  const manageCommand = defineCommand({
1565
2136
  meta: {
1566
2137
  name: "manage",
@@ -1593,6 +2164,16 @@ const manageCommand = defineCommand({
1593
2164
  label: "Remove profile",
1594
2165
  hint: "Remove an installed profile"
1595
2166
  },
2167
+ {
2168
+ value: "configure-ai",
2169
+ label: "Configure AI tools for this project",
2170
+ hint: "Choose which AI tools to sync"
2171
+ },
2172
+ {
2173
+ value: "configure-ides",
2174
+ label: "Configure IDEs for this project",
2175
+ hint: "Choose which IDEs to sync"
2176
+ },
1596
2177
  {
1597
2178
  value: "remove-baton",
1598
2179
  label: "Remove Baton",
@@ -1620,6 +2201,14 @@ const manageCommand = defineCommand({
1620
2201
  console.log("");
1621
2202
  await handleRemoveProfile(cwd);
1622
2203
  console.log("");
2204
+ } else if (action === "configure-ai") {
2205
+ console.log("");
2206
+ await handleConfigureAiTools(cwd);
2207
+ console.log("");
2208
+ } else if (action === "configure-ides") {
2209
+ console.log("");
2210
+ await handleConfigureIdes(cwd);
2211
+ console.log("");
1623
2212
  } else if (action === "remove-baton") {
1624
2213
  console.log("");
1625
2214
  if (await handleRemoveBaton(cwd)) {
@@ -1640,8 +2229,8 @@ const profileCommand = defineCommand({
1640
2229
  description: "Manage profiles (create, list, remove)"
1641
2230
  },
1642
2231
  subCommands: {
1643
- create: () => import("./create-C0x3t4GX.mjs").then((m) => m.createCommand),
1644
- list: () => import("./list-CGmYHSHW.mjs").then((m) => m.profileListCommand),
2232
+ create: () => import("./create-BOcW-DBk.mjs").then((m) => m.createCommand),
2233
+ list: () => import("./list-DmzVXCNF.mjs").then((m) => m.profileListCommand),
1645
2234
  remove: () => import("./remove-BBs6Mv8t.mjs").then((m) => m.profileRemoveCommand)
1646
2235
  }
1647
2236
  });
@@ -2181,23 +2770,7 @@ async function cleanupOrphanedFiles(params) {
2181
2770
  return;
2182
2771
  }
2183
2772
  spinner.start("Removing orphaned files...");
2184
- let removedCount = 0;
2185
- for (const orphanedPath of orphanedPaths) {
2186
- const absolutePath = orphanedPath.startsWith("/") ? orphanedPath : resolve(projectRoot, orphanedPath);
2187
- try {
2188
- await unlink(absolutePath);
2189
- removedCount++;
2190
- let dir = dirname(absolutePath);
2191
- while (dir !== projectRoot && dir.startsWith(projectRoot)) try {
2192
- if ((await readdir(dir)).length === 0) {
2193
- await rmdir(dir);
2194
- dir = dirname(dir);
2195
- } else break;
2196
- } catch {
2197
- break;
2198
- }
2199
- } catch {}
2200
- }
2773
+ const removedCount = await removePlacedFiles(orphanedPaths, projectRoot);
2201
2774
  spinner.stop(`Removed ${removedCount} orphaned file(s)`);
2202
2775
  }
2203
2776
  const syncCommand = defineCommand({
@@ -2260,6 +2833,7 @@ const syncCommand = defineCommand({
2260
2833
  else Ne(`Failed to load baton.yaml: ${error instanceof Error ? error.message : String(error)}`);
2261
2834
  process.exit(1);
2262
2835
  }
2836
+ await promptFirstRunPreferences(projectRoot, !!args.yes);
2263
2837
  const previousPaths = /* @__PURE__ */ new Set();
2264
2838
  try {
2265
2839
  const previousLock = await readLock(resolve(projectRoot, "baton.lock"));
@@ -2420,16 +2994,19 @@ const syncCommand = defineCommand({
2420
2994
  spinner.stop(`Merged: ${mergedSkills.length} skills, ${mergedRules.length} rules, ${mergedMemory.length} memory files, ${mergedCommandCount} commands, ${mergedFileCount} files, ${mergedIdeCount} IDE configs`);
2421
2995
  if (allWeightWarnings.length > 0) for (const w of allWeightWarnings) R.warn(`Weight conflict: "${w.profileA}" and "${w.profileB}" both define ${w.category} "${w.key}" with weight ${w.weight}. Last declared wins.`);
2422
2996
  spinner.start("Computing tool intersection...");
2423
- const globalAiTools = await getGlobalAiTools();
2424
- const globalIdePlatforms = await getGlobalIdePlatforms();
2997
+ const prefs = await resolvePreferences(projectRoot);
2425
2998
  const detectedAgents = await detectInstalledAgents();
2999
+ if (verbose) {
3000
+ R.info(`AI tools: ${prefs.ai.tools.join(", ") || "(none)"} (from ${prefs.ai.source} preferences)`);
3001
+ R.info(`IDE platforms: ${prefs.ide.platforms.join(", ") || "(none)"} (from ${prefs.ide.source} preferences)`);
3002
+ }
2426
3003
  let syncedAiTools;
2427
3004
  let syncedIdePlatforms = null;
2428
3005
  let allIntersections = null;
2429
- if (globalAiTools.length > 0) {
3006
+ if (prefs.ai.tools.length > 0) {
2430
3007
  const developerTools = {
2431
- aiTools: globalAiTools,
2432
- idePlatforms: globalIdePlatforms
3008
+ aiTools: prefs.ai.tools,
3009
+ idePlatforms: prefs.ide.platforms
2433
3010
  };
2434
3011
  const aggregatedSyncedAi = /* @__PURE__ */ new Set();
2435
3012
  const aggregatedSyncedIde = /* @__PURE__ */ new Set();
@@ -2641,9 +3218,10 @@ const syncCommand = defineCommand({
2641
3218
  const combinedContent = entry.parts.join("\n\n");
2642
3219
  const result = await placeFile(combinedContent, entry.adapter, entry.type, "project", entry.name, placementConfig);
2643
3220
  if (result.action !== "skipped") stats.created++;
3221
+ const relPath = isAbsolute(result.path) ? relative(projectRoot, result.path) : result.path;
2644
3222
  for (const profileName of entry.profiles) {
2645
3223
  const pf = getOrCreatePlacedFiles(placedFiles, profileName);
2646
- pf[result.path] = {
3224
+ pf[relPath] = {
2647
3225
  content: combinedContent,
2648
3226
  tool: entry.adapter.key,
2649
3227
  category: "ai"
@@ -2673,8 +3251,9 @@ const syncCommand = defineCommand({
2673
3251
  }
2674
3252
  const result = await placeFile(content, adapter, "commands", "project", commandName, placementConfig);
2675
3253
  if (result.action !== "skipped") stats.created++;
3254
+ const cmdRelPath = isAbsolute(result.path) ? relative(projectRoot, result.path) : result.path;
2676
3255
  const pf = getOrCreatePlacedFiles(placedFiles, profile.name);
2677
- pf[result.path] = {
3256
+ pf[cmdRelPath] = {
2678
3257
  content,
2679
3258
  tool: adapter.key,
2680
3259
  category: "ai"