codealmanac 0.1.6 → 0.1.7
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/chunk-2JJTTN7P.js +539 -0
- package/dist/chunk-2JJTTN7P.js.map +1 -0
- package/dist/chunk-3C5SY5SE.js +1239 -0
- package/dist/chunk-3C5SY5SE.js.map +1 -0
- package/dist/chunk-4CODZRHH.js +19 -0
- package/dist/chunk-4CODZRHH.js.map +1 -0
- package/dist/chunk-7JUX4ADQ.js +38 -0
- package/dist/chunk-7JUX4ADQ.js.map +1 -0
- package/dist/chunk-A6PUCAVJ.js +145 -0
- package/dist/chunk-A6PUCAVJ.js.map +1 -0
- package/dist/chunk-AXFPUHBN.js +227 -0
- package/dist/chunk-AXFPUHBN.js.map +1 -0
- package/dist/chunk-FM3VRDK7.js +20 -0
- package/dist/chunk-FM3VRDK7.js.map +1 -0
- package/dist/chunk-H6WU6PYH.js +441 -0
- package/dist/chunk-H6WU6PYH.js.map +1 -0
- package/dist/chunk-P3LDTCLB.js +34 -0
- package/dist/chunk-P3LDTCLB.js.map +1 -0
- package/dist/chunk-QHQ6YH7U.js +81 -0
- package/dist/chunk-QHQ6YH7U.js.map +1 -0
- package/dist/chunk-Z4MWLVS2.js +355 -0
- package/dist/chunk-Z4MWLVS2.js.map +1 -0
- package/dist/chunk-Z6MBJ3D2.js +203 -0
- package/dist/chunk-Z6MBJ3D2.js.map +1 -0
- package/dist/cli-AIH5QQ5H.js +393 -0
- package/dist/cli-AIH5QQ5H.js.map +1 -0
- package/dist/codealmanac.js +32 -5
- package/dist/codealmanac.js.map +1 -1
- package/dist/doctor-6FN5JO5F.js +15 -0
- package/dist/doctor-6FN5JO5F.js.map +1 -0
- package/dist/hook-CRJMWSSO.js +12 -0
- package/dist/hook-CRJMWSSO.js.map +1 -0
- package/dist/register-commands-PZMQNGCH.js +2644 -0
- package/dist/register-commands-PZMQNGCH.js.map +1 -0
- package/dist/uninstall-NBEZNNKM.js +12 -0
- package/dist/uninstall-NBEZNNKM.js.map +1 -0
- package/dist/update-IL243I4E.js +10 -0
- package/dist/update-IL243I4E.js.map +1 -0
- package/dist/wiki-EHZ7LG7R.js +238 -0
- package/dist/wiki-EHZ7LG7R.js.map +1 -0
- package/package.json +1 -1
- package/dist/cli-GTEC5PC7.js +0 -6237
- package/dist/cli-GTEC5PC7.js.map +0 -1
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
runHookInstall
|
|
4
|
+
} from "./chunk-Z4MWLVS2.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/setup.ts
|
|
7
|
+
import { existsSync as existsSync2 } from "fs";
|
|
8
|
+
import {
|
|
9
|
+
copyFile,
|
|
10
|
+
mkdir,
|
|
11
|
+
readFile,
|
|
12
|
+
writeFile
|
|
13
|
+
} from "fs/promises";
|
|
14
|
+
import { createRequire as createRequire3 } from "module";
|
|
15
|
+
import { homedir as homedir2 } from "os";
|
|
16
|
+
import path3 from "path";
|
|
17
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
18
|
+
|
|
19
|
+
// src/agent/auth.ts
|
|
20
|
+
import { spawn } from "child_process";
|
|
21
|
+
import { createRequire } from "module";
|
|
22
|
+
import { dirname, join } from "path";
|
|
23
|
+
var AUTH_TIMEOUT_MS = 1e4;
|
|
24
|
+
function resolveCliJsPath() {
|
|
25
|
+
const require2 = createRequire(import.meta.url);
|
|
26
|
+
const entry = require2.resolve("@anthropic-ai/claude-agent-sdk");
|
|
27
|
+
return join(dirname(entry), "cli.js");
|
|
28
|
+
}
|
|
29
|
+
var defaultSpawnCli = (args) => {
|
|
30
|
+
const cliPath = resolveCliJsPath();
|
|
31
|
+
const child = spawn(process.execPath, [cliPath, ...args], {
|
|
32
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
33
|
+
});
|
|
34
|
+
return child;
|
|
35
|
+
};
|
|
36
|
+
async function checkClaudeAuth(spawnCli = defaultSpawnCli) {
|
|
37
|
+
let child;
|
|
38
|
+
try {
|
|
39
|
+
child = spawnCli(["auth", "status", "--json"]);
|
|
40
|
+
} catch {
|
|
41
|
+
return { loggedIn: false };
|
|
42
|
+
}
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
let stdout = "";
|
|
45
|
+
let stderr = "";
|
|
46
|
+
let settled = false;
|
|
47
|
+
const settle = (value) => {
|
|
48
|
+
if (settled) return;
|
|
49
|
+
settled = true;
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
resolve(value);
|
|
52
|
+
};
|
|
53
|
+
const timer = setTimeout(() => {
|
|
54
|
+
try {
|
|
55
|
+
child.kill("SIGTERM");
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
settle({ loggedIn: false });
|
|
59
|
+
}, AUTH_TIMEOUT_MS);
|
|
60
|
+
child.stdout.on("data", (data) => {
|
|
61
|
+
stdout += data.toString();
|
|
62
|
+
});
|
|
63
|
+
child.stderr.on("data", (data) => {
|
|
64
|
+
stderr += data.toString();
|
|
65
|
+
});
|
|
66
|
+
child.on("error", () => {
|
|
67
|
+
settle({ loggedIn: false });
|
|
68
|
+
});
|
|
69
|
+
child.on("close", (code) => {
|
|
70
|
+
if (code !== 0 && stdout.trim().length === 0) {
|
|
71
|
+
void stderr;
|
|
72
|
+
settle({ loggedIn: false });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(stdout.trim());
|
|
77
|
+
const loggedIn = parsed.loggedIn === true;
|
|
78
|
+
const out = { loggedIn };
|
|
79
|
+
if (typeof parsed.email === "string") out.email = parsed.email;
|
|
80
|
+
if (typeof parsed.subscriptionType === "string") {
|
|
81
|
+
out.subscriptionType = parsed.subscriptionType;
|
|
82
|
+
}
|
|
83
|
+
if (typeof parsed.authMethod === "string") {
|
|
84
|
+
out.authMethod = parsed.authMethod;
|
|
85
|
+
}
|
|
86
|
+
settle(out);
|
|
87
|
+
} catch {
|
|
88
|
+
settle({ loggedIn: false });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
var UNAUTHENTICATED_MESSAGE = "not authenticated to Claude.\n\nOption 1 \u2014 use your Claude subscription (Pro/Max):\n claude auth login --claudeai\n\nOption 2 \u2014 use a pay-per-token API key:\n Get one at https://console.anthropic.com\n export ANTHROPIC_API_KEY=sk-ant-...\n\nVerify with: claude auth status";
|
|
94
|
+
async function assertClaudeAuth(spawnCli = defaultSpawnCli) {
|
|
95
|
+
const status = await checkClaudeAuth(spawnCli);
|
|
96
|
+
if (status.loggedIn) {
|
|
97
|
+
return status;
|
|
98
|
+
}
|
|
99
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
100
|
+
if (apiKey !== void 0 && apiKey.length > 0) {
|
|
101
|
+
return { loggedIn: true, authMethod: "apiKey" };
|
|
102
|
+
}
|
|
103
|
+
const err = new Error(UNAUTHENTICATED_MESSAGE);
|
|
104
|
+
err.code = "CLAUDE_AUTH_MISSING";
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/commands/setup/install-path.ts
|
|
109
|
+
import { execFile } from "child_process";
|
|
110
|
+
import { createRequire as createRequire2 } from "module";
|
|
111
|
+
import { homedir } from "os";
|
|
112
|
+
import path from "path";
|
|
113
|
+
import { fileURLToPath } from "url";
|
|
114
|
+
function detectCurrentInstallPath() {
|
|
115
|
+
try {
|
|
116
|
+
const req = createRequire2(import.meta.url);
|
|
117
|
+
const here = fileURLToPath(import.meta.url);
|
|
118
|
+
let dir = path.dirname(here);
|
|
119
|
+
for (let i = 0; i < 6; i++) {
|
|
120
|
+
const pkgPath = path.join(dir, "package.json");
|
|
121
|
+
try {
|
|
122
|
+
const raw = req("fs").readFileSync(pkgPath, "utf-8");
|
|
123
|
+
const pkg = JSON.parse(raw);
|
|
124
|
+
if (pkg.name === "codealmanac") return dir;
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
const parent = path.dirname(dir);
|
|
128
|
+
if (parent === dir) break;
|
|
129
|
+
dir = parent;
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
return "";
|
|
134
|
+
}
|
|
135
|
+
function detectEphemeral(installPath) {
|
|
136
|
+
if (installPath.length === 0) return false;
|
|
137
|
+
const home = homedir();
|
|
138
|
+
if (installPath.startsWith(path.join(home, ".npm", "_npx"))) return true;
|
|
139
|
+
if (installPath.startsWith(path.join(home, ".local", "share", "pnpm", "dlx"))) return true;
|
|
140
|
+
if (installPath.startsWith("/tmp/")) return true;
|
|
141
|
+
if (installPath.startsWith("/var/folders/")) return true;
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
function spawnGlobalInstall() {
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
execFile(
|
|
147
|
+
"npm",
|
|
148
|
+
["install", "-g", "codealmanac@latest"],
|
|
149
|
+
{ shell: false },
|
|
150
|
+
(err, _stdout, stderr) => {
|
|
151
|
+
if (err !== null) {
|
|
152
|
+
reject(
|
|
153
|
+
new Error(
|
|
154
|
+
stderr.length > 0 ? stderr.trim().split("\n")[0] ?? err.message : err.message
|
|
155
|
+
)
|
|
156
|
+
);
|
|
157
|
+
} else {
|
|
158
|
+
resolve();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/commands/setup/next-steps.ts
|
|
166
|
+
import { existsSync, readdirSync } from "fs";
|
|
167
|
+
import path2 from "path";
|
|
168
|
+
var RST = "\x1B[0m";
|
|
169
|
+
var BOLD = "\x1B[1m";
|
|
170
|
+
var DIM = "\x1B[2m";
|
|
171
|
+
var WHITE_BOLD = "\x1B[1;37m";
|
|
172
|
+
var BLUE = "\x1B[38;5;75m";
|
|
173
|
+
var BLUE_DIM = "\x1B[38;5;69m";
|
|
174
|
+
function printNextSteps(out, existingPageCount) {
|
|
175
|
+
const innerW = 62;
|
|
176
|
+
const vis = (s) => s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
177
|
+
const row = (content) => {
|
|
178
|
+
const padding = Math.max(0, innerW - vis(content));
|
|
179
|
+
return ` ${BLUE_DIM}\u2502${RST}${content}${" ".repeat(padding)}${BLUE_DIM}\u2502${RST}
|
|
180
|
+
`;
|
|
181
|
+
};
|
|
182
|
+
const empty = row("");
|
|
183
|
+
out.write(` ${BLUE_DIM}\u256D${"\u2500".repeat(innerW)}\u256E${RST}
|
|
184
|
+
`);
|
|
185
|
+
out.write(empty);
|
|
186
|
+
out.write(row(` ${WHITE_BOLD}Next steps${RST}`));
|
|
187
|
+
out.write(empty);
|
|
188
|
+
if (existingPageCount > 0) {
|
|
189
|
+
out.write(
|
|
190
|
+
row(
|
|
191
|
+
` ${BLUE}\u25C7${RST} This repo already has a wiki ${DIM}(${existingPageCount} page${existingPageCount === 1 ? "" : "s"})${RST}`
|
|
192
|
+
)
|
|
193
|
+
);
|
|
194
|
+
out.write(empty);
|
|
195
|
+
out.write(row(` ${BLUE}1.${RST} Start querying your wiki:`));
|
|
196
|
+
out.write(row(` ${BOLD}almanac search --mentions <file>${RST}`));
|
|
197
|
+
out.write(
|
|
198
|
+
row(` ${BLUE}2.${RST} Work normally \u2014 capture runs on session end`)
|
|
199
|
+
);
|
|
200
|
+
} else {
|
|
201
|
+
out.write(
|
|
202
|
+
row(` ${BLUE}1.${RST} ${BOLD}cd${RST} into a repo you want to document`)
|
|
203
|
+
);
|
|
204
|
+
out.write(
|
|
205
|
+
row(
|
|
206
|
+
` ${BLUE}2.${RST} ${BOLD}almanac bootstrap${RST} ${DIM}# scaffold the wiki${RST}`
|
|
207
|
+
)
|
|
208
|
+
);
|
|
209
|
+
out.write(
|
|
210
|
+
row(` ${BLUE}3.${RST} Work normally \u2014 capture runs on session end`)
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
out.write(empty);
|
|
214
|
+
out.write(` ${BLUE_DIM}\u2570${"\u2500".repeat(innerW)}\u256F${RST}
|
|
215
|
+
|
|
216
|
+
`);
|
|
217
|
+
}
|
|
218
|
+
function countExistingPages(cwd) {
|
|
219
|
+
try {
|
|
220
|
+
let dir = cwd;
|
|
221
|
+
for (let i = 0; i < 10; i++) {
|
|
222
|
+
const pagesDir = path2.join(dir, ".almanac", "pages");
|
|
223
|
+
if (existsSync(pagesDir)) {
|
|
224
|
+
try {
|
|
225
|
+
const entries = readdirSync(pagesDir);
|
|
226
|
+
return entries.filter((e) => e.endsWith(".md")).length;
|
|
227
|
+
} catch {
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const parent = path2.dirname(dir);
|
|
232
|
+
if (parent === dir) break;
|
|
233
|
+
dir = parent;
|
|
234
|
+
}
|
|
235
|
+
} catch {
|
|
236
|
+
}
|
|
237
|
+
return 0;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/commands/setup.ts
|
|
241
|
+
var RST2 = "\x1B[0m";
|
|
242
|
+
var DIM2 = "\x1B[2m";
|
|
243
|
+
var WHITE_BOLD2 = "\x1B[1;37m";
|
|
244
|
+
var BLUE2 = "\x1B[38;5;75m";
|
|
245
|
+
var ACCENT_BG = "\x1B[48;5;252m\x1B[38;5;16m";
|
|
246
|
+
var GRADIENT = [
|
|
247
|
+
"\x1B[38;5;255m",
|
|
248
|
+
"\x1B[38;5;253m",
|
|
249
|
+
"\x1B[38;5;251m",
|
|
250
|
+
"\x1B[38;5;249m",
|
|
251
|
+
"\x1B[38;5;246m",
|
|
252
|
+
"\x1B[38;5;243m"
|
|
253
|
+
];
|
|
254
|
+
var LOGO_LINES = [
|
|
255
|
+
" ___ ___ ___ ___ _ _ __ __ _ _ _ _ ___ ",
|
|
256
|
+
" / __/ _ \\| \\| __| /_\\ | | | \\/ | /_\\ | \\| | /_\\ / __|",
|
|
257
|
+
"| (_| (_) | |) | _| / _ \\| |__| |\\/| |/ _ \\| .` |/ _ \\ (__ ",
|
|
258
|
+
" \\___\\___/|___/|___/_/ \\_\\____|_| |_/_/ \\_\\_|\\_/_/ \\_\\___|",
|
|
259
|
+
" ",
|
|
260
|
+
" a living wiki for codebases, for your agent "
|
|
261
|
+
];
|
|
262
|
+
var BAR = ` ${DIM2}\u2502${RST2}`;
|
|
263
|
+
function printBanner(out) {
|
|
264
|
+
out.write("\n");
|
|
265
|
+
for (let i = 0; i < LOGO_LINES.length; i++) {
|
|
266
|
+
const color = GRADIENT[Math.min(i, GRADIENT.length - 1)] ?? "";
|
|
267
|
+
out.write(`${color}${LOGO_LINES[i]}${RST2}
|
|
268
|
+
`);
|
|
269
|
+
}
|
|
270
|
+
out.write(`
|
|
271
|
+
${WHITE_BOLD2} Install the hook + agent guides${RST2}
|
|
272
|
+
`);
|
|
273
|
+
}
|
|
274
|
+
function printBadge(out) {
|
|
275
|
+
out.write(`
|
|
276
|
+
${ACCENT_BG} codealmanac ${RST2}
|
|
277
|
+
|
|
278
|
+
`);
|
|
279
|
+
}
|
|
280
|
+
function stepDone(out, msg) {
|
|
281
|
+
out.write(` ${BLUE2}\u25C7${RST2} ${msg}
|
|
282
|
+
`);
|
|
283
|
+
}
|
|
284
|
+
function stepActive(out, msg) {
|
|
285
|
+
out.write(` ${BLUE2}\u25C6${RST2} ${msg}
|
|
286
|
+
`);
|
|
287
|
+
}
|
|
288
|
+
function stepSkipped(out, msg) {
|
|
289
|
+
out.write(` ${DIM2}\u25CB ${msg}${RST2}
|
|
290
|
+
`);
|
|
291
|
+
}
|
|
292
|
+
async function runSetup(options = {}) {
|
|
293
|
+
const out = options.stdout ?? process.stdout;
|
|
294
|
+
const isTTY = options.isTTY ?? process.stdin.isTTY === true;
|
|
295
|
+
const interactive = isTTY && options.yes !== true;
|
|
296
|
+
if (options.skipHook === true && options.skipGuides === true) {
|
|
297
|
+
out.write(
|
|
298
|
+
"codealmanac: nothing to install \u2014 use --help to see what setup does\n"
|
|
299
|
+
);
|
|
300
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
301
|
+
}
|
|
302
|
+
printBanner(out);
|
|
303
|
+
printBadge(out);
|
|
304
|
+
const auth = await safeCheckAuth(options.spawnCli);
|
|
305
|
+
reportAuth(out, auth);
|
|
306
|
+
out.write(BAR + "\n");
|
|
307
|
+
const ephem = options.installPath !== void 0 ? options.installPath !== null ? detectEphemeral(options.installPath) : false : detectEphemeral(detectCurrentInstallPath());
|
|
308
|
+
if (ephem) {
|
|
309
|
+
let globalAction = "install";
|
|
310
|
+
if (interactive) {
|
|
311
|
+
globalAction = await confirm(
|
|
312
|
+
out,
|
|
313
|
+
`Running from an ephemeral npx location. Install globally so 'almanac' stays on PATH?`,
|
|
314
|
+
true
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
if (globalAction === "install") {
|
|
318
|
+
stepActive(out, "Installing codealmanac globally\u2026");
|
|
319
|
+
try {
|
|
320
|
+
await (options.spawnGlobalInstall ?? spawnGlobalInstall)();
|
|
321
|
+
stepDone(out, "codealmanac installed globally (almanac now on PATH)");
|
|
322
|
+
} catch (err) {
|
|
323
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
324
|
+
stepActive(out, `Global install failed: ${msg}`);
|
|
325
|
+
out.write(
|
|
326
|
+
` ${DIM2}You can retry manually: npm install -g codealmanac${RST2}
|
|
327
|
+
`
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
stepSkipped(
|
|
332
|
+
out,
|
|
333
|
+
`Global install ${DIM2}skipped \u2014 almanac will not be on PATH after this session${RST2}`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
out.write(BAR + "\n");
|
|
337
|
+
}
|
|
338
|
+
let hookAction = "install";
|
|
339
|
+
if (options.skipHook === true) {
|
|
340
|
+
hookAction = "skip";
|
|
341
|
+
} else if (interactive) {
|
|
342
|
+
hookAction = await confirm(
|
|
343
|
+
out,
|
|
344
|
+
"Install the SessionEnd hook so capture runs at the end of every Claude Code session?",
|
|
345
|
+
true
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
let hookResultLine = "";
|
|
349
|
+
if (hookAction === "install") {
|
|
350
|
+
const res = await runHookInstall({
|
|
351
|
+
settingsPath: options.settingsPath,
|
|
352
|
+
hookScriptPath: options.hookScriptPath,
|
|
353
|
+
stableHooksDir: options.stableHooksDir
|
|
354
|
+
});
|
|
355
|
+
if (res.exitCode !== 0) {
|
|
356
|
+
stepActive(out, `SessionEnd hook: ${res.stderr.trim()}`);
|
|
357
|
+
return {
|
|
358
|
+
stdout: "",
|
|
359
|
+
stderr: res.stderr,
|
|
360
|
+
exitCode: res.exitCode
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
hookResultLine = res.stdout.includes("already installed") ? `SessionEnd hook ${DIM2}already installed${RST2}` : `SessionEnd hook installed`;
|
|
364
|
+
stepDone(out, hookResultLine);
|
|
365
|
+
} else {
|
|
366
|
+
stepSkipped(out, `SessionEnd hook ${DIM2}skipped${RST2}`);
|
|
367
|
+
}
|
|
368
|
+
out.write(BAR + "\n");
|
|
369
|
+
let guidesAction = "install";
|
|
370
|
+
if (options.skipGuides === true) {
|
|
371
|
+
guidesAction = "skip";
|
|
372
|
+
} else if (interactive) {
|
|
373
|
+
guidesAction = await confirm(
|
|
374
|
+
out,
|
|
375
|
+
"Install the codealmanac usage guides into ~/.claude/ and import them from CLAUDE.md?",
|
|
376
|
+
true
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
let guidesSummary;
|
|
380
|
+
if (guidesAction === "install") {
|
|
381
|
+
try {
|
|
382
|
+
const summary = await installGuides({
|
|
383
|
+
claudeDir: options.claudeDir ?? path3.join(homedir2(), ".claude"),
|
|
384
|
+
guidesDir: options.guidesDir ?? resolveGuidesDir()
|
|
385
|
+
});
|
|
386
|
+
guidesSummary = summary.anyChanges ? `Guides installed (${summary.filesWritten.join(", ")})` : `Guides ${DIM2}already installed${RST2}`;
|
|
387
|
+
stepDone(out, guidesSummary);
|
|
388
|
+
} catch (err) {
|
|
389
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
390
|
+
return {
|
|
391
|
+
stdout: "",
|
|
392
|
+
stderr: `almanac: guide install failed: ${msg}
|
|
393
|
+
`,
|
|
394
|
+
exitCode: 1
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
stepSkipped(out, `Guides ${DIM2}skipped${RST2}`);
|
|
399
|
+
}
|
|
400
|
+
out.write(BAR + "\n");
|
|
401
|
+
stepDone(out, `${BLUE2}Setup complete${RST2}`);
|
|
402
|
+
out.write("\n");
|
|
403
|
+
const existingPageCount = countExistingPages(process.cwd());
|
|
404
|
+
printNextSteps(out, existingPageCount);
|
|
405
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
406
|
+
}
|
|
407
|
+
async function safeCheckAuth(spawnCli) {
|
|
408
|
+
try {
|
|
409
|
+
return await checkClaudeAuth(spawnCli);
|
|
410
|
+
} catch {
|
|
411
|
+
return { loggedIn: false };
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function reportAuth(out, auth) {
|
|
415
|
+
if (auth.loggedIn) {
|
|
416
|
+
const who = auth.email ?? "Claude account";
|
|
417
|
+
const plan = auth.subscriptionType !== void 0 ? ` ${DIM2}(${auth.subscriptionType})${RST2}` : "";
|
|
418
|
+
stepDone(out, `Claude auth: ${WHITE_BOLD2}${who}${RST2}${plan}`);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (process.env.ANTHROPIC_API_KEY !== void 0 && process.env.ANTHROPIC_API_KEY.length > 0) {
|
|
422
|
+
stepDone(out, `Claude auth: ${WHITE_BOLD2}ANTHROPIC_API_KEY${RST2} set`);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
stepActive(out, `Claude auth: ${DIM2}not signed in${RST2}`);
|
|
426
|
+
for (const line of UNAUTHENTICATED_MESSAGE.split("\n")) {
|
|
427
|
+
out.write(` ${DIM2}\u2502 ${line}${RST2}
|
|
428
|
+
`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
async function installGuides(options) {
|
|
432
|
+
await mkdir(options.claudeDir, { recursive: true });
|
|
433
|
+
const srcMini = path3.join(options.guidesDir, "mini.md");
|
|
434
|
+
const srcRef = path3.join(options.guidesDir, "reference.md");
|
|
435
|
+
if (!existsSync2(srcMini)) {
|
|
436
|
+
throw new Error(`missing bundled guide: ${srcMini}`);
|
|
437
|
+
}
|
|
438
|
+
if (!existsSync2(srcRef)) {
|
|
439
|
+
throw new Error(`missing bundled guide: ${srcRef}`);
|
|
440
|
+
}
|
|
441
|
+
const destMini = path3.join(options.claudeDir, "codealmanac.md");
|
|
442
|
+
const destRef = path3.join(options.claudeDir, "codealmanac-reference.md");
|
|
443
|
+
const miniChanged = await copyIfChanged(srcMini, destMini);
|
|
444
|
+
const refChanged = await copyIfChanged(srcRef, destRef);
|
|
445
|
+
const claudeMd = path3.join(options.claudeDir, "CLAUDE.md");
|
|
446
|
+
const importChanged = await ensureImport(claudeMd);
|
|
447
|
+
const filesWritten = [];
|
|
448
|
+
if (miniChanged) filesWritten.push("codealmanac.md");
|
|
449
|
+
if (refChanged) filesWritten.push("codealmanac-reference.md");
|
|
450
|
+
if (importChanged) filesWritten.push("CLAUDE.md");
|
|
451
|
+
return { anyChanges: filesWritten.length > 0, filesWritten };
|
|
452
|
+
}
|
|
453
|
+
async function copyIfChanged(src, dest) {
|
|
454
|
+
const srcBytes = await readFile(src);
|
|
455
|
+
if (existsSync2(dest)) {
|
|
456
|
+
try {
|
|
457
|
+
const destBytes = await readFile(dest);
|
|
458
|
+
if (srcBytes.equals(destBytes)) return false;
|
|
459
|
+
} catch {
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
await copyFile(src, dest);
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
var IMPORT_LINE = "@~/.claude/codealmanac.md";
|
|
466
|
+
async function ensureImport(claudeMdPath) {
|
|
467
|
+
let existing = "";
|
|
468
|
+
if (existsSync2(claudeMdPath)) {
|
|
469
|
+
existing = await readFile(claudeMdPath, "utf8");
|
|
470
|
+
}
|
|
471
|
+
if (hasImportLine(existing)) return false;
|
|
472
|
+
const sep = existing.length === 0 ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
473
|
+
const body = `${existing}${sep}${IMPORT_LINE}
|
|
474
|
+
`;
|
|
475
|
+
await writeFile(claudeMdPath, body, "utf8");
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
function hasImportLine(contents) {
|
|
479
|
+
const lines = contents.split(/\r?\n/).map((l) => l.trim());
|
|
480
|
+
return lines.some((line) => {
|
|
481
|
+
if (line === IMPORT_LINE) return true;
|
|
482
|
+
if (!line.startsWith(IMPORT_LINE)) return false;
|
|
483
|
+
const next = line[IMPORT_LINE.length];
|
|
484
|
+
return next === " " || next === " ";
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
function confirm(out, question, defaultYes) {
|
|
488
|
+
return new Promise((resolve) => {
|
|
489
|
+
const hint = defaultYes ? "[Y/n]" : "[y/N]";
|
|
490
|
+
out.write(` ${BLUE2}\u25C6${RST2} ${question} ${DIM2}${hint}${RST2} `);
|
|
491
|
+
let buf = "";
|
|
492
|
+
const onData = (chunk) => {
|
|
493
|
+
buf += chunk.toString("utf8");
|
|
494
|
+
const nl = buf.indexOf("\n");
|
|
495
|
+
if (nl === -1) return;
|
|
496
|
+
process.stdin.removeListener("data", onData);
|
|
497
|
+
process.stdin.pause();
|
|
498
|
+
const answer = buf.slice(0, nl).trim().toLowerCase();
|
|
499
|
+
const accepted = answer.length === 0 ? defaultYes : answer === "y" || answer === "yes";
|
|
500
|
+
resolve(accepted ? "install" : "skip");
|
|
501
|
+
};
|
|
502
|
+
process.stdin.resume();
|
|
503
|
+
process.stdin.on("data", onData);
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
function resolveGuidesDir() {
|
|
507
|
+
const here = path3.dirname(fileURLToPath2(import.meta.url));
|
|
508
|
+
const candidates = [
|
|
509
|
+
path3.resolve(here, "..", "guides"),
|
|
510
|
+
// dist layout
|
|
511
|
+
path3.resolve(here, "..", "..", "guides"),
|
|
512
|
+
// src layout
|
|
513
|
+
path3.resolve(here, "..", "..", "..", "guides")
|
|
514
|
+
];
|
|
515
|
+
for (const dir of candidates) {
|
|
516
|
+
if (looksLikeGuidesDir(dir)) return dir;
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
const require2 = createRequire3(import.meta.url);
|
|
520
|
+
const pkgJson = require2.resolve("codealmanac/package.json");
|
|
521
|
+
const guides = path3.join(path3.dirname(pkgJson), "guides");
|
|
522
|
+
if (looksLikeGuidesDir(guides)) return guides;
|
|
523
|
+
} catch {
|
|
524
|
+
}
|
|
525
|
+
throw new Error(
|
|
526
|
+
"could not locate bundled guides/ directory. Tried:\n" + candidates.map((c) => ` - ${c}`).join("\n")
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
function looksLikeGuidesDir(dir) {
|
|
530
|
+
return existsSync2(path3.join(dir, "mini.md"));
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export {
|
|
534
|
+
checkClaudeAuth,
|
|
535
|
+
assertClaudeAuth,
|
|
536
|
+
runSetup,
|
|
537
|
+
IMPORT_LINE
|
|
538
|
+
};
|
|
539
|
+
//# sourceMappingURL=chunk-2JJTTN7P.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/setup.ts","../src/agent/auth.ts","../src/commands/setup/install-path.ts","../src/commands/setup/next-steps.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport {\n copyFile,\n mkdir,\n readFile,\n writeFile,\n} from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport {\n checkClaudeAuth,\n type ClaudeAuthStatus,\n type SpawnCliFn,\n UNAUTHENTICATED_MESSAGE,\n} from \"../agent/auth.js\";\nimport { runHookInstall } from \"./hook.js\";\nimport {\n detectCurrentInstallPath,\n detectEphemeral,\n spawnGlobalInstall,\n} from \"./setup/install-path.js\";\nimport {\n countExistingPages,\n printNextSteps,\n} from \"./setup/next-steps.js\";\n\n/**\n * `codealmanac setup` — the MCP-style branded TUI that runs when a user\n * invokes the bare `codealmanac` binary (or `almanac setup` / `codealmanac\n * setup` explicitly).\n *\n * Model: `mcp-ts/src/setup.ts` from openalmanac. Same ASCII banner + badge\n * + step-indicator style, same interactive + `--yes` + non-interactive\n * modes.\n *\n * Three things get installed:\n *\n * 1. The `SessionEnd` hook in `~/.claude/settings.json` (delegated to\n * `runHookInstall` from `./hook.ts`).\n * 2. The short \"how to use codealmanac\" guide at\n * `~/.claude/codealmanac.md`, sourced from `guides/mini.md` in the\n * package.\n * 3. The full reference at `~/.claude/codealmanac-reference.md`,\n * sourced from `guides/reference.md`.\n * 4. An `@~/.claude/codealmanac.md` import line in `~/.claude/CLAUDE.md`\n * so Claude Code picks up the short guide globally.\n *\n * Everything is idempotent — running setup again is safe. `--skip-hook`\n * and `--skip-guides` opt out of the individual installs. `--yes` or a\n * non-TTY stdin skips all prompts and installs everything.\n */\n\nexport interface SetupOptions {\n /** Install everything without prompting. */\n yes?: boolean;\n /** Don't install the SessionEnd hook. */\n skipHook?: boolean;\n /** Don't install the CLAUDE.md guides. */\n skipGuides?: boolean;\n\n // ─── Injection points (tests only) ────────────────────────────────\n /** Override the subprocess spawner for `claude auth status`. */\n spawnCli?: SpawnCliFn;\n /** Override `~/.claude/settings.json` path. */\n settingsPath?: string;\n /** Override the bundled hook script path. */\n hookScriptPath?: string;\n /** Override the stable hooks directory for the hook script copy. */\n stableHooksDir?: string;\n /** Override `~/.claude/` dir for guide install. */\n claudeDir?: string;\n /** Override the directory containing `mini.md` / `reference.md`. */\n guidesDir?: string;\n /** Override interactivity; defaults to `process.stdin.isTTY`. */\n isTTY?: boolean;\n /** Stdout sink; defaults to `process.stdout`. */\n stdout?: NodeJS.WritableStream;\n /**\n * Override the install-path probe result. When `null` the probe is\n * bypassed (tests that don't care about the ephemeral-path step).\n * When a string it's treated as the detected install path.\n */\n installPath?: string | null;\n /**\n * Override the npm global install spawner (tests inject a no-op to\n * avoid actually spawning npm during CI).\n */\n spawnGlobalInstall?: () => Promise<void>;\n}\n\nexport interface SetupResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n// ─── ANSI helpers ────────────────────────────────────────────────────\n\nconst RST = \"\\x1b[0m\";\nconst DIM = \"\\x1b[2m\";\nconst WHITE_BOLD = \"\\x1b[1;37m\";\nconst BLUE = \"\\x1b[38;5;75m\";\nconst ACCENT_BG = \"\\x1b[48;5;252m\\x1b[38;5;16m\";\n\nconst GRADIENT = [\n \"\\x1b[38;5;255m\",\n \"\\x1b[38;5;253m\",\n \"\\x1b[38;5;251m\",\n \"\\x1b[38;5;249m\",\n \"\\x1b[38;5;246m\",\n \"\\x1b[38;5;243m\",\n];\n\n// `codealmanac` 11-letter ASCII banner. Chosen for tasteful rendering —\n// same banner used in the MCP setup wizard design, retooled letters for\n// the word \"codealmanac\". Each glyph is 6 lines tall.\n//\n// If you tweak this, keep it to ≤80 visual columns wide so it fits in\n// narrow terminals (80 cols is the classic default).\nconst LOGO_LINES = [\n \" ___ ___ ___ ___ _ _ __ __ _ _ _ _ ___ \",\n \" / __/ _ \\\\| \\\\| __| /_\\\\ | | | \\\\/ | /_\\\\ | \\\\| | /_\\\\ / __|\",\n \"| (_| (_) | |) | _| / _ \\\\| |__| |\\\\/| |/ _ \\\\| .` |/ _ \\\\ (__ \",\n \" \\\\___\\\\___/|___/|___/_/ \\\\_\\\\____|_| |_/_/ \\\\_\\\\_|\\\\_/_/ \\\\_\\\\___|\",\n \" \",\n \" a living wiki for codebases, for your agent \",\n];\n\nconst BAR = ` ${DIM}\\u2502${RST}`;\n\nfunction printBanner(out: NodeJS.WritableStream): void {\n out.write(\"\\n\");\n for (let i = 0; i < LOGO_LINES.length; i++) {\n const color = GRADIENT[Math.min(i, GRADIENT.length - 1)] ?? \"\";\n out.write(`${color}${LOGO_LINES[i]}${RST}\\n`);\n }\n out.write(`\\n${WHITE_BOLD} Install the hook + agent guides${RST}\\n`);\n}\n\nfunction printBadge(out: NodeJS.WritableStream): void {\n out.write(`\\n ${ACCENT_BG} codealmanac ${RST}\\n\\n`);\n}\n\nfunction stepDone(out: NodeJS.WritableStream, msg: string): void {\n out.write(` ${BLUE}\\u25c7${RST} ${msg}\\n`);\n}\n\nfunction stepActive(out: NodeJS.WritableStream, msg: string): void {\n out.write(` ${BLUE}\\u25c6${RST} ${msg}\\n`);\n}\n\nfunction stepSkipped(out: NodeJS.WritableStream, msg: string): void {\n out.write(` ${DIM}\\u25cb ${msg}${RST}\\n`);\n}\n\n// ─── Entry point ─────────────────────────────────────────────────────\n\nexport async function runSetup(\n options: SetupOptions = {},\n): Promise<SetupResult> {\n const out = options.stdout ?? process.stdout;\n const isTTY =\n options.isTTY ?? (process.stdin.isTTY === true);\n const interactive = isTTY && options.yes !== true;\n\n // No-op fast path. When the caller explicitly skipped every install\n // step, rendering the full banner + step markers + \"Setup complete\"\n // box is actively misleading — nothing was actually set up. Emit a\n // single terse line and exit so the user gets honest feedback and\n // piped callers (CI, scripts) don't parse through nine lines of ANSI\n // to conclude nothing happened.\n if (options.skipHook === true && options.skipGuides === true) {\n out.write(\n \"codealmanac: nothing to install — use --help to see what setup does\\n\",\n );\n return { stdout: \"\", stderr: \"\", exitCode: 0 };\n }\n\n printBanner(out);\n printBadge(out);\n\n // Step 1: auth status. We report what we find; we don't block. The user\n // can still install the hook + guides without being logged in — they'll\n // hit the auth wall on first `capture`, not on setup.\n const auth = await safeCheckAuth(options.spawnCli);\n reportAuth(out, auth);\n out.write(BAR + \"\\n\");\n\n // Step 1b: ephemeral install detection. When codealmanac was invoked via\n // `npx codealmanac` (no prior `npm i -g`), the binary lives inside an\n // npx cache directory or pnpm store that can be evicted at any time.\n // `almanac` is also not on PATH, so the user can't use it after setup.\n //\n // When we detect an ephemeral location, we offer (or, on --yes, perform)\n // a `npm install -g codealmanac` to make the install permanent.\n //\n // This is Bug #2 from codealmanac-known-bugs.md.\n const ephem = options.installPath !== undefined\n ? (options.installPath !== null\n ? detectEphemeral(options.installPath)\n : false)\n : detectEphemeral(detectCurrentInstallPath());\n if (ephem) {\n let globalAction: InstallDecision = \"install\";\n if (interactive) {\n globalAction = await confirm(\n out,\n `Running from an ephemeral npx location. Install globally so 'almanac' stays on PATH?`,\n true,\n );\n }\n if (globalAction === \"install\") {\n stepActive(out, \"Installing codealmanac globally…\");\n try {\n await (options.spawnGlobalInstall ?? spawnGlobalInstall)();\n stepDone(out, \"codealmanac installed globally (almanac now on PATH)\");\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n stepActive(out, `Global install failed: ${msg}`);\n out.write(\n ` ${DIM}You can retry manually: npm install -g codealmanac${RST}\\n`,\n );\n }\n } else {\n stepSkipped(\n out,\n `Global install ${DIM}skipped — almanac will not be on PATH after this session${RST}`,\n );\n }\n out.write(BAR + \"\\n\");\n }\n\n // Step 2: install the hook (default yes).\n let hookAction: InstallDecision = \"install\";\n if (options.skipHook === true) {\n hookAction = \"skip\";\n } else if (interactive) {\n hookAction = await confirm(\n out,\n \"Install the SessionEnd hook so capture runs at the end of every Claude Code session?\",\n true,\n );\n }\n\n let hookResultLine = \"\";\n if (hookAction === \"install\") {\n const res = await runHookInstall({\n settingsPath: options.settingsPath,\n hookScriptPath: options.hookScriptPath,\n stableHooksDir: options.stableHooksDir,\n });\n if (res.exitCode !== 0) {\n stepActive(out, `SessionEnd hook: ${res.stderr.trim()}`);\n return {\n stdout: \"\",\n stderr: res.stderr,\n exitCode: res.exitCode,\n };\n }\n hookResultLine = res.stdout.includes(\"already installed\")\n ? `SessionEnd hook ${DIM}already installed${RST}`\n : `SessionEnd hook installed`;\n stepDone(out, hookResultLine);\n } else {\n stepSkipped(out, `SessionEnd hook ${DIM}skipped${RST}`);\n }\n out.write(BAR + \"\\n\");\n\n // Step 3: install the guides.\n let guidesAction: InstallDecision = \"install\";\n if (options.skipGuides === true) {\n guidesAction = \"skip\";\n } else if (interactive) {\n guidesAction = await confirm(\n out,\n \"Install the codealmanac usage guides into ~/.claude/ and import them from CLAUDE.md?\",\n true,\n );\n }\n\n let guidesSummary: string;\n if (guidesAction === \"install\") {\n try {\n const summary = await installGuides({\n claudeDir: options.claudeDir ?? path.join(homedir(), \".claude\"),\n guidesDir: options.guidesDir ?? resolveGuidesDir(),\n });\n guidesSummary = summary.anyChanges\n ? `Guides installed (${summary.filesWritten.join(\", \")})`\n : `Guides ${DIM}already installed${RST}`;\n stepDone(out, guidesSummary);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n stdout: \"\",\n stderr: `almanac: guide install failed: ${msg}\\n`,\n exitCode: 1,\n };\n }\n } else {\n stepSkipped(out, `Guides ${DIM}skipped${RST}`);\n }\n out.write(BAR + \"\\n\");\n\n stepDone(out, `${BLUE}Setup complete${RST}`);\n out.write(\"\\n\");\n\n // Detect whether the current working directory is inside a repo that\n // already has a wiki with pages. This fixes Bug #6 from\n // codealmanac-known-bugs.md: Engineer B clones a repo that already has\n // `.almanac/pages/` (committed by Engineer A) and gets told to run\n // `almanac bootstrap`, which is wrong — the wiki already exists.\n const existingPageCount = countExistingPages(process.cwd());\n printNextSteps(out, existingPageCount);\n\n return { stdout: \"\", stderr: \"\", exitCode: 0 };\n}\n\n// ─── Auth reporting ──────────────────────────────────────────────────\n\nasync function safeCheckAuth(\n spawnCli?: SpawnCliFn,\n): Promise<ClaudeAuthStatus> {\n try {\n return await checkClaudeAuth(spawnCli);\n } catch {\n return { loggedIn: false };\n }\n}\n\nfunction reportAuth(\n out: NodeJS.WritableStream,\n auth: ClaudeAuthStatus,\n): void {\n if (auth.loggedIn) {\n const who = auth.email ?? \"Claude account\";\n const plan =\n auth.subscriptionType !== undefined\n ? ` ${DIM}(${auth.subscriptionType})${RST}`\n : \"\";\n stepDone(out, `Claude auth: ${WHITE_BOLD}${who}${RST}${plan}`);\n return;\n }\n if (\n process.env.ANTHROPIC_API_KEY !== undefined &&\n process.env.ANTHROPIC_API_KEY.length > 0\n ) {\n stepDone(out, `Claude auth: ${WHITE_BOLD}ANTHROPIC_API_KEY${RST} set`);\n return;\n }\n // Not blocking — just report and show the two paths.\n stepActive(out, `Claude auth: ${DIM}not signed in${RST}`);\n for (const line of UNAUTHENTICATED_MESSAGE.split(\"\\n\")) {\n out.write(` ${DIM}\\u2502 ${line}${RST}\\n`);\n }\n}\n\n// ─── Guide installation ──────────────────────────────────────────────\n\ninterface InstallGuidesOptions {\n claudeDir: string;\n guidesDir: string;\n}\n\ninterface InstallGuidesResult {\n anyChanges: boolean;\n filesWritten: string[];\n}\n\n/**\n * Copy the two guide files into `~/.claude/` and append an `@import`\n * line to `~/.claude/CLAUDE.md`. Every step is idempotent:\n *\n * - Guide files are compared by bytes before we write. If the content\n * matches the bundled version, we skip (so `setup` doesn't cause a\n * spurious mtime bump on every invocation).\n * - The import line is appended only if `CLAUDE.md` doesn't already\n * contain the exact `@~/.claude/codealmanac.md` token on a line by\n * itself. We don't try to parse the file — any mention of the token\n * on a non-comment line is treated as \"already present\".\n *\n * Returns a summary the caller uses to decide whether to say \"installed\"\n * or \"already installed\" in the TUI.\n */\nasync function installGuides(\n options: InstallGuidesOptions,\n): Promise<InstallGuidesResult> {\n await mkdir(options.claudeDir, { recursive: true });\n\n const srcMini = path.join(options.guidesDir, \"mini.md\");\n const srcRef = path.join(options.guidesDir, \"reference.md\");\n if (!existsSync(srcMini)) {\n throw new Error(`missing bundled guide: ${srcMini}`);\n }\n if (!existsSync(srcRef)) {\n throw new Error(`missing bundled guide: ${srcRef}`);\n }\n\n const destMini = path.join(options.claudeDir, \"codealmanac.md\");\n const destRef = path.join(options.claudeDir, \"codealmanac-reference.md\");\n\n const miniChanged = await copyIfChanged(srcMini, destMini);\n const refChanged = await copyIfChanged(srcRef, destRef);\n\n const claudeMd = path.join(options.claudeDir, \"CLAUDE.md\");\n const importChanged = await ensureImport(claudeMd);\n\n const filesWritten: string[] = [];\n if (miniChanged) filesWritten.push(\"codealmanac.md\");\n if (refChanged) filesWritten.push(\"codealmanac-reference.md\");\n if (importChanged) filesWritten.push(\"CLAUDE.md\");\n\n return { anyChanges: filesWritten.length > 0, filesWritten };\n}\n\nasync function copyIfChanged(src: string, dest: string): Promise<boolean> {\n const srcBytes = await readFile(src);\n if (existsSync(dest)) {\n try {\n const destBytes = await readFile(dest);\n if (srcBytes.equals(destBytes)) return false;\n } catch {\n // Fall through to write.\n }\n }\n await copyFile(src, dest);\n return true;\n}\n\n/** The exact import line we manage. Changing this requires updating\n * uninstall too. */\nexport const IMPORT_LINE = \"@~/.claude/codealmanac.md\";\n\n/**\n * Append the import line to `~/.claude/CLAUDE.md` if it isn't already\n * present. Creates the file if absent. Returns true when we wrote, false\n * when the line was already there.\n *\n * We match on `@~/.claude/codealmanac.md` appearing on any non-empty\n * line (trimmed). This catches both the bare line we write and any\n * user-edited variant (comments, trailing whitespace). We deliberately\n * do NOT try to repair a user who deleted the newline — that's their\n * file to shape.\n */\nasync function ensureImport(claudeMdPath: string): Promise<boolean> {\n let existing = \"\";\n if (existsSync(claudeMdPath)) {\n existing = await readFile(claudeMdPath, \"utf8\");\n }\n if (hasImportLine(existing)) return false;\n\n const sep =\n existing.length === 0 ? \"\" : existing.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n const body = `${existing}${sep}${IMPORT_LINE}\\n`;\n await writeFile(claudeMdPath, body, \"utf8\");\n return true;\n}\n\nexport function hasImportLine(contents: string): boolean {\n // Match line-starts-with-token rather than exact-line equality so a\n // user who annotated the import line (`@~/.claude/codealmanac.md #\n // codealmanac`) doesn't cause us to re-append a duplicate below.\n // The trailing-character check rules out accidental matches on a\n // longer line like `@~/.claude/codealmanac.md-extra`.\n const lines = contents.split(/\\r?\\n/).map((l) => l.trim());\n return lines.some((line) => {\n if (line === IMPORT_LINE) return true;\n if (!line.startsWith(IMPORT_LINE)) return false;\n const next = line[IMPORT_LINE.length];\n return next === \" \" || next === \"\\t\";\n });\n}\n\n// ─── Interactive prompt ──────────────────────────────────────────────\n\ntype InstallDecision = \"install\" | \"skip\";\n\n/**\n * Minimal `[Y/n]` prompt. No raw mode, no cursor — just readline. The\n * MCP setup uses a fancy arrow-key TUI for multi-choice; we only have\n * binary decisions here, so a line-reader prompt is clearer and doesn't\n * fight with the step-indicator rendering above it.\n */\nfunction confirm(\n out: NodeJS.WritableStream,\n question: string,\n defaultYes: boolean,\n): Promise<InstallDecision> {\n return new Promise((resolve) => {\n const hint = defaultYes ? \"[Y/n]\" : \"[y/N]\";\n out.write(` ${BLUE}\\u25c6${RST} ${question} ${DIM}${hint}${RST} `);\n\n let buf = \"\";\n const onData = (chunk: Buffer): void => {\n buf += chunk.toString(\"utf8\");\n const nl = buf.indexOf(\"\\n\");\n if (nl === -1) return;\n process.stdin.removeListener(\"data\", onData);\n process.stdin.pause();\n\n const answer = buf.slice(0, nl).trim().toLowerCase();\n const accepted =\n answer.length === 0\n ? defaultYes\n : answer === \"y\" || answer === \"yes\";\n resolve(accepted ? \"install\" : \"skip\");\n };\n\n process.stdin.resume();\n process.stdin.on(\"data\", onData);\n });\n}\n\n// ─── Guides path resolution ──────────────────────────────────────────\n\n/**\n * Locate `guides/` relative to the installed package. Mirrors\n * `resolvePromptsDir` from `src/agent/prompts.ts`.\n *\n * Two runtime layouts to handle:\n *\n * 1. **Bundled dist.** `dist/codealmanac.js` → walk one level up →\n * `guides/`.\n * 2. **Source (tests / tsx).** `src/commands/setup.ts` → walk two\n * levels up → `guides/`.\n *\n * We also try `createRequire` to resolve the package root from the\n * `codealmanac/package.json` manifest, as a belt-and-suspenders fallback\n * for unusual install layouts (monorepo hoisting, etc.). That path is\n * only exercised when the direct walk-up fails.\n */\nexport function resolveGuidesDir(): string {\n const here = path.dirname(fileURLToPath(import.meta.url));\n const candidates = [\n path.resolve(here, \"..\", \"guides\"), // dist layout\n path.resolve(here, \"..\", \"..\", \"guides\"), // src layout\n path.resolve(here, \"..\", \"..\", \"..\", \"guides\"),\n ];\n for (const dir of candidates) {\n if (looksLikeGuidesDir(dir)) return dir;\n }\n // Fallback: resolve via the package.json of the currently-running\n // codealmanac. createRequire lets us ask Node's resolver rather than\n // guessing at directory layouts.\n try {\n const require = createRequire(import.meta.url);\n const pkgJson = require.resolve(\"codealmanac/package.json\");\n const guides = path.join(path.dirname(pkgJson), \"guides\");\n if (looksLikeGuidesDir(guides)) return guides;\n } catch {\n // Ignore — we'll throw with the candidate list below.\n }\n throw new Error(\n \"could not locate bundled guides/ directory. Tried:\\n\" +\n candidates.map((c) => ` - ${c}`).join(\"\\n\"),\n );\n}\n\nfunction looksLikeGuidesDir(dir: string): boolean {\n return existsSync(path.join(dir, \"mini.md\"));\n}\n","import { spawn, type ChildProcess } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * Claude auth gate — accepts either an active Claude subscription login\n * OR an `ANTHROPIC_API_KEY` environment variable.\n *\n * The Claude Agent SDK delegates authentication to its bundled `cli.js`,\n * which reads OAuth credentials from `~/.claude/credentials/` (the same\n * store Claude Code uses). Users who are logged in via `claude auth login\n * --claudeai` should be able to run bootstrap/capture without ever\n * exporting an API key. Conversely, users on pay-per-token API keys\n * shouldn't be required to go through the OAuth flow.\n *\n * We spawn the bundled SDK's `cli.js auth status --json` to answer \"are\n * we logged in?\" rather than poking at the credentials file directly —\n * that's the SDK's contract, and it handles all the edge cases (token\n * expiry, org switching, consoleloginpath, …) for us.\n *\n * The CLI path is resolved via `require.resolve(\"@anthropic-ai/claude-\n * agent-sdk/package.json\")` + the `cli.js` sibling. Going through\n * `createRequire` keeps this compatible with both ESM dev mode (tsx) and\n * the bundled dist (tsup externalizes the SDK, so Node's own resolver\n * does the lookup at runtime). If the SDK isn't installed at all we fall\n * back to treating the user as unauthenticated — the assert will then\n * surface the familiar two-path error so they can at least fix it via\n * `ANTHROPIC_API_KEY`.\n */\n\nexport interface ClaudeAuthStatus {\n loggedIn: boolean;\n email?: string;\n subscriptionType?: string;\n authMethod?: string;\n}\n\nexport interface SpawnedProcess {\n stdout: { on: (event: \"data\", cb: (data: Buffer | string) => void) => void };\n stderr: { on: (event: \"data\", cb: (data: Buffer | string) => void) => void };\n on: (event: \"close\" | \"error\", cb: (arg: number | null | Error) => void) => void;\n kill: (signal?: string) => void;\n}\n\n/**\n * The subprocess spawner is injectable so tests can replace it with a\n * fake that emits canned JSON without touching the filesystem. Production\n * code uses `defaultSpawnCli` which invokes the bundled SDK CLI.\n */\nexport type SpawnCliFn = (args: string[]) => SpawnedProcess;\n\nconst AUTH_TIMEOUT_MS = 10_000;\n\n/**\n * Resolve `cli.js` from the bundled `@anthropic-ai/claude-agent-sdk`\n * install. Uses `createRequire` so the lookup works regardless of\n * whether we're running from `dist/` (where tsup externalized the SDK)\n * or directly from source.\n *\n * Throws if the SDK can't be located — `checkClaudeAuth` catches this\n * and treats the user as not-logged-in, which lets the env-var path\n * still work for users with a borked install.\n */\nfunction resolveCliJsPath(): string {\n // `import.meta.url` points at this module (dev or dist). `createRequire`\n // from that URL can then resolve sibling packages the same way Node's\n // own CJS resolver would.\n //\n // We resolve the main entry (not `./package.json`) because the SDK's\n // `exports` field locks subpath access — `require.resolve(\".../package.json\")`\n // fails with `ERR_PACKAGE_PATH_NOT_EXPORTED` on current SDK versions.\n // The main entry resolves fine; `cli.js` is its sibling in the install\n // layout the SDK has used since day one.\n const require = createRequire(import.meta.url);\n const entry = require.resolve(\"@anthropic-ai/claude-agent-sdk\");\n return join(dirname(entry), \"cli.js\");\n}\n\n/**\n * Default subprocess spawner for production use — invokes the bundled\n * SDK's `cli.js` via the same Node runtime that's running codealmanac.\n * Tests inject a fake via the `spawnCli` parameter.\n */\nexport const defaultSpawnCli: SpawnCliFn = (args: string[]) => {\n const cliPath = resolveCliJsPath();\n // Use `process.execPath` so we inherit the Node runtime codealmanac\n // itself is running under — avoids PATH weirdness on systems where\n // `node` isn't on PATH but codealmanac was installed via npm.\n const child = spawn(process.execPath, [cliPath, ...args], {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n return child as unknown as SpawnedProcess;\n};\n\n/**\n * Check whether the user is authenticated via Claude subscription OAuth.\n *\n * Spawns the bundled SDK CLI's `auth status --json`. On any failure\n * (spawn error, non-JSON stdout, non-zero exit, timeout) we return\n * `{ loggedIn: false }` rather than propagating the error — the caller\n * will fall back to the `ANTHROPIC_API_KEY` path and, if that's also\n * missing, produce a clean two-option error message.\n *\n * The 10s timeout guards against the CLI hanging on a broken network or\n * keychain prompt. In practice `auth status` is a cheap local read.\n */\nexport async function checkClaudeAuth(\n spawnCli: SpawnCliFn = defaultSpawnCli,\n): Promise<ClaudeAuthStatus> {\n let child: SpawnedProcess;\n try {\n child = spawnCli([\"auth\", \"status\", \"--json\"]);\n } catch {\n return { loggedIn: false };\n }\n\n return new Promise<ClaudeAuthStatus>((resolve) => {\n let stdout = \"\";\n let stderr = \"\";\n let settled = false;\n\n const settle = (value: ClaudeAuthStatus): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve(value);\n };\n\n const timer = setTimeout(() => {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n // Kill can fail if the process already exited; nothing we can do.\n }\n settle({ loggedIn: false });\n }, AUTH_TIMEOUT_MS);\n\n child.stdout.on(\"data\", (data) => {\n stdout += data.toString();\n });\n child.stderr.on(\"data\", (data) => {\n stderr += data.toString();\n });\n\n child.on(\"error\", () => {\n settle({ loggedIn: false });\n });\n\n child.on(\"close\", (code) => {\n // The SDK writes `{\"loggedIn\": false, ...}` to stdout with a zero\n // exit code when the user isn't signed in, so we only reject on\n // non-zero + empty stdout. An empty stdout with zero exit (shouldn't\n // happen in practice) also fails safely to `loggedIn: false`.\n if (code !== 0 && stdout.trim().length === 0) {\n // `stderr` isn't surfaced to the user here — the caller's error\n // message covers both auth paths — but it would be captured by\n // `stderr` if we ever wanted to log it for debugging.\n void stderr;\n settle({ loggedIn: false });\n return;\n }\n try {\n const parsed = JSON.parse(stdout.trim()) as Record<string, unknown>;\n const loggedIn = parsed.loggedIn === true;\n const out: ClaudeAuthStatus = { loggedIn };\n if (typeof parsed.email === \"string\") out.email = parsed.email;\n if (typeof parsed.subscriptionType === \"string\") {\n out.subscriptionType = parsed.subscriptionType;\n }\n if (typeof parsed.authMethod === \"string\") {\n out.authMethod = parsed.authMethod;\n }\n settle(out);\n } catch {\n settle({ loggedIn: false });\n }\n });\n });\n}\n\n/**\n * Human-readable error when neither auth path is available. The text is\n * deliberately verbose — users hitting this wall for the first time\n * deserve both options in front of them, not a terse hint.\n */\nexport const UNAUTHENTICATED_MESSAGE =\n \"not authenticated to Claude.\\n\\n\" +\n \"Option 1 — use your Claude subscription (Pro/Max):\\n\" +\n \" claude auth login --claudeai\\n\\n\" +\n \"Option 2 — use a pay-per-token API key:\\n\" +\n \" Get one at https://console.anthropic.com\\n\" +\n \" export ANTHROPIC_API_KEY=sk-ant-...\\n\\n\" +\n \"Verify with: claude auth status\";\n\n/**\n * Assert that at least one auth path is satisfied. Prefers subscription\n * auth (fewer surprises for Claude Pro/Max users) but accepts\n * `ANTHROPIC_API_KEY` as a fallback. On failure throws with\n * `code = \"CLAUDE_AUTH_MISSING\"` so callers can distinguish this from\n * other errors if they ever want to.\n *\n * Returns the resolved auth status so callers that want to display the\n * logged-in email in a preamble can do so without a second subprocess.\n */\nexport async function assertClaudeAuth(\n spawnCli: SpawnCliFn = defaultSpawnCli,\n): Promise<ClaudeAuthStatus> {\n const status = await checkClaudeAuth(spawnCli);\n if (status.loggedIn) {\n return status;\n }\n const apiKey = process.env.ANTHROPIC_API_KEY;\n if (apiKey !== undefined && apiKey.length > 0) {\n // Signal to callers that we're on the API-key path. Not \"loggedIn\"\n // in the OAuth sense, but the SDK will pick up the env var and\n // succeed — so we return a status that tells bootstrap/capture the\n // gate is open.\n return { loggedIn: true, authMethod: \"apiKey\" };\n }\n const err = new Error(UNAUTHENTICATED_MESSAGE);\n (err as { code?: string }).code = \"CLAUDE_AUTH_MISSING\";\n throw err;\n}\n\n// Internal re-export — helps keep the public type surface minimal while\n// still letting tests import the `ChildProcess` shape when needed.\nexport type { ChildProcess };\n","import { execFile } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * Return the directory of the currently-running codealmanac install by\n * walking up from this module's file to the nearest `package.json` whose\n * `name` is `codealmanac`. Returns the empty string when the walk fails.\n */\nexport function detectCurrentInstallPath(): string {\n try {\n const req = createRequire(import.meta.url);\n const here = fileURLToPath(import.meta.url);\n let dir = path.dirname(here);\n for (let i = 0; i < 6; i++) {\n const pkgPath = path.join(dir, \"package.json\");\n try {\n const raw = req(\"fs\").readFileSync(pkgPath, \"utf-8\") as string;\n const pkg = JSON.parse(raw) as { name?: unknown };\n if (pkg.name === \"codealmanac\") return dir;\n } catch {\n // keep walking\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n } catch {\n // import.meta.url unavailable — not ephemeral for our purposes.\n }\n return \"\";\n}\n\n/**\n * Return true when the given install path looks ephemeral.\n *\n * Ephemeral locations we recognize:\n * - `~/.npm/_npx/` — npm's npx cache (GC'd on version bumps or\n * `npm cache clean`)\n * - `~/.local/share/pnpm/dlx/` — pnpm's dlx (like npx) cache\n * - `/tmp/` or `/var/folders/` — common CI / temp paths\n *\n * A global install (`~/.nvm/.../lib/node_modules/`, `/usr/local/lib/...`,\n * `~/.local/lib/node_modules/`) is NOT ephemeral.\n */\nexport function detectEphemeral(installPath: string): boolean {\n if (installPath.length === 0) return false;\n const home = homedir();\n if (installPath.startsWith(path.join(home, \".npm\", \"_npx\"))) return true;\n if (\n installPath.startsWith(path.join(home, \".local\", \"share\", \"pnpm\", \"dlx\"))\n ) return true;\n if (installPath.startsWith(\"/tmp/\")) return true;\n if (installPath.startsWith(\"/var/folders/\")) return true;\n return false;\n}\n\n/**\n * Spawn `npm install -g codealmanac@latest` in a child process and wait\n * for it to finish. Rejects on non-zero exit or spawn error.\n */\nexport function spawnGlobalInstall(): Promise<void> {\n return new Promise((resolve, reject) => {\n execFile(\n \"npm\",\n [\"install\", \"-g\", \"codealmanac@latest\"],\n { shell: false },\n (err, _stdout, stderr) => {\n if (err !== null) {\n reject(\n new Error(\n stderr.length > 0\n ? stderr.trim().split(\"\\n\")[0] ?? err.message\n : err.message,\n ),\n );\n } else {\n resolve();\n }\n },\n );\n });\n}\n","import { existsSync, readdirSync } from \"node:fs\";\nimport path from \"node:path\";\n\nconst RST = \"\\x1b[0m\";\nconst BOLD = \"\\x1b[1m\";\nconst DIM = \"\\x1b[2m\";\nconst WHITE_BOLD = \"\\x1b[1;37m\";\nconst BLUE = \"\\x1b[38;5;75m\";\nconst BLUE_DIM = \"\\x1b[38;5;69m\";\n\n/**\n * Print the \"Next steps\" box. When `existingPageCount` is greater than 0,\n * the current working directory already has a wiki with committed pages.\n * In that case we skip the `almanac bootstrap` step and tell the user to\n * start querying.\n */\nexport function printNextSteps(\n out: NodeJS.WritableStream,\n existingPageCount: number,\n): void {\n const innerW = 62;\n const vis = (s: string): number =>\n s.replace(/\\x1b\\[[0-9;]*m/g, \"\").length;\n const row = (content: string): string => {\n const padding = Math.max(0, innerW - vis(content));\n return ` ${BLUE_DIM}\\u2502${RST}${content}${\" \".repeat(padding)}${BLUE_DIM}\\u2502${RST}\\n`;\n };\n const empty = row(\"\");\n\n out.write(` ${BLUE_DIM}\\u256d${\"─\".repeat(innerW)}\\u256e${RST}\\n`);\n out.write(empty);\n out.write(row(` ${WHITE_BOLD}Next steps${RST}`));\n out.write(empty);\n\n if (existingPageCount > 0) {\n out.write(\n row(\n ` ${BLUE}\\u25c7${RST} This repo already has a wiki ${DIM}(${existingPageCount} page${existingPageCount === 1 ? \"\" : \"s\"})${RST}`,\n ),\n );\n out.write(empty);\n out.write(row(` ${BLUE}1.${RST} Start querying your wiki:`));\n out.write(row(` ${BOLD}almanac search --mentions <file>${RST}`));\n out.write(\n row(` ${BLUE}2.${RST} Work normally — capture runs on session end`),\n );\n } else {\n out.write(\n row(` ${BLUE}1.${RST} ${BOLD}cd${RST} into a repo you want to document`),\n );\n out.write(\n row(\n ` ${BLUE}2.${RST} ${BOLD}almanac bootstrap${RST} ${DIM}# scaffold the wiki${RST}`,\n ),\n );\n out.write(\n row(` ${BLUE}3.${RST} Work normally — capture runs on session end`),\n );\n }\n\n out.write(empty);\n out.write(` ${BLUE_DIM}\\u2570${\"─\".repeat(innerW)}\\u256f${RST}\\n\\n`);\n}\n\n/**\n * Count `.md` files in `.almanac/pages/` under the current working\n * directory or any parent. Returns 0 when no wiki is found or the pages\n * directory is empty.\n */\nexport function countExistingPages(cwd: string): number {\n try {\n let dir = cwd;\n for (let i = 0; i < 10; i++) {\n const pagesDir = path.join(dir, \".almanac\", \"pages\");\n if (existsSync(pagesDir)) {\n try {\n const entries = readdirSync(pagesDir);\n return entries.filter((e) => e.endsWith(\".md\")).length;\n } catch {\n return 0;\n }\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n } catch {\n // Swallow — never crash setup because of this.\n }\n return 0;\n}\n"],"mappings":"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","names":["existsSync","createRequire","homedir","path","fileURLToPath","require","createRequire","path","RST","DIM","WHITE_BOLD","BLUE","path","homedir","existsSync","fileURLToPath","require","createRequire"]}
|