codealmanac 0.1.6 → 0.1.8

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 (43) hide show
  1. package/dist/chunk-3C5SY5SE.js +1239 -0
  2. package/dist/chunk-3C5SY5SE.js.map +1 -0
  3. package/dist/chunk-3LC55TG6.js +566 -0
  4. package/dist/chunk-3LC55TG6.js.map +1 -0
  5. package/dist/chunk-4CODZRHH.js +19 -0
  6. package/dist/chunk-4CODZRHH.js.map +1 -0
  7. package/dist/chunk-73A5TGBC.js +441 -0
  8. package/dist/chunk-73A5TGBC.js.map +1 -0
  9. package/dist/chunk-7JUX4ADQ.js +38 -0
  10. package/dist/chunk-7JUX4ADQ.js.map +1 -0
  11. package/dist/chunk-AXFPUHBN.js +227 -0
  12. package/dist/chunk-AXFPUHBN.js.map +1 -0
  13. package/dist/chunk-BJVZLP6O.js +145 -0
  14. package/dist/chunk-BJVZLP6O.js.map +1 -0
  15. package/dist/chunk-FM3VRDK7.js +20 -0
  16. package/dist/chunk-FM3VRDK7.js.map +1 -0
  17. package/dist/chunk-P3LDTCLB.js +34 -0
  18. package/dist/chunk-P3LDTCLB.js.map +1 -0
  19. package/dist/chunk-QHQ6YH7U.js +81 -0
  20. package/dist/chunk-QHQ6YH7U.js.map +1 -0
  21. package/dist/chunk-Z4MWLVS2.js +355 -0
  22. package/dist/chunk-Z4MWLVS2.js.map +1 -0
  23. package/dist/chunk-Z6MBJ3D2.js +203 -0
  24. package/dist/chunk-Z6MBJ3D2.js.map +1 -0
  25. package/dist/cli-HIXXCUSQ.js +393 -0
  26. package/dist/cli-HIXXCUSQ.js.map +1 -0
  27. package/dist/codealmanac.js +32 -5
  28. package/dist/codealmanac.js.map +1 -1
  29. package/dist/doctor-IS6N7V63.js +15 -0
  30. package/dist/doctor-IS6N7V63.js.map +1 -0
  31. package/dist/hook-CRJMWSSO.js +12 -0
  32. package/dist/hook-CRJMWSSO.js.map +1 -0
  33. package/dist/register-commands-JAPO3AUB.js +2647 -0
  34. package/dist/register-commands-JAPO3AUB.js.map +1 -0
  35. package/dist/uninstall-HE2Z2LN2.js +12 -0
  36. package/dist/uninstall-HE2Z2LN2.js.map +1 -0
  37. package/dist/update-IL243I4E.js +10 -0
  38. package/dist/update-IL243I4E.js.map +1 -0
  39. package/dist/wiki-EHZ7LG7R.js +238 -0
  40. package/dist/wiki-EHZ7LG7R.js.map +1 -0
  41. package/package.json +2 -2
  42. package/dist/cli-GTEC5PC7.js +0 -6237
  43. package/dist/cli-GTEC5PC7.js.map +0 -1
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/commands/doctor-checks/duration.ts
4
+ function formatDuration(ms) {
5
+ if (ms < 0) return "just now";
6
+ const seconds = Math.floor(ms / 1e3);
7
+ if (seconds < 60) return `${seconds}s`;
8
+ const minutes = Math.floor(seconds / 60);
9
+ if (minutes < 60) return `${minutes}m`;
10
+ const hours = Math.floor(minutes / 60);
11
+ if (hours < 48) return `${hours}h`;
12
+ const days = Math.floor(hours / 24);
13
+ return `${days}d`;
14
+ }
15
+
16
+ export {
17
+ formatDuration
18
+ };
19
+ //# sourceMappingURL=chunk-4CODZRHH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/doctor-checks/duration.ts"],"sourcesContent":["export function formatDuration(ms: number): string {\n if (ms < 0) return \"just now\";\n const seconds = Math.floor(ms / 1000);\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m`;\n const hours = Math.floor(minutes / 60);\n if (hours < 48) return `${hours}h`;\n const days = Math.floor(hours / 24);\n return `${days}d`;\n}\n"],"mappings":";;;AAAO,SAAS,eAAe,IAAoB;AACjD,MAAI,KAAK,EAAG,QAAO;AACnB,QAAM,UAAU,KAAK,MAAM,KAAK,GAAI;AACpC,MAAI,UAAU,GAAI,QAAO,GAAG,OAAO;AACnC,QAAM,UAAU,KAAK,MAAM,UAAU,EAAE;AACvC,MAAI,UAAU,GAAI,QAAO,GAAG,OAAO;AACnC,QAAM,QAAQ,KAAK,MAAM,UAAU,EAAE;AACrC,MAAI,QAAQ,GAAI,QAAO,GAAG,KAAK;AAC/B,QAAM,OAAO,KAAK,MAAM,QAAQ,EAAE;AAClC,SAAO,GAAG,IAAI;AAChB;","names":[]}
@@ -0,0 +1,441 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ formatDuration
4
+ } from "./chunk-4CODZRHH.js";
5
+ import {
6
+ readStateForDoctor
7
+ } from "./chunk-QHQ6YH7U.js";
8
+ import {
9
+ BLUE,
10
+ BOLD,
11
+ DIM,
12
+ GREEN,
13
+ RED,
14
+ RST
15
+ } from "./chunk-FM3VRDK7.js";
16
+ import {
17
+ IMPORT_LINE,
18
+ checkClaudeAuth
19
+ } from "./chunk-3LC55TG6.js";
20
+ import {
21
+ isNewer,
22
+ readConfig
23
+ } from "./chunk-AXFPUHBN.js";
24
+
25
+ // src/commands/doctor-checks/format.ts
26
+ function formatReport(report, options) {
27
+ const color = options.stdout === void 0 && process.stdout.isTTY === true;
28
+ const lines = [];
29
+ lines.push(`codealmanac v${report.version}`);
30
+ lines.push("");
31
+ if (report.install.length > 0) {
32
+ lines.push(color ? `${BOLD}## Install${RST}` : "## Install");
33
+ for (const c of report.install) {
34
+ lines.push(formatCheck(c, color));
35
+ }
36
+ lines.push("");
37
+ }
38
+ if (report.updates.length > 0) {
39
+ lines.push(color ? `${BOLD}## Updates${RST}` : "## Updates");
40
+ for (const c of report.updates) {
41
+ lines.push(formatCheck(c, color));
42
+ }
43
+ lines.push("");
44
+ }
45
+ if (report.wiki.length > 0) {
46
+ lines.push(color ? `${BOLD}## Current wiki${RST}` : "## Current wiki");
47
+ for (const c of report.wiki) {
48
+ lines.push(formatCheck(c, color));
49
+ }
50
+ lines.push("");
51
+ }
52
+ return `${lines.join("\n")}
53
+ `;
54
+ }
55
+ function formatCheck(c, color) {
56
+ const { icon, tint } = iconFor(c.status, color);
57
+ const head = ` ${tint}${icon}${color ? RST : ""} ${c.message}`;
58
+ if (c.fix === void 0) return head;
59
+ const fixLine = color ? ` ${DIM}${c.fix}${RST}` : ` ${c.fix}`;
60
+ return `${head}
61
+ ${fixLine}`;
62
+ }
63
+ function iconFor(status, color) {
64
+ switch (status) {
65
+ case "ok":
66
+ return { icon: "\u2713", tint: color ? GREEN : "" };
67
+ case "problem":
68
+ return { icon: "\u2717", tint: color ? RED : "" };
69
+ case "info":
70
+ return { icon: "\u25C7", tint: color ? BLUE : "" };
71
+ }
72
+ }
73
+
74
+ // src/commands/doctor-checks/install.ts
75
+ import { existsSync as existsSync2 } from "fs";
76
+ import { readFile } from "fs/promises";
77
+ import { homedir as homedir2 } from "os";
78
+ import path2 from "path";
79
+
80
+ // src/commands/doctor-checks/probes.ts
81
+ import { existsSync, readFileSync } from "fs";
82
+ import { createRequire } from "module";
83
+ import { homedir } from "os";
84
+ import path from "path";
85
+ import { fileURLToPath } from "url";
86
+ var req = createRequire(import.meta.url);
87
+ function detectInstallPath() {
88
+ try {
89
+ const here = fileURLToPath(import.meta.url);
90
+ let dir = path.dirname(here);
91
+ for (let i = 0; i < 6; i++) {
92
+ const pkgPath = path.join(dir, "package.json");
93
+ if (existsSync(pkgPath)) {
94
+ try {
95
+ const raw = readFileSync(pkgPath, "utf-8");
96
+ const pkg = JSON.parse(raw);
97
+ if (pkg.name === "codealmanac") return dir;
98
+ } catch {
99
+ }
100
+ }
101
+ const parent = path.dirname(dir);
102
+ if (parent === dir) break;
103
+ dir = parent;
104
+ }
105
+ return null;
106
+ } catch {
107
+ return null;
108
+ }
109
+ }
110
+ function classifyInstallPath(raw) {
111
+ if (raw === null) return { installPath: null, isEphemeral: false };
112
+ const home = homedir();
113
+ const ephemeralPrefixes = [
114
+ path.join(home, ".npm", "_npx"),
115
+ path.join(home, ".local", "share", "pnpm", "dlx"),
116
+ "/tmp/",
117
+ "/var/folders/"
118
+ ];
119
+ const isEphemeral = ephemeralPrefixes.some((p) => raw.startsWith(p));
120
+ return { installPath: raw, isEphemeral };
121
+ }
122
+ function probeBetterSqlite3() {
123
+ try {
124
+ const Database = req("better-sqlite3");
125
+ const db = new Database(":memory:");
126
+ db.close();
127
+ return { ok: true, summary: "native binding loads cleanly" };
128
+ } catch (err) {
129
+ const msg = err instanceof Error ? err.message : String(err);
130
+ const firstLine = msg.split("\n")[0] ?? msg;
131
+ return { ok: false, summary: firstLine };
132
+ }
133
+ }
134
+ async function safeCheckAuth(spawnCli) {
135
+ try {
136
+ return await checkClaudeAuth(spawnCli);
137
+ } catch {
138
+ return { loggedIn: false };
139
+ }
140
+ }
141
+ function readPackageVersion() {
142
+ const candidates = [
143
+ "../../../package.json",
144
+ "../../package.json",
145
+ "../package.json"
146
+ ];
147
+ for (const candidate of candidates) {
148
+ try {
149
+ const pkg = req(candidate);
150
+ if (typeof pkg.version === "string" && pkg.version.length > 0) {
151
+ return pkg.version;
152
+ }
153
+ } catch {
154
+ }
155
+ }
156
+ return null;
157
+ }
158
+
159
+ // src/commands/doctor-checks/install.ts
160
+ async function gatherInstallChecks(options) {
161
+ const checks = [];
162
+ const rawPath = options.installPath ?? detectInstallPath();
163
+ const { installPath, isEphemeral } = classifyInstallPath(rawPath);
164
+ checks.push(describeInstallPath(installPath, isEphemeral));
165
+ const nodeVersion = options.nodeVersion ?? process.version;
166
+ const sqlite = options.sqliteProbe ?? probeBetterSqlite3();
167
+ checks.push({
168
+ status: sqlite.ok ? "ok" : "problem",
169
+ key: "install.sqlite",
170
+ message: sqlite.ok ? `better-sqlite3 native binding OK (Node ${nodeVersion})` : `better-sqlite3 native binding failed: ${sqlite.summary}`,
171
+ fix: sqlite.ok ? void 0 : "run: npm rebuild better-sqlite3 (in the install directory)"
172
+ });
173
+ const auth = await safeCheckAuth(options.spawnCli);
174
+ checks.push(describeAuth(auth));
175
+ const settingsPath = options.settingsPath ?? path2.join(homedir2(), ".claude", "settings.json");
176
+ checks.push(await describeHook(settingsPath));
177
+ const claudeDir = options.claudeDir ?? path2.join(homedir2(), ".claude");
178
+ checks.push(describeGuides(claudeDir));
179
+ checks.push(await describeImportLine(claudeDir));
180
+ return checks;
181
+ }
182
+ function describeInstallPath(installPath, isEphemeral) {
183
+ if (installPath === null) {
184
+ return {
185
+ status: "problem",
186
+ key: "install.path",
187
+ message: "could not detect codealmanac install path",
188
+ fix: "reinstall with: npm install -g codealmanac"
189
+ };
190
+ }
191
+ return {
192
+ status: isEphemeral ? "info" : "ok",
193
+ key: "install.path",
194
+ message: isEphemeral ? `codealmanac running from ephemeral npx location: ${installPath}` : `codealmanac installed at ${installPath}`,
195
+ fix: isEphemeral ? "run: npm install -g codealmanac (to make the install permanent)" : void 0
196
+ };
197
+ }
198
+ function describeAuth(auth) {
199
+ if (auth.loggedIn) {
200
+ if (auth.authMethod === "apiKey") {
201
+ return {
202
+ status: "ok",
203
+ key: "install.auth",
204
+ message: "claude auth: ANTHROPIC_API_KEY set"
205
+ };
206
+ }
207
+ const who = auth.email ?? "Claude account";
208
+ const plan = auth.subscriptionType !== void 0 ? ` (${auth.subscriptionType} subscription)` : "";
209
+ return {
210
+ status: "ok",
211
+ key: "install.auth",
212
+ message: `claude auth: ${who}${plan}`
213
+ };
214
+ }
215
+ if (process.env.ANTHROPIC_API_KEY !== void 0 && process.env.ANTHROPIC_API_KEY.length > 0) {
216
+ return {
217
+ status: "ok",
218
+ key: "install.auth",
219
+ message: "claude auth: ANTHROPIC_API_KEY set"
220
+ };
221
+ }
222
+ return {
223
+ status: "problem",
224
+ key: "install.auth",
225
+ message: "claude auth: not signed in",
226
+ fix: "run: claude auth login --claudeai (or export ANTHROPIC_API_KEY)"
227
+ };
228
+ }
229
+ async function describeHook(settingsPath) {
230
+ if (!existsSync2(settingsPath)) {
231
+ return {
232
+ status: "problem",
233
+ key: "install.hook",
234
+ message: "SessionEnd hook not installed",
235
+ fix: "run: almanac setup --yes"
236
+ };
237
+ }
238
+ try {
239
+ const raw = await readFile(settingsPath, "utf8");
240
+ const parsed = JSON.parse(raw);
241
+ const entries = parsed.hooks?.SessionEnd ?? [];
242
+ const found = entries.some((e) => {
243
+ if (typeof e?.command === "string" && e.command.endsWith("almanac-capture.sh")) {
244
+ return true;
245
+ }
246
+ if (Array.isArray(e?.hooks)) {
247
+ return e.hooks.some(
248
+ (h) => typeof h?.command === "string" && h.command.endsWith("almanac-capture.sh")
249
+ );
250
+ }
251
+ return false;
252
+ });
253
+ if (!found) {
254
+ return {
255
+ status: "problem",
256
+ key: "install.hook",
257
+ message: "SessionEnd hook not installed",
258
+ fix: "run: almanac setup --yes"
259
+ };
260
+ }
261
+ return {
262
+ status: "ok",
263
+ key: "install.hook",
264
+ message: `SessionEnd hook installed at ${settingsPath}`
265
+ };
266
+ } catch (err) {
267
+ const msg = err instanceof Error ? err.message : String(err);
268
+ return {
269
+ status: "problem",
270
+ key: "install.hook",
271
+ message: `could not read ${settingsPath}: ${msg}`,
272
+ fix: "check the file for malformed JSON"
273
+ };
274
+ }
275
+ }
276
+ function describeGuides(claudeDir) {
277
+ const mini = path2.join(claudeDir, "codealmanac.md");
278
+ const ref = path2.join(claudeDir, "codealmanac-reference.md");
279
+ const haveMini = existsSync2(mini);
280
+ const haveRef = existsSync2(ref);
281
+ if (haveMini && haveRef) {
282
+ return {
283
+ status: "ok",
284
+ key: "install.guides",
285
+ message: `Agent guides installed (${path2.basename(mini)}, ${path2.basename(ref)})`
286
+ };
287
+ }
288
+ const missing = [
289
+ haveMini ? null : "codealmanac.md",
290
+ haveRef ? null : "codealmanac-reference.md"
291
+ ].filter((s) => s !== null);
292
+ return {
293
+ status: "problem",
294
+ key: "install.guides",
295
+ message: `Agent guides missing (${missing.join(", ")})`,
296
+ fix: "run: almanac setup --yes"
297
+ };
298
+ }
299
+ async function describeImportLine(claudeDir) {
300
+ const claudeMd = path2.join(claudeDir, "CLAUDE.md");
301
+ if (!existsSync2(claudeMd)) {
302
+ return {
303
+ status: "problem",
304
+ key: "install.import",
305
+ message: "CLAUDE.md import not present (no ~/.claude/CLAUDE.md)",
306
+ fix: "run: almanac setup --yes"
307
+ };
308
+ }
309
+ try {
310
+ const contents = await readFile(claudeMd, "utf8");
311
+ const lines = contents.split(/\r?\n/).map((l) => l.trim());
312
+ const present = lines.some((line) => {
313
+ if (line === IMPORT_LINE) return true;
314
+ if (!line.startsWith(IMPORT_LINE)) return false;
315
+ const next = line[IMPORT_LINE.length];
316
+ return next === " " || next === " ";
317
+ });
318
+ if (present) {
319
+ return {
320
+ status: "ok",
321
+ key: "install.import",
322
+ message: "CLAUDE.md import present"
323
+ };
324
+ }
325
+ return {
326
+ status: "problem",
327
+ key: "install.import",
328
+ message: "CLAUDE.md import line missing",
329
+ fix: "run: almanac setup --yes"
330
+ };
331
+ } catch (err) {
332
+ const msg = err instanceof Error ? err.message : String(err);
333
+ return {
334
+ status: "problem",
335
+ key: "install.import",
336
+ message: `could not read ${claudeMd}: ${msg}`
337
+ };
338
+ }
339
+ }
340
+
341
+ // src/commands/doctor-checks/updates.ts
342
+ async function gatherUpdateChecks(options, installedVersion) {
343
+ const checks = [];
344
+ const state = readStateForDoctor(options.updateStatePath);
345
+ const config = await readConfig(options.updateConfigPath);
346
+ if (state === null || state.latest_version.length === 0) {
347
+ checks.push({
348
+ status: "info",
349
+ key: "update.status",
350
+ message: `on ${installedVersion}; no update check has run yet`,
351
+ fix: "run: almanac update --check"
352
+ });
353
+ } else if (isNewer(state.latest_version, installedVersion)) {
354
+ const dismissed = state.dismissed_versions.includes(state.latest_version) ? " (dismissed \u2014 run `almanac update` to install anyway)" : "";
355
+ checks.push({
356
+ status: "problem",
357
+ key: "update.status",
358
+ message: `${state.latest_version} available (you're on ${installedVersion})${dismissed}`,
359
+ fix: "run: almanac update"
360
+ });
361
+ } else {
362
+ checks.push({
363
+ status: "ok",
364
+ key: "update.status",
365
+ message: `on latest (${installedVersion})`
366
+ });
367
+ }
368
+ if (state !== null && state.last_check_at > 0) {
369
+ const now = (options.now?.() ?? /* @__PURE__ */ new Date()).getTime();
370
+ const ageMs = now - state.last_check_at * 1e3;
371
+ const failedSuffix = state.last_fetch_failed_at !== void 0 && state.last_fetch_failed_at === state.last_check_at ? " (last attempt failed \u2014 will retry next invocation)" : "";
372
+ checks.push({
373
+ status: "info",
374
+ key: "update.last_check",
375
+ message: `last checked: ${formatDuration(ageMs)} ago${failedSuffix}`
376
+ });
377
+ } else {
378
+ checks.push({
379
+ status: "info",
380
+ key: "update.last_check",
381
+ message: "last checked: never"
382
+ });
383
+ }
384
+ checks.push({
385
+ status: "info",
386
+ key: "update.notifier",
387
+ message: `update notifier: ${config.update_notifier ? "enabled" : "disabled"}`,
388
+ fix: config.update_notifier ? void 0 : "run: almanac update --enable-notifier"
389
+ });
390
+ if (state !== null && state.dismissed_versions.length > 0) {
391
+ checks.push({
392
+ status: "info",
393
+ key: "update.dismissed",
394
+ message: `dismissed versions: ${state.dismissed_versions.join(", ")}`
395
+ });
396
+ }
397
+ return checks;
398
+ }
399
+
400
+ // src/commands/doctor.ts
401
+ async function runDoctor(options) {
402
+ const version = options.versionOverride ?? readPackageVersion() ?? "unknown";
403
+ const install = options.wikiOnly === true ? [] : await gatherInstallChecks(options);
404
+ const updates = options.wikiOnly === true ? [] : await gatherUpdateChecks(options, version);
405
+ const wiki = options.installOnly === true ? [] : await safeGatherWikiChecks(options);
406
+ const report = { version, install, updates, wiki };
407
+ if (options.json === true) {
408
+ return {
409
+ stdout: `${JSON.stringify(report, null, 2)}
410
+ `,
411
+ stderr: "",
412
+ exitCode: 0
413
+ };
414
+ }
415
+ return {
416
+ stdout: formatReport(report, options),
417
+ stderr: "",
418
+ exitCode: 0
419
+ };
420
+ }
421
+ async function safeGatherWikiChecks(options) {
422
+ try {
423
+ const { gatherWikiChecks } = await import("./wiki-EHZ7LG7R.js");
424
+ return await gatherWikiChecks(options);
425
+ } catch (err) {
426
+ const msg = err instanceof Error ? err.message : String(err);
427
+ return [
428
+ {
429
+ status: "problem",
430
+ key: "wiki.checks",
431
+ message: `could not run wiki checks: ${msg.split("\n")[0] ?? msg}`,
432
+ fix: "run: npm rebuild better-sqlite3 (in the install directory)"
433
+ }
434
+ ];
435
+ }
436
+ }
437
+
438
+ export {
439
+ runDoctor
440
+ };
441
+ //# sourceMappingURL=chunk-73A5TGBC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/doctor-checks/format.ts","../src/commands/doctor-checks/install.ts","../src/commands/doctor-checks/probes.ts","../src/commands/doctor-checks/updates.ts","../src/commands/doctor.ts"],"sourcesContent":["import { BLUE, BOLD, DIM, GREEN, RED, RST } from \"../../ansi.js\";\nimport type { Check, CheckStatus, DoctorOptions, DoctorReport } from \"./types.js\";\n\nexport function formatReport(\n report: DoctorReport,\n options: DoctorOptions,\n): string {\n const color = options.stdout === undefined && process.stdout.isTTY === true;\n const lines: string[] = [];\n lines.push(`codealmanac v${report.version}`);\n lines.push(\"\");\n if (report.install.length > 0) {\n lines.push(color ? `${BOLD}## Install${RST}` : \"## Install\");\n for (const c of report.install) {\n lines.push(formatCheck(c, color));\n }\n lines.push(\"\");\n }\n if (report.updates.length > 0) {\n lines.push(color ? `${BOLD}## Updates${RST}` : \"## Updates\");\n for (const c of report.updates) {\n lines.push(formatCheck(c, color));\n }\n lines.push(\"\");\n }\n if (report.wiki.length > 0) {\n lines.push(color ? `${BOLD}## Current wiki${RST}` : \"## Current wiki\");\n for (const c of report.wiki) {\n lines.push(formatCheck(c, color));\n }\n lines.push(\"\");\n }\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction formatCheck(c: Check, color: boolean): string {\n const { icon, tint } = iconFor(c.status, color);\n const head = ` ${tint}${icon}${color ? RST : \"\"} ${c.message}`;\n if (c.fix === undefined) return head;\n const fixLine = color\n ? ` ${DIM}${c.fix}${RST}`\n : ` ${c.fix}`;\n return `${head}\\n${fixLine}`;\n}\n\nfunction iconFor(\n status: CheckStatus,\n color: boolean,\n): { icon: string; tint: string } {\n switch (status) {\n case \"ok\":\n return { icon: \"\\u2713\", tint: color ? GREEN : \"\" };\n case \"problem\":\n return { icon: \"\\u2717\", tint: color ? RED : \"\" };\n case \"info\":\n return { icon: \"\\u25c7\", tint: color ? BLUE : \"\" };\n }\n}\n","import { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\n\nimport type { ClaudeAuthStatus } from \"../../agent/auth.js\";\nimport { IMPORT_LINE } from \"../setup.js\";\nimport {\n classifyInstallPath,\n detectInstallPath,\n probeBetterSqlite3,\n safeCheckAuth,\n} from \"./probes.js\";\nimport type { Check, DoctorOptions } from \"./types.js\";\n\nexport async function gatherInstallChecks(\n options: DoctorOptions,\n): Promise<Check[]> {\n const checks: Check[] = [];\n\n const rawPath = options.installPath ?? detectInstallPath();\n const { installPath, isEphemeral } = classifyInstallPath(rawPath);\n checks.push(describeInstallPath(installPath, isEphemeral));\n\n const nodeVersion = options.nodeVersion ?? process.version;\n const sqlite = options.sqliteProbe ?? probeBetterSqlite3();\n checks.push({\n status: sqlite.ok ? \"ok\" : \"problem\",\n key: \"install.sqlite\",\n message: sqlite.ok\n ? `better-sqlite3 native binding OK (Node ${nodeVersion})`\n : `better-sqlite3 native binding failed: ${sqlite.summary}`,\n fix: sqlite.ok\n ? undefined\n : \"run: npm rebuild better-sqlite3 (in the install directory)\",\n });\n\n const auth = await safeCheckAuth(options.spawnCli);\n checks.push(describeAuth(auth));\n\n const settingsPath =\n options.settingsPath ?? path.join(homedir(), \".claude\", \"settings.json\");\n checks.push(await describeHook(settingsPath));\n\n const claudeDir = options.claudeDir ?? path.join(homedir(), \".claude\");\n checks.push(describeGuides(claudeDir));\n checks.push(await describeImportLine(claudeDir));\n\n return checks;\n}\n\nfunction describeInstallPath(\n installPath: string | null,\n isEphemeral: boolean,\n): Check {\n if (installPath === null) {\n return {\n status: \"problem\",\n key: \"install.path\",\n message: \"could not detect codealmanac install path\",\n fix: \"reinstall with: npm install -g codealmanac\",\n };\n }\n return {\n status: isEphemeral ? \"info\" : \"ok\",\n key: \"install.path\",\n message: isEphemeral\n ? `codealmanac running from ephemeral npx location: ${installPath}`\n : `codealmanac installed at ${installPath}`,\n fix: isEphemeral\n ? \"run: npm install -g codealmanac (to make the install permanent)\"\n : undefined,\n };\n}\n\nfunction describeAuth(auth: ClaudeAuthStatus): Check {\n if (auth.loggedIn) {\n if (auth.authMethod === \"apiKey\") {\n return {\n status: \"ok\",\n key: \"install.auth\",\n message: \"claude auth: ANTHROPIC_API_KEY set\",\n };\n }\n const who = auth.email ?? \"Claude account\";\n const plan =\n auth.subscriptionType !== undefined\n ? ` (${auth.subscriptionType} subscription)`\n : \"\";\n return {\n status: \"ok\",\n key: \"install.auth\",\n message: `claude auth: ${who}${plan}`,\n };\n }\n if (\n process.env.ANTHROPIC_API_KEY !== undefined &&\n process.env.ANTHROPIC_API_KEY.length > 0\n ) {\n return {\n status: \"ok\",\n key: \"install.auth\",\n message: \"claude auth: ANTHROPIC_API_KEY set\",\n };\n }\n return {\n status: \"problem\",\n key: \"install.auth\",\n message: \"claude auth: not signed in\",\n fix: \"run: claude auth login --claudeai (or export ANTHROPIC_API_KEY)\",\n };\n}\n\nasync function describeHook(settingsPath: string): Promise<Check> {\n if (!existsSync(settingsPath)) {\n return {\n status: \"problem\",\n key: \"install.hook\",\n message: \"SessionEnd hook not installed\",\n fix: \"run: almanac setup --yes\",\n };\n }\n try {\n const raw = await readFile(settingsPath, \"utf8\");\n const parsed = JSON.parse(raw) as {\n hooks?: {\n SessionEnd?: {\n command?: string;\n hooks?: { command?: string }[];\n }[];\n };\n };\n const entries = parsed.hooks?.SessionEnd ?? [];\n const found = entries.some((e) => {\n if (\n typeof e?.command === \"string\" &&\n e.command.endsWith(\"almanac-capture.sh\")\n ) {\n return true;\n }\n if (Array.isArray(e?.hooks)) {\n return e.hooks.some(\n (h) =>\n typeof h?.command === \"string\" &&\n h.command.endsWith(\"almanac-capture.sh\"),\n );\n }\n return false;\n });\n if (!found) {\n return {\n status: \"problem\",\n key: \"install.hook\",\n message: \"SessionEnd hook not installed\",\n fix: \"run: almanac setup --yes\",\n };\n }\n return {\n status: \"ok\",\n key: \"install.hook\",\n message: `SessionEnd hook installed at ${settingsPath}`,\n };\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n status: \"problem\",\n key: \"install.hook\",\n message: `could not read ${settingsPath}: ${msg}`,\n fix: \"check the file for malformed JSON\",\n };\n }\n}\n\nfunction describeGuides(claudeDir: string): Check {\n const mini = path.join(claudeDir, \"codealmanac.md\");\n const ref = path.join(claudeDir, \"codealmanac-reference.md\");\n const haveMini = existsSync(mini);\n const haveRef = existsSync(ref);\n if (haveMini && haveRef) {\n return {\n status: \"ok\",\n key: \"install.guides\",\n message: `Agent guides installed (${path.basename(mini)}, ${path.basename(ref)})`,\n };\n }\n const missing = [\n haveMini ? null : \"codealmanac.md\",\n haveRef ? null : \"codealmanac-reference.md\",\n ].filter((s): s is string => s !== null);\n return {\n status: \"problem\",\n key: \"install.guides\",\n message: `Agent guides missing (${missing.join(\", \")})`,\n fix: \"run: almanac setup --yes\",\n };\n}\n\nasync function describeImportLine(claudeDir: string): Promise<Check> {\n const claudeMd = path.join(claudeDir, \"CLAUDE.md\");\n if (!existsSync(claudeMd)) {\n return {\n status: \"problem\",\n key: \"install.import\",\n message: \"CLAUDE.md import not present (no ~/.claude/CLAUDE.md)\",\n fix: \"run: almanac setup --yes\",\n };\n }\n try {\n const contents = await readFile(claudeMd, \"utf8\");\n const lines = contents.split(/\\r?\\n/).map((l) => l.trim());\n const present = 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 if (present) {\n return {\n status: \"ok\",\n key: \"install.import\",\n message: \"CLAUDE.md import present\",\n };\n }\n return {\n status: \"problem\",\n key: \"install.import\",\n message: \"CLAUDE.md import line missing\",\n fix: \"run: almanac setup --yes\",\n };\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n status: \"problem\",\n key: \"install.import\",\n message: `could not read ${claudeMd}: ${msg}`,\n };\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { checkClaudeAuth, type ClaudeAuthStatus, type SpawnCliFn } from \"../../agent/auth.js\";\nimport type { SqliteProbeResult } from \"./types.js\";\n\n// Single `createRequire` instance — used by package/binding probes.\nconst req = createRequire(import.meta.url);\n\n/**\n * Detect where codealmanac is installed by walking up from the running\n * module until we find a `package.json` whose `name` is `codealmanac`.\n */\nexport function detectInstallPath(): string | null {\n try {\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 if (existsSync(pkgPath)) {\n try {\n const raw = readFileSync(pkgPath, \"utf-8\");\n const pkg = JSON.parse(raw) as { name?: unknown };\n if (pkg.name === \"codealmanac\") return dir;\n } catch {\n // ignore — keep walking\n }\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Classify the detected install path as permanent or ephemeral.\n * Ephemeral locations (npm npx cache, pnpm dlx cache, /tmp/) are valid\n * installs but will disappear when the cache is evicted or the machine\n * reboots. Doctor reports them as `info` rather than `ok`.\n */\nexport function classifyInstallPath(\n raw: string | null,\n): { installPath: string | null; isEphemeral: boolean } {\n if (raw === null) return { installPath: null, isEphemeral: false };\n const home = homedir();\n const ephemeralPrefixes = [\n path.join(home, \".npm\", \"_npx\"),\n path.join(home, \".local\", \"share\", \"pnpm\", \"dlx\"),\n \"/tmp/\",\n \"/var/folders/\",\n ];\n const isEphemeral = ephemeralPrefixes.some((p) => raw.startsWith(p));\n return { installPath: raw, isEphemeral };\n}\n\n/**\n * Probe the better-sqlite3 native binding by opening an in-memory DB.\n */\nexport function probeBetterSqlite3(): SqliteProbeResult {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const Database = req(\"better-sqlite3\") as typeof import(\"better-sqlite3\");\n const db = new Database(\":memory:\");\n db.close();\n return { ok: true, summary: \"native binding loads cleanly\" };\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n const firstLine = msg.split(\"\\n\")[0] ?? msg;\n return { ok: false, summary: firstLine };\n }\n}\n\nexport async function safeCheckAuth(\n spawnCli?: SpawnCliFn,\n): Promise<ClaudeAuthStatus> {\n try {\n return await checkClaudeAuth(spawnCli);\n } catch {\n return { loggedIn: false };\n }\n}\n\nexport function readPackageVersion(): string | null {\n const candidates = [\n \"../../../package.json\",\n \"../../package.json\",\n \"../package.json\",\n ];\n for (const candidate of candidates) {\n try {\n const pkg = req(candidate) as { version?: unknown };\n if (typeof pkg.version === \"string\" && pkg.version.length > 0) {\n return pkg.version;\n }\n } catch {\n // Fall through to the next runtime layout candidate.\n }\n }\n return null;\n}\n","import { readConfig } from \"../../update/config.js\";\nimport { readStateForDoctor } from \"../../update/schedule.js\";\nimport { isNewer } from \"../../update/semver.js\";\nimport { formatDuration } from \"./duration.js\";\nimport type { Check, DoctorOptions } from \"./types.js\";\n\nexport async function gatherUpdateChecks(\n options: DoctorOptions,\n installedVersion: string,\n): Promise<Check[]> {\n const checks: Check[] = [];\n const state = readStateForDoctor(options.updateStatePath);\n const config = await readConfig(options.updateConfigPath);\n\n if (state === null || state.latest_version.length === 0) {\n checks.push({\n status: \"info\",\n key: \"update.status\",\n message: `on ${installedVersion}; no update check has run yet`,\n fix: \"run: almanac update --check\",\n });\n } else if (isNewer(state.latest_version, installedVersion)) {\n const dismissed = state.dismissed_versions.includes(state.latest_version)\n ? \" (dismissed — run `almanac update` to install anyway)\"\n : \"\";\n checks.push({\n status: \"problem\",\n key: \"update.status\",\n message:\n `${state.latest_version} available (you're on ${installedVersion})${dismissed}`,\n fix: \"run: almanac update\",\n });\n } else {\n checks.push({\n status: \"ok\",\n key: \"update.status\",\n message: `on latest (${installedVersion})`,\n });\n }\n\n if (state !== null && state.last_check_at > 0) {\n const now = (options.now?.() ?? new Date()).getTime();\n const ageMs = now - state.last_check_at * 1000;\n const failedSuffix =\n state.last_fetch_failed_at !== undefined &&\n state.last_fetch_failed_at === state.last_check_at\n ? \" (last attempt failed — will retry next invocation)\"\n : \"\";\n checks.push({\n status: \"info\",\n key: \"update.last_check\",\n message: `last checked: ${formatDuration(ageMs)} ago${failedSuffix}`,\n });\n } else {\n checks.push({\n status: \"info\",\n key: \"update.last_check\",\n message: \"last checked: never\",\n });\n }\n\n checks.push({\n status: \"info\",\n key: \"update.notifier\",\n message: `update notifier: ${config.update_notifier ? \"enabled\" : \"disabled\"}`,\n fix: config.update_notifier\n ? undefined\n : \"run: almanac update --enable-notifier\",\n });\n\n if (state !== null && state.dismissed_versions.length > 0) {\n checks.push({\n status: \"info\",\n key: \"update.dismissed\",\n message: `dismissed versions: ${state.dismissed_versions.join(\", \")}`,\n });\n }\n\n return checks;\n}\n","import { formatReport } from \"./doctor-checks/format.js\";\nimport { gatherInstallChecks } from \"./doctor-checks/install.js\";\nimport { readPackageVersion } from \"./doctor-checks/probes.js\";\nimport type {\n Check,\n CheckStatus,\n DoctorOptions,\n DoctorReport,\n DoctorResult,\n SqliteProbeResult,\n} from \"./doctor-checks/types.js\";\nimport { gatherUpdateChecks } from \"./doctor-checks/updates.js\";\n\nexport type {\n Check,\n CheckStatus,\n DoctorOptions,\n DoctorReport,\n DoctorResult,\n SqliteProbeResult,\n};\n\n/**\n * `almanac doctor` — install + wiki health report.\n *\n * Separate from `almanac health` (which checks graph integrity of a\n * specific wiki). `doctor` answers the \"is this install even set up\n * correctly?\" question that users hit when first trying the tool or when\n * sessions silently stop getting captured.\n *\n * This file is the command composition root. The section-specific probes\n * and formatting live in `doctor-checks/` so each durable fact has one\n * obvious owner.\n */\nexport async function runDoctor(\n options: DoctorOptions,\n): Promise<DoctorResult> {\n const version =\n options.versionOverride ?? readPackageVersion() ?? \"unknown\";\n\n const install: Check[] = options.wikiOnly === true\n ? []\n : await gatherInstallChecks(options);\n\n const updates: Check[] = options.wikiOnly === true\n ? []\n : await gatherUpdateChecks(options, version);\n\n const wiki: Check[] = options.installOnly === true\n ? []\n : await safeGatherWikiChecks(options);\n\n const report: DoctorReport = { version, install, updates, wiki };\n\n if (options.json === true) {\n return {\n stdout: `${JSON.stringify(report, null, 2)}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n return {\n stdout: formatReport(report, options),\n stderr: \"\",\n exitCode: 0,\n };\n}\n\nasync function safeGatherWikiChecks(\n options: DoctorOptions,\n): Promise<Check[]> {\n try {\n const { gatherWikiChecks } = await import(\"./doctor-checks/wiki.js\");\n return await gatherWikiChecks(options);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return [\n {\n status: \"problem\",\n key: \"wiki.checks\",\n message: `could not run wiki checks: ${msg.split(\"\\n\")[0] ?? msg}`,\n fix: \"run: npm rebuild better-sqlite3 (in the install directory)\",\n },\n ];\n }\n}\n"],"mappings":"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","names":["existsSync","homedir","path","path","homedir","existsSync"]}
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/paths.ts
4
+ import { existsSync } from "fs";
5
+ import { homedir } from "os";
6
+ import { dirname, isAbsolute, join, resolve } from "path";
7
+ function getGlobalAlmanacDir() {
8
+ return join(homedir(), ".almanac");
9
+ }
10
+ function getRegistryPath() {
11
+ return join(getGlobalAlmanacDir(), "registry.json");
12
+ }
13
+ function getRepoAlmanacDir(cwd) {
14
+ return join(cwd, ".almanac");
15
+ }
16
+ function findNearestAlmanacDir(startDir) {
17
+ const globalDir = getGlobalAlmanacDir();
18
+ let current = isAbsolute(startDir) ? startDir : resolve(startDir);
19
+ while (true) {
20
+ const candidate = join(current, ".almanac");
21
+ if (candidate !== globalDir && existsSync(candidate)) {
22
+ return current;
23
+ }
24
+ const parent = dirname(current);
25
+ if (parent === current) {
26
+ return null;
27
+ }
28
+ current = parent;
29
+ }
30
+ }
31
+
32
+ export {
33
+ getGlobalAlmanacDir,
34
+ getRegistryPath,
35
+ getRepoAlmanacDir,
36
+ findNearestAlmanacDir
37
+ };
38
+ //# sourceMappingURL=chunk-7JUX4ADQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/paths.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\n\n/**\n * Absolute path to the user-level `~/.almanac/` directory.\n *\n * All global state (the registry, future global config) lives here, not in\n * the repo. We resolve this via `os.homedir()` rather than `$HOME` so the\n * CLI behaves the same on macOS, Linux, and Windows.\n */\nexport function getGlobalAlmanacDir(): string {\n return join(homedir(), \".almanac\");\n}\n\n/**\n * Absolute path to the global registry file.\n *\n * The registry is the single source of truth for \"which wikis exist on this\n * machine.\" It is intentionally stored outside any repo so it survives\n * branch switches, clones, and repo deletions.\n */\nexport function getRegistryPath(): string {\n return join(getGlobalAlmanacDir(), \"registry.json\");\n}\n\n/**\n * Repo-level `.almanac/` path for a given working directory (not resolved —\n * just `join(cwd, \".almanac\")`). Use `findNearestAlmanacDir` when you need\n * to walk upward like git does.\n */\nexport function getRepoAlmanacDir(cwd: string): string {\n return join(cwd, \".almanac\");\n}\n\n/**\n * Walk upward from `startDir` looking for a directory that contains\n * `.almanac/`. Returns the absolute path to the repo root (the directory\n * containing `.almanac/`), or `null` if none is found before hitting the\n * filesystem root.\n *\n * Mirrors how `git` locates the enclosing repository. This lets `almanac`\n * work from any subdirectory inside a repo, not just the root.\n *\n * We explicitly skip the global `~/.almanac/` directory. It shares the\n * `.almanac` name with the per-repo wiki dir, but it's not a wiki — it\n * only holds the registry and global state. If the user runs `almanac\n * init` anywhere inside their home directory (outside a real wiki), we\n * must NOT treat `~` as an enclosing wiki root. Otherwise init would try\n * to register the home dir itself as a wiki.\n */\nexport function findNearestAlmanacDir(startDir: string): string | null {\n const globalDir = getGlobalAlmanacDir();\n let current = isAbsolute(startDir) ? startDir : resolve(startDir);\n\n // Walk until we hit the filesystem root. `dirname(\"/\")` returns `\"/\"`,\n // so the loop terminates when we stop ascending.\n while (true) {\n const candidate = join(current, \".almanac\");\n if (candidate !== globalDir && existsSync(candidate)) {\n return current;\n }\n const parent = dirname(current);\n if (parent === current) {\n return null;\n }\n current = parent;\n }\n}\n"],"mappings":";;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,SAAS,YAAY,MAAM,eAAe;AAS5C,SAAS,sBAA8B;AAC5C,SAAO,KAAK,QAAQ,GAAG,UAAU;AACnC;AASO,SAAS,kBAA0B;AACxC,SAAO,KAAK,oBAAoB,GAAG,eAAe;AACpD;AAOO,SAAS,kBAAkB,KAAqB;AACrD,SAAO,KAAK,KAAK,UAAU;AAC7B;AAkBO,SAAS,sBAAsB,UAAiC;AACrE,QAAM,YAAY,oBAAoB;AACtC,MAAI,UAAU,WAAW,QAAQ,IAAI,WAAW,QAAQ,QAAQ;AAIhE,SAAO,MAAM;AACX,UAAM,YAAY,KAAK,SAAS,UAAU;AAC1C,QAAI,cAAc,aAAa,WAAW,SAAS,GAAG;AACpD,aAAO;AAAA,IACT;AACA,UAAM,SAAS,QAAQ,OAAO;AAC9B,QAAI,WAAW,SAAS;AACtB,aAAO;AAAA,IACT;AACA,cAAU;AAAA,EACZ;AACF;","names":[]}