codealmanac 0.1.9 → 0.2.0

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 (49) hide show
  1. package/README.md +36 -21
  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-D3B2EEHL.js} +12 -8
  7. package/dist/{chunk-QLHJP2XK.js.map → chunk-D3B2EEHL.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-BJVZLP6O.js → chunk-MX2EW5MR.js} +3 -3
  11. package/dist/{chunk-Z6MBJ3D2.js → chunk-QQHIVTXT.js} +6 -4
  12. package/dist/{chunk-Z6MBJ3D2.js.map → chunk-QQHIVTXT.js.map} +1 -1
  13. package/dist/chunk-R3URPHGH.js +194 -0
  14. package/dist/chunk-R3URPHGH.js.map +1 -0
  15. package/dist/chunk-SSYMRT4I.js +126 -0
  16. package/dist/chunk-SSYMRT4I.js.map +1 -0
  17. package/dist/{chunk-QHQ6YH7U.js → chunk-V3QOQSXI.js} +5 -3
  18. package/dist/{chunk-QHQ6YH7U.js.map → chunk-V3QOQSXI.js.map} +1 -1
  19. package/dist/chunk-WRUSDYYE.js +97 -0
  20. package/dist/chunk-WRUSDYYE.js.map +1 -0
  21. package/dist/{chunk-3LC55TG6.js → chunk-ZDJSJIB6.js} +77 -126
  22. package/dist/chunk-ZDJSJIB6.js.map +1 -0
  23. package/dist/{cli-W3OYVJYH.js → cli-CMGYLJSN.js} +52 -13
  24. package/dist/cli-CMGYLJSN.js.map +1 -0
  25. package/dist/codealmanac.js +1 -1
  26. package/dist/doctor-BTH7GCFV.js +17 -0
  27. package/dist/{hook-CRJMWSSO.js → hook-2NP3UE7U.js} +2 -2
  28. package/dist/{register-commands-JHC2OFKM.js → register-commands-7SKQLQW4.js} +330 -32
  29. package/dist/register-commands-7SKQLQW4.js.map +1 -0
  30. package/dist/uninstall-FDIOBAAR.js +15 -0
  31. package/dist/uninstall-FDIOBAAR.js.map +1 -0
  32. package/dist/update-RAF7QRYF.js +11 -0
  33. package/dist/update-RAF7QRYF.js.map +1 -0
  34. package/hooks/almanac-capture.sh +40 -7
  35. package/package.json +1 -1
  36. package/prompts/bootstrap.md +11 -1
  37. package/dist/chunk-3LC55TG6.js.map +0 -1
  38. package/dist/chunk-AXFPUHBN.js.map +0 -1
  39. package/dist/chunk-Z4MWLVS2.js.map +0 -1
  40. package/dist/cli-W3OYVJYH.js.map +0 -1
  41. package/dist/doctor-ODFNJUKH.js +0 -15
  42. package/dist/register-commands-JHC2OFKM.js.map +0 -1
  43. package/dist/uninstall-HE2Z2LN2.js +0 -12
  44. package/dist/update-IL243I4E.js +0 -10
  45. /package/dist/{doctor-ODFNJUKH.js.map → agents-A4II4YJC.js.map} +0 -0
  46. /package/dist/{hook-CRJMWSSO.js.map → auth-S5DVUIUJ.js.map} +0 -0
  47. /package/dist/{chunk-BJVZLP6O.js.map → chunk-MX2EW5MR.js.map} +0 -0
  48. /package/dist/{uninstall-HE2Z2LN2.js.map → doctor-BTH7GCFV.js.map} +0 -0
  49. /package/dist/{update-IL243I4E.js.map → hook-2NP3UE7U.js.map} +0 -0
@@ -1 +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"]}
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"]}
@@ -3,44 +3,6 @@ import {
3
3
  getGlobalAlmanacDir
4
4
  } from "./chunk-7JUX4ADQ.js";
5
5
 
6
- // src/update/config.ts
7
- import { mkdir, readFile, rename, writeFile } from "fs/promises";
8
- import { dirname, join } from "path";
9
- function defaultConfig() {
10
- return { update_notifier: true };
11
- }
12
- function getConfigPath() {
13
- return join(getGlobalAlmanacDir(), "config.json");
14
- }
15
- async function readConfig(path) {
16
- const file = path ?? getConfigPath();
17
- let raw;
18
- try {
19
- raw = await readFile(file, "utf8");
20
- } catch {
21
- return defaultConfig();
22
- }
23
- const trimmed = raw.trim();
24
- if (trimmed.length === 0) return defaultConfig();
25
- try {
26
- const parsed = JSON.parse(trimmed);
27
- return {
28
- update_notifier: typeof parsed.update_notifier === "boolean" ? parsed.update_notifier : true
29
- };
30
- } catch {
31
- return defaultConfig();
32
- }
33
- }
34
- async function writeConfig(config, path) {
35
- const file = path ?? getConfigPath();
36
- await mkdir(dirname(file), { recursive: true });
37
- const body = `${JSON.stringify(config, null, 2)}
38
- `;
39
- const tmp = `${file}.tmp`;
40
- await writeFile(tmp, body, "utf8");
41
- await rename(tmp, file);
42
- }
43
-
44
6
  // src/update/semver.ts
45
7
  function parse(v) {
46
8
  const trimmed = v.trim().replace(/^v/i, "");
@@ -73,8 +35,8 @@ function isNewer(latest, installed) {
73
35
  }
74
36
 
75
37
  // src/update/state.ts
76
- import { mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
77
- import { dirname as dirname2, join as join2 } from "path";
38
+ import { mkdir, readFile, rename, writeFile } from "fs/promises";
39
+ import { dirname, join } from "path";
78
40
  function emptyState() {
79
41
  return {
80
42
  last_check_at: 0,
@@ -84,13 +46,13 @@ function emptyState() {
84
46
  };
85
47
  }
86
48
  function getStatePath() {
87
- return join2(getGlobalAlmanacDir(), "update-state.json");
49
+ return join(getGlobalAlmanacDir(), "update-state.json");
88
50
  }
89
51
  async function readState(path) {
90
52
  const file = path ?? getStatePath();
91
53
  let raw;
92
54
  try {
93
- raw = await readFile2(file, "utf8");
55
+ raw = await readFile(file, "utf8");
94
56
  } catch {
95
57
  return emptyState();
96
58
  }
@@ -113,12 +75,12 @@ async function readState(path) {
113
75
  }
114
76
  async function writeState(state, path) {
115
77
  const file = path ?? getStatePath();
116
- await mkdir2(dirname2(file), { recursive: true });
78
+ await mkdir(dirname(file), { recursive: true });
117
79
  const body = `${JSON.stringify(state, null, 2)}
118
80
  `;
119
81
  const tmp = `${file}.tmp`;
120
- await writeFile2(tmp, body, "utf8");
121
- await rename2(tmp, file);
82
+ await writeFile(tmp, body, "utf8");
83
+ await rename(tmp, file);
122
84
  }
123
85
 
124
86
  // src/update/check.ts
@@ -215,13 +177,10 @@ function readInstalledVersion() {
215
177
  }
216
178
 
217
179
  export {
218
- getConfigPath,
219
- readConfig,
220
- writeConfig,
221
180
  isNewer,
222
181
  getStatePath,
223
182
  readState,
224
183
  writeState,
225
184
  checkForUpdate
226
185
  };
227
- //# sourceMappingURL=chunk-AXFPUHBN.js.map
186
+ //# sourceMappingURL=chunk-F53U6JQG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/update/semver.ts","../src/update/state.ts","../src/update/check.ts"],"sourcesContent":["/**\n * Tiny semver comparator for update-version checks. We do NOT take a\n * dependency on `semver` — we only need a subset (compare two\n * well-formed `x.y.z` strings, optionally with a pre-release tag), and\n * adding a 400KB install for six lines of logic isn't a good trade.\n *\n * What we handle:\n * - Numeric `major.minor.patch` with or without a `-pre` tag.\n * - Missing parts default to 0 (`1.2` → `1.2.0`).\n * - Leading `v` is stripped.\n *\n * What we don't handle:\n * - Build metadata (`+sha.abcd`) — ignored.\n * - Pre-release precedence rules beyond \"tagged < untagged at same\n * numeric triple\". Good enough for codealmanac's linear release\n * cadence; if we ever publish `-rc.1` vs `-rc.2` and care which\n * comes first, revisit.\n */\n\ninterface Parsed {\n major: number;\n minor: number;\n patch: number;\n /** Empty string = no pre-release (counts as \"higher\" than a pre-release at the same triple). */\n pre: string;\n}\n\nfunction parse(v: string): Parsed | null {\n const trimmed = v.trim().replace(/^v/i, \"\");\n // Strip build metadata (everything from the first `+`).\n const noBuild = trimmed.split(\"+\")[0] ?? \"\";\n // Split off pre-release tag.\n const dashAt = noBuild.indexOf(\"-\");\n const core = dashAt === -1 ? noBuild : noBuild.slice(0, dashAt);\n const pre = dashAt === -1 ? \"\" : noBuild.slice(dashAt + 1);\n\n const parts = core.split(\".\").map((p) => Number.parseInt(p, 10));\n if (parts.length === 0 || parts.some((n) => !Number.isFinite(n) || n < 0)) {\n return null;\n }\n return {\n major: parts[0] ?? 0,\n minor: parts[1] ?? 0,\n patch: parts[2] ?? 0,\n pre,\n };\n}\n\n/**\n * Return `true` iff `latest` > `installed`. Returns `false` on unparseable\n * input rather than throwing — a bad version string must not be able to\n * crash the CLI's every-command banner path.\n */\nexport function isNewer(latest: string, installed: string): boolean {\n const a = parse(latest);\n const b = parse(installed);\n if (a === null || b === null) return false;\n\n if (a.major !== b.major) return a.major > b.major;\n if (a.minor !== b.minor) return a.minor > b.minor;\n if (a.patch !== b.patch) return a.patch > b.patch;\n\n // Same numeric triple. Empty pre-release beats a tagged pre-release\n // (1.2.3 > 1.2.3-rc.1). Two tagged pre-releases compare lexically.\n if (a.pre === b.pre) return false;\n if (a.pre === \"\" && b.pre !== \"\") return true;\n if (a.pre !== \"\" && b.pre === \"\") return false;\n return a.pre > b.pre;\n}\n","import { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport { getGlobalAlmanacDir } from \"../paths.js\";\n\n/**\n * `~/.almanac/update-state.json` — the only piece of persistent state\n * the update system owns. Written by the background check worker (once\n * per 24h) and by `almanac update` / `almanac update --dismiss`; read\n * by the pre-command banner, `almanac doctor`, and the check path to\n * decide whether 24h have elapsed since the last check.\n *\n * Format is a single flat object with no schema version: fields are\n * only ever added (and read with defaults), never removed or reshaped.\n * If we ever need a breaking migration, the file is trivially\n * regenerable — the worst case is a single extra registry round-trip.\n *\n * Corruption handling: every read path tolerates missing or malformed\n * JSON as \"no state\" (empty defaults). We never propagate a parse\n * error up to the CLI banner, because a corrupt state file must not\n * be able to break every invocation.\n */\nexport interface UpdateState {\n /** Unix epoch seconds of the last registry query. */\n last_check_at: number;\n /** The codealmanac version running when we last wrote state. */\n installed_version: string;\n /** The newest version the registry has published at last check. */\n latest_version: string;\n /** Versions the user dismissed via `almanac update --dismiss`. */\n dismissed_versions: string[];\n /**\n * Epoch seconds of the last registry fetch attempt that FAILED. Used\n * by the check scheduler to back off (one failure shouldn't hammer\n * the registry on every command) — reads tolerate a missing field.\n */\n last_fetch_failed_at?: number;\n}\n\nexport function emptyState(): UpdateState {\n return {\n last_check_at: 0,\n installed_version: \"\",\n latest_version: \"\",\n dismissed_versions: [],\n };\n}\n\nexport function getStatePath(): string {\n return join(getGlobalAlmanacDir(), \"update-state.json\");\n}\n\n/**\n * Read the state file. Missing, empty, or malformed → empty state.\n * We deliberately swallow all errors here: the update system is\n * best-effort, and a read failure must not break any command.\n */\nexport async function readState(path?: string): Promise<UpdateState> {\n const file = path ?? getStatePath();\n let raw: string;\n try {\n raw = await readFile(file, \"utf8\");\n } catch {\n return emptyState();\n }\n const trimmed = raw.trim();\n if (trimmed.length === 0) return emptyState();\n try {\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(\n (v): v is string => typeof v === \"string\" && v.length > 0,\n )\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 emptyState();\n }\n}\n\n/**\n * Write the state file atomically (tmp + rename). Makes the concurrent\n * \"two commands ran their update checks at once\" race safe — one rename\n * wins, the other is dropped. Creates `~/.almanac/` if missing.\n */\nexport async function writeState(\n state: UpdateState,\n path?: string,\n): Promise<void> {\n const file = path ?? getStatePath();\n await mkdir(dirname(file), { recursive: true });\n const body = `${JSON.stringify(state, null, 2)}\\n`;\n const tmp = `${file}.tmp`;\n await writeFile(tmp, body, \"utf8\");\n await rename(tmp, file);\n}\n","import { createRequire } from \"node:module\";\n\nimport { readState, writeState, type UpdateState } from \"./state.js\";\n\n/**\n * Background update check. Called by the detached worker (`--internal-\n * check-updates`) after any normal command exits; also reachable via\n * `almanac update --check` for a synchronous \"am I current?\" readout.\n *\n * Contract:\n * - Reads `~/.almanac/update-state.json` if present.\n * - If the last check is older than `cacheSeconds` (default 24h), queries\n * the npm registry for `codealmanac`'s `dist-tags.latest` and writes a\n * new state file.\n * - Network timeout is 3s: registry flakes must not prevent a check\n * cycle on the next invocation.\n * - All errors are swallowed; the returned state is always a usable\n * snapshot (possibly the old one when the fetch failed).\n *\n * The fetch function is injectable. Tests pass a stub; production uses\n * the native `globalThis.fetch`. No dependency on `node-fetch`.\n */\n\nexport interface CheckOptions {\n /** Override the installed version (prod: read from package.json). */\n installedVersion?: string;\n /** Cache window; no registry call if last check is newer than this. */\n cacheSeconds?: number;\n /** Network timeout in ms (default 3000). */\n timeoutMs?: number;\n /** Clock. Tests inject to make \"24h ago\" deterministic. */\n now?: () => number;\n /** Fetch function (default `globalThis.fetch`). */\n fetchFn?: typeof fetch;\n /** Override the state file path (tests point it at a tmpdir). */\n statePath?: string;\n /** Force a registry call regardless of the cache. Used by `update --check`. */\n force?: boolean;\n}\n\nexport interface CheckResult {\n /** The state after the check (either refreshed or unchanged). */\n state: UpdateState;\n /** True when a registry call actually happened this run. */\n fetched: boolean;\n /** True when the registry call failed (network / timeout / parse). */\n fetchFailed: boolean;\n}\n\nconst DEFAULT_CACHE_SECONDS = 24 * 60 * 60;\nconst DEFAULT_TIMEOUT_MS = 3000;\nconst REGISTRY_URL = \"https://registry.npmjs.org/codealmanac\";\n\nexport async function checkForUpdate(\n opts: CheckOptions = {},\n): Promise<CheckResult> {\n const now = opts.now ?? (() => Math.floor(Date.now() / 1000));\n const cacheSeconds = opts.cacheSeconds ?? DEFAULT_CACHE_SECONDS;\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const fetchFn = opts.fetchFn ?? globalThis.fetch;\n const installed = opts.installedVersion ?? readInstalledVersion();\n\n const state = await readState(opts.statePath);\n\n // Cache gate. Skip the registry call when the previous check is\n // fresh enough, unless `force: true` (used by `update --check` so\n // the user can see real-time status without waiting out the window).\n if (\n !opts.force &&\n state.last_check_at > 0 &&\n now() - state.last_check_at < cacheSeconds\n ) {\n return { state, fetched: false, fetchFailed: false };\n }\n\n // Query the registry with a hard timeout. `AbortController` is the\n // idiomatic Node 20+ way to bound a fetch; `setTimeout` fires abort,\n // `clearTimeout` cancels if fetch resolves first.\n let latest: string | null = null;\n let failed = false;\n try {\n const ac = new AbortController();\n const timer = setTimeout(() => ac.abort(), timeoutMs);\n try {\n const res = await fetchFn(REGISTRY_URL, {\n signal: ac.signal,\n headers: { accept: \"application/json\" },\n });\n if (!res.ok) {\n failed = true;\n } else {\n const body = (await res.json()) as {\n [\"dist-tags\"]?: { latest?: unknown };\n };\n const tag = body[\"dist-tags\"]?.latest;\n if (typeof tag === \"string\" && tag.length > 0) {\n latest = tag;\n } else {\n failed = true;\n }\n }\n } finally {\n clearTimeout(timer);\n }\n } catch {\n failed = true;\n }\n\n if (failed || latest === null) {\n // Record the failure but DON'T clobber the previous latest_version —\n // an offline check shouldn't make us forget that 0.1.6 is out.\n const next: UpdateState = {\n ...state,\n // We still bump last_check_at on a failed attempt; without this,\n // every subsequent command would re-try the registry. A one-shot\n // retry on the next invocation is enough; sustained failure gets\n // retried on the 24h cadence like a success.\n last_check_at: now(),\n installed_version: installed,\n last_fetch_failed_at: now(),\n };\n try {\n await writeState(next, opts.statePath);\n } catch {\n // Even the state write failed (permissions, disk full). Return\n // whatever we have — the CLI doesn't care.\n }\n return { state: next, fetched: true, fetchFailed: true };\n }\n\n const next: UpdateState = {\n last_check_at: now(),\n installed_version: installed,\n latest_version: latest,\n dismissed_versions: state.dismissed_versions,\n // Clear the failure marker on success.\n last_fetch_failed_at: undefined,\n };\n try {\n await writeState(next, opts.statePath);\n } catch {\n // Silent — same rationale as above.\n }\n return { state: next, fetched: true, fetchFailed: false };\n}\n\n/**\n * Read the `version` field from `package.json`. Matches the same\n * lookup strategy as `readPackageVersion` in `cli.ts` and `doctor.ts`;\n * duplicated here to keep the update module self-contained and to\n * avoid a circular import at CLI startup.\n */\nfunction readInstalledVersion(): string {\n try {\n const require = createRequire(import.meta.url);\n const pkg = require(\"../../package.json\") as { version?: unknown };\n if (typeof pkg.version === \"string\" && pkg.version.length > 0) {\n return pkg.version;\n }\n } catch {\n // Fall through.\n }\n try {\n const require = createRequire(import.meta.url);\n const pkg = require(\"../package.json\") as { version?: unknown };\n if (typeof pkg.version === \"string\" && pkg.version.length > 0) {\n return pkg.version;\n }\n } catch {\n // Fall through.\n }\n return \"unknown\";\n}\n"],"mappings":"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","names":["next","require"]}
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  IMPORT_LINE
4
- } from "./chunk-3LC55TG6.js";
4
+ } from "./chunk-ZDJSJIB6.js";
5
5
  import {
6
6
  runHookUninstall
7
- } from "./chunk-Z4MWLVS2.js";
7
+ } from "./chunk-447U3GQJ.js";
8
8
 
9
9
  // src/commands/uninstall.ts
10
10
  import { existsSync } from "fs";
@@ -142,4 +142,4 @@ export {
142
142
  runUninstall,
143
143
  removeImportLine
144
144
  };
145
- //# sourceMappingURL=chunk-BJVZLP6O.js.map
145
+ //# sourceMappingURL=chunk-MX2EW5MR.js.map
@@ -2,11 +2,13 @@
2
2
  import {
3
3
  checkForUpdate,
4
4
  isNewer,
5
- readConfig,
6
5
  readState,
7
- writeConfig,
8
6
  writeState
9
- } from "./chunk-AXFPUHBN.js";
7
+ } from "./chunk-F53U6JQG.js";
8
+ import {
9
+ readConfig,
10
+ writeConfig
11
+ } from "./chunk-WRUSDYYE.js";
10
12
 
11
13
  // src/commands/update.ts
12
14
  import { spawn } from "child_process";
@@ -200,4 +202,4 @@ function readInstalledVersion() {
200
202
  export {
201
203
  runUpdate
202
204
  };
203
- //# sourceMappingURL=chunk-Z6MBJ3D2.js.map
205
+ //# sourceMappingURL=chunk-QQHIVTXT.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/update.ts"],"sourcesContent":["import { spawn, type SpawnOptions } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\n\nimport { checkForUpdate } from \"../update/check.js\";\nimport {\n readConfig,\n writeConfig,\n type GlobalConfig,\n} from \"../update/config.js\";\nimport { isNewer } from \"../update/semver.js\";\nimport { readState, writeState } from \"../update/state.js\";\n\n/**\n * `almanac update` — manual upgrade command, the counterpart to the\n * persistent nag banner.\n *\n * Default action: shell out to `npm i -g codealmanac@latest` with\n * inherited stdio so the user sees real-time download/install/permission\n * output. Synchronous in the user's terminal — no background install,\n * no mid-invocation swap (see the pair review's Tier-B design for\n * rationale).\n *\n * Flags:\n * --dismiss — mark the current `latest_version` as \"don't nag about\n * this one again\". No install. Writes state and exits.\n * --check — force a registry query regardless of the 24h cache.\n * Shows the result and exits. No install.\n * --enable-notifier / --disable-notifier — flip the global\n * `update_notifier` config. Default is enabled; after\n * `--disable-notifier` the banner won't show even when a new\n * version is available.\n */\n\nexport interface UpdateOptions {\n dismiss?: boolean;\n check?: boolean;\n enableNotifier?: boolean;\n disableNotifier?: boolean;\n\n // ─── Test injection points ──────────────────────────────────────\n /** Override state file path (tests point at a tmpdir). */\n statePath?: string;\n /** Override config file path (tests point at a tmpdir). */\n configPath?: string;\n /** Override the installed version report. */\n installedVersion?: string;\n /**\n * Replace `checkForUpdate` — tests inject a stub that returns a\n * canned state without hitting the registry.\n */\n checkFn?: typeof checkForUpdate;\n /** Replace `spawn` for tests (install path shouldn't run npm). */\n spawnFn?: typeof spawn;\n /** Clock for deterministic `last_check_at` assertions. */\n now?: () => number;\n}\n\nexport interface UpdateResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nexport async function runUpdate(\n opts: UpdateOptions = {},\n): Promise<UpdateResult> {\n // Precedence: config toggles > --dismiss > --check > install.\n // Config toggles are disjoint from the other flags (you'd never\n // `update --dismiss --disable-notifier`), but if someone does we\n // apply them in order and take the last action as the \"command\"\n // that sets the exit code.\n if (opts.enableNotifier === true) {\n return await toggleNotifier(true, opts);\n }\n if (opts.disableNotifier === true) {\n return await toggleNotifier(false, opts);\n }\n if (opts.dismiss === true) {\n return await dismissLatest(opts);\n }\n if (opts.check === true) {\n return await forceCheck(opts);\n }\n return await installLatest(opts);\n}\n\n// ─── --dismiss ────────────────────────────────────────────────────\n\nasync function dismissLatest(opts: UpdateOptions): Promise<UpdateResult> {\n const state = await readState(opts.statePath);\n // Nothing to dismiss when we don't know of a newer version. Silently\n // no-op with a message — more helpful than pretending to write state\n // that no future banner would consult.\n if (state.latest_version.length === 0) {\n return {\n stdout:\n \"codealmanac: no pending update to dismiss. \" +\n \"Run `almanac update --check` to query the registry.\\n\",\n stderr: \"\",\n exitCode: 0,\n };\n }\n const installed = opts.installedVersion ?? readInstalledVersion();\n if (!isNewer(state.latest_version, installed)) {\n return {\n stdout: `codealmanac: already on latest (${installed}); nothing to dismiss.\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n if (state.dismissed_versions.includes(state.latest_version)) {\n return {\n stdout: `codealmanac: ${state.latest_version} already dismissed.\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n const next = {\n ...state,\n dismissed_versions: [...state.dismissed_versions, state.latest_version],\n };\n await writeState(next, opts.statePath);\n return {\n stdout:\n `codealmanac: dismissed ${state.latest_version}. The nag banner ` +\n `will not show for this version.\\n` +\n `Run \\`almanac update\\` to upgrade, or \\`almanac update --enable-notifier\\` to re-enable nags.\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n\n// ─── --check ───────────────────────────────────────────────────────\n\nasync function forceCheck(opts: UpdateOptions): Promise<UpdateResult> {\n const installed = opts.installedVersion ?? readInstalledVersion();\n const checkFn = opts.checkFn ?? checkForUpdate;\n const result = await checkFn({\n installedVersion: installed,\n force: true,\n statePath: opts.statePath,\n now: opts.now,\n });\n if (result.fetchFailed) {\n return {\n stdout: \"\",\n stderr:\n `codealmanac: could not reach registry.npmjs.org (timeout or network error).\\n` +\n `Installed: ${installed}. No cached latest available.\\n`,\n exitCode: 1,\n };\n }\n const latest = result.state.latest_version;\n if (latest.length === 0) {\n return {\n stdout: `codealmanac: installed ${installed}; registry did not report a latest tag.\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n if (isNewer(latest, installed)) {\n const dismissed = result.state.dismissed_versions.includes(latest)\n ? \" (dismissed — banner suppressed; `almanac update` still installs)\"\n : \"\";\n return {\n stdout:\n `codealmanac ${latest} available (you're on ${installed})${dismissed}.\\n` +\n `Run: almanac update\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n return {\n stdout: `codealmanac: up to date (${installed}).\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n\n// ─── --enable/--disable-notifier ──────────────────────────────────\n\nasync function toggleNotifier(\n enable: boolean,\n opts: UpdateOptions,\n): Promise<UpdateResult> {\n const config = await readConfig(opts.configPath);\n const next: GlobalConfig = { ...config, update_notifier: enable };\n await writeConfig(next, opts.configPath);\n return {\n stdout:\n enable\n ? \"codealmanac: update notifier enabled. \" +\n \"The pre-command banner will show when a new version is available.\\n\"\n : \"codealmanac: update notifier disabled. \" +\n \"No more pre-command banners. Run `almanac update --check` to see status.\\n\",\n stderr: \"\",\n exitCode: 0,\n };\n}\n\n// ─── default: install ─────────────────────────────────────────────\n\nasync function installLatest(opts: UpdateOptions): Promise<UpdateResult> {\n const spawnFn = opts.spawnFn ?? spawn;\n const installed = opts.installedVersion ?? readInstalledVersion();\n\n // Inherit stdio so npm's progress bar, permission prompts, and\n // peer-dep warnings land in the user's terminal verbatim. No\n // wrapping, no capture — npm output is its own contract.\n const spawnOpts: SpawnOptions = { stdio: \"inherit\" };\n\n return await new Promise<UpdateResult>((resolve) => {\n const child = spawnFn(\n \"npm\",\n [\"i\", \"-g\", \"codealmanac@latest\"],\n spawnOpts,\n );\n\n // Two failure modes need distinct messaging:\n // - ENOENT: npm isn't on PATH. Rare on dev laptops, common in\n // stripped-down CI containers. Tell the user what we tried to\n // run so they can diagnose.\n // - EACCES / exit code 243 / etc.: npm ran but couldn't write\n // to the global prefix. Suggest sudo; don't try it ourselves\n // (silently escalating privileges would be a trust violation,\n // and the pair review explicitly rejected it).\n child.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"ENOENT\") {\n resolve({\n stdout: \"\",\n stderr:\n \"codealmanac: `npm` not found on PATH. \" +\n \"Install Node.js + npm, or install codealmanac via your package manager.\\n\",\n exitCode: 1,\n });\n return;\n }\n resolve({\n stdout: \"\",\n stderr: `codealmanac: failed to run npm: ${err.message}\\n`,\n exitCode: 1,\n });\n });\n\n child.on(\"exit\", async (code, _signal) => {\n const exitCode = code ?? 1;\n if (exitCode !== 0) {\n // Check for the common EACCES cause. npm prints \"EACCES\" to\n // stderr, which we don't have (inherited stdio), so we rely\n // on exit code heuristics + a generic hint.\n const hint =\n `codealmanac: npm install failed (exit ${exitCode}).\\n` +\n `If you see \"EACCES\" above, try: sudo npm i -g codealmanac@latest\\n` +\n `Or install with a version manager (nvm, volta, fnm) to avoid sudo.\\n`;\n resolve({ stdout: \"\", stderr: hint, exitCode });\n return;\n }\n // On success, refresh the state file so the next command's\n // banner reflects that we're current. We can't read the new\n // version out of our own process (we're still running the old\n // build); we record what the state file's latest_version was,\n // on the assumption that npm installed that version.\n try {\n const state = await readState(opts.statePath);\n const now =\n opts.now ?? (() => Math.floor(Date.now() / 1000));\n await writeState(\n {\n last_check_at: now(),\n installed_version: state.latest_version || installed,\n latest_version: state.latest_version || installed,\n dismissed_versions: state.dismissed_versions,\n },\n opts.statePath,\n );\n } catch {\n // Non-fatal: the next `almanac` invocation will re-run the\n // background check and refresh state properly.\n }\n resolve({\n stdout: \"codealmanac: updated.\\n\",\n stderr: \"\",\n exitCode: 0,\n });\n });\n });\n}\n\nfunction readInstalledVersion(): string {\n // Dev layout: `src/commands/update.ts` → `../../package.json`.\n // Bundled layout: `dist/codealmanac.js` → `../package.json`. We try\n // both so the version lookup works from both. (Same approach as\n // `cli.ts` and `doctor.ts`, which hit the same ambiguity.)\n try {\n const require = createRequire(import.meta.url);\n const pkg = require(\"../../package.json\") as { version?: unknown };\n if (typeof pkg.version === \"string\" && pkg.version.length > 0) {\n return pkg.version;\n }\n } catch {\n // Fall through.\n }\n try {\n const require = createRequire(import.meta.url);\n const pkg = require(\"../package.json\") as { version?: unknown };\n if (typeof pkg.version === \"string\" && pkg.version.length > 0) {\n return pkg.version;\n }\n } catch {\n // Fall through.\n }\n return \"unknown\";\n}\n"],"mappings":";;;;;;;;;;;AAAA,SAAS,aAAgC;AACzC,SAAS,qBAAqB;AA8D9B,eAAsB,UACpB,OAAsB,CAAC,GACA;AAMvB,MAAI,KAAK,mBAAmB,MAAM;AAChC,WAAO,MAAM,eAAe,MAAM,IAAI;AAAA,EACxC;AACA,MAAI,KAAK,oBAAoB,MAAM;AACjC,WAAO,MAAM,eAAe,OAAO,IAAI;AAAA,EACzC;AACA,MAAI,KAAK,YAAY,MAAM;AACzB,WAAO,MAAM,cAAc,IAAI;AAAA,EACjC;AACA,MAAI,KAAK,UAAU,MAAM;AACvB,WAAO,MAAM,WAAW,IAAI;AAAA,EAC9B;AACA,SAAO,MAAM,cAAc,IAAI;AACjC;AAIA,eAAe,cAAc,MAA4C;AACvE,QAAM,QAAQ,MAAM,UAAU,KAAK,SAAS;AAI5C,MAAI,MAAM,eAAe,WAAW,GAAG;AACrC,WAAO;AAAA,MACL,QACE;AAAA,MAEF,QAAQ;AAAA,MACR,UAAU;AAAA,IACZ;AAAA,EACF;AACA,QAAM,YAAY,KAAK,oBAAoB,qBAAqB;AAChE,MAAI,CAAC,QAAQ,MAAM,gBAAgB,SAAS,GAAG;AAC7C,WAAO;AAAA,MACL,QAAQ,mCAAmC,SAAS;AAAA;AAAA,MACpD,QAAQ;AAAA,MACR,UAAU;AAAA,IACZ;AAAA,EACF;AACA,MAAI,MAAM,mBAAmB,SAAS,MAAM,cAAc,GAAG;AAC3D,WAAO;AAAA,MACL,QAAQ,gBAAgB,MAAM,cAAc;AAAA;AAAA,MAC5C,QAAQ;AAAA,MACR,UAAU;AAAA,IACZ;AAAA,EACF;AACA,QAAM,OAAO;AAAA,IACX,GAAG;AAAA,IACH,oBAAoB,CAAC,GAAG,MAAM,oBAAoB,MAAM,cAAc;AAAA,EACxE;AACA,QAAM,WAAW,MAAM,KAAK,SAAS;AACrC,SAAO;AAAA,IACL,QACE,0BAA0B,MAAM,cAAc;AAAA;AAAA;AAAA,IAGhD,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AACF;AAIA,eAAe,WAAW,MAA4C;AACpE,QAAM,YAAY,KAAK,oBAAoB,qBAAqB;AAChE,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,SAAS,MAAM,QAAQ;AAAA,IAC3B,kBAAkB;AAAA,IAClB,OAAO;AAAA,IACP,WAAW,KAAK;AAAA,IAChB,KAAK,KAAK;AAAA,EACZ,CAAC;AACD,MAAI,OAAO,aAAa;AACtB,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QACE;AAAA,aACc,SAAS;AAAA;AAAA,MACzB,UAAU;AAAA,IACZ;AAAA,EACF;AACA,QAAM,SAAS,OAAO,MAAM;AAC5B,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO;AAAA,MACL,QAAQ,0BAA0B,SAAS;AAAA;AAAA,MAC3C,QAAQ;AAAA,MACR,UAAU;AAAA,IACZ;AAAA,EACF;AACA,MAAI,QAAQ,QAAQ,SAAS,GAAG;AAC9B,UAAM,YAAY,OAAO,MAAM,mBAAmB,SAAS,MAAM,IAC7D,2EACA;AACJ,WAAO;AAAA,MACL,QACE,eAAe,MAAM,yBAAyB,SAAS,IAAI,SAAS;AAAA;AAAA;AAAA,MAEtE,QAAQ;AAAA,MACR,UAAU;AAAA,IACZ;AAAA,EACF;AACA,SAAO;AAAA,IACL,QAAQ,4BAA4B,SAAS;AAAA;AAAA,IAC7C,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AACF;AAIA,eAAe,eACb,QACA,MACuB;AACvB,QAAM,SAAS,MAAM,WAAW,KAAK,UAAU;AAC/C,QAAM,OAAqB,EAAE,GAAG,QAAQ,iBAAiB,OAAO;AAChE,QAAM,YAAY,MAAM,KAAK,UAAU;AACvC,SAAO;AAAA,IACL,QACE,SACI,8GAEA;AAAA,IAEN,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AACF;AAIA,eAAe,cAAc,MAA4C;AACvE,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,YAAY,KAAK,oBAAoB,qBAAqB;AAKhE,QAAM,YAA0B,EAAE,OAAO,UAAU;AAEnD,SAAO,MAAM,IAAI,QAAsB,CAAC,YAAY;AAClD,UAAM,QAAQ;AAAA,MACZ;AAAA,MACA,CAAC,KAAK,MAAM,oBAAoB;AAAA,MAChC;AAAA,IACF;AAUA,UAAM,GAAG,SAAS,CAAC,QAA+B;AAChD,UAAI,IAAI,SAAS,UAAU;AACzB,gBAAQ;AAAA,UACN,QAAQ;AAAA,UACR,QACE;AAAA,UAEF,UAAU;AAAA,QACZ,CAAC;AACD;AAAA,MACF;AACA,cAAQ;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ,mCAAmC,IAAI,OAAO;AAAA;AAAA,QACtD,UAAU;AAAA,MACZ,CAAC;AAAA,IACH,CAAC;AAED,UAAM,GAAG,QAAQ,OAAO,MAAM,YAAY;AACxC,YAAM,WAAW,QAAQ;AACzB,UAAI,aAAa,GAAG;AAIlB,cAAM,OACJ,yCAAyC,QAAQ;AAAA;AAAA;AAAA;AAGnD,gBAAQ,EAAE,QAAQ,IAAI,QAAQ,MAAM,SAAS,CAAC;AAC9C;AAAA,MACF;AAMA,UAAI;AACF,cAAM,QAAQ,MAAM,UAAU,KAAK,SAAS;AAC5C,cAAM,MACJ,KAAK,QAAQ,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACjD,cAAM;AAAA,UACJ;AAAA,YACE,eAAe,IAAI;AAAA,YACnB,mBAAmB,MAAM,kBAAkB;AAAA,YAC3C,gBAAgB,MAAM,kBAAkB;AAAA,YACxC,oBAAoB,MAAM;AAAA,UAC5B;AAAA,UACA,KAAK;AAAA,QACP;AAAA,MACF,QAAQ;AAAA,MAGR;AACA,cAAQ;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,UAAU;AAAA,MACZ,CAAC;AAAA,IACH,CAAC;AAAA,EACH,CAAC;AACH;AAEA,SAAS,uBAA+B;AAKtC,MAAI;AACF,UAAMA,WAAU,cAAc,YAAY,GAAG;AAC7C,UAAM,MAAMA,SAAQ,oBAAoB;AACxC,QAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,SAAS,GAAG;AAC7D,aAAO,IAAI;AAAA,IACb;AAAA,EACF,QAAQ;AAAA,EAER;AACA,MAAI;AACF,UAAMA,WAAU,cAAc,YAAY,GAAG;AAC7C,UAAM,MAAMA,SAAQ,iBAAiB;AACrC,QAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,SAAS,GAAG;AAC7D,aAAO,IAAI;AAAA,IACb;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;","names":["require"]}
1
+ {"version":3,"sources":["../src/commands/update.ts"],"sourcesContent":["import { spawn, type SpawnOptions } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\n\nimport { checkForUpdate } from \"../update/check.js\";\nimport {\n readConfig,\n writeConfig,\n type GlobalConfig,\n} from \"../update/config.js\";\nimport { isNewer } from \"../update/semver.js\";\nimport { readState, writeState } from \"../update/state.js\";\n\n/**\n * `almanac update` — manual upgrade command, the counterpart to the\n * persistent nag banner.\n *\n * Default action: shell out to `npm i -g codealmanac@latest` with\n * inherited stdio so the user sees real-time download/install/permission\n * output. Synchronous in the user's terminal — no background install,\n * no mid-invocation swap (see the pair review's Tier-B design for\n * rationale).\n *\n * Flags:\n * --dismiss — mark the current `latest_version` as \"don't nag about\n * this one again\". No install. Writes state and exits.\n * --check — force a registry query regardless of the 24h cache.\n * Shows the result and exits. No install.\n * --enable-notifier / --disable-notifier — flip the global\n * `update_notifier` config. Default is enabled; after\n * `--disable-notifier` the banner won't show even when a new\n * version is available.\n */\n\nexport interface UpdateOptions {\n dismiss?: boolean;\n check?: boolean;\n enableNotifier?: boolean;\n disableNotifier?: boolean;\n\n // ─── Test injection points ──────────────────────────────────────\n /** Override state file path (tests point at a tmpdir). */\n statePath?: string;\n /** Override config file path (tests point at a tmpdir). */\n configPath?: string;\n /** Override the installed version report. */\n installedVersion?: string;\n /**\n * Replace `checkForUpdate` — tests inject a stub that returns a\n * canned state without hitting the registry.\n */\n checkFn?: typeof checkForUpdate;\n /** Replace `spawn` for tests (install path shouldn't run npm). */\n spawnFn?: typeof spawn;\n /** Clock for deterministic `last_check_at` assertions. */\n now?: () => number;\n}\n\nexport interface UpdateResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nexport async function runUpdate(\n opts: UpdateOptions = {},\n): Promise<UpdateResult> {\n // Precedence: config toggles > --dismiss > --check > install.\n // Config toggles are disjoint from the other flags (you'd never\n // `update --dismiss --disable-notifier`), but if someone does we\n // apply them in order and take the last action as the \"command\"\n // that sets the exit code.\n if (opts.enableNotifier === true) {\n return await toggleNotifier(true, opts);\n }\n if (opts.disableNotifier === true) {\n return await toggleNotifier(false, opts);\n }\n if (opts.dismiss === true) {\n return await dismissLatest(opts);\n }\n if (opts.check === true) {\n return await forceCheck(opts);\n }\n return await installLatest(opts);\n}\n\n// ─── --dismiss ────────────────────────────────────────────────────\n\nasync function dismissLatest(opts: UpdateOptions): Promise<UpdateResult> {\n const state = await readState(opts.statePath);\n // Nothing to dismiss when we don't know of a newer version. Silently\n // no-op with a message — more helpful than pretending to write state\n // that no future banner would consult.\n if (state.latest_version.length === 0) {\n return {\n stdout:\n \"codealmanac: no pending update to dismiss. \" +\n \"Run `almanac update --check` to query the registry.\\n\",\n stderr: \"\",\n exitCode: 0,\n };\n }\n const installed = opts.installedVersion ?? readInstalledVersion();\n if (!isNewer(state.latest_version, installed)) {\n return {\n stdout: `codealmanac: already on latest (${installed}); nothing to dismiss.\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n if (state.dismissed_versions.includes(state.latest_version)) {\n return {\n stdout: `codealmanac: ${state.latest_version} already dismissed.\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n const next = {\n ...state,\n dismissed_versions: [...state.dismissed_versions, state.latest_version],\n };\n await writeState(next, opts.statePath);\n return {\n stdout:\n `codealmanac: dismissed ${state.latest_version}. The nag banner ` +\n `will not show for this version.\\n` +\n `Run \\`almanac update\\` to upgrade, or \\`almanac update --enable-notifier\\` to re-enable nags.\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n\n// ─── --check ───────────────────────────────────────────────────────\n\nasync function forceCheck(opts: UpdateOptions): Promise<UpdateResult> {\n const installed = opts.installedVersion ?? readInstalledVersion();\n const checkFn = opts.checkFn ?? checkForUpdate;\n const result = await checkFn({\n installedVersion: installed,\n force: true,\n statePath: opts.statePath,\n now: opts.now,\n });\n if (result.fetchFailed) {\n return {\n stdout: \"\",\n stderr:\n `codealmanac: could not reach registry.npmjs.org (timeout or network error).\\n` +\n `Installed: ${installed}. No cached latest available.\\n`,\n exitCode: 1,\n };\n }\n const latest = result.state.latest_version;\n if (latest.length === 0) {\n return {\n stdout: `codealmanac: installed ${installed}; registry did not report a latest tag.\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n if (isNewer(latest, installed)) {\n const dismissed = result.state.dismissed_versions.includes(latest)\n ? \" (dismissed — banner suppressed; `almanac update` still installs)\"\n : \"\";\n return {\n stdout:\n `codealmanac ${latest} available (you're on ${installed})${dismissed}.\\n` +\n `Run: almanac update\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n return {\n stdout: `codealmanac: up to date (${installed}).\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n\n// ─── --enable/--disable-notifier ──────────────────────────────────\n\nasync function toggleNotifier(\n enable: boolean,\n opts: UpdateOptions,\n): Promise<UpdateResult> {\n const config = await readConfig(opts.configPath);\n const next: GlobalConfig = { ...config, update_notifier: enable };\n await writeConfig(next, opts.configPath);\n return {\n stdout:\n enable\n ? \"codealmanac: update notifier enabled. \" +\n \"The pre-command banner will show when a new version is available.\\n\"\n : \"codealmanac: update notifier disabled. \" +\n \"No more pre-command banners. Run `almanac update --check` to see status.\\n\",\n stderr: \"\",\n exitCode: 0,\n };\n}\n\n// ─── default: install ─────────────────────────────────────────────\n\nasync function installLatest(opts: UpdateOptions): Promise<UpdateResult> {\n const spawnFn = opts.spawnFn ?? spawn;\n const installed = opts.installedVersion ?? readInstalledVersion();\n\n // Inherit stdio so npm's progress bar, permission prompts, and\n // peer-dep warnings land in the user's terminal verbatim. No\n // wrapping, no capture — npm output is its own contract.\n const spawnOpts: SpawnOptions = { stdio: \"inherit\" };\n\n return await new Promise<UpdateResult>((resolve) => {\n const child = spawnFn(\n \"npm\",\n [\"i\", \"-g\", \"codealmanac@latest\"],\n spawnOpts,\n );\n\n // Two failure modes need distinct messaging:\n // - ENOENT: npm isn't on PATH. Rare on dev laptops, common in\n // stripped-down CI containers. Tell the user what we tried to\n // run so they can diagnose.\n // - EACCES / exit code 243 / etc.: npm ran but couldn't write\n // to the global prefix. Suggest sudo; don't try it ourselves\n // (silently escalating privileges would be a trust violation,\n // and the pair review explicitly rejected it).\n child.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"ENOENT\") {\n resolve({\n stdout: \"\",\n stderr:\n \"codealmanac: `npm` not found on PATH. \" +\n \"Install Node.js + npm, or install codealmanac via your package manager.\\n\",\n exitCode: 1,\n });\n return;\n }\n resolve({\n stdout: \"\",\n stderr: `codealmanac: failed to run npm: ${err.message}\\n`,\n exitCode: 1,\n });\n });\n\n child.on(\"exit\", async (code, _signal) => {\n const exitCode = code ?? 1;\n if (exitCode !== 0) {\n // Check for the common EACCES cause. npm prints \"EACCES\" to\n // stderr, which we don't have (inherited stdio), so we rely\n // on exit code heuristics + a generic hint.\n const hint =\n `codealmanac: npm install failed (exit ${exitCode}).\\n` +\n `If you see \"EACCES\" above, try: sudo npm i -g codealmanac@latest\\n` +\n `Or install with a version manager (nvm, volta, fnm) to avoid sudo.\\n`;\n resolve({ stdout: \"\", stderr: hint, exitCode });\n return;\n }\n // On success, refresh the state file so the next command's\n // banner reflects that we're current. We can't read the new\n // version out of our own process (we're still running the old\n // build); we record what the state file's latest_version was,\n // on the assumption that npm installed that version.\n try {\n const state = await readState(opts.statePath);\n const now =\n opts.now ?? (() => Math.floor(Date.now() / 1000));\n await writeState(\n {\n last_check_at: now(),\n installed_version: state.latest_version || installed,\n latest_version: state.latest_version || installed,\n dismissed_versions: state.dismissed_versions,\n },\n opts.statePath,\n );\n } catch {\n // Non-fatal: the next `almanac` invocation will re-run the\n // background check and refresh state properly.\n }\n resolve({\n stdout: \"codealmanac: updated.\\n\",\n stderr: \"\",\n exitCode: 0,\n });\n });\n });\n}\n\nfunction readInstalledVersion(): string {\n // Dev layout: `src/commands/update.ts` → `../../package.json`.\n // Bundled layout: `dist/codealmanac.js` → `../package.json`. We try\n // both so the version lookup works from both. (Same approach as\n // `cli.ts` and `doctor.ts`, which hit the same ambiguity.)\n try {\n const require = createRequire(import.meta.url);\n const pkg = require(\"../../package.json\") as { version?: unknown };\n if (typeof pkg.version === \"string\" && pkg.version.length > 0) {\n return pkg.version;\n }\n } catch {\n // Fall through.\n }\n try {\n const require = createRequire(import.meta.url);\n const pkg = require(\"../package.json\") as { version?: unknown };\n if (typeof pkg.version === \"string\" && pkg.version.length > 0) {\n return pkg.version;\n }\n } catch {\n // Fall through.\n }\n return \"unknown\";\n}\n"],"mappings":"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","names":["require"]}
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ assertClaudeAuth
4
+ } from "./chunk-SSYMRT4I.js";
5
+ import {
6
+ AGENT_PROVIDER_IDS,
7
+ isAgentProviderId,
8
+ readConfig,
9
+ writeConfig
10
+ } from "./chunk-WRUSDYYE.js";
11
+
12
+ // src/agent/providers.ts
13
+ import { spawn, spawnSync } from "child_process";
14
+ async function assertAgentAuth(args) {
15
+ if (args.provider === "claude") {
16
+ await assertClaudeAuth(args.spawnCli);
17
+ return;
18
+ }
19
+ const status = await checkProviderStatus(args.provider);
20
+ if (!status.installed || !status.authenticated) {
21
+ const err = new Error(`${status.id} not ready: ${status.detail}`);
22
+ err.code = "AGENT_AUTH_MISSING";
23
+ throw err;
24
+ }
25
+ }
26
+ async function listProviderStatuses(spawnCli) {
27
+ const out = [];
28
+ for (const id of AGENT_PROVIDER_IDS) {
29
+ if (id === "claude") {
30
+ out.push(await checkClaudeProvider(spawnCli));
31
+ } else {
32
+ out.push(await checkProviderStatus(id));
33
+ }
34
+ }
35
+ return out;
36
+ }
37
+ async function checkClaudeProvider(spawnCli) {
38
+ let auth = { loggedIn: false };
39
+ try {
40
+ auth = await import("./auth-S5DVUIUJ.js").then((m) => m.checkClaudeAuth(spawnCli));
41
+ } catch {
42
+ auth = { loggedIn: false };
43
+ }
44
+ const hasApiKey = process.env.ANTHROPIC_API_KEY !== void 0 && process.env.ANTHROPIC_API_KEY.length > 0;
45
+ const installed = commandExists("claude");
46
+ const authenticated = auth.loggedIn || hasApiKey;
47
+ const detail = authenticated ? auth.email ?? (hasApiKey ? "ANTHROPIC_API_KEY set" : "logged in") : installed ? "not logged in" : "claude not found on PATH";
48
+ return { id: "claude", installed, authenticated, detail };
49
+ }
50
+ async function checkProviderStatus(provider) {
51
+ const command = provider === "codex" ? "codex" : "cursor-agent";
52
+ if (!commandExists(command)) {
53
+ return {
54
+ id: provider,
55
+ installed: false,
56
+ authenticated: false,
57
+ detail: `${command} not found on PATH`
58
+ };
59
+ }
60
+ const auth = provider === "codex" ? await runStatusCommand(command, ["login", "status"]) : await runStatusCommand(command, ["status"]);
61
+ return {
62
+ id: provider,
63
+ installed: true,
64
+ authenticated: auth.ok,
65
+ detail: auth.detail
66
+ };
67
+ }
68
+ function commandExists(command) {
69
+ const result = spawnSync("sh", ["-lc", `command -v ${command}`], {
70
+ encoding: "utf8"
71
+ });
72
+ return result.status === 0 && result.stdout.trim().length > 0;
73
+ }
74
+ function runStatusCommand(command, args) {
75
+ return new Promise((resolve) => {
76
+ let stdout = "";
77
+ let stderr = "";
78
+ let child;
79
+ try {
80
+ child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
81
+ } catch (err) {
82
+ const msg = err instanceof Error ? err.message : String(err);
83
+ resolve({ ok: false, detail: msg });
84
+ return;
85
+ }
86
+ const timer = setTimeout(() => {
87
+ try {
88
+ child.kill("SIGTERM");
89
+ } catch {
90
+ }
91
+ resolve({ ok: false, detail: `${command} status timed out` });
92
+ }, 1e4);
93
+ child.stdout?.on("data", (chunk) => {
94
+ stdout += chunk.toString("utf8");
95
+ });
96
+ child.stderr?.on("data", (chunk) => {
97
+ stderr += chunk.toString("utf8");
98
+ });
99
+ child.on("error", (err) => {
100
+ clearTimeout(timer);
101
+ resolve({ ok: false, detail: err.message });
102
+ });
103
+ child.on("close", (code) => {
104
+ clearTimeout(timer);
105
+ const text = `${stdout}
106
+ ${stderr}`.trim();
107
+ resolve({
108
+ ok: code === 0,
109
+ detail: text.split("\n").find((line) => line.trim().length > 0)?.trim() ?? (code === 0 ? "ready" : `${command} exited ${code ?? 1}`)
110
+ });
111
+ });
112
+ });
113
+ }
114
+
115
+ // src/commands/agents.ts
116
+ async function runAgentsList() {
117
+ const config = await readConfig();
118
+ const statuses = await listProviderStatuses();
119
+ const lines = ["codealmanac agents\n"];
120
+ for (const status of statuses) {
121
+ const selected = status.id === config.agent.default ? "*" : " ";
122
+ const auth = status.authenticated ? "ready" : "not ready";
123
+ const installed = status.installed ? "installed" : "missing";
124
+ lines.push(
125
+ `${selected} ${status.id.padEnd(6)} ${installed.padEnd(9)} ${auth.padEnd(9)} ${status.detail}`
126
+ );
127
+ }
128
+ lines.push("\nChange default with: almanac set default-agent <claude|codex|cursor>");
129
+ return { stdout: `${lines.join("\n")}
130
+ `, stderr: "", exitCode: 0 };
131
+ }
132
+ async function runSetDefaultAgent(opts) {
133
+ if (!isAgentProviderId(opts.provider)) {
134
+ return {
135
+ stdout: "",
136
+ stderr: `almanac: unknown agent '${opts.provider}'. Expected one of: claude, codex, cursor.
137
+ `,
138
+ exitCode: 1
139
+ };
140
+ }
141
+ const config = await readConfig();
142
+ const next = {
143
+ ...config,
144
+ agent: {
145
+ ...config.agent,
146
+ default: opts.provider
147
+ }
148
+ };
149
+ await writeConfig(next);
150
+ return {
151
+ stdout: `codealmanac: default agent set to ${opts.provider}.
152
+ `,
153
+ stderr: "",
154
+ exitCode: 0
155
+ };
156
+ }
157
+ async function runSetAgentModel(opts) {
158
+ if (!isAgentProviderId(opts.provider)) {
159
+ return {
160
+ stdout: "",
161
+ stderr: `almanac: unknown agent '${opts.provider}'. Expected one of: claude, codex, cursor.
162
+ `,
163
+ exitCode: 1
164
+ };
165
+ }
166
+ const provider = opts.provider;
167
+ const config = await readConfig();
168
+ const model = opts.model !== void 0 && opts.model.length > 0 ? opts.model : null;
169
+ await writeConfig({
170
+ ...config,
171
+ agent: {
172
+ ...config.agent,
173
+ models: {
174
+ ...config.agent.models,
175
+ [provider]: model
176
+ }
177
+ }
178
+ });
179
+ return {
180
+ stdout: model === null ? `codealmanac: ${provider} model reset to provider default.
181
+ ` : `codealmanac: ${provider} model set to ${model}.
182
+ `,
183
+ stderr: "",
184
+ exitCode: 0
185
+ };
186
+ }
187
+
188
+ export {
189
+ assertAgentAuth,
190
+ runAgentsList,
191
+ runSetDefaultAgent,
192
+ runSetAgentModel
193
+ };
194
+ //# sourceMappingURL=chunk-R3URPHGH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/agent/providers.ts","../src/commands/agents.ts"],"sourcesContent":["import { spawn, spawnSync, type ChildProcess } from \"node:child_process\";\n\nimport {\n assertClaudeAuth,\n type ClaudeAuthStatus,\n type SpawnCliFn,\n} from \"./auth.js\";\nimport {\n AGENT_PROVIDER_IDS,\n type AgentProviderId,\n} from \"../update/config.js\";\n\nexport interface ProviderStatus {\n id: AgentProviderId;\n installed: boolean;\n authenticated: boolean;\n detail: string;\n}\n\nexport async function assertAgentAuth(args: {\n provider: AgentProviderId;\n spawnCli?: SpawnCliFn;\n}): Promise<void> {\n if (args.provider === \"claude\") {\n await assertClaudeAuth(args.spawnCli);\n return;\n }\n const status = await checkProviderStatus(args.provider);\n if (!status.installed || !status.authenticated) {\n const err = new Error(`${status.id} not ready: ${status.detail}`);\n (err as { code?: string }).code = \"AGENT_AUTH_MISSING\";\n throw err;\n }\n}\n\nexport async function listProviderStatuses(\n spawnCli?: SpawnCliFn,\n): Promise<ProviderStatus[]> {\n const out: ProviderStatus[] = [];\n for (const id of AGENT_PROVIDER_IDS) {\n if (id === \"claude\") {\n out.push(await checkClaudeProvider(spawnCli));\n } else {\n out.push(await checkProviderStatus(id));\n }\n }\n return out;\n}\n\nasync function checkClaudeProvider(\n spawnCli?: SpawnCliFn,\n): Promise<ProviderStatus> {\n let auth: ClaudeAuthStatus = { loggedIn: false };\n try {\n auth = await import(\"./auth.js\").then((m) => m.checkClaudeAuth(spawnCli));\n } catch {\n auth = { loggedIn: false };\n }\n const hasApiKey =\n process.env.ANTHROPIC_API_KEY !== undefined &&\n process.env.ANTHROPIC_API_KEY.length > 0;\n const installed = commandExists(\"claude\");\n const authenticated = auth.loggedIn || hasApiKey;\n const detail = authenticated\n ? auth.email ?? (hasApiKey ? \"ANTHROPIC_API_KEY set\" : \"logged in\")\n : installed\n ? \"not logged in\"\n : \"claude not found on PATH\";\n return { id: \"claude\", installed, authenticated, detail };\n}\n\nasync function checkProviderStatus(\n provider: Exclude<AgentProviderId, \"claude\">,\n): Promise<ProviderStatus> {\n const command = provider === \"codex\" ? \"codex\" : \"cursor-agent\";\n if (!commandExists(command)) {\n return {\n id: provider,\n installed: false,\n authenticated: false,\n detail: `${command} not found on PATH`,\n };\n }\n\n const auth =\n provider === \"codex\"\n ? await runStatusCommand(command, [\"login\", \"status\"])\n : await runStatusCommand(command, [\"status\"]);\n\n return {\n id: provider,\n installed: true,\n authenticated: auth.ok,\n detail: auth.detail,\n };\n}\n\nfunction commandExists(command: string): boolean {\n const result = spawnSync(\"sh\", [\"-lc\", `command -v ${command}`], {\n encoding: \"utf8\",\n });\n return result.status === 0 && result.stdout.trim().length > 0;\n}\n\nfunction runStatusCommand(\n command: string,\n args: string[],\n): Promise<{ ok: boolean; detail: string }> {\n return new Promise((resolve) => {\n let stdout = \"\";\n let stderr = \"\";\n let child: ChildProcess;\n try {\n child = spawn(command, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n resolve({ ok: false, detail: msg });\n return;\n }\n const timer = setTimeout(() => {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n // already exited\n }\n resolve({ ok: false, detail: `${command} status timed out` });\n }, 10_000);\n child.stdout?.on(\"data\", (chunk) => {\n stdout += chunk.toString(\"utf8\");\n });\n child.stderr?.on(\"data\", (chunk) => {\n stderr += chunk.toString(\"utf8\");\n });\n child.on(\"error\", (err) => {\n clearTimeout(timer);\n resolve({ ok: false, detail: err.message });\n });\n child.on(\"close\", (code) => {\n clearTimeout(timer);\n const text = `${stdout}\\n${stderr}`.trim();\n resolve({\n ok: code === 0,\n detail: text.split(\"\\n\").find((line) => line.trim().length > 0)?.trim() ??\n (code === 0 ? \"ready\" : `${command} exited ${code ?? 1}`),\n });\n });\n });\n}\n","import { listProviderStatuses } from \"../agent/providers.js\";\nimport {\n isAgentProviderId,\n readConfig,\n writeConfig,\n type AgentProviderId,\n} from \"../update/config.js\";\n\nexport interface AgentsResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nexport async function runAgentsList(): Promise<AgentsResult> {\n const config = await readConfig();\n const statuses = await listProviderStatuses();\n const lines = [\"codealmanac agents\\n\"];\n for (const status of statuses) {\n const selected = status.id === config.agent.default ? \"*\" : \" \";\n const auth = status.authenticated ? \"ready\" : \"not ready\";\n const installed = status.installed ? \"installed\" : \"missing\";\n lines.push(\n `${selected} ${status.id.padEnd(6)} ${installed.padEnd(9)} ${auth.padEnd(9)} ${status.detail}`,\n );\n }\n lines.push(\"\\nChange default with: almanac set default-agent <claude|codex|cursor>\");\n return { stdout: `${lines.join(\"\\n\")}\\n`, stderr: \"\", exitCode: 0 };\n}\n\nexport interface SetDefaultAgentOptions {\n provider: string;\n}\n\nexport async function runSetDefaultAgent(\n opts: SetDefaultAgentOptions,\n): Promise<AgentsResult> {\n if (!isAgentProviderId(opts.provider)) {\n return {\n stdout: \"\",\n stderr:\n `almanac: unknown agent '${opts.provider}'. ` +\n \"Expected one of: claude, codex, cursor.\\n\",\n exitCode: 1,\n };\n }\n const config = await readConfig();\n const next = {\n ...config,\n agent: {\n ...config.agent,\n default: opts.provider,\n },\n };\n await writeConfig(next);\n return {\n stdout: `codealmanac: default agent set to ${opts.provider}.\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n\nexport async function runSetAgentModel(opts: {\n provider: string;\n model?: string;\n}): Promise<AgentsResult> {\n if (!isAgentProviderId(opts.provider)) {\n return {\n stdout: \"\",\n stderr:\n `almanac: unknown agent '${opts.provider}'. ` +\n \"Expected one of: claude, codex, cursor.\\n\",\n exitCode: 1,\n };\n }\n const provider = opts.provider as AgentProviderId;\n const config = await readConfig();\n const model =\n opts.model !== undefined && opts.model.length > 0 ? opts.model : null;\n await writeConfig({\n ...config,\n agent: {\n ...config.agent,\n models: {\n ...config.agent.models,\n [provider]: model,\n },\n },\n });\n return {\n stdout:\n model === null\n ? `codealmanac: ${provider} model reset to provider default.\\n`\n : `codealmanac: ${provider} model set to ${model}.\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n"],"mappings":"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","names":[]}