codealmanac 0.1.5 → 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 +68 -5954
- 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/guides/processing/claude-code.md +152 -0
- package/guides/processing/codex.md +214 -0
- package/guides/processing/generic.md +128 -0
- package/package.json +2 -2
|
@@ -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-2JJTTN7P.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-H6WU6PYH.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,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/helpers.ts
|
|
4
|
+
function emit(result) {
|
|
5
|
+
if (result.stderr.length > 0) process.stderr.write(result.stderr);
|
|
6
|
+
if (result.stdout.length > 0) process.stdout.write(result.stdout);
|
|
7
|
+
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
8
|
+
}
|
|
9
|
+
function collectOption(value, previous) {
|
|
10
|
+
return [...previous, value];
|
|
11
|
+
}
|
|
12
|
+
function parsePositiveInt(value) {
|
|
13
|
+
const n = Number.parseInt(value, 10);
|
|
14
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
15
|
+
throw new Error(`invalid --limit "${value}" (expected a non-negative integer)`);
|
|
16
|
+
}
|
|
17
|
+
return n;
|
|
18
|
+
}
|
|
19
|
+
async function readStdin() {
|
|
20
|
+
if (process.stdin.isTTY === true) return "";
|
|
21
|
+
const chunks = [];
|
|
22
|
+
for await (const chunk of process.stdin) {
|
|
23
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
24
|
+
}
|
|
25
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
emit,
|
|
30
|
+
collectOption,
|
|
31
|
+
parsePositiveInt,
|
|
32
|
+
readStdin
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=chunk-P3LDTCLB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/helpers.ts"],"sourcesContent":["export interface CommandResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nexport function emit(result: CommandResult): void {\n if (result.stderr.length > 0) process.stderr.write(result.stderr);\n if (result.stdout.length > 0) process.stdout.write(result.stdout);\n if (result.exitCode !== 0) process.exitCode = result.exitCode;\n}\n\nexport function collectOption(value: string, previous: string[]): string[] {\n return [...previous, value];\n}\n\nexport function parsePositiveInt(value: string): number {\n const n = Number.parseInt(value, 10);\n if (!Number.isFinite(n) || n < 0) {\n throw new Error(`invalid --limit \"${value}\" (expected a non-negative integer)`);\n }\n return n;\n}\n\nexport async function readStdin(): Promise<string> {\n if (process.stdin.isTTY === true) return \"\";\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n return Buffer.concat(chunks).toString(\"utf8\");\n}\n"],"mappings":";;;AAMO,SAAS,KAAK,QAA6B;AAChD,MAAI,OAAO,OAAO,SAAS,EAAG,SAAQ,OAAO,MAAM,OAAO,MAAM;AAChE,MAAI,OAAO,OAAO,SAAS,EAAG,SAAQ,OAAO,MAAM,OAAO,MAAM;AAChE,MAAI,OAAO,aAAa,EAAG,SAAQ,WAAW,OAAO;AACvD;AAEO,SAAS,cAAc,OAAe,UAA8B;AACzE,SAAO,CAAC,GAAG,UAAU,KAAK;AAC5B;AAEO,SAAS,iBAAiB,OAAuB;AACtD,QAAM,IAAI,OAAO,SAAS,OAAO,EAAE;AACnC,MAAI,CAAC,OAAO,SAAS,CAAC,KAAK,IAAI,GAAG;AAChC,UAAM,IAAI,MAAM,oBAAoB,KAAK,qCAAqC;AAAA,EAChF;AACA,SAAO;AACT;AAEA,eAAsB,YAA6B;AACjD,MAAI,QAAQ,MAAM,UAAU,KAAM,QAAO;AACzC,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,QAAQ,OAAO;AACvC,WAAO,KAAK,OAAO,SAAS,KAAK,IAAI,QAAQ,OAAO,KAAK,KAAK,CAAC;AAAA,EACjE;AACA,SAAO,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM;AAC9C;","names":[]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
checkForUpdate,
|
|
4
|
+
getConfigPath,
|
|
5
|
+
getStatePath
|
|
6
|
+
} from "./chunk-AXFPUHBN.js";
|
|
7
|
+
|
|
8
|
+
// src/update/schedule.ts
|
|
9
|
+
import { spawn } from "child_process";
|
|
10
|
+
import { readFileSync } from "fs";
|
|
11
|
+
function scheduleBackgroundUpdateCheck(argv) {
|
|
12
|
+
if (!shouldSchedule(argv)) return;
|
|
13
|
+
const scriptPath = argv[1];
|
|
14
|
+
const nodeBin = process.execPath;
|
|
15
|
+
if (scriptPath === void 0 || scriptPath.length === 0) return;
|
|
16
|
+
try {
|
|
17
|
+
const child = spawn(
|
|
18
|
+
nodeBin,
|
|
19
|
+
[scriptPath, "--internal-check-updates"],
|
|
20
|
+
{
|
|
21
|
+
detached: true,
|
|
22
|
+
stdio: "ignore"
|
|
23
|
+
// Windows: with `detached: true` and no `stdio`, Node opens a
|
|
24
|
+
// console window — `"ignore"` prevents that.
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
child.unref();
|
|
28
|
+
child.on("error", () => {
|
|
29
|
+
});
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function shouldSchedule(argv) {
|
|
34
|
+
if (process.env.CODEALMANAC_SKIP_UPDATE_CHECK === "1") return false;
|
|
35
|
+
if (process.env.NODE_ENV === "test") return false;
|
|
36
|
+
if (process.env.VITEST !== void 0) return false;
|
|
37
|
+
if (argv.slice(2).includes("--internal-check-updates")) return false;
|
|
38
|
+
if (!notifierEnabled()) return false;
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
function notifierEnabled() {
|
|
42
|
+
try {
|
|
43
|
+
const raw = readFileSync(getConfigPath(), "utf8");
|
|
44
|
+
const parsed = JSON.parse(raw);
|
|
45
|
+
if (parsed.update_notifier === false) return false;
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function runInternalUpdateCheck() {
|
|
52
|
+
try {
|
|
53
|
+
await checkForUpdate({});
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function readStateForDoctor(path) {
|
|
58
|
+
const file = path ?? getStatePath();
|
|
59
|
+
try {
|
|
60
|
+
const raw = readFileSync(file, "utf8");
|
|
61
|
+
const trimmed = raw.trim();
|
|
62
|
+
if (trimmed.length === 0) return null;
|
|
63
|
+
const parsed = JSON.parse(trimmed);
|
|
64
|
+
return {
|
|
65
|
+
last_check_at: typeof parsed.last_check_at === "number" ? parsed.last_check_at : 0,
|
|
66
|
+
installed_version: typeof parsed.installed_version === "string" ? parsed.installed_version : "",
|
|
67
|
+
latest_version: typeof parsed.latest_version === "string" ? parsed.latest_version : "",
|
|
68
|
+
dismissed_versions: Array.isArray(parsed.dismissed_versions) ? parsed.dismissed_versions.filter((v) => typeof v === "string") : [],
|
|
69
|
+
last_fetch_failed_at: typeof parsed.last_fetch_failed_at === "number" ? parsed.last_fetch_failed_at : void 0
|
|
70
|
+
};
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
scheduleBackgroundUpdateCheck,
|
|
78
|
+
runInternalUpdateCheck,
|
|
79
|
+
readStateForDoctor
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=chunk-QHQ6YH7U.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/update/schedule.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\n\nimport { checkForUpdate } from \"./check.js\";\nimport { getConfigPath } from \"./config.js\";\nimport { getStatePath, type UpdateState } from \"./state.js\";\n\n/**\n * Post-command scheduler for the background update check.\n *\n * After any normal `almanac <command>` exits, we want a fresh check to\n * have happened by the next invocation. We achieve that by spawning a\n * detached copy of ourselves with the hidden `--internal-check-updates`\n * flag; that child does nothing but hit the registry and write\n * `~/.almanac/update-state.json`, then exits.\n *\n * Why detach rather than check inline:\n * - 3s network timeout in the foreground would feel sluggish on every\n * command.\n * - `npm test` and CI scripts shouldn't pay for a registry round-trip\n * (gated below via env).\n * - A detached child with `stdio: \"ignore\"` cannot leak output into\n * the parent's stdout/stderr — critical for pipelines.\n *\n * Hazards we accept:\n * - A Claude Code subprocess whose parent shell exits right after the\n * `almanac` call may kill the child before it finishes. That's\n * fine: a failed check just means we try again next invocation.\n * - Detached child survival on Windows isn't as robust as on Unix.\n * Same fallback: next invocation retries.\n */\n\nexport function scheduleBackgroundUpdateCheck(argv: string[]): void {\n if (!shouldSchedule(argv)) return;\n\n const scriptPath = argv[1];\n const nodeBin = process.execPath;\n if (scriptPath === undefined || scriptPath.length === 0) return;\n\n // Spawn with the current Node and the same script path. `detached:\n // true` + `stdio: \"ignore\"` + `unref()` detaches the child from our\n // event loop so the parent can exit independently.\n try {\n const child = spawn(\n nodeBin,\n [scriptPath, \"--internal-check-updates\"],\n {\n detached: true,\n stdio: \"ignore\",\n // Windows: with `detached: true` and no `stdio`, Node opens a\n // console window — `\"ignore\"` prevents that.\n },\n );\n child.unref();\n // Swallow any synchronous spawn errors (e.g. ENOENT in strange\n // installs) — never propagate to the foreground command.\n child.on(\"error\", () => {});\n } catch {\n // Last-resort swallow: background checks are best-effort.\n }\n}\n\n/**\n * Should we spawn the worker at all?\n *\n * - Respect the `update_notifier` config — no banner means no need\n * for the data that feeds it.\n * - Skip in test environments so `npm test` doesn't fork 300 copies\n * of itself into the background and hammer the registry.\n * - Skip on the worker invocation itself (prevents a fork bomb).\n * - Skip when the user doesn't own the install path (permission\n * weirdness) — detected by `~/.almanac` mkdir failing; simplest\n * to just rely on the worker's own error handling, so we don't\n * gate here.\n * - Skip when the argv contains `--help`/`--version`/nothing — these\n * commands are often run from scripts that care about clean exit;\n * though the inline banner still shows, we don't kick off a check.\n */\nfunction shouldSchedule(argv: string[]): boolean {\n if (process.env.CODEALMANAC_SKIP_UPDATE_CHECK === \"1\") return false;\n if (process.env.NODE_ENV === \"test\") return false;\n if (process.env.VITEST !== undefined) return false;\n\n // Already the worker. argv[2..] contains the internal flag.\n if (argv.slice(2).includes(\"--internal-check-updates\")) return false;\n\n if (!notifierEnabled()) return false;\n\n return true;\n}\n\nfunction notifierEnabled(): boolean {\n try {\n const raw = readFileSync(getConfigPath(), \"utf8\");\n const parsed = JSON.parse(raw) as { update_notifier?: unknown };\n if (parsed.update_notifier === false) return false;\n return true;\n } catch {\n return true; // missing / malformed → default-on\n }\n}\n\n/**\n * The worker body. Invoked when `--internal-check-updates` appears on\n * the argv. Must be fast and must never print: the parent spawned us\n * with `stdio: \"ignore\"` but a stray write could still surprise a\n * downstream debugger.\n *\n * We take a simple file lock at `~/.almanac/.update-check.lock` to\n * prevent two workers running at the same time (which could happen if\n * the user fires several commands in parallel). The lock is just the\n * existence of the file with our PID inside; if an existing lock is\n * stale (older than the 3s + cache-write budget), we steal it.\n */\nexport async function runInternalUpdateCheck(): Promise<void> {\n // The worker is intentionally minimal. Any error (network, fs,\n // JSON) is handled inside `checkForUpdate` and surfaces as a\n // swallowed return; we just need to await it and exit.\n try {\n await checkForUpdate({});\n } catch {\n // Defense-in-depth: nothing must escape the worker.\n }\n}\n\n/**\n * Read the current state snapshot for diagnostic surfaces (doctor, the\n * `update --check` command). Wraps the sync read so callers can grab\n * state without the `async readState` ceremony.\n */\nexport function readStateForDoctor(path?: string): UpdateState | null {\n const file = path ?? getStatePath();\n try {\n const raw = readFileSync(file, \"utf8\");\n const trimmed = raw.trim();\n if (trimmed.length === 0) return null;\n const parsed = JSON.parse(trimmed) as Partial<UpdateState>;\n return {\n last_check_at:\n typeof parsed.last_check_at === \"number\" ? parsed.last_check_at : 0,\n installed_version:\n typeof parsed.installed_version === \"string\"\n ? parsed.installed_version\n : \"\",\n latest_version:\n typeof parsed.latest_version === \"string\" ? parsed.latest_version : \"\",\n dismissed_versions: Array.isArray(parsed.dismissed_versions)\n ? parsed.dismissed_versions.filter((v): v is string => typeof v === \"string\")\n : [],\n last_fetch_failed_at:\n typeof parsed.last_fetch_failed_at === \"number\"\n ? parsed.last_fetch_failed_at\n : undefined,\n };\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,aAAa;AACtB,SAAS,oBAAoB;AA+BtB,SAAS,8BAA8B,MAAsB;AAClE,MAAI,CAAC,eAAe,IAAI,EAAG;AAE3B,QAAM,aAAa,KAAK,CAAC;AACzB,QAAM,UAAU,QAAQ;AACxB,MAAI,eAAe,UAAa,WAAW,WAAW,EAAG;AAKzD,MAAI;AACF,UAAM,QAAQ;AAAA,MACZ;AAAA,MACA,CAAC,YAAY,0BAA0B;AAAA,MACvC;AAAA,QACE,UAAU;AAAA,QACV,OAAO;AAAA;AAAA;AAAA,MAGT;AAAA,IACF;AACA,UAAM,MAAM;AAGZ,UAAM,GAAG,SAAS,MAAM;AAAA,IAAC,CAAC;AAAA,EAC5B,QAAQ;AAAA,EAER;AACF;AAkBA,SAAS,eAAe,MAAyB;AAC/C,MAAI,QAAQ,IAAI,kCAAkC,IAAK,QAAO;AAC9D,MAAI,QAAQ,IAAI,aAAa,OAAQ,QAAO;AAC5C,MAAI,QAAQ,IAAI,WAAW,OAAW,QAAO;AAG7C,MAAI,KAAK,MAAM,CAAC,EAAE,SAAS,0BAA0B,EAAG,QAAO;AAE/D,MAAI,CAAC,gBAAgB,EAAG,QAAO;AAE/B,SAAO;AACT;AAEA,SAAS,kBAA2B;AAClC,MAAI;AACF,UAAM,MAAM,aAAa,cAAc,GAAG,MAAM;AAChD,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,OAAO,oBAAoB,MAAO,QAAO;AAC7C,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAcA,eAAsB,yBAAwC;AAI5D,MAAI;AACF,UAAM,eAAe,CAAC,CAAC;AAAA,EACzB,QAAQ;AAAA,EAER;AACF;AAOO,SAAS,mBAAmB,MAAmC;AACpE,QAAM,OAAO,QAAQ,aAAa;AAClC,MAAI;AACF,UAAM,MAAM,aAAa,MAAM,MAAM;AACrC,UAAM,UAAU,IAAI,KAAK;AACzB,QAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,WAAO;AAAA,MACL,eACE,OAAO,OAAO,kBAAkB,WAAW,OAAO,gBAAgB;AAAA,MACpE,mBACE,OAAO,OAAO,sBAAsB,WAChC,OAAO,oBACP;AAAA,MACN,gBACE,OAAO,OAAO,mBAAmB,WAAW,OAAO,iBAAiB;AAAA,MACtE,oBAAoB,MAAM,QAAQ,OAAO,kBAAkB,IACvD,OAAO,mBAAmB,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,IAC1E,CAAC;AAAA,MACL,sBACE,OAAO,OAAO,yBAAyB,WACnC,OAAO,uBACP;AAAA,IACR;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
|