@baton-dx/cli 0.1.4 → 0.3.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,14 +1,166 @@
1
1
  #!/usr/bin/env node
2
2
  import { r as __toESM } from "./chunk-BbwQpWto.mjs";
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-mMNLg_4F.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-Dh0ZvHbV.mjs";
5
- import { n as detectInstalledAgents, t as clearAgentCache } from "./agent-detection-C5gaTtah.mjs";
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 { $ as getAgentConfig, A as discoverProfilesInSourceRepo, B as getRegisteredIdePlatforms, C as isLockedProfile, D as resolveProfileChain, E as resolveProfileSupport, F as resolveVersion, G as parseFrontmatter, H as isKnownIdePlatform, I as cloneGitSource, J as loadProfileManifest, K as parseSource, L as collectProfileSupportPatterns, M as generateLock, N as readLock, O as detectLegacyPaths, P as writeLock, Q as SourceParseError, R as updateGitignore, S as getProfileWeight, T as mergeContentParts, U as getAdaptersForKeys, V as idePlatformRegistry, W as getAllAdapters, X as KEBAB_CASE_REGEX, Y as loadProjectManifest, Z as FileNotFoundError, _ as mergeMemoryWithWarnings, a as clearIdeCache, b as mergeSkills, c as getDefaultGlobalSource, d as getGlobalSources, et as getAgentPath, 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, o as detectInstalledIdes, p as setGlobalAiTools, q as loadLockfile, r as writeProjectPreferences, s as addGlobalSource, t as resolvePreferences, tt as getAllAgentKeys, u as getGlobalIdePlatforms, v as mergeRules, w as sortProfilesByWeight, x as mergeSkillsWithWarnings, y as mergeRulesWithWarnings, z as getIdePlatformTargetDir } from "./src-BgiJfm14.mjs";
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
7
  import { access, mkdir, readFile, readdir, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
8
8
  import { dirname, join, 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?",
@@ -1561,6 +1972,146 @@ async function handleRemoveBaton(cwd) {
1561
1972
  R.info("Run 'baton sync' before removing to clean up, or delete them manually.");
1562
1973
  return true;
1563
1974
  }
1975
+ function formatIdeName(ideKey) {
1976
+ return {
1977
+ vscode: "VS Code",
1978
+ jetbrains: "JetBrains",
1979
+ cursor: "Cursor",
1980
+ windsurf: "Windsurf",
1981
+ antigravity: "Antigravity",
1982
+ zed: "Zed"
1983
+ }[ideKey] ?? ideKey;
1984
+ }
1985
+ async function handleConfigureAiTools(cwd) {
1986
+ const existing = await readProjectPreferences(cwd);
1987
+ const globalTools = await getGlobalAiTools();
1988
+ if (globalTools.length > 0) R.info(`Global AI tools: ${globalTools.join(", ")}`);
1989
+ const mode = await Je({
1990
+ message: "How should this project resolve AI tools?",
1991
+ options: [{
1992
+ value: "global",
1993
+ label: "Use global config",
1994
+ hint: "always follows your global AI tools setting"
1995
+ }, {
1996
+ value: "project",
1997
+ label: "Customize for this project",
1998
+ hint: "choose specific tools for this project"
1999
+ }],
2000
+ initialValue: existing?.ai.useGlobal === false ? "project" : "global"
2001
+ });
2002
+ if (Ct(mode)) {
2003
+ R.warn("Cancelled.");
2004
+ return;
2005
+ }
2006
+ if (mode === "global") {
2007
+ await writeProjectPreferences(cwd, {
2008
+ version: "1.0",
2009
+ ai: {
2010
+ useGlobal: true,
2011
+ tools: []
2012
+ },
2013
+ ide: existing?.ide ?? {
2014
+ useGlobal: true,
2015
+ platforms: []
2016
+ }
2017
+ });
2018
+ R.success("Project configured to use global AI tools.");
2019
+ return;
2020
+ }
2021
+ const allAdapters = getAllAdapters();
2022
+ const currentProjectTools = existing?.ai.useGlobal === false ? existing.ai.tools : globalTools;
2023
+ const options = allAdapters.map((adapter) => ({
2024
+ value: adapter.key,
2025
+ label: globalTools.includes(adapter.key) ? `${adapter.name} (in global config)` : adapter.name
2026
+ }));
2027
+ const selected = await je({
2028
+ message: "Select AI tools for this project:",
2029
+ options,
2030
+ initialValues: currentProjectTools
2031
+ });
2032
+ if (Ct(selected)) {
2033
+ R.warn("Cancelled.");
2034
+ return;
2035
+ }
2036
+ const selectedKeys = selected;
2037
+ await writeProjectPreferences(cwd, {
2038
+ version: "1.0",
2039
+ ai: {
2040
+ useGlobal: false,
2041
+ tools: selectedKeys
2042
+ },
2043
+ ide: existing?.ide ?? {
2044
+ useGlobal: true,
2045
+ platforms: []
2046
+ }
2047
+ });
2048
+ R.success(`Project configured with ${selectedKeys.length} AI tool(s).`);
2049
+ }
2050
+ async function handleConfigureIdes(cwd) {
2051
+ const existing = await readProjectPreferences(cwd);
2052
+ const globalPlatforms = await getGlobalIdePlatforms();
2053
+ if (globalPlatforms.length > 0) R.info(`Global IDE platforms: ${globalPlatforms.join(", ")}`);
2054
+ const mode = await Je({
2055
+ message: "How should this project resolve IDE platforms?",
2056
+ options: [{
2057
+ value: "global",
2058
+ label: "Use global config",
2059
+ hint: "always follows your global IDE platforms setting"
2060
+ }, {
2061
+ value: "project",
2062
+ label: "Customize for this project",
2063
+ hint: "choose specific IDEs for this project"
2064
+ }],
2065
+ initialValue: existing?.ide.useGlobal === false ? "project" : "global"
2066
+ });
2067
+ if (Ct(mode)) {
2068
+ R.warn("Cancelled.");
2069
+ return;
2070
+ }
2071
+ if (mode === "global") {
2072
+ await writeProjectPreferences(cwd, {
2073
+ version: "1.0",
2074
+ ai: existing?.ai ?? {
2075
+ useGlobal: true,
2076
+ tools: []
2077
+ },
2078
+ ide: {
2079
+ useGlobal: true,
2080
+ platforms: []
2081
+ }
2082
+ });
2083
+ R.success("Project configured to use global IDE platforms.");
2084
+ return;
2085
+ }
2086
+ const allIdeKeys = getRegisteredIdePlatforms();
2087
+ const currentProjectPlatforms = existing?.ide.useGlobal === false ? existing.ide.platforms : globalPlatforms;
2088
+ const options = allIdeKeys.map((ideKey) => ({
2089
+ value: ideKey,
2090
+ label: globalPlatforms.includes(ideKey) ? `${formatIdeName(ideKey)} (in global config)` : formatIdeName(ideKey)
2091
+ }));
2092
+ const selected = await je({
2093
+ message: "Select IDE platforms for this project:",
2094
+ options,
2095
+ initialValues: currentProjectPlatforms
2096
+ });
2097
+ if (Ct(selected)) {
2098
+ R.warn("Cancelled.");
2099
+ return;
2100
+ }
2101
+ const selectedKeys = selected;
2102
+ await writeProjectPreferences(cwd, {
2103
+ version: "1.0",
2104
+ ai: existing?.ai ?? {
2105
+ useGlobal: true,
2106
+ tools: []
2107
+ },
2108
+ ide: {
2109
+ useGlobal: false,
2110
+ platforms: selectedKeys
2111
+ }
2112
+ });
2113
+ R.success(`Project configured with ${selectedKeys.length} IDE platform(s).`);
2114
+ }
1564
2115
  const manageCommand = defineCommand({
1565
2116
  meta: {
1566
2117
  name: "manage",
@@ -1593,6 +2144,16 @@ const manageCommand = defineCommand({
1593
2144
  label: "Remove profile",
1594
2145
  hint: "Remove an installed profile"
1595
2146
  },
2147
+ {
2148
+ value: "configure-ai",
2149
+ label: "Configure AI tools for this project",
2150
+ hint: "Choose which AI tools to sync"
2151
+ },
2152
+ {
2153
+ value: "configure-ides",
2154
+ label: "Configure IDEs for this project",
2155
+ hint: "Choose which IDEs to sync"
2156
+ },
1596
2157
  {
1597
2158
  value: "remove-baton",
1598
2159
  label: "Remove Baton",
@@ -1620,6 +2181,14 @@ const manageCommand = defineCommand({
1620
2181
  console.log("");
1621
2182
  await handleRemoveProfile(cwd);
1622
2183
  console.log("");
2184
+ } else if (action === "configure-ai") {
2185
+ console.log("");
2186
+ await handleConfigureAiTools(cwd);
2187
+ console.log("");
2188
+ } else if (action === "configure-ides") {
2189
+ console.log("");
2190
+ await handleConfigureIdes(cwd);
2191
+ console.log("");
1623
2192
  } else if (action === "remove-baton") {
1624
2193
  console.log("");
1625
2194
  if (await handleRemoveBaton(cwd)) {
@@ -1640,9 +2209,9 @@ const profileCommand = defineCommand({
1640
2209
  description: "Manage profiles (create, list, remove)"
1641
2210
  },
1642
2211
  subCommands: {
1643
- create: () => import("./create-Bo1zwMs0.mjs").then((m) => m.createCommand),
1644
- list: () => import("./list-B5xUVBTU.mjs").then((m) => m.profileListCommand),
1645
- remove: () => import("./remove-6S8F9xcE.mjs").then((m) => m.profileRemoveCommand)
2212
+ create: () => import("./create-BG_VVOTI.mjs").then((m) => m.createCommand),
2213
+ list: () => import("./list-CCzjta6J.mjs").then((m) => m.profileListCommand),
2214
+ remove: () => import("./remove-BBs6Mv8t.mjs").then((m) => m.profileRemoveCommand)
1646
2215
  }
1647
2216
  });
1648
2217
 
@@ -2260,6 +2829,7 @@ const syncCommand = defineCommand({
2260
2829
  else Ne(`Failed to load baton.yaml: ${error instanceof Error ? error.message : String(error)}`);
2261
2830
  process.exit(1);
2262
2831
  }
2832
+ await promptFirstRunPreferences(projectRoot, !!args.yes);
2263
2833
  const previousPaths = /* @__PURE__ */ new Set();
2264
2834
  try {
2265
2835
  const previousLock = await readLock(resolve(projectRoot, "baton.lock"));
@@ -2420,16 +2990,19 @@ const syncCommand = defineCommand({
2420
2990
  spinner.stop(`Merged: ${mergedSkills.length} skills, ${mergedRules.length} rules, ${mergedMemory.length} memory files, ${mergedCommandCount} commands, ${mergedFileCount} files, ${mergedIdeCount} IDE configs`);
2421
2991
  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
2992
  spinner.start("Computing tool intersection...");
2423
- const globalAiTools = await getGlobalAiTools();
2424
- const globalIdePlatforms = await getGlobalIdePlatforms();
2993
+ const prefs = await resolvePreferences(projectRoot);
2425
2994
  const detectedAgents = await detectInstalledAgents();
2995
+ if (verbose) {
2996
+ R.info(`AI tools: ${prefs.ai.tools.join(", ") || "(none)"} (from ${prefs.ai.source} preferences)`);
2997
+ R.info(`IDE platforms: ${prefs.ide.platforms.join(", ") || "(none)"} (from ${prefs.ide.source} preferences)`);
2998
+ }
2426
2999
  let syncedAiTools;
2427
3000
  let syncedIdePlatforms = null;
2428
3001
  let allIntersections = null;
2429
- if (globalAiTools.length > 0) {
3002
+ if (prefs.ai.tools.length > 0) {
2430
3003
  const developerTools = {
2431
- aiTools: globalAiTools,
2432
- idePlatforms: globalIdePlatforms
3004
+ aiTools: prefs.ai.tools,
3005
+ idePlatforms: prefs.ide.platforms
2433
3006
  };
2434
3007
  const aggregatedSyncedAi = /* @__PURE__ */ new Set();
2435
3008
  const aggregatedSyncedIde = /* @__PURE__ */ new Set();
@@ -2503,6 +3076,7 @@ const syncCommand = defineCommand({
2503
3076
  for (const prof of allProfiles) if (prof.source === profileSource.source) profileLocalPaths.set(prof.name, cloned.localPath);
2504
3077
  }
2505
3078
  }
3079
+ for (const prof of allProfiles) if (!profileLocalPaths.has(prof.name) && prof.localPath) profileLocalPaths.set(prof.name, prof.localPath);
2506
3080
  const contentAccumulator = /* @__PURE__ */ new Map();
2507
3081
  if (!dryRun && syncAi) for (const adapter of adapters) {
2508
3082
  if (verbose) R.step(`[${adapter.key}] Placing memory files...`);