codealmanac 0.1.10 → 0.2.1

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 (53) hide show
  1. package/README.md +124 -104
  2. package/dist/agents-A4II4YJC.js +15 -0
  3. package/dist/auth-S5DVUIUJ.js +18 -0
  4. package/dist/{chunk-Z4MWLVS2.js → chunk-447U3GQJ.js} +162 -5
  5. package/dist/chunk-447U3GQJ.js.map +1 -0
  6. package/dist/{chunk-QLHJP2XK.js → chunk-B2AGSRXL.js} +13 -9
  7. package/dist/{chunk-QLHJP2XK.js.map → chunk-B2AGSRXL.js.map} +1 -1
  8. package/dist/{chunk-AXFPUHBN.js → chunk-F53U6JQG.js} +8 -49
  9. package/dist/chunk-F53U6JQG.js.map +1 -0
  10. package/dist/{chunk-3C5SY5SE.js → chunk-KQUVMF27.js} +5 -2
  11. package/dist/chunk-KQUVMF27.js.map +1 -0
  12. package/dist/{chunk-BJVZLP6O.js → chunk-MX2EW5MR.js} +3 -3
  13. package/dist/{chunk-Z6MBJ3D2.js → chunk-QQHIVTXT.js} +6 -4
  14. package/dist/{chunk-Z6MBJ3D2.js.map → chunk-QQHIVTXT.js.map} +1 -1
  15. package/dist/chunk-R3URPHGH.js +194 -0
  16. package/dist/chunk-R3URPHGH.js.map +1 -0
  17. package/dist/chunk-SSYMRT4I.js +126 -0
  18. package/dist/chunk-SSYMRT4I.js.map +1 -0
  19. package/dist/{chunk-QHQ6YH7U.js → chunk-V3QOQSXI.js} +5 -3
  20. package/dist/{chunk-QHQ6YH7U.js.map → chunk-V3QOQSXI.js.map} +1 -1
  21. package/dist/chunk-WRUSDYYE.js +97 -0
  22. package/dist/chunk-WRUSDYYE.js.map +1 -0
  23. package/dist/{chunk-3LC55TG6.js → chunk-ZDJSJIB6.js} +77 -126
  24. package/dist/chunk-ZDJSJIB6.js.map +1 -0
  25. package/dist/{cli-W3OYVJYH.js → cli-MZEXRV6E.js} +238 -24
  26. package/dist/cli-MZEXRV6E.js.map +1 -0
  27. package/dist/codealmanac.js +1 -1
  28. package/dist/doctor-3BYSF3JD.js +17 -0
  29. package/dist/{hook-CRJMWSSO.js → hook-2NP3UE7U.js} +2 -2
  30. package/dist/{register-commands-JHC2OFKM.js → register-commands-DPH4ZWEE.js} +621 -60
  31. package/dist/register-commands-DPH4ZWEE.js.map +1 -0
  32. package/dist/uninstall-FDIOBAAR.js +15 -0
  33. package/dist/uninstall-FDIOBAAR.js.map +1 -0
  34. package/dist/update-RAF7QRYF.js +11 -0
  35. package/dist/update-RAF7QRYF.js.map +1 -0
  36. package/dist/{wiki-IPSRRGOT.js → wiki-IGNRNLUZ.js} +2 -2
  37. package/hooks/almanac-capture.sh +40 -7
  38. package/package.json +3 -2
  39. package/dist/chunk-3C5SY5SE.js.map +0 -1
  40. package/dist/chunk-3LC55TG6.js.map +0 -1
  41. package/dist/chunk-AXFPUHBN.js.map +0 -1
  42. package/dist/chunk-Z4MWLVS2.js.map +0 -1
  43. package/dist/cli-W3OYVJYH.js.map +0 -1
  44. package/dist/doctor-ODFNJUKH.js +0 -15
  45. package/dist/register-commands-JHC2OFKM.js.map +0 -1
  46. package/dist/uninstall-HE2Z2LN2.js +0 -12
  47. package/dist/update-IL243I4E.js +0 -10
  48. /package/dist/{doctor-ODFNJUKH.js.map → agents-A4II4YJC.js.map} +0 -0
  49. /package/dist/{hook-CRJMWSSO.js.map → auth-S5DVUIUJ.js.map} +0 -0
  50. /package/dist/{chunk-BJVZLP6O.js.map → chunk-MX2EW5MR.js.map} +0 -0
  51. /package/dist/{uninstall-HE2Z2LN2.js.map → doctor-3BYSF3JD.js.map} +0 -0
  52. /package/dist/{update-IL243I4E.js.map → hook-2NP3UE7U.js.map} +0 -0
  53. /package/dist/{wiki-IPSRRGOT.js.map → wiki-IGNRNLUZ.js.map} +0 -0
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/agent/auth.ts
4
+ import { spawn, spawnSync } from "child_process";
5
+ import { createRequire } from "module";
6
+ import { dirname, join } from "path";
7
+ var AUTH_TIMEOUT_MS = 1e4;
8
+ function resolveClaudeExecutable() {
9
+ const result = spawnSync("sh", ["-lc", "command -v claude"], {
10
+ encoding: "utf8"
11
+ });
12
+ if (result.status !== 0) return void 0;
13
+ const found = result.stdout.trim().split("\n")[0]?.trim();
14
+ return found !== void 0 && found.length > 0 ? found : void 0;
15
+ }
16
+ function resolveCliJsPath() {
17
+ const require2 = createRequire(import.meta.url);
18
+ const entry = require2.resolve("@anthropic-ai/claude-agent-sdk");
19
+ return join(dirname(entry), "cli.js");
20
+ }
21
+ var defaultSpawnCli = (args) => {
22
+ const command = resolveClaudeExecutable() ?? "claude";
23
+ const child = spawn(command, args, {
24
+ stdio: ["ignore", "pipe", "pipe"]
25
+ });
26
+ return child;
27
+ };
28
+ var legacySdkSpawnCli = (args) => {
29
+ const cliPath = resolveCliJsPath();
30
+ const child = spawn(process.execPath, [cliPath, ...args], {
31
+ stdio: ["ignore", "pipe", "pipe"]
32
+ });
33
+ return child;
34
+ };
35
+ async function checkClaudeAuth(spawnCli = defaultSpawnCli) {
36
+ if (spawnCli === defaultSpawnCli) {
37
+ const status = await checkClaudeAuthWith(defaultSpawnCli);
38
+ if (status.loggedIn) return status;
39
+ return await checkClaudeAuthWith(legacySdkSpawnCli);
40
+ }
41
+ return await checkClaudeAuthWith(spawnCli);
42
+ }
43
+ async function checkClaudeAuthWith(spawnCli) {
44
+ let child;
45
+ try {
46
+ child = spawnCli(["auth", "status", "--json"]);
47
+ } catch {
48
+ return { loggedIn: false };
49
+ }
50
+ return new Promise((resolve) => {
51
+ let stdout = "";
52
+ let stderr = "";
53
+ let settled = false;
54
+ const settle = (value) => {
55
+ if (settled) return;
56
+ settled = true;
57
+ clearTimeout(timer);
58
+ resolve(value);
59
+ };
60
+ const timer = setTimeout(() => {
61
+ try {
62
+ child.kill("SIGTERM");
63
+ } catch {
64
+ }
65
+ settle({ loggedIn: false });
66
+ }, AUTH_TIMEOUT_MS);
67
+ child.stdout.on("data", (data) => {
68
+ stdout += data.toString();
69
+ });
70
+ child.stderr.on("data", (data) => {
71
+ stderr += data.toString();
72
+ });
73
+ child.on("error", () => {
74
+ settle({ loggedIn: false });
75
+ });
76
+ child.on("close", (code) => {
77
+ if (code !== 0 && stdout.trim().length === 0) {
78
+ void stderr;
79
+ settle({ loggedIn: false });
80
+ return;
81
+ }
82
+ try {
83
+ settle(parseClaudeAuthStatus(stdout.trim()));
84
+ } catch {
85
+ settle({ loggedIn: false });
86
+ }
87
+ });
88
+ });
89
+ }
90
+ function parseClaudeAuthStatus(raw) {
91
+ const parsed = JSON.parse(raw);
92
+ const loggedIn = parsed.loggedIn === true;
93
+ const out = { loggedIn };
94
+ if (typeof parsed.email === "string") out.email = parsed.email;
95
+ if (typeof parsed.subscriptionType === "string") {
96
+ out.subscriptionType = parsed.subscriptionType;
97
+ }
98
+ if (typeof parsed.authMethod === "string") {
99
+ out.authMethod = parsed.authMethod;
100
+ }
101
+ return out;
102
+ }
103
+ 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";
104
+ async function assertClaudeAuth(spawnCli = defaultSpawnCli) {
105
+ const status = await checkClaudeAuth(spawnCli);
106
+ if (status.loggedIn) {
107
+ return status;
108
+ }
109
+ const apiKey = process.env.ANTHROPIC_API_KEY;
110
+ if (apiKey !== void 0 && apiKey.length > 0) {
111
+ return { loggedIn: true, authMethod: "apiKey" };
112
+ }
113
+ const err = new Error(UNAUTHENTICATED_MESSAGE);
114
+ err.code = "CLAUDE_AUTH_MISSING";
115
+ throw err;
116
+ }
117
+
118
+ export {
119
+ resolveClaudeExecutable,
120
+ defaultSpawnCli,
121
+ legacySdkSpawnCli,
122
+ checkClaudeAuth,
123
+ UNAUTHENTICATED_MESSAGE,
124
+ assertClaudeAuth
125
+ };
126
+ //# sourceMappingURL=chunk-SSYMRT4I.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/agent/auth.ts"],"sourcesContent":["import { spawn, spawnSync, 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 * Claude Code owns subscription OAuth credentials. Users who are logged in\n * via `claude auth login --claudeai` should be able to run bootstrap/capture\n * without 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 * Current Claude Agent SDK packages no longer ship the old private\n * `cli.js` entrypoint, so the primary probe is the public Claude Code CLI:\n * `claude auth status --json`. We keep the SDK `cli.js` probe as a legacy\n * fallback for older SDK layouts.\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.\n */\nexport type SpawnCliFn = (args: string[]) => SpawnedProcess;\n\nconst AUTH_TIMEOUT_MS = 10_000;\n\n/**\n * Resolve the installed Claude Code executable from PATH. The Agent SDK can\n * accept this path via `pathToClaudeCodeExecutable`, and the auth probe uses\n * the same binary so CodeAlmanac agrees with `claude auth status`.\n */\nexport function resolveClaudeExecutable(): string | undefined {\n const result = spawnSync(\"sh\", [\"-lc\", \"command -v claude\"], {\n encoding: \"utf8\",\n });\n if (result.status !== 0) return undefined;\n const found = result.stdout.trim().split(\"\\n\")[0]?.trim();\n return found !== undefined && found.length > 0 ? found : undefined;\n}\n\n/**\n * Resolve legacy `cli.js` from older `@anthropic-ai/claude-agent-sdk`\n * installs. SDK 0.2.129+ no longer ships this file; callers must treat\n * failure as expected and fall back to the public `claude` binary.\n */\nfunction resolveCliJsPath(): string {\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 installed\n * Claude Code CLI.\n */\nexport const defaultSpawnCli: SpawnCliFn = (args: string[]) => {\n const command = resolveClaudeExecutable() ?? \"claude\";\n const child = spawn(command, args, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n return child as unknown as SpawnedProcess;\n};\n\nexport const legacySdkSpawnCli: SpawnCliFn = (args: string[]) => {\n const cliPath = resolveCliJsPath();\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 `claude auth status --json`, falling back to the legacy SDK CLI\n * layout when available. On any failure (spawn error, non-JSON stdout,\n * non-zero exit, timeout) we return `{ loggedIn: false }` rather than\n * propagating the error — the caller will fall back to the\n * `ANTHROPIC_API_KEY` path and, if that's also missing, produce a clean\n * 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 if (spawnCli === defaultSpawnCli) {\n const status = await checkClaudeAuthWith(defaultSpawnCli);\n if (status.loggedIn) return status;\n return await checkClaudeAuthWith(legacySdkSpawnCli);\n }\n return await checkClaudeAuthWith(spawnCli);\n}\n\nasync function checkClaudeAuthWith(\n spawnCli: SpawnCliFn,\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 settle(parseClaudeAuthStatus(stdout.trim()));\n } catch {\n settle({ loggedIn: false });\n }\n });\n });\n}\n\nfunction parseClaudeAuthStatus(raw: string): ClaudeAuthStatus {\n const parsed = JSON.parse(raw) 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 return out;\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"],"mappings":"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","names":["require"]}
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  checkForUpdate,
4
- getConfigPath,
5
4
  getStatePath
6
- } from "./chunk-AXFPUHBN.js";
5
+ } from "./chunk-F53U6JQG.js";
6
+ import {
7
+ getConfigPath
8
+ } from "./chunk-WRUSDYYE.js";
7
9
 
8
10
  // src/update/schedule.ts
9
11
  import { spawn } from "child_process";
@@ -78,4 +80,4 @@ export {
78
80
  runInternalUpdateCheck,
79
81
  readStateForDoctor
80
82
  };
81
- //# sourceMappingURL=chunk-QHQ6YH7U.js.map
83
+ //# sourceMappingURL=chunk-V3QOQSXI.js.map
@@ -1 +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":[]}
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":[]}
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getGlobalAlmanacDir
4
+ } from "./chunk-7JUX4ADQ.js";
5
+
6
+ // src/update/config.ts
7
+ import { mkdir, readFile, rename, writeFile } from "fs/promises";
8
+ import { dirname, join } from "path";
9
+ var AGENT_PROVIDER_IDS = ["claude", "codex", "cursor"];
10
+ function isAgentProviderId(value) {
11
+ return AGENT_PROVIDER_IDS.includes(value);
12
+ }
13
+ function defaultConfig() {
14
+ return {
15
+ update_notifier: true,
16
+ agent: {
17
+ default: "claude",
18
+ models: {
19
+ claude: "claude-sonnet-4-6",
20
+ codex: null,
21
+ cursor: null
22
+ }
23
+ }
24
+ };
25
+ }
26
+ function getConfigPath() {
27
+ return join(getGlobalAlmanacDir(), "config.json");
28
+ }
29
+ async function readConfig(path) {
30
+ const file = path ?? getConfigPath();
31
+ let raw;
32
+ try {
33
+ raw = await readFile(file, "utf8");
34
+ } catch {
35
+ return defaultConfig();
36
+ }
37
+ const trimmed = raw.trim();
38
+ if (trimmed.length === 0) return defaultConfig();
39
+ try {
40
+ const parsed = JSON.parse(trimmed);
41
+ const defaults = defaultConfig();
42
+ const rawAgent = parsed.agent !== void 0 && parsed.agent !== null && typeof parsed.agent === "object" ? parsed.agent : {};
43
+ const rawDefault = typeof rawAgent.default === "string" && isAgentProviderId(rawAgent.default) ? rawAgent.default : defaults.agent.default;
44
+ const rawModels = rawAgent.models !== void 0 && rawAgent.models !== null && typeof rawAgent.models === "object" ? rawAgent.models : {};
45
+ const models = {
46
+ ...defaults.agent.models
47
+ };
48
+ for (const id of AGENT_PROVIDER_IDS) {
49
+ const value = rawModels[id];
50
+ if (typeof value === "string" && value.length > 0) {
51
+ models[id] = value;
52
+ } else if (value === null) {
53
+ models[id] = null;
54
+ }
55
+ }
56
+ return {
57
+ update_notifier: typeof parsed.update_notifier === "boolean" ? parsed.update_notifier : true,
58
+ agent: {
59
+ default: rawDefault,
60
+ models
61
+ }
62
+ };
63
+ } catch {
64
+ return defaultConfig();
65
+ }
66
+ }
67
+ async function writeConfig(config, path) {
68
+ const file = path ?? getConfigPath();
69
+ await mkdir(dirname(file), { recursive: true });
70
+ const body = `${JSON.stringify(normalizeConfig(config), null, 2)}
71
+ `;
72
+ const tmp = `${file}.tmp`;
73
+ await writeFile(tmp, body, "utf8");
74
+ await rename(tmp, file);
75
+ }
76
+ function normalizeConfig(config) {
77
+ const defaults = defaultConfig();
78
+ return {
79
+ update_notifier: typeof config.update_notifier === "boolean" ? config.update_notifier : defaults.update_notifier,
80
+ agent: {
81
+ default: config.agent !== void 0 && isAgentProviderId(config.agent.default) ? config.agent.default : defaults.agent.default,
82
+ models: {
83
+ ...defaults.agent.models,
84
+ ...config.agent?.models ?? {}
85
+ }
86
+ }
87
+ };
88
+ }
89
+
90
+ export {
91
+ AGENT_PROVIDER_IDS,
92
+ isAgentProviderId,
93
+ getConfigPath,
94
+ readConfig,
95
+ writeConfig
96
+ };
97
+ //# sourceMappingURL=chunk-WRUSDYYE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/update/config.ts"],"sourcesContent":["import { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport { getGlobalAlmanacDir } from \"../paths.js\";\n\nexport const AGENT_PROVIDER_IDS = [\"claude\", \"codex\", \"cursor\"] as const;\nexport type AgentProviderId = (typeof AGENT_PROVIDER_IDS)[number];\n\nexport function isAgentProviderId(value: string): value is AgentProviderId {\n return (AGENT_PROVIDER_IDS as readonly string[]).includes(value);\n}\n\nexport interface AgentConfig {\n /** Default provider for bootstrap/capture. Default: \"claude\". */\n default: AgentProviderId;\n /** Optional per-provider model override. `null` means provider default. */\n models: Partial<Record<AgentProviderId, string | null>>;\n}\n\n/**\n * `~/.almanac/config.json` — global, cross-wiki configuration. Today\n * the only field is `update_notifier` (on/off toggle for the pre-command\n * banner); designed as an object so we can add more knobs without\n * breaking users who already have the file on disk.\n *\n * Missing or malformed → defaults. Same tolerance as `UpdateState`:\n * the CLI must not be able to fail because this file drifted.\n */\nexport interface GlobalConfig {\n /** When `false`, suppress the pre-command update-nag banner. Default: true. */\n update_notifier: boolean;\n /** Agent-provider settings for bootstrap/capture. */\n agent: AgentConfig;\n}\n\nexport function defaultConfig(): GlobalConfig {\n return {\n update_notifier: true,\n agent: {\n default: \"claude\",\n models: {\n claude: \"claude-sonnet-4-6\",\n codex: null,\n cursor: null,\n },\n },\n };\n}\n\nexport function getConfigPath(): string {\n return join(getGlobalAlmanacDir(), \"config.json\");\n}\n\nexport async function readConfig(path?: string): Promise<GlobalConfig> {\n const file = path ?? getConfigPath();\n let raw: string;\n try {\n raw = await readFile(file, \"utf8\");\n } catch {\n return defaultConfig();\n }\n const trimmed = raw.trim();\n if (trimmed.length === 0) return defaultConfig();\n try {\n const parsed = JSON.parse(trimmed) as Partial<GlobalConfig>;\n const defaults = defaultConfig();\n const rawAgent =\n parsed.agent !== undefined &&\n parsed.agent !== null &&\n typeof parsed.agent === \"object\"\n ? (parsed.agent as Partial<AgentConfig>)\n : {};\n const rawDefault =\n typeof rawAgent.default === \"string\" &&\n isAgentProviderId(rawAgent.default)\n ? rawAgent.default\n : defaults.agent.default;\n const rawModels =\n rawAgent.models !== undefined &&\n rawAgent.models !== null &&\n typeof rawAgent.models === \"object\"\n ? (rawAgent.models as Record<string, unknown>)\n : {};\n const models: Partial<Record<AgentProviderId, string | null>> = {\n ...defaults.agent.models,\n };\n for (const id of AGENT_PROVIDER_IDS) {\n const value = rawModels[id];\n if (typeof value === \"string\" && value.length > 0) {\n models[id] = value;\n } else if (value === null) {\n models[id] = null;\n }\n }\n return {\n update_notifier:\n typeof parsed.update_notifier === \"boolean\"\n ? parsed.update_notifier\n : true,\n agent: {\n default: rawDefault,\n models,\n },\n };\n } catch {\n return defaultConfig();\n }\n}\n\nexport async function writeConfig(\n config: GlobalConfig | Partial<GlobalConfig>,\n path?: string,\n): Promise<void> {\n const file = path ?? getConfigPath();\n await mkdir(dirname(file), { recursive: true });\n const body = `${JSON.stringify(normalizeConfig(config), null, 2)}\\n`;\n const tmp = `${file}.tmp`;\n await writeFile(tmp, body, \"utf8\");\n await rename(tmp, file);\n}\n\nfunction normalizeConfig(config: GlobalConfig | Partial<GlobalConfig>): GlobalConfig {\n const defaults = defaultConfig();\n return {\n update_notifier:\n typeof config.update_notifier === \"boolean\"\n ? config.update_notifier\n : defaults.update_notifier,\n agent: {\n default:\n config.agent !== undefined && isAgentProviderId(config.agent.default)\n ? config.agent.default\n : defaults.agent.default,\n models: {\n ...defaults.agent.models,\n ...(config.agent?.models ?? {}),\n },\n },\n };\n}\n"],"mappings":";;;;;;AAAA,SAAS,OAAO,UAAU,QAAQ,iBAAiB;AACnD,SAAS,SAAS,YAAY;AAIvB,IAAM,qBAAqB,CAAC,UAAU,SAAS,QAAQ;AAGvD,SAAS,kBAAkB,OAAyC;AACzE,SAAQ,mBAAyC,SAAS,KAAK;AACjE;AAyBO,SAAS,gBAA8B;AAC5C,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,OAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ;AAAA,QACN,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,QAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,gBAAwB;AACtC,SAAO,KAAK,oBAAoB,GAAG,aAAa;AAClD;AAEA,eAAsB,WAAW,MAAsC;AACrE,QAAM,OAAO,QAAQ,cAAc;AACnC,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,SAAS,MAAM,MAAM;AAAA,EACnC,QAAQ;AACN,WAAO,cAAc;AAAA,EACvB;AACA,QAAM,UAAU,IAAI,KAAK;AACzB,MAAI,QAAQ,WAAW,EAAG,QAAO,cAAc;AAC/C,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,UAAM,WAAW,cAAc;AAC/B,UAAM,WACJ,OAAO,UAAU,UACjB,OAAO,UAAU,QACjB,OAAO,OAAO,UAAU,WACnB,OAAO,QACR,CAAC;AACP,UAAM,aACJ,OAAO,SAAS,YAAY,YAC5B,kBAAkB,SAAS,OAAO,IAC9B,SAAS,UACT,SAAS,MAAM;AACrB,UAAM,YACJ,SAAS,WAAW,UACpB,SAAS,WAAW,QACpB,OAAO,SAAS,WAAW,WACtB,SAAS,SACV,CAAC;AACP,UAAM,SAA0D;AAAA,MAC9D,GAAG,SAAS,MAAM;AAAA,IACpB;AACA,eAAW,MAAM,oBAAoB;AACnC,YAAM,QAAQ,UAAU,EAAE;AAC1B,UAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,eAAO,EAAE,IAAI;AAAA,MACf,WAAW,UAAU,MAAM;AACzB,eAAO,EAAE,IAAI;AAAA,MACf;AAAA,IACF;AACA,WAAO;AAAA,MACL,iBACE,OAAO,OAAO,oBAAoB,YAC9B,OAAO,kBACP;AAAA,MACN,OAAO;AAAA,QACL,SAAS;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAAA,EACF,QAAQ;AACN,WAAO,cAAc;AAAA,EACvB;AACF;AAEA,eAAsB,YACpB,QACA,MACe;AACf,QAAM,OAAO,QAAQ,cAAc;AACnC,QAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,QAAM,OAAO,GAAG,KAAK,UAAU,gBAAgB,MAAM,GAAG,MAAM,CAAC,CAAC;AAAA;AAChE,QAAM,MAAM,GAAG,IAAI;AACnB,QAAM,UAAU,KAAK,MAAM,MAAM;AACjC,QAAM,OAAO,KAAK,IAAI;AACxB;AAEA,SAAS,gBAAgB,QAA4D;AACnF,QAAM,WAAW,cAAc;AAC/B,SAAO;AAAA,IACL,iBACE,OAAO,OAAO,oBAAoB,YAC9B,OAAO,kBACP,SAAS;AAAA,IACf,OAAO;AAAA,MACL,SACE,OAAO,UAAU,UAAa,kBAAkB,OAAO,MAAM,OAAO,IAChE,OAAO,MAAM,UACb,SAAS,MAAM;AAAA,MACrB,QAAQ;AAAA,QACN,GAAG,SAAS,MAAM;AAAA,QAClB,GAAI,OAAO,OAAO,UAAU,CAAC;AAAA,MAC/B;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -1,7 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runHookInstall
4
- } from "./chunk-Z4MWLVS2.js";
4
+ } from "./chunk-447U3GQJ.js";
5
+ import {
6
+ UNAUTHENTICATED_MESSAGE,
7
+ checkClaudeAuth
8
+ } from "./chunk-SSYMRT4I.js";
9
+ import {
10
+ isAgentProviderId,
11
+ readConfig,
12
+ writeConfig
13
+ } from "./chunk-WRUSDYYE.js";
5
14
 
6
15
  // src/commands/setup.ts
7
16
  import { existsSync as existsSync2 } from "fs";
@@ -11,135 +20,20 @@ import {
11
20
  readFile,
12
21
  writeFile
13
22
  } from "fs/promises";
14
- import { createRequire as createRequire3 } from "module";
23
+ import { createRequire as createRequire2 } from "module";
15
24
  import { homedir as homedir2 } from "os";
16
25
  import path3 from "path";
17
26
  import { fileURLToPath as fileURLToPath2 } from "url";
18
27
 
19
- // src/agent/auth.ts
20
- import { spawn, spawnSync } from "child_process";
21
- import { createRequire } from "module";
22
- import { dirname, join } from "path";
23
- var AUTH_TIMEOUT_MS = 1e4;
24
- function resolveClaudeExecutable() {
25
- const result = spawnSync("sh", ["-lc", "command -v claude"], {
26
- encoding: "utf8"
27
- });
28
- if (result.status !== 0) return void 0;
29
- const found = result.stdout.trim().split("\n")[0]?.trim();
30
- return found !== void 0 && found.length > 0 ? found : void 0;
31
- }
32
- function resolveCliJsPath() {
33
- const require2 = createRequire(import.meta.url);
34
- const entry = require2.resolve("@anthropic-ai/claude-agent-sdk");
35
- return join(dirname(entry), "cli.js");
36
- }
37
- var defaultSpawnCli = (args) => {
38
- const command = resolveClaudeExecutable() ?? "claude";
39
- const child = spawn(command, args, {
40
- stdio: ["ignore", "pipe", "pipe"]
41
- });
42
- return child;
43
- };
44
- var legacySdkSpawnCli = (args) => {
45
- const cliPath = resolveCliJsPath();
46
- const child = spawn(process.execPath, [cliPath, ...args], {
47
- stdio: ["ignore", "pipe", "pipe"]
48
- });
49
- return child;
50
- };
51
- async function checkClaudeAuth(spawnCli = defaultSpawnCli) {
52
- if (spawnCli === defaultSpawnCli) {
53
- const status = await checkClaudeAuthWith(defaultSpawnCli);
54
- if (status.loggedIn) return status;
55
- return await checkClaudeAuthWith(legacySdkSpawnCli);
56
- }
57
- return await checkClaudeAuthWith(spawnCli);
58
- }
59
- async function checkClaudeAuthWith(spawnCli) {
60
- let child;
61
- try {
62
- child = spawnCli(["auth", "status", "--json"]);
63
- } catch {
64
- return { loggedIn: false };
65
- }
66
- return new Promise((resolve) => {
67
- let stdout = "";
68
- let stderr = "";
69
- let settled = false;
70
- const settle = (value) => {
71
- if (settled) return;
72
- settled = true;
73
- clearTimeout(timer);
74
- resolve(value);
75
- };
76
- const timer = setTimeout(() => {
77
- try {
78
- child.kill("SIGTERM");
79
- } catch {
80
- }
81
- settle({ loggedIn: false });
82
- }, AUTH_TIMEOUT_MS);
83
- child.stdout.on("data", (data) => {
84
- stdout += data.toString();
85
- });
86
- child.stderr.on("data", (data) => {
87
- stderr += data.toString();
88
- });
89
- child.on("error", () => {
90
- settle({ loggedIn: false });
91
- });
92
- child.on("close", (code) => {
93
- if (code !== 0 && stdout.trim().length === 0) {
94
- void stderr;
95
- settle({ loggedIn: false });
96
- return;
97
- }
98
- try {
99
- settle(parseClaudeAuthStatus(stdout.trim()));
100
- } catch {
101
- settle({ loggedIn: false });
102
- }
103
- });
104
- });
105
- }
106
- function parseClaudeAuthStatus(raw) {
107
- const parsed = JSON.parse(raw);
108
- const loggedIn = parsed.loggedIn === true;
109
- const out = { loggedIn };
110
- if (typeof parsed.email === "string") out.email = parsed.email;
111
- if (typeof parsed.subscriptionType === "string") {
112
- out.subscriptionType = parsed.subscriptionType;
113
- }
114
- if (typeof parsed.authMethod === "string") {
115
- out.authMethod = parsed.authMethod;
116
- }
117
- return out;
118
- }
119
- 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";
120
- async function assertClaudeAuth(spawnCli = defaultSpawnCli) {
121
- const status = await checkClaudeAuth(spawnCli);
122
- if (status.loggedIn) {
123
- return status;
124
- }
125
- const apiKey = process.env.ANTHROPIC_API_KEY;
126
- if (apiKey !== void 0 && apiKey.length > 0) {
127
- return { loggedIn: true, authMethod: "apiKey" };
128
- }
129
- const err = new Error(UNAUTHENTICATED_MESSAGE);
130
- err.code = "CLAUDE_AUTH_MISSING";
131
- throw err;
132
- }
133
-
134
28
  // src/commands/setup/install-path.ts
135
29
  import { execFile } from "child_process";
136
- import { createRequire as createRequire2 } from "module";
30
+ import { createRequire } from "module";
137
31
  import { homedir } from "os";
138
32
  import path from "path";
139
33
  import { fileURLToPath } from "url";
140
34
  function detectCurrentInstallPath() {
141
35
  try {
142
- const req = createRequire2(import.meta.url);
36
+ const req = createRequire(import.meta.url);
143
37
  const here = fileURLToPath(import.meta.url);
144
38
  let dir = path.dirname(here);
145
39
  for (let i = 0; i < 6; i++) {
@@ -330,6 +224,21 @@ async function runSetup(options = {}) {
330
224
  const auth = await safeCheckAuth(options.spawnCli);
331
225
  reportAuth(out, auth);
332
226
  out.write(BAR + "\n");
227
+ const agentChoice = await chooseDefaultAgent({
228
+ out,
229
+ interactive,
230
+ requested: options.agent
231
+ });
232
+ if (!agentChoice.ok) {
233
+ return {
234
+ stdout: "",
235
+ stderr: `almanac: ${agentChoice.error}
236
+ `,
237
+ exitCode: 1
238
+ };
239
+ }
240
+ stepDone(out, `Default agent: ${WHITE_BOLD2}${agentChoice.provider}${RST2}`);
241
+ out.write(BAR + "\n");
333
242
  const ephem = options.installPath !== void 0 ? options.installPath !== null ? detectEphemeral(options.installPath) : false : detectEphemeral(detectCurrentInstallPath());
334
243
  if (ephem) {
335
244
  let globalAction = "install";
@@ -367,13 +276,14 @@ async function runSetup(options = {}) {
367
276
  } else if (interactive) {
368
277
  hookAction = await confirm(
369
278
  out,
370
- "Install the SessionEnd hook so capture runs at the end of every Claude Code session?",
279
+ "Install auto-capture hooks for Claude, Codex, and Cursor?",
371
280
  true
372
281
  );
373
282
  }
374
283
  let hookResultLine = "";
375
284
  if (hookAction === "install") {
376
285
  const res = await runHookInstall({
286
+ source: "all",
377
287
  settingsPath: options.settingsPath,
378
288
  hookScriptPath: options.hookScriptPath,
379
289
  stableHooksDir: options.stableHooksDir
@@ -386,7 +296,7 @@ async function runSetup(options = {}) {
386
296
  exitCode: res.exitCode
387
297
  };
388
298
  }
389
- hookResultLine = res.stdout.includes("already installed") ? `SessionEnd hook ${DIM2}already installed${RST2}` : `SessionEnd hook installed`;
299
+ hookResultLine = res.stdout.includes("already installed") ? `Auto-capture hooks ${DIM2}already installed${RST2}` : `Auto-capture hooks installed`;
390
300
  stepDone(out, hookResultLine);
391
301
  } else {
392
302
  stepSkipped(out, `SessionEnd hook ${DIM2}skipped${RST2}`);
@@ -437,6 +347,31 @@ async function safeCheckAuth(spawnCli) {
437
347
  return { loggedIn: false };
438
348
  }
439
349
  }
350
+ async function chooseDefaultAgent(args) {
351
+ const config = await readConfig();
352
+ let selected = args.requested ?? config.agent.default;
353
+ if (args.interactive && args.requested === void 0) {
354
+ selected = await promptText(
355
+ args.out,
356
+ "Choose default agent: claude, codex, or cursor",
357
+ config.agent.default
358
+ );
359
+ }
360
+ if (!isAgentProviderId(selected)) {
361
+ return {
362
+ ok: false,
363
+ error: `unknown agent '${selected}'. Expected one of: claude, codex, cursor.`
364
+ };
365
+ }
366
+ await writeConfig({
367
+ ...config,
368
+ agent: {
369
+ ...config.agent,
370
+ default: selected
371
+ }
372
+ });
373
+ return { ok: true, provider: selected };
374
+ }
440
375
  function reportAuth(out, auth) {
441
376
  if (auth.loggedIn) {
442
377
  const who = auth.email ?? "Claude account";
@@ -529,6 +464,25 @@ function confirm(out, question, defaultYes) {
529
464
  process.stdin.on("data", onData);
530
465
  });
531
466
  }
467
+ function promptText(out, question, defaultValue) {
468
+ return new Promise((resolve) => {
469
+ out.write(
470
+ ` ${BLUE2}\u25C6${RST2} ${question} ${DIM2}[${defaultValue}]${RST2} `
471
+ );
472
+ let buf = "";
473
+ const onData = (chunk) => {
474
+ buf += chunk.toString("utf8");
475
+ const nl = buf.indexOf("\n");
476
+ if (nl === -1) return;
477
+ process.stdin.removeListener("data", onData);
478
+ process.stdin.pause();
479
+ const answer = buf.slice(0, nl).trim().toLowerCase();
480
+ resolve(answer.length === 0 ? defaultValue : answer);
481
+ };
482
+ process.stdin.resume();
483
+ process.stdin.on("data", onData);
484
+ });
485
+ }
532
486
  function resolveGuidesDir() {
533
487
  const here = path3.dirname(fileURLToPath2(import.meta.url));
534
488
  const candidates = [
@@ -542,7 +496,7 @@ function resolveGuidesDir() {
542
496
  if (looksLikeGuidesDir(dir)) return dir;
543
497
  }
544
498
  try {
545
- const require2 = createRequire3(import.meta.url);
499
+ const require2 = createRequire2(import.meta.url);
546
500
  const pkgJson = require2.resolve("codealmanac/package.json");
547
501
  const guides = path3.join(path3.dirname(pkgJson), "guides");
548
502
  if (looksLikeGuidesDir(guides)) return guides;
@@ -557,10 +511,7 @@ function looksLikeGuidesDir(dir) {
557
511
  }
558
512
 
559
513
  export {
560
- resolveClaudeExecutable,
561
- checkClaudeAuth,
562
- assertClaudeAuth,
563
514
  runSetup,
564
515
  IMPORT_LINE
565
516
  };
566
- //# sourceMappingURL=chunk-3LC55TG6.js.map
517
+ //# sourceMappingURL=chunk-ZDJSJIB6.js.map