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.
- package/COMMERCIAL.md +9 -0
- package/LICENSE +133 -21
- package/README.md +2 -2
- package/dist/{agents-4Y7X24WW.js → agents-HYRWRHRX.js} +4 -4
- package/dist/{chunk-TT6ZP4GS.js → chunk-2BNDNGUR.js} +8 -4
- package/dist/{chunk-TT6ZP4GS.js.map → chunk-2BNDNGUR.js.map} +1 -1
- package/dist/{chunk-P5WGG4FJ.js → chunk-3E7JNMTZ.js} +28 -3
- package/dist/chunk-3E7JNMTZ.js.map +1 -0
- package/dist/{chunk-CW4HRLMS.js → chunk-DW32TL5W.js} +53 -11
- package/dist/chunk-DW32TL5W.js.map +1 -0
- package/dist/{chunk-UU6FBRQO.js → chunk-GPFVEF6V.js} +24 -6
- package/dist/chunk-GPFVEF6V.js.map +1 -0
- package/dist/{chunk-TILAKDN6.js → chunk-HJ3WREGP.js} +2 -2
- package/dist/{chunk-BF2J4XTC.js → chunk-J7DNV2DH.js} +219 -26
- package/dist/chunk-J7DNV2DH.js.map +1 -0
- package/dist/{chunk-H6QKCB7M.js → chunk-K2JBCB7R.js} +43 -7
- package/dist/chunk-K2JBCB7R.js.map +1 -0
- package/dist/{chunk-MRRX4UQB.js → chunk-ODJAAJGZ.js} +2 -2
- package/dist/{chunk-447U3GQJ.js → chunk-PDFS5VFE.js} +17 -5
- package/dist/chunk-PDFS5VFE.js.map +1 -0
- package/dist/{chunk-QRK3JLFX.js → chunk-VXDPUOQ5.js} +381 -126
- package/dist/chunk-VXDPUOQ5.js.map +1 -0
- package/dist/{cli-MYMZ66EN.js → cli-MKXCNEMW.js} +14 -14
- package/dist/codealmanac.js +1 -1
- package/dist/{config-ML2RCR7J.js → config-F7FKEQ7F.js} +3 -3
- package/dist/doctor-37UH3HT5.js +17 -0
- package/dist/{hook-2NP3UE7U.js → hook-4SVX446M.js} +4 -2
- package/dist/{register-commands-XTK2G2FB.js → register-commands-2F6SXLDI.js} +28 -19
- package/dist/register-commands-2F6SXLDI.js.map +1 -0
- package/dist/uninstall-C62ZOK32.js +17 -0
- package/dist/{update-P2IPG7RO.js → update-2UGOFN5C.js} +3 -3
- package/package.json +4 -3
- package/dist/chunk-447U3GQJ.js.map +0 -1
- package/dist/chunk-BF2J4XTC.js.map +0 -1
- package/dist/chunk-CW4HRLMS.js.map +0 -1
- package/dist/chunk-H6QKCB7M.js.map +0 -1
- package/dist/chunk-P5WGG4FJ.js.map +0 -1
- package/dist/chunk-QRK3JLFX.js.map +0 -1
- package/dist/chunk-UU6FBRQO.js.map +0 -1
- package/dist/doctor-W5KQQLAX.js +0 -17
- package/dist/register-commands-XTK2G2FB.js.map +0 -1
- package/dist/uninstall-N7JY7ZV2.js +0 -15
- /package/dist/{agents-4Y7X24WW.js.map → agents-HYRWRHRX.js.map} +0 -0
- /package/dist/{chunk-TILAKDN6.js.map → chunk-HJ3WREGP.js.map} +0 -0
- /package/dist/{chunk-MRRX4UQB.js.map → chunk-ODJAAJGZ.js.map} +0 -0
- /package/dist/{cli-MYMZ66EN.js.map → cli-MKXCNEMW.js.map} +0 -0
- /package/dist/{config-ML2RCR7J.js.map → config-F7FKEQ7F.js.map} +0 -0
- /package/dist/{doctor-W5KQQLAX.js.map → doctor-37UH3HT5.js.map} +0 -0
- /package/dist/{hook-2NP3UE7U.js.map → hook-4SVX446M.js.map} +0 -0
- /package/dist/{uninstall-N7JY7ZV2.js.map → uninstall-C62ZOK32.js.map} +0 -0
- /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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
101
|
+
let dir = path2.dirname(here);
|
|
43
102
|
for (let i = 0; i < 6; i++) {
|
|
44
|
-
const pkgPath =
|
|
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 =
|
|
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(
|
|
63
|
-
if (installPath.startsWith(
|
|
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
|
|
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 =
|
|
147
|
-
if (
|
|
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 =
|
|
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}
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
`
|
|
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
|
-
"
|
|
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
|
|
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, `
|
|
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
|
-
"
|
|
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 ??
|
|
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 ? `
|
|
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, `
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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:
|
|
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 =
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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, "
|
|
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
|
|
510
|
-
const srcMini =
|
|
511
|
-
const srcRef =
|
|
512
|
-
if (!
|
|
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 (!
|
|
761
|
+
if (!existsSync3(srcRef)) {
|
|
516
762
|
throw new Error(`missing bundled guide: ${srcRef}`);
|
|
517
763
|
}
|
|
518
|
-
const destMini =
|
|
519
|
-
const destRef =
|
|
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 =
|
|
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
|
|
532
|
-
if (
|
|
779
|
+
const srcBytes = await readFile2(src);
|
|
780
|
+
if (existsSync3(dest)) {
|
|
533
781
|
try {
|
|
534
|
-
const destBytes = await
|
|
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 (
|
|
546
|
-
existing = await
|
|
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
|
|
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 =
|
|
854
|
+
const here = path4.dirname(fileURLToPath2(import.meta.url));
|
|
604
855
|
const candidates = [
|
|
605
|
-
|
|
856
|
+
path4.resolve(here, "..", "guides"),
|
|
606
857
|
// dist layout
|
|
607
|
-
|
|
858
|
+
path4.resolve(here, "..", "..", "guides"),
|
|
608
859
|
// src layout
|
|
609
|
-
|
|
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 =
|
|
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
|
|
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-
|
|
888
|
+
//# sourceMappingURL=chunk-VXDPUOQ5.js.map
|