codealmanac 0.2.3 → 0.2.5

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 (58) hide show
  1. package/README.md +27 -14
  2. package/dist/agents-RVTQYE6A.js +25 -0
  3. package/dist/chunk-6BJUYZ43.js +195 -0
  4. package/dist/chunk-6BJUYZ43.js.map +1 -0
  5. package/dist/chunk-BGUID5BS.js +766 -0
  6. package/dist/chunk-BGUID5BS.js.map +1 -0
  7. package/dist/{chunk-NBVIEZZQ.js → chunk-DL5BXZCX.js} +53 -3
  8. package/dist/chunk-DL5BXZCX.js.map +1 -0
  9. package/dist/{chunk-XNTNXEWY.js → chunk-GFUB57IT.js} +243 -83
  10. package/dist/chunk-GFUB57IT.js.map +1 -0
  11. package/dist/{chunk-P3LDTCLB.js → chunk-H37GKBWI.js} +13 -1
  12. package/dist/chunk-H37GKBWI.js.map +1 -0
  13. package/dist/{chunk-QQHIVTXT.js → chunk-MRRX4UQB.js} +4 -4
  14. package/dist/{chunk-QQHIVTXT.js.map → chunk-MRRX4UQB.js.map} +1 -1
  15. package/dist/chunk-P5WGG4FJ.js +359 -0
  16. package/dist/chunk-P5WGG4FJ.js.map +1 -0
  17. package/dist/{chunk-HNVOYWC2.js → chunk-SMIK2YLU.js} +165 -76
  18. package/dist/chunk-SMIK2YLU.js.map +1 -0
  19. package/dist/{chunk-V3QOQSXI.js → chunk-TILAKDN6.js} +14 -8
  20. package/dist/chunk-TILAKDN6.js.map +1 -0
  21. package/dist/chunk-TT6ZP4GS.js +282 -0
  22. package/dist/chunk-TT6ZP4GS.js.map +1 -0
  23. package/dist/{cli-6BOB6KAN.js → cli-CL4ID7EO.js} +123 -33
  24. package/dist/cli-CL4ID7EO.js.map +1 -0
  25. package/dist/codealmanac.js +1 -1
  26. package/dist/config-ML2RCR7J.js +16 -0
  27. package/dist/doctor-DOLJRGS4.js +17 -0
  28. package/dist/{register-commands-IXYE5CNZ.js → register-commands-FBJ6XQ3L.js} +296 -398
  29. package/dist/register-commands-FBJ6XQ3L.js.map +1 -0
  30. package/dist/uninstall-DX6LFKMX.js +15 -0
  31. package/dist/{update-RAF7QRYF.js → update-P2IPG7RO.js} +3 -3
  32. package/guides/mini.md +4 -4
  33. package/guides/reference.md +75 -16
  34. package/package.json +1 -1
  35. package/dist/agents-RVYQ44DB.js +0 -16
  36. package/dist/auth-S5DVUIUJ.js +0 -18
  37. package/dist/chunk-HNVOYWC2.js.map +0 -1
  38. package/dist/chunk-NBVIEZZQ.js.map +0 -1
  39. package/dist/chunk-P3LDTCLB.js.map +0 -1
  40. package/dist/chunk-PIYJQE4Z.js +0 -102
  41. package/dist/chunk-PIYJQE4Z.js.map +0 -1
  42. package/dist/chunk-SSYMRT4I.js +0 -126
  43. package/dist/chunk-SSYMRT4I.js.map +0 -1
  44. package/dist/chunk-TWM7I2LU.js +0 -116
  45. package/dist/chunk-TWM7I2LU.js.map +0 -1
  46. package/dist/chunk-V3QOQSXI.js.map +0 -1
  47. package/dist/chunk-WRUSDYYE.js +0 -97
  48. package/dist/chunk-WRUSDYYE.js.map +0 -1
  49. package/dist/chunk-XNTNXEWY.js.map +0 -1
  50. package/dist/cli-6BOB6KAN.js.map +0 -1
  51. package/dist/doctor-DD7EQGCA.js +0 -18
  52. package/dist/register-commands-IXYE5CNZ.js.map +0 -1
  53. package/dist/uninstall-OBV4Z3JE.js +0 -16
  54. /package/dist/{agents-RVYQ44DB.js.map → agents-RVTQYE6A.js.map} +0 -0
  55. /package/dist/{auth-S5DVUIUJ.js.map → config-ML2RCR7J.js.map} +0 -0
  56. /package/dist/{doctor-DD7EQGCA.js.map → doctor-DOLJRGS4.js.map} +0 -0
  57. /package/dist/{uninstall-OBV4Z3JE.js.map → uninstall-DX6LFKMX.js.map} +0 -0
  58. /package/dist/{update-RAF7QRYF.js.map → update-P2IPG7RO.js.map} +0 -0
@@ -8,7 +8,7 @@ import {
8
8
  import {
9
9
  readConfig,
10
10
  writeConfig
11
- } from "./chunk-WRUSDYYE.js";
11
+ } from "./chunk-P5WGG4FJ.js";
12
12
 
13
13
  // src/commands/update.ts
14
14
  import { spawn } from "child_process";
@@ -61,7 +61,7 @@ async function dismissLatest(opts) {
61
61
  await writeState(next, opts.statePath);
62
62
  return {
63
63
  stdout: `codealmanac: dismissed ${state.latest_version}. The nag banner will not show for this version.
64
- Run \`almanac update\` to upgrade, or \`almanac update --enable-notifier\` to re-enable nags.
64
+ Run \`almanac update\` to upgrade, or \`almanac config set update_notifier true\` to re-enable nags.
65
65
  `,
66
66
  stderr: "",
67
67
  exitCode: 0
@@ -117,7 +117,7 @@ async function toggleNotifier(enable, opts) {
117
117
  await writeConfig(next, opts.configPath);
118
118
  return {
119
119
  stdout: enable ? "codealmanac: update notifier enabled. The pre-command banner will show when a new version is available.\n" : "codealmanac: update notifier disabled. No more pre-command banners. Run `almanac update --check` to see status.\n",
120
- stderr: "",
120
+ stderr: enable ? "almanac: warning: `almanac update --enable-notifier` is deprecated; use `almanac config set update_notifier true`.\n" : "almanac: warning: `almanac update --disable-notifier` is deprecated; use `almanac config set update_notifier false`.\n",
121
121
  exitCode: 0
122
122
  };
123
123
  }
@@ -202,4 +202,4 @@ function readInstalledVersion() {
202
202
  export {
203
203
  runUpdate
204
204
  };
205
- //# sourceMappingURL=chunk-QQHIVTXT.js.map
205
+ //# sourceMappingURL=chunk-MRRX4UQB.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":"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","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 — deprecated compatibility\n * flags for `config set update_notifier true|false`.\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 config set update_notifier true\\` 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: enable\n ? \"almanac: warning: `almanac update --enable-notifier` is deprecated; use `almanac config set update_notifier true`.\\n\"\n : \"almanac: warning: `almanac update --disable-notifier` is deprecated; use `almanac config set update_notifier false`.\\n\",\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,359 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ findNearestAlmanacDir,
4
+ getGlobalAlmanacDir,
5
+ getRepoAlmanacDir
6
+ } from "./chunk-7JUX4ADQ.js";
7
+
8
+ // src/update/config.ts
9
+ import { existsSync } from "fs";
10
+ import { mkdir, readFile, rename, writeFile } from "fs/promises";
11
+ import { dirname, join } from "path";
12
+ var AGENT_PROVIDER_IDS = ["claude", "codex", "cursor"];
13
+ function isAgentProviderId(value) {
14
+ return AGENT_PROVIDER_IDS.includes(value);
15
+ }
16
+ function defaultConfig() {
17
+ return {
18
+ update_notifier: true,
19
+ agent: {
20
+ default: "claude",
21
+ models: {
22
+ claude: null,
23
+ codex: null,
24
+ cursor: null
25
+ }
26
+ }
27
+ };
28
+ }
29
+ function getConfigPath() {
30
+ return join(getGlobalAlmanacDir(), "config.toml");
31
+ }
32
+ function getLegacyConfigPath() {
33
+ return join(getGlobalAlmanacDir(), "config.json");
34
+ }
35
+ function getProjectConfigPath(cwd) {
36
+ const repoRoot = findNearestAlmanacDir(cwd);
37
+ return repoRoot === null ? null : join(getRepoAlmanacDir(repoRoot), "config.toml");
38
+ }
39
+ async function readConfig(input) {
40
+ return (await readConfigWithOrigins(input)).config;
41
+ }
42
+ async function readConfigWithOrigins(input) {
43
+ const opts = normalizeReadOptions(input);
44
+ if (opts.path !== void 0) {
45
+ const raw = await readRawConfigObject(opts.path);
46
+ return {
47
+ config: normalizeRawConfig(raw),
48
+ origins: originsFromRaw(raw, "user"),
49
+ raw
50
+ };
51
+ }
52
+ const file = getConfigPath();
53
+ await migrateLegacyConfigIfNeeded(file);
54
+ const userRaw = await readRawConfigObject(file);
55
+ const mergedRaw = cloneJsonObject(userRaw);
56
+ const origins = originsFromRaw(userRaw, "user");
57
+ const projectPath = opts.cwd !== void 0 ? getProjectConfigPath(opts.cwd) : null;
58
+ if (projectPath !== null) {
59
+ const projectRaw = await readRawConfigObject(projectPath);
60
+ applyProjectConfig(mergedRaw, projectRaw);
61
+ Object.assign(origins, originsFromRaw(projectRaw, "project", true));
62
+ }
63
+ return {
64
+ config: normalizeRawConfig(mergedRaw),
65
+ origins,
66
+ raw: mergedRaw
67
+ };
68
+ }
69
+ function normalizeReadOptions(input) {
70
+ return typeof input === "string" ? { path: input } : input ?? {};
71
+ }
72
+ async function migrateLegacyConfigIfNeeded(file) {
73
+ if (existsSync(file)) return;
74
+ const legacy = getLegacyConfigPath();
75
+ if (!existsSync(legacy)) return;
76
+ const raw = await readRawConfigObject(legacy);
77
+ if (Object.keys(raw).length === 0) return;
78
+ await writeConfig(normalizeRawConfig(raw), file);
79
+ }
80
+ function normalizeRawConfig(raw) {
81
+ const defaults = defaultConfig();
82
+ const rawAgent = raw.agent !== void 0 && raw.agent !== null && typeof raw.agent === "object" && !Array.isArray(raw.agent) ? raw.agent : {};
83
+ const rawDefault = typeof rawAgent.default === "string" && isAgentProviderId(rawAgent.default) ? rawAgent.default : defaults.agent.default;
84
+ const rawModels = rawAgent.models !== void 0 && rawAgent.models !== null && typeof rawAgent.models === "object" && !Array.isArray(rawAgent.models) ? rawAgent.models : {};
85
+ const models = {
86
+ ...defaults.agent.models
87
+ };
88
+ for (const id of AGENT_PROVIDER_IDS) {
89
+ const value = rawModels[id];
90
+ if (typeof value === "string" && value.length > 0) {
91
+ models[id] = value === "default" || value === "null" ? null : value;
92
+ } else if (value === null) {
93
+ models[id] = null;
94
+ }
95
+ }
96
+ return {
97
+ update_notifier: typeof raw.update_notifier === "boolean" ? raw.update_notifier : defaults.update_notifier,
98
+ agent: {
99
+ default: rawDefault,
100
+ models
101
+ }
102
+ };
103
+ }
104
+ function applyProjectConfig(target, projectRaw) {
105
+ const projectAgent = projectRaw.agent !== null && typeof projectRaw.agent === "object" && !Array.isArray(projectRaw.agent) ? projectRaw.agent : {};
106
+ if (Object.keys(projectAgent).length === 0) return;
107
+ const targetAgent = target.agent !== null && typeof target.agent === "object" && !Array.isArray(target.agent) ? target.agent : {};
108
+ target.agent = targetAgent;
109
+ if (typeof projectAgent.default === "string") {
110
+ targetAgent.default = projectAgent.default;
111
+ }
112
+ const projectModels = projectAgent.models !== null && typeof projectAgent.models === "object" && !Array.isArray(projectAgent.models) ? projectAgent.models : {};
113
+ if (Object.keys(projectModels).length === 0) return;
114
+ const targetModels = targetAgent.models !== null && typeof targetAgent.models === "object" && !Array.isArray(targetAgent.models) ? targetAgent.models : {};
115
+ targetAgent.models = targetModels;
116
+ for (const id of AGENT_PROVIDER_IDS) {
117
+ if (Object.prototype.hasOwnProperty.call(projectModels, id)) {
118
+ targetModels[id] = projectModels[id];
119
+ }
120
+ }
121
+ }
122
+ function originsFromRaw(raw, origin, agentOnly = false) {
123
+ const origins = {};
124
+ if (!agentOnly && Object.prototype.hasOwnProperty.call(raw, "update_notifier")) {
125
+ origins.update_notifier = origin;
126
+ }
127
+ const agent = raw.agent !== null && typeof raw.agent === "object" && !Array.isArray(raw.agent) ? raw.agent : {};
128
+ if (Object.prototype.hasOwnProperty.call(agent, "default")) {
129
+ origins["agent.default"] = origin;
130
+ }
131
+ const models = agent.models !== null && typeof agent.models === "object" && !Array.isArray(agent.models) ? agent.models : {};
132
+ for (const id of AGENT_PROVIDER_IDS) {
133
+ if (Object.prototype.hasOwnProperty.call(models, id)) {
134
+ origins[`agent.models.${id}`] = origin;
135
+ }
136
+ }
137
+ return origins;
138
+ }
139
+ async function readSingleConfig(file) {
140
+ let raw;
141
+ try {
142
+ raw = await readFile(file, "utf8");
143
+ } catch {
144
+ return defaultConfig();
145
+ }
146
+ const trimmed = raw.trim();
147
+ if (trimmed.length === 0) return defaultConfig();
148
+ try {
149
+ return normalizeRawConfig(parseConfigText(trimmed, file));
150
+ } catch {
151
+ return defaultConfig();
152
+ }
153
+ }
154
+ async function writeConfig(config, path) {
155
+ const file = path ?? getConfigPath();
156
+ await mkdir(dirname(file), { recursive: true });
157
+ const current = await readSingleConfig(file);
158
+ const existingRaw = await readRawConfigObject(file);
159
+ const stored = toStoredConfigPatch(config, current, existingRaw);
160
+ const body = serializeConfig(stored, file);
161
+ const tmp = `${file}.tmp`;
162
+ await writeFile(tmp, body, "utf8");
163
+ await rename(tmp, file);
164
+ }
165
+ function normalizeConfig(config) {
166
+ const defaults = defaultConfig();
167
+ return {
168
+ update_notifier: typeof config.update_notifier === "boolean" ? config.update_notifier : defaults.update_notifier,
169
+ agent: {
170
+ default: config.agent !== void 0 && isAgentProviderId(config.agent.default) ? config.agent.default : defaults.agent.default,
171
+ models: {
172
+ ...defaults.agent.models,
173
+ ...config.agent?.models ?? {}
174
+ }
175
+ }
176
+ };
177
+ }
178
+ async function readRawConfigObject(path) {
179
+ try {
180
+ return parseConfigText(await readFile(path, "utf8"), path);
181
+ } catch {
182
+ }
183
+ return {};
184
+ }
185
+ function parseConfigText(raw, path = "config.toml") {
186
+ const trimmed = raw.trim();
187
+ if (trimmed.length === 0) return {};
188
+ if (path.endsWith(".json") || trimmed.startsWith("{")) {
189
+ const parsed = JSON.parse(trimmed);
190
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
191
+ }
192
+ return parseTomlConfig(trimmed);
193
+ }
194
+ function serializeConfig(raw, path = "config.toml") {
195
+ return path.endsWith(".json") ? `${JSON.stringify(raw, null, 2)}
196
+ ` : serializeTomlConfig(raw);
197
+ }
198
+ function parseTomlConfig(raw) {
199
+ const result = {};
200
+ let section = [];
201
+ for (const original of raw.split(/\r?\n/)) {
202
+ const line = stripTomlComment(original).trim();
203
+ if (line.length === 0) continue;
204
+ const sectionMatch = line.match(/^\[([A-Za-z0-9_.-]+)\]$/);
205
+ if (sectionMatch !== null) {
206
+ section = sectionMatch[1].split(".");
207
+ continue;
208
+ }
209
+ const eq = line.indexOf("=");
210
+ if (eq === -1) continue;
211
+ const key = line.slice(0, eq).trim();
212
+ const value = parseTomlValue(line.slice(eq + 1).trim());
213
+ setObjectPath(result, [...section, key], value);
214
+ }
215
+ return result;
216
+ }
217
+ function serializeTomlConfig(raw) {
218
+ const lines = [];
219
+ if (typeof raw.update_notifier === "boolean") {
220
+ lines.push(`update_notifier = ${raw.update_notifier ? "true" : "false"}`);
221
+ }
222
+ const agent = raw.agent !== null && typeof raw.agent === "object" && !Array.isArray(raw.agent) ? raw.agent : {};
223
+ if (typeof agent.default === "string") {
224
+ if (lines.length > 0) lines.push("");
225
+ lines.push("[agent]");
226
+ lines.push(`default = ${tomlString(agent.default)}`);
227
+ }
228
+ const models = agent.models !== null && typeof agent.models === "object" && !Array.isArray(agent.models) ? agent.models : {};
229
+ const modelLines = [];
230
+ for (const id of AGENT_PROVIDER_IDS) {
231
+ if (!Object.prototype.hasOwnProperty.call(models, id)) continue;
232
+ const value = models[id] === null ? "default" : models[id];
233
+ if (typeof value === "string" && value.length > 0) {
234
+ modelLines.push(`${id} = ${tomlString(value)}`);
235
+ }
236
+ }
237
+ if (modelLines.length > 0) {
238
+ if (lines.length > 0) lines.push("");
239
+ lines.push("[agent.models]", ...modelLines);
240
+ }
241
+ return `${lines.join("\n")}
242
+ `;
243
+ }
244
+ function stripTomlComment(line) {
245
+ let inString = false;
246
+ let escaped = false;
247
+ for (let i = 0; i < line.length; i++) {
248
+ const ch = line[i];
249
+ if (escaped) {
250
+ escaped = false;
251
+ continue;
252
+ }
253
+ if (ch === "\\") {
254
+ escaped = true;
255
+ continue;
256
+ }
257
+ if (ch === '"') inString = !inString;
258
+ if (ch === "#" && !inString) return line.slice(0, i);
259
+ }
260
+ return line;
261
+ }
262
+ function parseTomlValue(raw) {
263
+ if (raw === "true") return true;
264
+ if (raw === "false") return false;
265
+ if (raw.startsWith('"') && raw.endsWith('"')) {
266
+ return JSON.parse(raw);
267
+ }
268
+ return raw;
269
+ }
270
+ function tomlString(value) {
271
+ return JSON.stringify(value);
272
+ }
273
+ function setObjectPath(raw, path, value) {
274
+ let cursor = raw;
275
+ for (const part of path.slice(0, -1)) {
276
+ const next = cursor[part];
277
+ if (next === null || typeof next !== "object" || Array.isArray(next)) {
278
+ cursor[part] = {};
279
+ }
280
+ cursor = cursor[part];
281
+ }
282
+ const leaf = path[path.length - 1];
283
+ if (leaf !== void 0) cursor[leaf] = value;
284
+ }
285
+ function toStoredConfigPatch(config, current, raw) {
286
+ const normalized = normalizeConfig(config);
287
+ const defaults = defaultConfig();
288
+ const stored = cloneJsonObject(raw);
289
+ if (config.update_notifier !== void 0 && normalized.update_notifier !== current.update_notifier) {
290
+ setStoredValue(
291
+ stored,
292
+ ["update_notifier"],
293
+ normalized.update_notifier,
294
+ defaults.update_notifier
295
+ );
296
+ }
297
+ if (config.agent !== void 0) {
298
+ if (config.agent.default !== void 0 && normalized.agent.default !== current.agent.default) {
299
+ setStoredValue(
300
+ stored,
301
+ ["agent", "default"],
302
+ normalized.agent.default,
303
+ defaults.agent.default
304
+ );
305
+ }
306
+ const inputModels = config.agent.models ?? {};
307
+ for (const id of AGENT_PROVIDER_IDS) {
308
+ if (!Object.prototype.hasOwnProperty.call(inputModels, id)) continue;
309
+ const value = normalized.agent.models[id] ?? null;
310
+ const currentValue = current.agent.models[id] ?? null;
311
+ const defaultValue = defaults.agent.models[id] ?? null;
312
+ if (value !== currentValue) {
313
+ setStoredValue(stored, ["agent", "models", id], value, defaultValue);
314
+ }
315
+ }
316
+ }
317
+ pruneEmptyObjects(stored);
318
+ return stored;
319
+ }
320
+ function setStoredValue(raw, path, value, defaultValue) {
321
+ let cursor = raw;
322
+ for (const part of path.slice(0, -1)) {
323
+ const next = cursor[part];
324
+ if (next === null || typeof next !== "object" || Array.isArray(next)) {
325
+ cursor[part] = {};
326
+ }
327
+ cursor = cursor[part];
328
+ }
329
+ const leaf = path[path.length - 1];
330
+ if (leaf === void 0) return;
331
+ cursor[leaf] = value;
332
+ if (value !== defaultValue) return;
333
+ }
334
+ function cloneJsonObject(raw) {
335
+ return JSON.parse(JSON.stringify(raw));
336
+ }
337
+ function pruneEmptyObjects(raw) {
338
+ for (const [key, value] of Object.entries(raw)) {
339
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
340
+ continue;
341
+ }
342
+ pruneEmptyObjects(value);
343
+ if (Object.keys(value).length === 0) delete raw[key];
344
+ }
345
+ }
346
+
347
+ export {
348
+ AGENT_PROVIDER_IDS,
349
+ isAgentProviderId,
350
+ getConfigPath,
351
+ getLegacyConfigPath,
352
+ getProjectConfigPath,
353
+ readConfig,
354
+ readConfigWithOrigins,
355
+ writeConfig,
356
+ parseConfigText,
357
+ serializeConfig
358
+ };
359
+ //# sourceMappingURL=chunk-P5WGG4FJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/update/config.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport {\n findNearestAlmanacDir,\n getGlobalAlmanacDir,\n getRepoAlmanacDir,\n} 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.toml` — global, cross-wiki configuration. Legacy\n * `config.json` is read and migrated forward on first normal access.\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: null,\n codex: null,\n cursor: null,\n },\n },\n };\n}\n\nexport function getConfigPath(): string {\n return join(getGlobalAlmanacDir(), \"config.toml\");\n}\n\nexport function getLegacyConfigPath(): string {\n return join(getGlobalAlmanacDir(), \"config.json\");\n}\n\nexport function getProjectConfigPath(cwd: string): string | null {\n const repoRoot = findNearestAlmanacDir(cwd);\n return repoRoot === null ? null : join(getRepoAlmanacDir(repoRoot), \"config.toml\");\n}\n\nexport type ConfigOrigin = \"default\" | \"user\" | \"project\";\n\nexport interface ConfigReadOptions {\n path?: string;\n cwd?: string;\n}\n\nexport interface ConfigReadResult {\n config: GlobalConfig;\n origins: Record<string, ConfigOrigin>;\n raw: Record<string, unknown>;\n}\n\nexport async function readConfig(\n input?: string | ConfigReadOptions,\n): Promise<GlobalConfig> {\n return (await readConfigWithOrigins(input)).config;\n}\n\nexport async function readConfigWithOrigins(\n input?: string | ConfigReadOptions,\n): Promise<ConfigReadResult> {\n const opts = normalizeReadOptions(input);\n if (opts.path !== undefined) {\n const raw = await readRawConfigObject(opts.path);\n return {\n config: normalizeRawConfig(raw),\n origins: originsFromRaw(raw, \"user\"),\n raw,\n };\n }\n\n const file = getConfigPath();\n await migrateLegacyConfigIfNeeded(file);\n const userRaw = await readRawConfigObject(file);\n const mergedRaw = cloneJsonObject(userRaw);\n const origins = originsFromRaw(userRaw, \"user\");\n const projectPath = opts.cwd !== undefined ? getProjectConfigPath(opts.cwd) : null;\n if (projectPath !== null) {\n const projectRaw = await readRawConfigObject(projectPath);\n applyProjectConfig(mergedRaw, projectRaw);\n Object.assign(origins, originsFromRaw(projectRaw, \"project\", true));\n }\n return {\n config: normalizeRawConfig(mergedRaw),\n origins,\n raw: mergedRaw,\n };\n}\n\nfunction normalizeReadOptions(\n input?: string | ConfigReadOptions,\n): ConfigReadOptions {\n return typeof input === \"string\" ? { path: input } : input ?? {};\n}\n\nasync function migrateLegacyConfigIfNeeded(file: string): Promise<void> {\n if (existsSync(file)) return;\n const legacy = getLegacyConfigPath();\n if (!existsSync(legacy)) return;\n const raw = await readRawConfigObject(legacy);\n if (Object.keys(raw).length === 0) return;\n await writeConfig(normalizeRawConfig(raw), file);\n}\n\nfunction normalizeRawConfig(raw: Record<string, unknown>): GlobalConfig {\n const defaults = defaultConfig();\n const rawAgent =\n raw.agent !== undefined &&\n raw.agent !== null &&\n typeof raw.agent === \"object\" &&\n !Array.isArray(raw.agent)\n ? (raw.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 !Array.isArray(rawAgent.models)\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 === \"default\" || value === \"null\" ? null : value;\n } else if (value === null) {\n models[id] = null;\n }\n }\n return {\n update_notifier:\n typeof raw.update_notifier === \"boolean\"\n ? raw.update_notifier\n : defaults.update_notifier,\n agent: {\n default: rawDefault,\n models,\n },\n };\n}\n\nfunction applyProjectConfig(\n target: Record<string, unknown>,\n projectRaw: Record<string, unknown>,\n): void {\n const projectAgent =\n projectRaw.agent !== null &&\n typeof projectRaw.agent === \"object\" &&\n !Array.isArray(projectRaw.agent)\n ? projectRaw.agent as Record<string, unknown>\n : {};\n if (Object.keys(projectAgent).length === 0) return;\n const targetAgent =\n target.agent !== null &&\n typeof target.agent === \"object\" &&\n !Array.isArray(target.agent)\n ? target.agent as Record<string, unknown>\n : {};\n target.agent = targetAgent;\n if (typeof projectAgent.default === \"string\") {\n targetAgent.default = projectAgent.default;\n }\n const projectModels =\n projectAgent.models !== null &&\n typeof projectAgent.models === \"object\" &&\n !Array.isArray(projectAgent.models)\n ? projectAgent.models as Record<string, unknown>\n : {};\n if (Object.keys(projectModels).length === 0) return;\n const targetModels =\n targetAgent.models !== null &&\n typeof targetAgent.models === \"object\" &&\n !Array.isArray(targetAgent.models)\n ? targetAgent.models as Record<string, unknown>\n : {};\n targetAgent.models = targetModels;\n for (const id of AGENT_PROVIDER_IDS) {\n if (Object.prototype.hasOwnProperty.call(projectModels, id)) {\n targetModels[id] = projectModels[id];\n }\n }\n}\n\nfunction originsFromRaw(\n raw: Record<string, unknown>,\n origin: ConfigOrigin,\n agentOnly = false,\n): Record<string, ConfigOrigin> {\n const origins: Record<string, ConfigOrigin> = {};\n if (!agentOnly && Object.prototype.hasOwnProperty.call(raw, \"update_notifier\")) {\n origins.update_notifier = origin;\n }\n const agent =\n raw.agent !== null &&\n typeof raw.agent === \"object\" &&\n !Array.isArray(raw.agent)\n ? raw.agent as Record<string, unknown>\n : {};\n if (Object.prototype.hasOwnProperty.call(agent, \"default\")) {\n origins[\"agent.default\"] = origin;\n }\n const models =\n agent.models !== null &&\n typeof agent.models === \"object\" &&\n !Array.isArray(agent.models)\n ? agent.models as Record<string, unknown>\n : {};\n for (const id of AGENT_PROVIDER_IDS) {\n if (Object.prototype.hasOwnProperty.call(models, id)) {\n origins[`agent.models.${id}`] = origin;\n }\n }\n return origins;\n}\n\nasync function readSingleConfig(file: string): Promise<GlobalConfig> {\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 return normalizeRawConfig(parseConfigText(trimmed, file));\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 current = await readSingleConfig(file);\n const existingRaw = await readRawConfigObject(file);\n const stored = toStoredConfigPatch(config, current, existingRaw);\n const body = serializeConfig(stored, file);\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\nasync function readRawConfigObject(\n path: string,\n): Promise<Record<string, unknown>> {\n try {\n return parseConfigText(await readFile(path, \"utf8\"), path);\n } catch {\n // Fall through to empty.\n }\n return {};\n}\n\nexport function parseConfigText(\n raw: string,\n path = \"config.toml\",\n): Record<string, unknown> {\n const trimmed = raw.trim();\n if (trimmed.length === 0) return {};\n if (path.endsWith(\".json\") || trimmed.startsWith(\"{\")) {\n const parsed = JSON.parse(trimmed) as unknown;\n return parsed !== null && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? parsed as Record<string, unknown>\n : {};\n }\n return parseTomlConfig(trimmed);\n}\n\nexport function serializeConfig(\n raw: Record<string, unknown>,\n path = \"config.toml\",\n): string {\n return path.endsWith(\".json\")\n ? `${JSON.stringify(raw, null, 2)}\\n`\n : serializeTomlConfig(raw);\n}\n\nfunction parseTomlConfig(raw: string): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n let section: string[] = [];\n for (const original of raw.split(/\\r?\\n/)) {\n const line = stripTomlComment(original).trim();\n if (line.length === 0) continue;\n const sectionMatch = line.match(/^\\[([A-Za-z0-9_.-]+)\\]$/);\n if (sectionMatch !== null) {\n section = sectionMatch[1]!.split(\".\");\n continue;\n }\n const eq = line.indexOf(\"=\");\n if (eq === -1) continue;\n const key = line.slice(0, eq).trim();\n const value = parseTomlValue(line.slice(eq + 1).trim());\n setObjectPath(result, [...section, key], value);\n }\n return result;\n}\n\nfunction serializeTomlConfig(raw: Record<string, unknown>): string {\n const lines: string[] = [];\n if (typeof raw.update_notifier === \"boolean\") {\n lines.push(`update_notifier = ${raw.update_notifier ? \"true\" : \"false\"}`);\n }\n const agent =\n raw.agent !== null &&\n typeof raw.agent === \"object\" &&\n !Array.isArray(raw.agent)\n ? raw.agent as Record<string, unknown>\n : {};\n if (typeof agent.default === \"string\") {\n if (lines.length > 0) lines.push(\"\");\n lines.push(\"[agent]\");\n lines.push(`default = ${tomlString(agent.default)}`);\n }\n const models =\n agent.models !== null &&\n typeof agent.models === \"object\" &&\n !Array.isArray(agent.models)\n ? agent.models as Record<string, unknown>\n : {};\n const modelLines: string[] = [];\n for (const id of AGENT_PROVIDER_IDS) {\n if (!Object.prototype.hasOwnProperty.call(models, id)) continue;\n const value = models[id] === null ? \"default\" : models[id];\n if (typeof value === \"string\" && value.length > 0) {\n modelLines.push(`${id} = ${tomlString(value)}`);\n }\n }\n if (modelLines.length > 0) {\n if (lines.length > 0) lines.push(\"\");\n lines.push(\"[agent.models]\", ...modelLines);\n }\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction stripTomlComment(line: string): string {\n let inString = false;\n let escaped = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i];\n if (escaped) {\n escaped = false;\n continue;\n }\n if (ch === \"\\\\\") {\n escaped = true;\n continue;\n }\n if (ch === \"\\\"\") inString = !inString;\n if (ch === \"#\" && !inString) return line.slice(0, i);\n }\n return line;\n}\n\nfunction parseTomlValue(raw: string): string | boolean {\n if (raw === \"true\") return true;\n if (raw === \"false\") return false;\n if (raw.startsWith(\"\\\"\") && raw.endsWith(\"\\\"\")) {\n return JSON.parse(raw) as string;\n }\n return raw;\n}\n\nfunction tomlString(value: string): string {\n return JSON.stringify(value);\n}\n\nfunction setObjectPath(\n raw: Record<string, unknown>,\n path: string[],\n value: string | boolean,\n): void {\n let cursor = raw;\n for (const part of path.slice(0, -1)) {\n const next = cursor[part];\n if (next === null || typeof next !== \"object\" || Array.isArray(next)) {\n cursor[part] = {};\n }\n cursor = cursor[part] as Record<string, unknown>;\n }\n const leaf = path[path.length - 1];\n if (leaf !== undefined) cursor[leaf] = value;\n}\n\nfunction toStoredConfigPatch(\n config: GlobalConfig | Partial<GlobalConfig>,\n current: GlobalConfig,\n raw: Record<string, unknown>,\n): Record<string, unknown> {\n const normalized = normalizeConfig(config);\n const defaults = defaultConfig();\n const stored = cloneJsonObject(raw);\n\n if (\n config.update_notifier !== undefined &&\n normalized.update_notifier !== current.update_notifier\n ) {\n setStoredValue(\n stored,\n [\"update_notifier\"],\n normalized.update_notifier,\n defaults.update_notifier,\n );\n }\n\n if (config.agent !== undefined) {\n if (\n config.agent.default !== undefined &&\n normalized.agent.default !== current.agent.default\n ) {\n setStoredValue(\n stored,\n [\"agent\", \"default\"],\n normalized.agent.default,\n defaults.agent.default,\n );\n }\n\n const inputModels = config.agent.models ?? {};\n for (const id of AGENT_PROVIDER_IDS) {\n if (!Object.prototype.hasOwnProperty.call(inputModels, id)) continue;\n const value = normalized.agent.models[id] ?? null;\n const currentValue = current.agent.models[id] ?? null;\n const defaultValue = defaults.agent.models[id] ?? null;\n if (value !== currentValue) {\n setStoredValue(stored, [\"agent\", \"models\", id], value, defaultValue);\n }\n }\n }\n pruneEmptyObjects(stored);\n return stored;\n}\n\nfunction setStoredValue(\n raw: Record<string, unknown>,\n path: string[],\n value: string | boolean | null,\n defaultValue: string | boolean | null,\n): void {\n let cursor = raw;\n for (const part of path.slice(0, -1)) {\n const next = cursor[part];\n if (next === null || typeof next !== \"object\" || Array.isArray(next)) {\n cursor[part] = {};\n }\n cursor = cursor[part] as Record<string, unknown>;\n }\n const leaf = path[path.length - 1];\n if (leaf === undefined) return;\n cursor[leaf] = value;\n if (value !== defaultValue) return;\n // Keep explicit defaults only when the caller changed the value to the\n // default. Unchanged explicit defaults are preserved by cloning `raw`.\n}\n\nfunction cloneJsonObject(raw: Record<string, unknown>): Record<string, unknown> {\n return JSON.parse(JSON.stringify(raw)) as Record<string, unknown>;\n}\n\nfunction pruneEmptyObjects(raw: Record<string, unknown>): void {\n for (const [key, value] of Object.entries(raw)) {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n continue;\n }\n pruneEmptyObjects(value as Record<string, unknown>);\n if (Object.keys(value).length === 0) delete raw[key];\n }\n}\n"],"mappings":"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","names":[]}