codealmanac 0.2.4 → 0.2.6

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.
Files changed (51) hide show
  1. package/COMMERCIAL.md +9 -0
  2. package/LICENSE +133 -21
  3. package/README.md +2 -2
  4. package/dist/{agents-4Y7X24WW.js → agents-HYRWRHRX.js} +4 -4
  5. package/dist/{chunk-TT6ZP4GS.js → chunk-2BNDNGUR.js} +8 -4
  6. package/dist/{chunk-TT6ZP4GS.js.map → chunk-2BNDNGUR.js.map} +1 -1
  7. package/dist/{chunk-P5WGG4FJ.js → chunk-3E7JNMTZ.js} +28 -3
  8. package/dist/chunk-3E7JNMTZ.js.map +1 -0
  9. package/dist/{chunk-CW4HRLMS.js → chunk-DW32TL5W.js} +53 -11
  10. package/dist/chunk-DW32TL5W.js.map +1 -0
  11. package/dist/{chunk-UU6FBRQO.js → chunk-GPFVEF6V.js} +24 -6
  12. package/dist/chunk-GPFVEF6V.js.map +1 -0
  13. package/dist/{chunk-TILAKDN6.js → chunk-HJ3WREGP.js} +2 -2
  14. package/dist/{chunk-BF2J4XTC.js → chunk-J7DNV2DH.js} +219 -26
  15. package/dist/chunk-J7DNV2DH.js.map +1 -0
  16. package/dist/{chunk-H6QKCB7M.js → chunk-K2JBCB7R.js} +43 -7
  17. package/dist/chunk-K2JBCB7R.js.map +1 -0
  18. package/dist/{chunk-MRRX4UQB.js → chunk-ODJAAJGZ.js} +2 -2
  19. package/dist/{chunk-447U3GQJ.js → chunk-PDFS5VFE.js} +17 -5
  20. package/dist/chunk-PDFS5VFE.js.map +1 -0
  21. package/dist/{chunk-QRK3JLFX.js → chunk-VXDPUOQ5.js} +381 -126
  22. package/dist/chunk-VXDPUOQ5.js.map +1 -0
  23. package/dist/{cli-MYMZ66EN.js → cli-MKXCNEMW.js} +14 -14
  24. package/dist/codealmanac.js +1 -1
  25. package/dist/{config-ML2RCR7J.js → config-F7FKEQ7F.js} +3 -3
  26. package/dist/doctor-37UH3HT5.js +17 -0
  27. package/dist/{hook-2NP3UE7U.js → hook-4SVX446M.js} +4 -2
  28. package/dist/{register-commands-XTK2G2FB.js → register-commands-2F6SXLDI.js} +28 -19
  29. package/dist/register-commands-2F6SXLDI.js.map +1 -0
  30. package/dist/uninstall-C62ZOK32.js +17 -0
  31. package/dist/{update-P2IPG7RO.js → update-2UGOFN5C.js} +3 -3
  32. package/package.json +4 -3
  33. package/dist/chunk-447U3GQJ.js.map +0 -1
  34. package/dist/chunk-BF2J4XTC.js.map +0 -1
  35. package/dist/chunk-CW4HRLMS.js.map +0 -1
  36. package/dist/chunk-H6QKCB7M.js.map +0 -1
  37. package/dist/chunk-P5WGG4FJ.js.map +0 -1
  38. package/dist/chunk-QRK3JLFX.js.map +0 -1
  39. package/dist/chunk-UU6FBRQO.js.map +0 -1
  40. package/dist/doctor-W5KQQLAX.js +0 -17
  41. package/dist/register-commands-XTK2G2FB.js.map +0 -1
  42. package/dist/uninstall-N7JY7ZV2.js +0 -15
  43. /package/dist/{agents-4Y7X24WW.js.map → agents-HYRWRHRX.js.map} +0 -0
  44. /package/dist/{chunk-TILAKDN6.js.map → chunk-HJ3WREGP.js.map} +0 -0
  45. /package/dist/{chunk-MRRX4UQB.js.map → chunk-ODJAAJGZ.js.map} +0 -0
  46. /package/dist/{cli-MYMZ66EN.js.map → cli-MKXCNEMW.js.map} +0 -0
  47. /package/dist/{config-ML2RCR7J.js.map → config-F7FKEQ7F.js.map} +0 -0
  48. /package/dist/{doctor-W5KQQLAX.js.map → doctor-37UH3HT5.js.map} +0 -0
  49. /package/dist/{hook-2NP3UE7U.js.map → hook-4SVX446M.js.map} +0 -0
  50. /package/dist/{uninstall-N7JY7ZV2.js.map → uninstall-C62ZOK32.js.map} +0 -0
  51. /package/dist/{update-P2IPG7RO.js.map → update-2UGOFN5C.js.map} +0 -0
@@ -1,54 +1,113 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runHookInstall
4
- } from "./chunk-447U3GQJ.js";
4
+ } from "./chunk-PDFS5VFE.js";
5
5
  import {
6
- UNAUTHENTICATED_MESSAGE,
7
6
  buildProviderModelChoices,
8
7
  buildProviderSetupView,
9
- checkClaudeAuth,
10
8
  parseAgentSelection
11
- } from "./chunk-BF2J4XTC.js";
9
+ } from "./chunk-J7DNV2DH.js";
12
10
  import {
11
+ disabledAgentProviderMessage,
12
+ formatEnabledAgentProviderList,
13
13
  isAgentProviderId,
14
+ isEnabledAgentProviderId,
14
15
  readConfig,
15
16
  writeConfig
16
- } from "./chunk-P5WGG4FJ.js";
17
+ } from "./chunk-3E7JNMTZ.js";
18
+
19
+ // src/agent/providers/codex-instructions.ts
20
+ import { existsSync } from "fs";
21
+ import { mkdir, readFile, writeFile } from "fs/promises";
22
+ import path from "path";
23
+ var CODEX_INSTRUCTIONS_START = "<!-- codealmanac:start -->";
24
+ var CODEX_INSTRUCTIONS_END = "<!-- codealmanac:end -->";
25
+ var CODEX_INSTRUCTIONS_BODY = `${CODEX_INSTRUCTIONS_START}
26
+ ## codealmanac
27
+
28
+ Use codealmanac before answering codebase questions:
29
+ - Search the wiki for relevant context.
30
+ - Read matching pages before making claims about the codebase.
31
+ - Update the wiki when implementation decisions, workflows, invariants, or gotchas change.
32
+
33
+ ${CODEX_INSTRUCTIONS_END}`;
34
+ async function ensureCodexInstructions(codexDir) {
35
+ await mkdir(codexDir, { recursive: true });
36
+ const agentsPath = await resolveCodexAgentsPath(codexDir);
37
+ let existing = "";
38
+ if (existsSync(agentsPath)) {
39
+ existing = await readFile(agentsPath, "utf8");
40
+ }
41
+ const next = upsertManagedBlock(
42
+ existing,
43
+ CODEX_INSTRUCTIONS_START,
44
+ CODEX_INSTRUCTIONS_END,
45
+ CODEX_INSTRUCTIONS_BODY
46
+ );
47
+ if (next === existing) return false;
48
+ await writeFile(agentsPath, next, "utf8");
49
+ return true;
50
+ }
51
+ async function resolveCodexAgentsPath(codexDir) {
52
+ const overridePath = path.join(codexDir, "AGENTS.override.md");
53
+ if (existsSync(overridePath)) {
54
+ try {
55
+ const body = await readFile(overridePath, "utf8");
56
+ if (body.trim().length > 0) return overridePath;
57
+ } catch {
58
+ }
59
+ }
60
+ return path.join(codexDir, "AGENTS.md");
61
+ }
62
+ function hasCodexInstructions(contents) {
63
+ return contents.includes(CODEX_INSTRUCTIONS_START) && contents.includes(CODEX_INSTRUCTIONS_END);
64
+ }
65
+ function upsertManagedBlock(contents, start, end, block) {
66
+ const startIndex = contents.indexOf(start);
67
+ const endIndex = contents.indexOf(end);
68
+ if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
69
+ const afterEnd = endIndex + end.length;
70
+ return `${contents.slice(0, startIndex)}${block}${contents.slice(afterEnd)}`;
71
+ }
72
+ const sep = contents.length === 0 ? "" : contents.endsWith("\n") ? "\n" : "\n\n";
73
+ return `${contents}${sep}${block}
74
+ `;
75
+ }
17
76
 
18
77
  // src/commands/setup.ts
19
- import { existsSync as existsSync2 } from "fs";
78
+ import { existsSync as existsSync3 } from "fs";
20
79
  import { spawn } from "child_process";
21
80
  import {
22
81
  copyFile,
23
- mkdir,
24
- readFile,
25
- writeFile
82
+ mkdir as mkdir2,
83
+ readFile as readFile2,
84
+ writeFile as writeFile2
26
85
  } from "fs/promises";
27
86
  import { createRequire as createRequire2 } from "module";
28
87
  import { homedir as homedir2 } from "os";
29
- import path3 from "path";
88
+ import path4 from "path";
30
89
  import { fileURLToPath as fileURLToPath2 } from "url";
31
90
 
32
91
  // src/commands/setup/install-path.ts
33
92
  import { execFile } from "child_process";
34
93
  import { createRequire } from "module";
35
94
  import { homedir } from "os";
36
- import path from "path";
95
+ import path2 from "path";
37
96
  import { fileURLToPath } from "url";
38
97
  function detectCurrentInstallPath() {
39
98
  try {
40
99
  const req = createRequire(import.meta.url);
41
100
  const here = fileURLToPath(import.meta.url);
42
- let dir = path.dirname(here);
101
+ let dir = path2.dirname(here);
43
102
  for (let i = 0; i < 6; i++) {
44
- const pkgPath = path.join(dir, "package.json");
103
+ const pkgPath = path2.join(dir, "package.json");
45
104
  try {
46
105
  const raw = req("fs").readFileSync(pkgPath, "utf-8");
47
106
  const pkg = JSON.parse(raw);
48
107
  if (pkg.name === "codealmanac") return dir;
49
108
  } catch {
50
109
  }
51
- const parent = path.dirname(dir);
110
+ const parent = path2.dirname(dir);
52
111
  if (parent === dir) break;
53
112
  dir = parent;
54
113
  }
@@ -59,8 +118,8 @@ function detectCurrentInstallPath() {
59
118
  function detectEphemeral(installPath) {
60
119
  if (installPath.length === 0) return false;
61
120
  const home = homedir();
62
- if (installPath.startsWith(path.join(home, ".npm", "_npx"))) return true;
63
- if (installPath.startsWith(path.join(home, ".local", "share", "pnpm", "dlx"))) return true;
121
+ if (installPath.startsWith(path2.join(home, ".npm", "_npx"))) return true;
122
+ if (installPath.startsWith(path2.join(home, ".local", "share", "pnpm", "dlx"))) return true;
64
123
  if (installPath.startsWith("/tmp/")) return true;
65
124
  if (installPath.startsWith("/var/folders/")) return true;
66
125
  return false;
@@ -87,8 +146,8 @@ function spawnGlobalInstall() {
87
146
  }
88
147
 
89
148
  // src/commands/setup/next-steps.ts
90
- import { existsSync, readdirSync } from "fs";
91
- import path2 from "path";
149
+ import { existsSync as existsSync2, readdirSync } from "fs";
150
+ import path3 from "path";
92
151
  var RST = "\x1B[0m";
93
152
  var BOLD = "\x1B[1m";
94
153
  var DIM = "\x1B[2m";
@@ -143,8 +202,8 @@ function countExistingPages(cwd) {
143
202
  try {
144
203
  let dir = cwd;
145
204
  for (let i = 0; i < 10; i++) {
146
- const pagesDir = path2.join(dir, ".almanac", "pages");
147
- if (existsSync(pagesDir)) {
205
+ const pagesDir = path3.join(dir, ".almanac", "pages");
206
+ if (existsSync2(pagesDir)) {
148
207
  try {
149
208
  const entries = readdirSync(pagesDir);
150
209
  return entries.filter((e) => e.endsWith(".md")).length;
@@ -152,7 +211,7 @@ function countExistingPages(cwd) {
152
211
  return 0;
153
212
  }
154
213
  }
155
- const parent = path2.dirname(dir);
214
+ const parent = path3.dirname(dir);
156
215
  if (parent === dir) break;
157
216
  dir = parent;
158
217
  }
@@ -192,7 +251,7 @@ function printBanner(out) {
192
251
  `);
193
252
  }
194
253
  out.write(`
195
- ${WHITE_BOLD2} Install the hook + agent guides${RST2}
254
+ ${WHITE_BOLD2} Set up your automatic codebase wiki${RST2}
196
255
  `);
197
256
  }
198
257
  function printBadge(out) {
@@ -225,16 +284,25 @@ async function runSetup(options = {}) {
225
284
  }
226
285
  printBanner(out);
227
286
  printBadge(out);
228
- const auth = await safeCheckAuth(options.spawnCli);
229
- reportAuth(out, auth);
230
- out.write(BAR + "\n");
231
- const agentChoice = await chooseDefaultAgent({
232
- out,
233
- interactive,
234
- requested: options.agent,
235
- requestedModel: options.model,
236
- spawnCli: options.spawnCli
237
- });
287
+ let agentChoice;
288
+ try {
289
+ agentChoice = await chooseDefaultAgent({
290
+ out,
291
+ interactive,
292
+ requested: options.agent,
293
+ requestedModel: options.model,
294
+ spawnCli: options.spawnCli
295
+ });
296
+ } catch (err) {
297
+ if (isSetupInterrupted(err)) {
298
+ return {
299
+ stdout: "",
300
+ stderr: "almanac: setup cancelled\n",
301
+ exitCode: 130
302
+ };
303
+ }
304
+ throw err;
305
+ }
238
306
  if (!agentChoice.ok) {
239
307
  return {
240
308
  stdout: "",
@@ -245,7 +313,7 @@ async function runSetup(options = {}) {
245
313
  }
246
314
  stepDone(
247
315
  out,
248
- `Default agent: ${WHITE_BOLD2}${agentChoice.provider}${RST2} (${agentChoice.model ?? "provider default"})`
316
+ `Agent: ${WHITE_BOLD2}${agentChoice.provider}${RST2} (${agentChoice.model ?? "provider default"})`
249
317
  );
250
318
  out.write(BAR + "\n");
251
319
  const ephem = options.installPath !== void 0 ? options.installPath !== null ? detectEphemeral(options.installPath) : false : detectEphemeral(detectCurrentInstallPath());
@@ -285,7 +353,7 @@ async function runSetup(options = {}) {
285
353
  } else if (interactive) {
286
354
  hookAction = await confirm(
287
355
  out,
288
- "Install auto-capture hooks for Claude, Codex, and Cursor?",
356
+ "Keep your codebase wiki up to date automatically?",
289
357
  true
290
358
  );
291
359
  }
@@ -305,10 +373,10 @@ async function runSetup(options = {}) {
305
373
  exitCode: res.exitCode
306
374
  };
307
375
  }
308
- hookResultLine = res.stdout.includes("already installed") ? `Auto-capture hooks ${DIM2}already installed${RST2}` : `Auto-capture hooks installed`;
376
+ hookResultLine = res.stdout.includes("already installed") ? `Auto-capture hooks ${DIM2}already installed${RST2}` : `Auto-capture installed`;
309
377
  stepDone(out, hookResultLine);
310
378
  } else {
311
- stepSkipped(out, `SessionEnd hook ${DIM2}skipped${RST2}`);
379
+ stepSkipped(out, `Auto-capture ${DIM2}skipped${RST2}`);
312
380
  }
313
381
  out.write(BAR + "\n");
314
382
  let guidesAction = "install";
@@ -317,7 +385,7 @@ async function runSetup(options = {}) {
317
385
  } else if (interactive) {
318
386
  guidesAction = await confirm(
319
387
  out,
320
- "Install the codealmanac usage guides into ~/.claude/ and import them from CLAUDE.md?",
388
+ "Add codealmanac instructions for your AI agents?",
321
389
  true
322
390
  );
323
391
  }
@@ -325,10 +393,11 @@ async function runSetup(options = {}) {
325
393
  if (guidesAction === "install") {
326
394
  try {
327
395
  const summary = await installGuides({
328
- claudeDir: options.claudeDir ?? path3.join(homedir2(), ".claude"),
396
+ claudeDir: options.claudeDir ?? path4.join(homedir2(), ".claude"),
397
+ codexDir: options.codexDir ?? path4.join(homedir2(), ".codex"),
329
398
  guidesDir: options.guidesDir ?? resolveGuidesDir()
330
399
  });
331
- guidesSummary = summary.anyChanges ? `Guides installed (${summary.filesWritten.join(", ")})` : `Guides ${DIM2}already installed${RST2}`;
400
+ guidesSummary = summary.anyChanges ? `Agent instructions added` : `Agent instructions ${DIM2}already added${RST2}`;
332
401
  stepDone(out, guidesSummary);
333
402
  } catch (err) {
334
403
  const msg = err instanceof Error ? err.message : String(err);
@@ -340,7 +409,7 @@ async function runSetup(options = {}) {
340
409
  };
341
410
  }
342
411
  } else {
343
- stepSkipped(out, `Guides ${DIM2}skipped${RST2}`);
412
+ stepSkipped(out, `Agent instructions ${DIM2}skipped${RST2}`);
344
413
  }
345
414
  out.write(BAR + "\n");
346
415
  stepDone(out, `${BLUE2}Setup complete${RST2}`);
@@ -349,13 +418,6 @@ async function runSetup(options = {}) {
349
418
  printNextSteps(out, existingPageCount);
350
419
  return { stdout: "", stderr: "", exitCode: 0 };
351
420
  }
352
- async function safeCheckAuth(spawnCli) {
353
- try {
354
- return await checkClaudeAuth(spawnCli);
355
- } catch {
356
- return { loggedIn: false };
357
- }
358
- }
359
421
  async function chooseDefaultAgent(args) {
360
422
  const config = await readConfig();
361
423
  let view = null;
@@ -364,34 +426,66 @@ async function chooseDefaultAgent(args) {
364
426
  view = await buildProviderSetupView({ config, spawnCli: args.spawnCli });
365
427
  }
366
428
  if (args.interactive && args.requested === void 0 && view !== null) {
367
- args.out.write(" Choose default agent:\n");
368
- view.choices.forEach((choice, index) => {
369
- const tag = choice.recommended ? " recommended" : "";
370
- const status = choice.ready ? "ready" : "not ready";
371
- const detail = choice.account ?? choice.fixCommand ?? choice.detail;
372
- args.out.write(
373
- ` ${index + 1}. ${choice.label.padEnd(6)} ${status.padEnd(9)}${tag} ${detail}
374
- `
375
- );
376
- });
377
- selected = (await promptText(
378
- args.out,
379
- "Default agent",
380
- view.recommendedProvider
381
- )).toLowerCase();
382
- const number = Number.parseInt(selected, 10);
383
- if (Number.isInteger(number) && number >= 1 && number <= view.choices.length) {
384
- selected = view.choices[number - 1]?.id ?? selected;
429
+ while (true) {
430
+ const choice = await selectChoice({
431
+ out: args.out,
432
+ title: "Choose your agent",
433
+ help: "Choose the AI agent codealmanac should use.",
434
+ choices: view.choices.map((choice2) => ({
435
+ value: choice2,
436
+ line: formatProviderChoice(choice2),
437
+ aliases: [choice2.id, choice2.label.toLowerCase()]
438
+ })),
439
+ defaultIndex: Math.max(
440
+ 0,
441
+ view.choices.findIndex(
442
+ (choice2) => choice2.id === view?.recommendedProvider
443
+ )
444
+ )
445
+ });
446
+ if (choice.ready) {
447
+ selected = choice.id;
448
+ break;
449
+ }
450
+ if (choice.readiness === "not-authenticated" && choice.fixCommand !== null) {
451
+ const command = choice.fixCommand.startsWith("run: ") ? choice.fixCommand.slice("run: ".length) : choice.fixCommand;
452
+ const runLogin = await confirm(
453
+ args.out,
454
+ `${choice.label} sign-in is needed. Run '${command}' now?`,
455
+ true
456
+ );
457
+ if (runLogin === "install") {
458
+ const login = await runLoginCommand(command);
459
+ if (!login.ok) {
460
+ stepActive(args.out, `${choice.label} login failed: ${login.error}`);
461
+ }
462
+ view = await buildProviderSetupView({ config, spawnCli: args.spawnCli });
463
+ const refreshed = view.choices.find((next) => next.id === choice.id);
464
+ if (refreshed?.ready === true) {
465
+ selected = refreshed.id;
466
+ break;
467
+ }
468
+ }
469
+ continue;
470
+ }
471
+ showUnavailableProvider(args.out, choice);
472
+ await waitForEnter(args.out, "Press Enter to choose a different agent.");
385
473
  }
386
474
  }
387
475
  const parsed = parseAgentSelection(selected);
388
476
  if (parsed.provider === null || !isAgentProviderId(parsed.provider)) {
389
477
  return {
390
478
  ok: false,
391
- error: `unknown agent '${selected}'. Expected one of: claude, codex, cursor.`
479
+ error: `unknown agent '${selected}'. Expected one of: ${formatEnabledAgentProviderList()}.`
392
480
  };
393
481
  }
394
482
  const provider = parsed.provider;
483
+ if (!isEnabledAgentProviderId(provider)) {
484
+ return {
485
+ ok: false,
486
+ error: disabledAgentProviderMessage(provider)
487
+ };
488
+ }
395
489
  let selectedChoice = view?.choices.find((choice) => choice.id === provider);
396
490
  if (args.interactive && selectedChoice !== void 0 && !selectedChoice.ready && selectedChoice.fixCommand?.startsWith("run: ") === true) {
397
491
  const command = selectedChoice.fixCommand.slice("run: ".length);
@@ -410,6 +504,12 @@ async function chooseDefaultAgent(args) {
410
504
  }
411
505
  }
412
506
  }
507
+ if (selectedChoice !== void 0 && !selectedChoice.ready) {
508
+ return {
509
+ ok: false,
510
+ error: `${selectedChoice.label} is not ready: ${selectedChoice.fixCommand ?? selectedChoice.detail}`
511
+ };
512
+ }
413
513
  const requestedModel = args.requestedModel ?? parsed.model;
414
514
  const model = requestedModel ?? await chooseProviderModel({
415
515
  out: args.out,
@@ -429,47 +529,210 @@ async function chooseDefaultAgent(args) {
429
529
  }
430
530
  }
431
531
  });
432
- if (!args.interactive || args.requested !== void 0) {
532
+ if ((!args.interactive || args.requested !== void 0) && selectedChoice !== void 0) {
433
533
  const detail = selectedChoice?.ready === true ? "ready" : selectedChoice?.fixCommand ?? selectedChoice?.detail ?? "status unknown";
434
534
  stepDone(args.out, `Agent readiness: ${detail}`);
435
535
  }
436
536
  return { ok: true, provider, model };
437
537
  }
438
538
  async function chooseProviderModel(args) {
439
- const choices = args.choice?.modelChoices ?? buildProviderModelChoices(args.provider, args.configuredModel);
539
+ const choices = args.choice?.modelChoices ?? await buildProviderModelChoices(args.provider, args.configuredModel);
440
540
  const recommended = choices.find((choice) => choice.recommended) ?? choices.find((choice) => choice.source === "provider-default");
441
541
  if (!args.interactive) {
442
542
  return args.configuredModel ?? recommended?.value ?? null;
443
543
  }
444
- args.out.write(` Choose ${args.provider} model:
445
- `);
446
- choices.forEach((choice, index) => {
447
- const marker = choice.recommended ? " recommended" : "";
448
- const current = choice.value === args.configuredModel ? " current" : "";
449
- args.out.write(
450
- ` ${index + 1}. ${choice.label}${marker}${current}
451
- `
452
- );
453
- });
454
544
  const currentIndex = choices.findIndex(
455
545
  (choice) => choice.value === args.configuredModel
456
546
  );
457
547
  const recommendedIndex = choices.findIndex((choice) => choice.recommended);
458
- const defaultIndex = currentIndex >= 0 ? currentIndex + 1 : recommendedIndex >= 0 ? recommendedIndex + 1 : 1;
459
- const selected = await promptText(args.out, "Model", String(defaultIndex));
460
- const number = Number.parseInt(selected, 10);
461
- let modelChoice;
462
- if (Number.isInteger(number) && number >= 1 && number <= choices.length) {
463
- modelChoice = choices[number - 1];
464
- } else {
465
- modelChoice = choices.find((choice) => choice.value === selected);
466
- }
548
+ const defaultIndex = Math.max(
549
+ 0,
550
+ currentIndex >= 0 ? currentIndex : recommendedIndex >= 0 ? recommendedIndex : 0
551
+ );
552
+ const modelChoice = await selectChoice({
553
+ out: args.out,
554
+ title: `Choose ${providerDisplayName(args.provider)} model`,
555
+ choices: choices.map((choice) => ({
556
+ value: choice,
557
+ line: formatModelChoice(choice, args.configuredModel),
558
+ aliases: choice.value === null ? ["default", "provider default"] : [String(choice.value)]
559
+ })),
560
+ defaultIndex
561
+ });
467
562
  if (modelChoice?.source === "custom") {
468
- const custom = await promptText(args.out, "Custom model id", "");
563
+ const custom = await promptText(args.out, "Model name", "");
469
564
  return custom.length > 0 ? custom : recommended?.value ?? null;
470
565
  }
471
566
  return modelChoice?.value ?? recommended?.value ?? null;
472
567
  }
568
+ async function selectChoice(args) {
569
+ const selected = clampIndex(args.defaultIndex, args.choices.length);
570
+ if (canUseRawSelect()) {
571
+ return await selectChoiceRaw({ ...args, defaultIndex: selected });
572
+ }
573
+ renderSelect(args.out, {
574
+ title: args.title,
575
+ help: args.help,
576
+ choices: args.choices,
577
+ selected,
578
+ raw: false
579
+ });
580
+ const answer = await promptText(args.out, "Select", String(selected + 1));
581
+ const index = Number.parseInt(answer, 10);
582
+ if (Number.isInteger(index) && index >= 1 && index <= args.choices.length) {
583
+ return args.choices[index - 1].value;
584
+ }
585
+ const normalized = answer.trim().toLowerCase();
586
+ const matched = args.choices.find(
587
+ (choice) => choice.aliases?.some((alias) => alias.toLowerCase() === normalized)
588
+ );
589
+ return (matched ?? args.choices[selected]).value;
590
+ }
591
+ async function selectChoiceRaw(args) {
592
+ return new Promise((resolve, reject) => {
593
+ let selected = args.defaultIndex;
594
+ let renderedLines = 0;
595
+ const input = process.stdin;
596
+ const render = () => {
597
+ if (renderedLines > 0) {
598
+ args.out.write(`\x1B[${renderedLines}A\x1B[0J`);
599
+ }
600
+ renderedLines = renderSelect(args.out, {
601
+ title: args.title,
602
+ help: args.help,
603
+ choices: args.choices,
604
+ selected,
605
+ raw: true
606
+ });
607
+ };
608
+ const cleanup = () => {
609
+ input.removeListener("data", onData);
610
+ input.setRawMode?.(false);
611
+ input.pause();
612
+ };
613
+ const onData = (chunk) => {
614
+ const key = chunk.toString("utf8");
615
+ if (key === "") {
616
+ cleanup();
617
+ args.out.write("\n");
618
+ reject(new SetupInterruptedError());
619
+ return;
620
+ }
621
+ if (key === "\r" || key === "\n") {
622
+ cleanup();
623
+ args.out.write("\n");
624
+ resolve(args.choices[selected].value);
625
+ return;
626
+ }
627
+ if (key === "\x1B[A") {
628
+ selected = selected === 0 ? args.choices.length - 1 : selected - 1;
629
+ render();
630
+ } else if (key === "\x1B[B") {
631
+ selected = selected === args.choices.length - 1 ? 0 : selected + 1;
632
+ render();
633
+ }
634
+ };
635
+ input.setRawMode?.(true);
636
+ input.resume();
637
+ input.on("data", onData);
638
+ render();
639
+ });
640
+ }
641
+ function renderSelect(out, args) {
642
+ let lines = 0;
643
+ out.write(` ${WHITE_BOLD2}${args.title}${RST2}
644
+ `);
645
+ lines++;
646
+ if (args.help !== void 0) {
647
+ out.write(` ${DIM2}${args.help}${RST2}
648
+ `);
649
+ lines++;
650
+ }
651
+ out.write("\n");
652
+ lines++;
653
+ args.choices.forEach((choice, index) => {
654
+ const pointer = index === args.selected ? `${BLUE2}\u203A${RST2}` : " ";
655
+ out.write(` ${pointer} ${choice.line}
656
+ `);
657
+ lines++;
658
+ });
659
+ const hint = args.raw ? `Use \u2191/\u2193 to move, Enter to select` : `Type a number or name, then press Enter`;
660
+ out.write(`
661
+ ${DIM2}${hint}${RST2}
662
+ `);
663
+ lines += 2;
664
+ return lines;
665
+ }
666
+ var SetupInterruptedError = class extends Error {
667
+ constructor() {
668
+ super("setup interrupted");
669
+ }
670
+ };
671
+ function isSetupInterrupted(err) {
672
+ return err instanceof SetupInterruptedError;
673
+ }
674
+ function canUseRawSelect() {
675
+ const input = process.stdin;
676
+ return process.stdin.isTTY === true && typeof input.setRawMode === "function";
677
+ }
678
+ function clampIndex(index, length) {
679
+ if (length <= 0) return 0;
680
+ if (index < 0) return 0;
681
+ if (index >= length) return length - 1;
682
+ return index;
683
+ }
684
+ function formatProviderChoice(choice) {
685
+ const status = providerStatusLabel(choice);
686
+ const detail = providerDetailLabel(choice);
687
+ const tag = choice.recommended ? ` ${DIM2}recommended${RST2}` : "";
688
+ return `${choice.label.padEnd(8)} ${status.padEnd(15)} ${detail}${tag}`;
689
+ }
690
+ function providerStatusLabel(choice) {
691
+ if (choice.ready) {
692
+ return choice.detail === "ANTHROPIC_API_KEY set" ? "API key set" : "signed in";
693
+ }
694
+ return choice.readiness === "missing" ? "not installed" : "sign in needed";
695
+ }
696
+ function providerDetailLabel(choice) {
697
+ if (choice.ready) return choice.account ?? choice.detail;
698
+ if (choice.fixCommand === null) return choice.detail;
699
+ return choice.fixCommand.startsWith("run: ") ? choice.fixCommand.slice("run: ".length) : choice.fixCommand;
700
+ }
701
+ function showUnavailableProvider(out, choice) {
702
+ if (choice.readiness === "missing") {
703
+ out.write(
704
+ `
705
+ ${WHITE_BOLD2}${choice.label} is not installed.${RST2}
706
+ ${providerDetailLabel(choice)}
707
+
708
+ `
709
+ );
710
+ return;
711
+ }
712
+ out.write(
713
+ `
714
+ ${WHITE_BOLD2}${choice.label} is not signed in.${RST2}
715
+ Run: ${providerDetailLabel(choice)}
716
+
717
+ `
718
+ );
719
+ }
720
+ function formatModelChoice(choice, configuredModel) {
721
+ const marker = choice.recommended ? ` ${DIM2}recommended${RST2}` : choice.value === configuredModel ? ` ${DIM2}current${RST2}` : "";
722
+ const label = choice.source === "provider-default" && choice.value !== null ? friendlyModelLabel(choice.value) : choice.label;
723
+ return `${label}${marker}`;
724
+ }
725
+ function friendlyModelLabel(value) {
726
+ if (value === "claude-sonnet-4-6") return "Sonnet 4.6";
727
+ if (value === "claude-opus-4-7") return "Opus 4.7";
728
+ if (value === "claude-haiku-4-5-20251001") return "Haiku 4.5";
729
+ return value;
730
+ }
731
+ function providerDisplayName(provider) {
732
+ if (provider === "claude") return "Claude";
733
+ if (provider === "codex") return "Codex";
734
+ return "Cursor";
735
+ }
473
736
  async function runLoginCommand(command) {
474
737
  return new Promise((resolve) => {
475
738
  const child = spawn(command, {
@@ -488,50 +751,35 @@ async function runLoginCommand(command) {
488
751
  });
489
752
  });
490
753
  }
491
- function reportAuth(out, auth) {
492
- if (auth.loggedIn) {
493
- const who = auth.email ?? "Claude account";
494
- const plan = auth.subscriptionType !== void 0 ? ` ${DIM2}(${auth.subscriptionType})${RST2}` : "";
495
- stepDone(out, `Claude auth: ${WHITE_BOLD2}${who}${RST2}${plan}`);
496
- return;
497
- }
498
- if (process.env.ANTHROPIC_API_KEY !== void 0 && process.env.ANTHROPIC_API_KEY.length > 0) {
499
- stepDone(out, `Claude auth: ${WHITE_BOLD2}ANTHROPIC_API_KEY${RST2} set`);
500
- return;
501
- }
502
- stepActive(out, `Claude auth: ${DIM2}not signed in${RST2}`);
503
- for (const line of UNAUTHENTICATED_MESSAGE.split("\n")) {
504
- out.write(` ${DIM2}\u2502 ${line}${RST2}
505
- `);
506
- }
507
- }
508
754
  async function installGuides(options) {
509
- await mkdir(options.claudeDir, { recursive: true });
510
- const srcMini = path3.join(options.guidesDir, "mini.md");
511
- const srcRef = path3.join(options.guidesDir, "reference.md");
512
- if (!existsSync2(srcMini)) {
755
+ await mkdir2(options.claudeDir, { recursive: true });
756
+ const srcMini = path4.join(options.guidesDir, "mini.md");
757
+ const srcRef = path4.join(options.guidesDir, "reference.md");
758
+ if (!existsSync3(srcMini)) {
513
759
  throw new Error(`missing bundled guide: ${srcMini}`);
514
760
  }
515
- if (!existsSync2(srcRef)) {
761
+ if (!existsSync3(srcRef)) {
516
762
  throw new Error(`missing bundled guide: ${srcRef}`);
517
763
  }
518
- const destMini = path3.join(options.claudeDir, "codealmanac.md");
519
- const destRef = path3.join(options.claudeDir, "codealmanac-reference.md");
764
+ const destMini = path4.join(options.claudeDir, "codealmanac.md");
765
+ const destRef = path4.join(options.claudeDir, "codealmanac-reference.md");
520
766
  const miniChanged = await copyIfChanged(srcMini, destMini);
521
767
  const refChanged = await copyIfChanged(srcRef, destRef);
522
- const claudeMd = path3.join(options.claudeDir, "CLAUDE.md");
768
+ const claudeMd = path4.join(options.claudeDir, "CLAUDE.md");
523
769
  const importChanged = await ensureImport(claudeMd);
770
+ const codexChanged = await ensureCodexInstructions(options.codexDir);
524
771
  const filesWritten = [];
525
772
  if (miniChanged) filesWritten.push("codealmanac.md");
526
773
  if (refChanged) filesWritten.push("codealmanac-reference.md");
527
774
  if (importChanged) filesWritten.push("CLAUDE.md");
775
+ if (codexChanged) filesWritten.push("AGENTS.md");
528
776
  return { anyChanges: filesWritten.length > 0, filesWritten };
529
777
  }
530
778
  async function copyIfChanged(src, dest) {
531
- const srcBytes = await readFile(src);
532
- if (existsSync2(dest)) {
779
+ const srcBytes = await readFile2(src);
780
+ if (existsSync3(dest)) {
533
781
  try {
534
- const destBytes = await readFile(dest);
782
+ const destBytes = await readFile2(dest);
535
783
  if (srcBytes.equals(destBytes)) return false;
536
784
  } catch {
537
785
  }
@@ -542,14 +790,14 @@ async function copyIfChanged(src, dest) {
542
790
  var IMPORT_LINE = "@~/.claude/codealmanac.md";
543
791
  async function ensureImport(claudeMdPath) {
544
792
  let existing = "";
545
- if (existsSync2(claudeMdPath)) {
546
- existing = await readFile(claudeMdPath, "utf8");
793
+ if (existsSync3(claudeMdPath)) {
794
+ existing = await readFile2(claudeMdPath, "utf8");
547
795
  }
548
796
  if (hasImportLine(existing)) return false;
549
797
  const sep = existing.length === 0 ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
550
798
  const body = `${existing}${sep}${IMPORT_LINE}
551
799
  `;
552
- await writeFile(claudeMdPath, body, "utf8");
800
+ await writeFile2(claudeMdPath, body, "utf8");
553
801
  return true;
554
802
  }
555
803
  function hasImportLine(contents) {
@@ -599,14 +847,17 @@ function promptText(out, question, defaultValue) {
599
847
  process.stdin.on("data", onData);
600
848
  });
601
849
  }
850
+ async function waitForEnter(out, message) {
851
+ await promptText(out, message, "");
852
+ }
602
853
  function resolveGuidesDir() {
603
- const here = path3.dirname(fileURLToPath2(import.meta.url));
854
+ const here = path4.dirname(fileURLToPath2(import.meta.url));
604
855
  const candidates = [
605
- path3.resolve(here, "..", "guides"),
856
+ path4.resolve(here, "..", "guides"),
606
857
  // dist layout
607
- path3.resolve(here, "..", "..", "guides"),
858
+ path4.resolve(here, "..", "..", "guides"),
608
859
  // src layout
609
- path3.resolve(here, "..", "..", "..", "guides")
860
+ path4.resolve(here, "..", "..", "..", "guides")
610
861
  ];
611
862
  for (const dir of candidates) {
612
863
  if (looksLikeGuidesDir(dir)) return dir;
@@ -614,7 +865,7 @@ function resolveGuidesDir() {
614
865
  try {
615
866
  const require2 = createRequire2(import.meta.url);
616
867
  const pkgJson = require2.resolve("codealmanac/package.json");
617
- const guides = path3.join(path3.dirname(pkgJson), "guides");
868
+ const guides = path4.join(path4.dirname(pkgJson), "guides");
618
869
  if (looksLikeGuidesDir(guides)) return guides;
619
870
  } catch {
620
871
  }
@@ -623,11 +874,15 @@ function resolveGuidesDir() {
623
874
  );
624
875
  }
625
876
  function looksLikeGuidesDir(dir) {
626
- return existsSync2(path3.join(dir, "mini.md"));
877
+ return existsSync3(path4.join(dir, "mini.md"));
627
878
  }
628
879
 
629
880
  export {
881
+ CODEX_INSTRUCTIONS_START,
882
+ CODEX_INSTRUCTIONS_END,
883
+ resolveCodexAgentsPath,
884
+ hasCodexInstructions,
630
885
  runSetup,
631
886
  IMPORT_LINE
632
887
  };
633
- //# sourceMappingURL=chunk-QRK3JLFX.js.map
888
+ //# sourceMappingURL=chunk-VXDPUOQ5.js.map