@letrquan/book 0.3.0 → 0.4.0-beta.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 (148) hide show
  1. package/CHANGELOG.md +1024 -0
  2. package/README.md +6 -3
  3. package/dist/{additional-roots-H7UNAEHS.js → additional-roots-VAWRQ625.js} +8 -7
  4. package/dist/{app-GE4OHJOC.js → app-J7BVPSUH.js} +5397 -2953
  5. package/dist/app-J7BVPSUH.js.map +1 -0
  6. package/dist/chunk-32WBCUAN.js +660 -0
  7. package/dist/chunk-32WBCUAN.js.map +1 -0
  8. package/dist/{chunk-525CAZPY.js → chunk-4DKHLZRI.js} +6 -6
  9. package/dist/{chunk-4AS7LKKQ.js → chunk-62EEAUID.js} +2 -2
  10. package/dist/{chunk-AQEQX4FF.js → chunk-AHC7OU75.js} +385 -55
  11. package/dist/chunk-AHC7OU75.js.map +1 -0
  12. package/dist/{chunk-EFJ2NH6E.js → chunk-AJZWLKLQ.js} +63 -14
  13. package/dist/chunk-AJZWLKLQ.js.map +1 -0
  14. package/dist/chunk-B5FC3MYZ.js +395 -0
  15. package/dist/chunk-B5FC3MYZ.js.map +1 -0
  16. package/dist/{chunk-67UZEWWF.js → chunk-DNFQTSMJ.js} +53 -11
  17. package/dist/chunk-DNFQTSMJ.js.map +1 -0
  18. package/dist/{chunk-TLX3H2CL.js → chunk-E5WLQSKA.js} +231 -40
  19. package/dist/chunk-E5WLQSKA.js.map +1 -0
  20. package/dist/chunk-ECLC4CIJ.js +1181 -0
  21. package/dist/chunk-ECLC4CIJ.js.map +1 -0
  22. package/dist/chunk-EEX6QNXV.js +127 -0
  23. package/dist/chunk-EEX6QNXV.js.map +1 -0
  24. package/dist/chunk-EFXVSQ5J.js +931 -0
  25. package/dist/chunk-EFXVSQ5J.js.map +1 -0
  26. package/dist/{chunk-RFJ6Q6LE.js → chunk-ENLB7NYV.js} +577 -164
  27. package/dist/chunk-ENLB7NYV.js.map +1 -0
  28. package/dist/{chunk-IME7R2UI.js → chunk-FPR7SKIE.js} +1268 -883
  29. package/dist/chunk-FPR7SKIE.js.map +1 -0
  30. package/dist/{chunk-VPNLCMVR.js → chunk-HFB74CLM.js} +13 -5
  31. package/dist/chunk-HFB74CLM.js.map +1 -0
  32. package/dist/{chunk-5RDRAO4B.js → chunk-HOZXKOE3.js} +37 -1
  33. package/dist/chunk-HOZXKOE3.js.map +1 -0
  34. package/dist/chunk-IDYWN37T.js +36 -0
  35. package/dist/chunk-IDYWN37T.js.map +1 -0
  36. package/dist/{chunk-YC6W4EJH.js → chunk-JGF5HLSI.js} +193 -75
  37. package/dist/chunk-JGF5HLSI.js.map +1 -0
  38. package/dist/{chunk-5YHTKWJN.js → chunk-KDNWMKXV.js} +2447 -907
  39. package/dist/chunk-KDNWMKXV.js.map +1 -0
  40. package/dist/{chunk-Y2VHN7TC.js → chunk-MEOB2DZA.js} +11 -1
  41. package/dist/chunk-MEOB2DZA.js.map +1 -0
  42. package/dist/{chunk-KBQS35OV.js → chunk-MV5PQNIA.js} +2 -2
  43. package/dist/chunk-N2G4Q7ME.js +2672 -0
  44. package/dist/chunk-N2G4Q7ME.js.map +1 -0
  45. package/dist/{chunk-62QL5QUP.js → chunk-OJBMDM5H.js} +17 -1
  46. package/dist/chunk-OJBMDM5H.js.map +1 -0
  47. package/dist/{chunk-56SIEMDX.js → chunk-PTDC7ZUP.js} +2 -2
  48. package/dist/{chunk-X4NJT436.js → chunk-REASC2QG.js} +67 -28
  49. package/dist/chunk-REASC2QG.js.map +1 -0
  50. package/dist/chunk-SBTU5BEC.js +114 -0
  51. package/dist/chunk-SBTU5BEC.js.map +1 -0
  52. package/dist/{chunk-HQI7NYGX.js → chunk-SWPDKLNM.js} +2 -2
  53. package/dist/{chunk-W3XFHEF4.js → chunk-UCFJ43OS.js} +140 -28
  54. package/dist/chunk-UCFJ43OS.js.map +1 -0
  55. package/dist/{chunk-QYBD22H5.js → chunk-W7SLYS3M.js} +80 -16
  56. package/dist/chunk-W7SLYS3M.js.map +1 -0
  57. package/dist/{chunk-SOW4WRQP.js → chunk-WV3O5PGX.js} +3 -3
  58. package/dist/{chunk-JTPOI3KU.js → chunk-XJRJVXJR.js} +262 -38
  59. package/dist/chunk-XJRJVXJR.js.map +1 -0
  60. package/dist/{chunk-224FJZLH.js → chunk-YIL6HTJX.js} +228 -5
  61. package/dist/chunk-YIL6HTJX.js.map +1 -0
  62. package/dist/{command-approvals-VBIU6BLM.js → command-approvals-ZRY66LCW.js} +5 -4
  63. package/dist/context-C3RAP6K5.js +41 -0
  64. package/dist/{hook-approvals-ZVT4V7IM.js → hook-approvals-DPQD5H65.js} +5 -4
  65. package/dist/index.js +657 -754
  66. package/dist/index.js.map +1 -1
  67. package/dist/{interactive-assets-T4I4SEHU.js → interactive-assets-73AQO5WP.js} +11 -6
  68. package/dist/{interactive-assets-T4I4SEHU.js.map → interactive-assets-73AQO5WP.js.map} +1 -1
  69. package/dist/job-runner.js +10 -3
  70. package/dist/job-runner.js.map +1 -1
  71. package/dist/job-supervisor.js +13 -4
  72. package/dist/job-supervisor.js.map +1 -1
  73. package/dist/loader-DQNPDKOX.js +27 -0
  74. package/dist/{mcp-approvals-HJ2VDYMC.js → mcp-approvals-27LJYADQ.js} +5 -4
  75. package/dist/{mcp-config-RNB5LEVE.js → mcp-config-GXSODNCM.js} +3 -2
  76. package/dist/memory-store-I6Z54VUN.js +51 -0
  77. package/dist/{model-window-store-MTSQ5JDJ.js → model-window-store-6VKXXTPJ.js} +4 -3
  78. package/dist/{openai-compatible-2OB54YST.js → openai-compatible-IPUNBJ53.js} +5 -3
  79. package/dist/{permission-approvals-YLV6UC5W.js → permission-approvals-Q5IMFEZI.js} +5 -4
  80. package/dist/{permissions-DHOKF3W2.js → permissions-7Y35X6HZ.js} +26 -9
  81. package/dist/provider-approvals-5AGH3RVT.js +76 -0
  82. package/dist/{sandbox-WRYTZH3D.js → sandbox-N2RY72GN.js} +6 -5
  83. package/dist/{scrollback-35ME2GFX.js → scrollback-GD76GNM4.js} +36 -28
  84. package/dist/scrollback-GD76GNM4.js.map +1 -0
  85. package/dist/sdk.d.ts +876 -64
  86. package/dist/sdk.js +57 -37
  87. package/dist/sdk.js.map +1 -1
  88. package/dist/settings-loader-KDBH45AQ.js +43 -0
  89. package/dist/{settings-removed-JWWPT4WA.js → settings-removed-3ATWJEF3.js} +2 -2
  90. package/dist/shell-selection-Z5WORZU4.js +31 -0
  91. package/dist/shell-selection-Z5WORZU4.js.map +1 -0
  92. package/package.json +5 -5
  93. package/dist/app-GE4OHJOC.js.map +0 -1
  94. package/dist/chunk-224FJZLH.js.map +0 -1
  95. package/dist/chunk-5RDRAO4B.js.map +0 -1
  96. package/dist/chunk-5YHTKWJN.js.map +0 -1
  97. package/dist/chunk-62QL5QUP.js.map +0 -1
  98. package/dist/chunk-67UZEWWF.js.map +0 -1
  99. package/dist/chunk-6OQQOMZR.js +0 -785
  100. package/dist/chunk-6OQQOMZR.js.map +0 -1
  101. package/dist/chunk-AQEQX4FF.js.map +0 -1
  102. package/dist/chunk-C7ADUNPR.js +0 -273
  103. package/dist/chunk-C7ADUNPR.js.map +0 -1
  104. package/dist/chunk-EFJ2NH6E.js.map +0 -1
  105. package/dist/chunk-IME7R2UI.js.map +0 -1
  106. package/dist/chunk-J62VZYOB.js +0 -1339
  107. package/dist/chunk-J62VZYOB.js.map +0 -1
  108. package/dist/chunk-JTPOI3KU.js.map +0 -1
  109. package/dist/chunk-QYBD22H5.js.map +0 -1
  110. package/dist/chunk-RFJ6Q6LE.js.map +0 -1
  111. package/dist/chunk-S4JMID4G.js +0 -259
  112. package/dist/chunk-S4JMID4G.js.map +0 -1
  113. package/dist/chunk-TLX3H2CL.js.map +0 -1
  114. package/dist/chunk-VPNLCMVR.js.map +0 -1
  115. package/dist/chunk-W3XFHEF4.js.map +0 -1
  116. package/dist/chunk-X4NJT436.js.map +0 -1
  117. package/dist/chunk-X66I2NQO.js +0 -137
  118. package/dist/chunk-X66I2NQO.js.map +0 -1
  119. package/dist/chunk-Y2VHN7TC.js.map +0 -1
  120. package/dist/chunk-YC6W4EJH.js.map +0 -1
  121. package/dist/context-CBOBDY5Q.js +0 -38
  122. package/dist/loader-S77OVTHH.js +0 -18
  123. package/dist/memory-store-DZUT5HUH.js +0 -75
  124. package/dist/scrollback-35ME2GFX.js.map +0 -1
  125. package/dist/settings-loader-JUNPGAAM.js +0 -35
  126. package/dist/shell-selection-CDHGJ4GC.js +0 -17
  127. /package/dist/{additional-roots-H7UNAEHS.js.map → additional-roots-VAWRQ625.js.map} +0 -0
  128. /package/dist/{chunk-525CAZPY.js.map → chunk-4DKHLZRI.js.map} +0 -0
  129. /package/dist/{chunk-4AS7LKKQ.js.map → chunk-62EEAUID.js.map} +0 -0
  130. /package/dist/{chunk-KBQS35OV.js.map → chunk-MV5PQNIA.js.map} +0 -0
  131. /package/dist/{chunk-56SIEMDX.js.map → chunk-PTDC7ZUP.js.map} +0 -0
  132. /package/dist/{chunk-HQI7NYGX.js.map → chunk-SWPDKLNM.js.map} +0 -0
  133. /package/dist/{chunk-SOW4WRQP.js.map → chunk-WV3O5PGX.js.map} +0 -0
  134. /package/dist/{command-approvals-VBIU6BLM.js.map → command-approvals-ZRY66LCW.js.map} +0 -0
  135. /package/dist/{context-CBOBDY5Q.js.map → context-C3RAP6K5.js.map} +0 -0
  136. /package/dist/{hook-approvals-ZVT4V7IM.js.map → hook-approvals-DPQD5H65.js.map} +0 -0
  137. /package/dist/{loader-S77OVTHH.js.map → loader-DQNPDKOX.js.map} +0 -0
  138. /package/dist/{mcp-approvals-HJ2VDYMC.js.map → mcp-approvals-27LJYADQ.js.map} +0 -0
  139. /package/dist/{mcp-config-RNB5LEVE.js.map → mcp-config-GXSODNCM.js.map} +0 -0
  140. /package/dist/{memory-store-DZUT5HUH.js.map → memory-store-I6Z54VUN.js.map} +0 -0
  141. /package/dist/{model-window-store-MTSQ5JDJ.js.map → model-window-store-6VKXXTPJ.js.map} +0 -0
  142. /package/dist/{openai-compatible-2OB54YST.js.map → openai-compatible-IPUNBJ53.js.map} +0 -0
  143. /package/dist/{permission-approvals-YLV6UC5W.js.map → permission-approvals-Q5IMFEZI.js.map} +0 -0
  144. /package/dist/{permissions-DHOKF3W2.js.map → permissions-7Y35X6HZ.js.map} +0 -0
  145. /package/dist/{sandbox-WRYTZH3D.js.map → provider-approvals-5AGH3RVT.js.map} +0 -0
  146. /package/dist/{settings-loader-JUNPGAAM.js.map → sandbox-N2RY72GN.js.map} +0 -0
  147. /package/dist/{settings-removed-JWWPT4WA.js.map → settings-loader-KDBH45AQ.js.map} +0 -0
  148. /package/dist/{shell-selection-CDHGJ4GC.js.map → settings-removed-3ATWJEF3.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/settings-loader.ts","../src/env-boolean.ts"],"sourcesContent":["import { readFileSync, existsSync } from 'fs';\nimport { join, normalize } from 'path';\nimport {\n bookSettingsSchema,\n DEFAULT_SETTINGS,\n HOOK_EVENTS,\n type BookSettings,\n type HookEntry,\n type HookEvent,\n type ResolvedSettings,\n} from './settings.js';\nimport { SettingsRepository, writeFileAtomic } from './settings-repository.js';\nimport { resolveBookHome } from './book-home.js';\nimport { collectDeclaredDirectories, partitionProjectDirectories } from './additional-roots.js';\nimport { partitionProjectAllowRules } from './permission-approvals.js';\nimport { collectDeclaredHooks, partitionProjectHooks } from './hook-approvals.js';\nimport { defaultTrustStorePath, loadWorkspaceTrust } from './workspace-trust.js';\nimport { normalizeRemovedSettings } from './settings-removed.js';\nimport { parseEnvBoolean } from './env-boolean.js';\n\nconst LEGACY_PERMISSIONS_MIGRATION_VERSION = 1;\n\n/**\n * The sandbox deny lists: paths a command may not write and paths it may not\n * read, plus the domains it may not reach.\n *\n * One list for all three, because they are one rule applied to three keys — and\n * because the rule is no longer \"these keys concatenate\" but \"these keys\n * concatenate *for a workspace layer*\", which both the concatenating set and the\n * deduping one are derived from. A second hand-written copy is how\n * `Notification` went missing from the hooks list in the first place (#295).\n */\nconst SANDBOX_DENY_LIST_PATHS = [\n 'sandbox.filesystem.denyWrite',\n 'sandbox.filesystem.denyRead',\n 'sandbox.network.deniedDomains',\n] as const;\n\n/**\n * Deep-merge two settings objects. For arrays, concatenate (used for\n * permission rules and additionalDirectories). For objects, merge recursively.\n * For scalars, the override wins.\n */\nconst CONCATENATED_ARRAY_PATHS = new Set<string>([\n 'permissions.allow',\n 'permissions.ask',\n 'permissions.deny',\n // Straight from the list the hooks schema is built from: a hand-written copy of\n // it went stale the moment `Notification` was added, and a later layer's\n // notification hooks then replaced the user layer's instead of appending (#295).\n ...HOOK_EVENTS.map((event) => `hooks.${event}`),\n // A later layer adds to the deny lists rather than replacing them, so a\n // workspace layer shipping `denyRead: []` cannot erase the user's entries\n // (#373) — and a later *trusted* layer still can, which is the one way to\n // narrow a list that turned out to be too broad.\n ...SANDBOX_DENY_LIST_PATHS,\n]);\n\n/**\n * The same three paths as a set, for the two questions a merge asks about one of\n * them: whether the layer's entries add to or replace the earlier ones, and\n * whether repeating a path across layers stores it once.\n */\nconst SANDBOX_DENY_LIST_PATH_SET: ReadonlySet<string> = new Set(SANDBOX_DENY_LIST_PATHS);\n\nfunction mergeObject(\n base: Record<string, unknown>,\n override: Record<string, unknown>,\n /** Whether a deny list from this layer adds to the earlier one or replaces it. */\n accumulateDenyLists: boolean,\n prefix = '',\n): Record<string, unknown> {\n const result = structuredClone(base);\n\n for (const [key, value] of Object.entries(override)) {\n // JSON.parse keeps `\"__proto__\"` as an ordinary own key, but `result[key] = …`\n // below would go through the prototype setter: a repository layer could then\n // supply inherited `shell` or `defaultMode` values that sanitizeLayer, which\n // deletes own keys, never sees. No setting has that name, so drop it.\n if (value === undefined || key === '__proto__') continue;\n const path = prefix ? `${prefix}.${key}` : key;\n const existing = result[key];\n\n if (Array.isArray(value)) {\n if (path === 'additionalDirectories') {\n const combined = [...(Array.isArray(existing) ? existing : []), ...value].map((entry) =>\n normalize(String(entry)),\n );\n result[key] = [...new Set(combined)];\n } else if (\n CONCATENATED_ARRAY_PATHS.has(path) &&\n (accumulateDenyLists || !SANDBOX_DENY_LIST_PATH_SET.has(path))\n ) {\n const combined = [...(Array.isArray(existing) ? existing : []), ...value];\n result[key] = SANDBOX_DENY_LIST_PATH_SET.has(path) ? [...new Set(combined)] : combined;\n } else {\n result[key] = structuredClone(value);\n }\n } else if (\n typeof value === 'object' &&\n value !== null &&\n typeof existing === 'object' &&\n existing !== null &&\n !Array.isArray(existing)\n ) {\n result[key] = mergeObject(\n existing as Record<string, unknown>,\n value as Record<string, unknown>,\n accumulateDenyLists,\n path,\n );\n } else {\n result[key] = value;\n }\n }\n\n return result;\n}\n\n/**\n * How much authority a settings layer carries.\n *\n * - `trusted` — the user's own global file, or a path they passed on the CLI.\n * - `local` — `<workspace>/.book/settings.local.json`: gitignored and normally\n * written by the user, or by Book on their behalf.\n * - `repository` — `<workspace>/.book/settings.json`: checked in and controlled\n * by whoever wrote the repository. It may never grant itself trust.\n */\nexport type SettingsLayerTrust = 'trusted' | 'local' | 'repository';\n\n/**\n * Settings paths that a workspace file may not supply for itself. Most record\n * a decision the user made *about* repository-controlled input; honouring one\n * from the workspace would let a clone approve itself. Experimental capability\n * flags are included because merely opening a clone must not opt the user into\n * unstable runtime behavior.\n *\n * The local layer is no safer than the checked-in one here. `.gitignore` does\n * not stop a *tracked* file from reaching a clone, so a repository that\n * force-adds `.book/settings.local.json` ships its own approvals with it. Both\n * workspace layers are therefore stripped. Trust decisions come from the\n * user-global store; experimental flags come from a trusted user-global or\n * explicit settings document (or from a process environment opt-in).\n *\n * An empty `parent` names a key at the document root, which is how the\n * `additionalDirectories` decisions map is expressed: the setting it decides\n * about is itself top-level, so there is no container object to put it in.\n */\nconst WORKSPACE_FORBIDDEN_PATHS: ReadonlyArray<readonly [string, string]> = [\n ['mcp', 'projectServers'],\n ['permissions', 'projectAllowRules'],\n ['hooks', 'projectEntries'],\n ['commands', 'projectCommands'],\n ['', 'projectDirectories'],\n];\n\nfunction stripPaths(\n settings: Partial<BookSettings>,\n paths: ReadonlyArray<readonly [string, string]>,\n): void {\n for (const [parent, key] of paths) {\n if (parent === '') {\n delete (settings as Record<string, unknown>)[key];\n continue;\n }\n const container = (settings as Record<string, unknown>)[parent];\n if (container && typeof container === 'object' && !Array.isArray(container)) {\n delete (container as Record<string, unknown>)[key];\n }\n }\n}\n\n/** One `sandbox.*` key a workspace layer supplied that the loader will not honour. */\nexport interface IgnoredWorkspaceSandboxKey {\n /** Dotted path of the key, e.g. `sandbox.filesystem.allowWrite`. */\n key: string;\n /** The value the layer wrote, so a report can quote what the file actually said. */\n value: unknown;\n}\n\n/**\n * `sandbox.*` keys a workspace layer supplies that only loosen, and therefore\n * never survive to the merge. The second element is the one value the layer may\n * set — the tightening direction — or `null` when any value is dropped.\n *\n * The sandbox is the boundary between a command and the host, so a file inside\n * the workspace must not be able to widen it: a checked-in `settings.json`\n * could switch off the sandbox the user enabled, exclude every command\n * (`excludedCommands: [\"*\"]`), or open a writable root (`allowWrite: [\"/\"]`)\n * — and because those arrays *replaced* the earlier layer's, even the user's own\n * `denyRead` list could be emptied. Turning the sandbox on, adding deny entries\n * and refusing unsandboxed commands stay available to a workspace layer; they\n * only ever narrow it (#373).\n */\nconst WORKSPACE_SANDBOX_ONLY_TIGHTENING: ReadonlyArray<readonly [readonly string[], unknown]> = [\n [['sandbox', 'enabled'], true],\n [['sandbox', 'failIfUnavailable'], true],\n [['sandbox', 'allowUnsandboxedCommands'], false],\n [['sandbox', 'autoAllowBashIfSandboxed'], false],\n [['sandbox', 'excludedCommands'], null],\n [['sandbox', 'filesystem', 'allowWrite'], null],\n [['sandbox', 'network', 'allowedDomains'], null],\n];\n\n/**\n * The value at a path in a settings document, or `undefined` when a step is\n * missing or is not a plain object.\n *\n * One walker rather than two near-copies: `readContainer` used to be this with an\n * extra condition at the end, and a loop that differs only in its last line is\n * how two helpers drift apart and a delete lands on the wrong object.\n */\nfunction readPath(settings: Record<string, unknown>, path: readonly string[]): unknown {\n let value: unknown = settings;\n for (const key of path) {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n value = (value as Record<string, unknown>)[key];\n if (value === undefined) return undefined;\n }\n return value;\n}\n\nfunction deletePath(settings: Record<string, unknown>, path: readonly string[]): void {\n const parent = path.slice(0, -1);\n const key = path[path.length - 1];\n const container = parent.length === 0 ? settings : readObjectPath(settings, parent);\n if (container) delete container[key];\n}\n\n/** `readPath` for a path that has to end at an object, which `deletePath` needs. */\nfunction readObjectPath(\n settings: Record<string, unknown>,\n path: readonly string[],\n): Record<string, unknown> | undefined {\n const value = readPath(settings, path);\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\n/**\n * A workspace layer as the file wrote it: parsed, but not required to be\n * complete. The sandbox rule is about what a document *says*, so the reader\n * takes a partial view — a layer that omits `excludedCommands` entirely is\n * describing itself, not supplying an empty list. Every key is optional here,\n * including the nested ones, which is the whole point of the type.\n */\nexport interface WorkspaceLayerSandboxView {\n sandbox?: Omit<Partial<BookSettings['sandbox']>, 'filesystem' | 'network'> & {\n filesystem?: Partial<BookSettings['sandbox']['filesystem']>;\n network?: Partial<BookSettings['sandbox']['network']>;\n };\n}\n\n/**\n * The `sandbox.*` keys `settings` declares that a workspace layer may not\n * supply, with the values it wrote. Pure: `sanitizeLayer` deletes exactly the\n * keys this reports, and `book doctor` prints the same list, so the enforced\n * rule and the reported one cannot drift apart.\n */\nexport function ignoredWorkspaceSandboxKeys(\n settings: WorkspaceLayerSandboxView,\n): IgnoredWorkspaceSandboxKey[] {\n const record = settings as Record<string, unknown>;\n const ignored: IgnoredWorkspaceSandboxKey[] = [];\n for (const [path, tighteningValue] of WORKSPACE_SANDBOX_ONLY_TIGHTENING) {\n const value = readPath(record, path);\n if (value === undefined) continue;\n if (tighteningValue !== null && value === tighteningValue) continue;\n ignored.push({ key: path.join('.'), value });\n }\n return ignored;\n}\n\n/**\n * How much of an ignored value a report prints.\n *\n * `excludedCommands` and `allowWrite` are globs and directory lists, so a\n * repository can put as much text in one as it likes, and every host that\n * reports an ignored key would then print a screenful of it — pushing the part\n * of the diagnostic that says where the value belongs off the screen. The head\n * is kept because it identifies the value; the length is what follows it.\n */\nconst IGNORED_VALUE_PREVIEW_CHARS = 200;\n\n/** One ignored key as a report line reads it: `sandbox.enabled=false`. */\nexport function formatIgnoredWorkspaceSandboxKey(entry: IgnoredWorkspaceSandboxKey): string {\n const json = JSON.stringify(entry.value) ?? String(entry.value);\n if (json.length <= IGNORED_VALUE_PREVIEW_CHARS) return `${entry.key}=${json}`;\n return `${entry.key}=${json.slice(0, IGNORED_VALUE_PREVIEW_CHARS)}… (truncated, ${\n json.length - IGNORED_VALUE_PREVIEW_CHARS\n } more characters)`;\n}\n\n/**\n * Drop every loosening `sandbox.*` key from a workspace layer. Deleting rather\n * than overwriting is what makes the key harmless: the merge keeps whatever the\n * earlier, more trusted layer said.\n */\nfunction applyIgnoredWorkspaceSandboxKeys(settings: Partial<BookSettings>): void {\n const record = settings as Record<string, unknown>;\n for (const entry of ignoredWorkspaceSandboxKeys(settings)) {\n deletePath(record, entry.key.split('.'));\n }\n}\n\nfunction sanitizeLayer(\n settings: Partial<BookSettings>,\n trust: SettingsLayerTrust,\n): Partial<BookSettings> {\n if (trust === 'trusted') return settings;\n const sanitized = structuredClone(settings);\n // Project/local settings cannot opt a session into the most permissive mode.\n if (sanitized.defaultMode === 'bypassPermissions') delete sanitized.defaultMode;\n // `shell` names the program every Bash command is handed to. A clone that\n // could point it at a binary it ships would run that binary on the first\n // command, so the key is honoured from trusted layers only.\n delete sanitized.shell;\n // Nor may a workspace file widen the sandbox it is supposed to be confined by.\n applyIgnoredWorkspaceSandboxKeys(sanitized);\n stripPaths(sanitized, WORKSPACE_FORBIDDEN_PATHS);\n return sanitized;\n}\n\n/**\n * Merge one layer over the resolved settings.\n *\n * The layer's trust decides two things that are not visible in the layer itself:\n * whether a deny list adds to or replaces the earlier one, and whether turning\n * the sandbox on costs the session its auto-allow. Both are stated here, next to\n * the sanitizing that already reads `trust`, rather than left to each caller.\n */\nfunction mergeLayer(\n resolved: ResolvedSettings,\n layer: Partial<BookSettings>,\n trust: SettingsLayerTrust,\n): ResolvedSettings {\n const candidate = sanitizeLayer(layer, trust);\n // A trusted global safety ceiling cannot be disabled by a lower-trust layer.\n if (\n resolved.disableBypassPermissionsMode === true &&\n candidate.disableBypassPermissionsMode === false\n ) {\n candidate.disableBypassPermissionsMode = true;\n }\n if (trust !== 'trusted' && sandboxTurnedOnByWorkspaceLayer(resolved, candidate)) {\n // Written through the partial view the sanitizer uses: `BookSettings`\n // declares every sandbox key as present, and this layer supplies one.\n const tightened: WorkspaceLayerSandboxView = {\n sandbox: { ...candidate.sandbox, autoAllowBashIfSandboxed: false },\n };\n (candidate as WorkspaceLayerSandboxView).sandbox = tightened.sandbox;\n }\n return mergeSettings(resolved, candidate, trust);\n}\n\n/**\n * Whether this workspace layer is what turns the sandbox on.\n *\n * `enabled: true` is the one `sandbox` key a workspace layer may set, because it\n * only ever applies to sessions that had it off. On its own it narrows nothing a\n * user chose; with `autoAllowBashIfSandboxed` it stops the session from asking\n * before each command, so a checked-in `settings.json` would turn a workspace\n * where the user reads every command into one where nobody is asked at all.\n * Setting it here makes the pair equivalent to the auto-allow alone, which the\n * loader already drops.\n */\nfunction sandboxTurnedOnByWorkspaceLayer(\n resolved: ResolvedSettings,\n candidate: Partial<BookSettings>,\n): boolean {\n return candidate.sandbox?.enabled === true && resolved.sandbox?.enabled !== true;\n}\n\n/** One layer as a caller of {@link workspaceLayerThatEnabledSandbox} has it. */\nexport interface SettingsLayerForSandboxActivation {\n path: string;\n trust: SettingsLayerTrust;\n /** Null when the file is absent, which is not a layer that declared anything. */\n document: Partial<BookSettings> | null;\n}\n\n/**\n * The workspace layer that flipped `sandbox.enabled` from not-true to true, or\n * undefined when none did: the file whose `enabled: true` cost the session its\n * `autoAllowBashIfSandboxed`.\n *\n * Two hosts have to say so — `book doctor`, which lists what each layer changed,\n * and the print/SDK notices, which have no other channel — and both were asking\n * the raw layers \"does this one say `enabled: true`\". That is a different\n * question: once a trusted layer has enabled the sandbox, a repository repeating\n * the key changes nothing and was still reported, every session, as a decision it\n * did not make.\n *\n * The layers go in resolution order *including* the trusted ones, because the\n * trusted baseline is exactly what decides whether a workspace layer is the\n * flipper. The walk is this function's own and reads only `sandbox.enabled`, and\n * it asks the same question through the same {@link sanitizeLayer} the merge\n * applies, so it cannot disagree with\n * {@link sandboxTurnedOnByWorkspaceLayer} about which layer that is.\n */\nexport function workspaceLayerThatEnabledSandbox(\n layers: readonly SettingsLayerForSandboxActivation[],\n): string | undefined {\n let enabled = DEFAULT_SETTINGS.sandbox.enabled;\n for (const layer of layers) {\n if (!layer.document) continue;\n const candidate = sanitizeLayer(layer.document, layer.trust);\n if (layer.trust !== 'trusted' && candidate.sandbox?.enabled === true && enabled !== true) {\n return layer.path;\n }\n if (candidate.sandbox?.enabled !== undefined) enabled = candidate.sandbox.enabled;\n }\n return undefined;\n}\n\n/**\n * Merge a settings layer over resolved settings.\n *\n * @param trust - what the layer is (`trusted` by default: the user-global file\n * or a `--settings` path). Only a workspace layer accumulates the sandbox deny\n * lists; every other array behaviour is the same for all layers.\n */\nexport function mergeSettings(\n base: ResolvedSettings,\n override: Partial<BookSettings>,\n trust: SettingsLayerTrust = 'trusted',\n): ResolvedSettings {\n return mergeObject(\n base as unknown as Record<string, unknown>,\n override as Record<string, unknown>,\n trust !== 'trusted',\n ) as unknown as ResolvedSettings;\n}\n\n/**\n * Load and validate a single settings.json file. Returns null if the file\n * doesn't exist. Throws on parse/validation errors.\n */\nfunction loadSettingsFile(path: string): BookSettings | null {\n if (!existsSync(path)) return null;\n const raw = readFileSync(path, 'utf-8');\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (e) {\n throw new Error(\n `Invalid JSON in settings file: ${path}\\n${e instanceof Error ? e.message : String(e)}`,\n );\n }\n // A setting that was removed must not brick a working install: an obsolete\n // value of a surviving key would otherwise fail the whole document and stop\n // Book from starting, with a schema dump that names no remedy. Removed keys\n // are reported by `book doctor`, which reads the file itself.\n parsed = normalizeRemovedSettings(parsed);\n const result = bookSettingsSchema.safeParse(parsed);\n if (!result.success) throw new Error(`Invalid settings in ${path}:\\n${result.error.message}`);\n // Resolution owns defaults. Returning the source document preserves the distinction\n // between an omitted field and a field explicitly set to an empty collection.\n return parsed as BookSettings;\n}\n\n/**\n * Resolve settings from all scopes: user → project → local.\n * Priority: Local > Project > User.\n *\n * @param workspace - Project root directory\n * @param overridePath - Optional path to an ad-hoc settings file (--settings flag)\n * @returns Fully resolved settings with all defaults filled\n */\nexport interface SettingsResolutionPaths {\n home?: string;\n userSettingsPath?: string;\n projectSettingsPath?: string;\n localSettingsPath?: string;\n trustStorePath?: string;\n}\n\n/**\n * Every settings file this resolution would read, in layer order.\n *\n * Exported so callers that need to inspect the files themselves — `book doctor`\n * reporting removed keys, the credential error checking for a stale `auth`\n * block — resolve the same paths the loader does rather than rebuilding them.\n */\nexport function settingsLayerPaths(\n workspace: string,\n overridePath?: string,\n paths: SettingsResolutionPaths = {},\n): string[] {\n const userPath =\n paths.userSettingsPath ??\n (paths.home\n ? join(paths.home, '.book', 'settings.json')\n : join(resolveBookHome(), 'settings.json'));\n const layers = [\n userPath,\n paths.projectSettingsPath ?? join(workspace, '.book', 'settings.json'),\n paths.localSettingsPath ?? join(workspace, '.book', 'settings.local.json'),\n ];\n if (overridePath) layers.push(overridePath);\n return layers;\n}\n\nexport function resolveSettings(\n workspace: string,\n overridePath?: string,\n paths: SettingsResolutionPaths = {},\n): ResolvedSettings {\n let resolved = structuredClone(DEFAULT_SETTINGS);\n\n // Layer 1: User settings (~/.book/settings.json)\n const userPath =\n paths.userSettingsPath ??\n (paths.home\n ? join(paths.home, '.book', 'settings.json')\n : join(resolveBookHome(), 'settings.json'));\n const user = loadSettingsFile(userPath);\n if (user) resolved = mergeLayer(resolved, user, 'trusted');\n\n // Layer 2: Project settings (<workspace>/.book/settings.json)\n const projectPath = paths.projectSettingsPath ?? join(workspace, '.book', 'settings.json');\n const project = loadSettingsFile(projectPath);\n // `allow` rules from the repository layer are held back rather than merged:\n // they only widen authority, and the decisions that release them live in the\n // local layer, which has not been merged yet.\n const declaredProjectAllow = project?.permissions?.allow ?? [];\n // Project-declared hook entries are held back the same way: each one is a\n // shell command the repository would otherwise get Book to run.\n const declaredProjectHooks = collectDeclaredHooks(project);\n // `additionalDirectories` widens the roots the file tools serve, so a checked-in\n // declaration is held back for the same reason a project allow rule is: it\n // grants authority, and the decisions that release it live in the local layer,\n // which has not been merged yet. Released after the trust store loads, by real path.\n const declaredProjectDirectories = project?.additionalDirectories ?? [];\n // Released declarations belong between the user and local layers, matching the\n // relative merge order ungated layers still produce.\n const userHookCounts = Object.fromEntries(\n HOOK_EVENTS.map((event) => [event, resolved.hooks[event].length]),\n ) as Record<HookEvent, number>;\n if (project) {\n let withheld: BookSettings = project;\n if (declaredProjectAllow.length > 0) {\n withheld = { ...withheld, permissions: { ...withheld.permissions, allow: [] } };\n }\n if (declaredProjectDirectories.length > 0) {\n withheld = { ...withheld, additionalDirectories: [] };\n }\n if (declaredProjectHooks.length > 0) {\n withheld = { ...withheld, hooks: structuredClone(DEFAULT_SETTINGS.hooks) };\n }\n resolved = mergeLayer(resolved, withheld, 'repository');\n }\n\n // Layer 3: Local settings (<workspace>/.book/settings.local.json)\n const localPath = paths.localSettingsPath ?? join(workspace, '.book', 'settings.local.json');\n const local = loadSettingsFile(localPath);\n if (local) resolved = mergeLayer(resolved, local, 'local');\n\n // Layer 4 (optional): Ad-hoc override (--settings flag)\n if (overridePath) {\n const override = loadSettingsFile(overridePath);\n if (override) resolved = mergeLayer(resolved, override, 'trusted');\n }\n\n // Trust decisions come from outside the workspace, so a repository cannot\n // ship its own. Layers the user controls may still carry them — the global\n // file and an explicit `--settings` path are trusted for everything else —\n // but the store has the final say on any key it records.\n const trust = loadWorkspaceTrust(\n workspace,\n paths.trustStorePath ?? defaultTrustStorePath(paths.home),\n );\n resolved.permissions.projectAllowRules = {\n ...resolved.permissions.projectAllowRules,\n ...trust.permissionAllowRules,\n };\n resolved.mcp.projectServers = { ...resolved.mcp.projectServers, ...trust.mcpServers };\n resolved.hooks.projectEntries = { ...resolved.hooks.projectEntries, ...trust.hookEntries };\n resolved.commands.projectCommands = {\n ...resolved.commands.projectCommands,\n ...trust.projectCommands,\n };\n resolved.projectDirectories = {\n ...resolved.projectDirectories,\n ...trust.projectDirectories,\n };\n\n // Every decision source is in place now, so the withheld repository rules can\n // be released — approved ones only.\n if (declaredProjectAllow.length > 0) {\n const { approved } = partitionProjectAllowRules(\n declaredProjectAllow,\n resolved.permissions?.projectAllowRules,\n );\n if (approved.length > 0) {\n resolved.permissions.allow = [...(resolved.permissions.allow ?? []), ...approved];\n }\n }\n\n if (declaredProjectDirectories.length > 0) {\n // Released by real path, not by the text the repository wrote: see `additional-roots.ts`.\n // Approved entries join the resolved list as their declared text, which is what the merge\n // above normalizes and deduplicates; the file tools resolve it again to the same place.\n const { approved } = partitionProjectDirectories(\n collectDeclaredDirectories(workspace, declaredProjectDirectories),\n resolved.projectDirectories,\n );\n if (approved.length > 0) {\n // The approved **real path**, never the declared text. A consumer resolves this list again\n // later with no trust store in hand (see `resolveAdditionalRoots`), so a relative or\n // symlinked spelling would let a relink move a root the user approved as somewhere else.\n // The user saw the real path when the prompt named it; the repository's spelling is what\n // would be dangerous to re-resolve (PR #334 finding 7).\n resolved.additionalDirectories = [\n ...(resolved.additionalDirectories ?? []),\n ...approved.map((directory) => directory.realPath),\n ];\n }\n }\n\n if (declaredProjectHooks.length > 0) {\n const { approved } = partitionProjectHooks(\n declaredProjectHooks,\n resolved.hooks?.projectEntries,\n );\n const approvedByEvent = new Map<HookEvent, HookEntry[]>();\n for (const { event, entry } of approved) {\n const entries = approvedByEvent.get(event) ?? [];\n entries.push(entry);\n approvedByEvent.set(event, entries);\n }\n for (const [event, entries] of approvedByEvent) {\n resolved.hooks[event].splice(userHookCounts[event], 0, ...entries);\n }\n }\n\n const settings = bookSettingsSchema.parse(resolved) as ResolvedSettings;\n return settings;\n}\n\n/**\n * The environment's say over the merged layers, applied to settings that are\n * about to be used rather than saved.\n *\n * `BOOK_STARTUP_ANIMATION` is here, and not in `loadConfig`, because the layers\n * are only half the answer: a reader that resolved the files and stopped would\n * report the file's value while Book acts on the variable's. So every path that\n * produces the *effective* settings calls this — the startup config, the\n * re-read after a provider is removed, `book config get`/`list`. A path that\n * computes what gets written back to a settings file must not: the variable\n * decides the next launch, and baking it into a file would make a later\n * `unset` look like it had failed.\n *\n * The returned object is a new one; the input is left as the caller resolved it.\n */\nexport function applySettingsEnvOverrides(\n settings: ResolvedSettings,\n env: NodeJS.ProcessEnv = process.env,\n): ResolvedSettings {\n const startupAnimation = parseEnvBoolean(env.BOOK_STARTUP_ANIMATION);\n if (startupAnimation === undefined) return settings;\n return { ...settings, ui: { ...settings.ui, startupAnimation } };\n}\n\n/**\n * What to tell a user whose read or save is being decided by the environment\n * rather than by their settings — the `BOOK_MODEL` warning in one sentence, or\n * nothing when the variable is unset or unreadable.\n */\nexport function startupAnimationEnvNote(env: NodeJS.ProcessEnv = process.env): string | undefined {\n const raw = env.BOOK_STARTUP_ANIMATION;\n if (parseEnvBoolean(raw) === undefined) return undefined;\n return (\n `BOOK_STARTUP_ANIMATION is set to \"${raw}\" — it decides the splash at every ` +\n 'launch, so ui.startupAnimation will not apply while it is set.'\n );\n}\n\nexport { loadSettingsFile };\n\n/**\n * Migrate rules from the legacy ~/.book/permissions.json into the local\n * settings file (<workspace>/.book/settings.local.json). Runs once on first\n * load when the legacy file exists and the local settings don't have rules yet.\n *\n * @param home - User home directory; injectable for isolated migration tests\n * @param validatedSettings - Already-resolved settings from the startup preflight\n * @returns true if migration occurred, false otherwise\n */\nexport function migrateLegacyPermissions(\n workspace: string,\n home?: string,\n validatedSettings?: ResolvedSettings,\n): boolean {\n // Direct callers must observe the same fail-before-storage boundary as the\n // normal startup path. Internal callers pass already-resolved settings to\n // avoid reading the layers twice on the common off path.\n if (!validatedSettings) {\n resolveSettings(workspace, undefined, home ? { home } : undefined);\n }\n\n const legacyPath = join(home ? join(home, '.book') : resolveBookHome(), 'permissions.json');\n if (!existsSync(legacyPath)) return false;\n const markerPath = join(workspace, '.book', 'migrations.json');\n try {\n const marker = JSON.parse(readFileSync(markerPath, 'utf-8')) as {\n legacyPermissions?: number;\n };\n if ((marker.legacyPermissions ?? 0) >= LEGACY_PERMISSIONS_MIGRATION_VERSION) return false;\n } catch {\n // Missing or malformed markers are safely rebuilt after a successful migration.\n }\n\n let legacyRules: Array<{ toolName: string; pattern?: string; effect: string }> = [];\n try {\n const raw = readFileSync(legacyPath, 'utf-8');\n const parsed = JSON.parse(raw) as {\n rules: Array<{ toolName: string; pattern?: string; effect: string }>;\n };\n legacyRules = parsed.rules ?? [];\n } catch {\n return false; // corrupt — leave the legacy file alone\n }\n\n if (legacyRules.length === 0) return false;\n\n const localDir = join(workspace, '.book');\n const localPath = join(localDir, 'settings.local.json');\n const result = new SettingsRepository(localPath).update((existing) => {\n const permissions = (existing.permissions ?? {}) as {\n allow?: string[];\n ask?: string[];\n deny?: string[];\n };\n for (const key of ['allow', 'ask', 'deny'] as const) {\n if (!Array.isArray(permissions[key])) permissions[key] = [];\n }\n\n for (const rule of legacyRules) {\n const specifier = rule.pattern ? `${rule.toolName}(${rule.pattern})` : rule.toolName;\n const effect = rule.effect as 'allow' | 'ask' | 'deny';\n if (!permissions[effect]!.includes(specifier)) permissions[effect]!.push(specifier);\n }\n existing.permissions = permissions;\n });\n if (!result.ok) return false;\n\n writeFileAtomic(\n markerPath,\n `${JSON.stringify({ legacyPermissions: LEGACY_PERMISSIONS_MIGRATION_VERSION }, null, 2)}\\n`,\n );\n if (!result.changed) return false;\n\n console.warn(\n `⚠ Migrated ${legacyRules.length} permission rule(s) from ~/.book/permissions.json to ${localPath}. ` +\n 'Delete the legacy file after verifying the migration.',\n );\n return true;\n}\n","/**\n * The one grammar for a boolean environment variable: `1`/`true`/`on`/`yes` and\n * `0`/`false`/`off`/`no`, case-insensitive and trimmed, and undefined for\n * anything else.\n *\n * Tri-state on purpose. A flag that reads \"unset\" as \"off\" cannot tell a caller\n * that wants the default from one that was never told, and every such caller\n * then hard-codes a second grammar beside it. `BOOK_STARTUP_ANIMATION` needs all\n * three: it has to outrank a settings file when set, leave that file alone when\n * not, and say nothing rather than guess when the value is a typo — a wrong guess\n * either delays the first render or hides the input bar a script is waiting for.\n */\nexport function parseEnvBoolean(raw: string | undefined): boolean | undefined {\n switch (raw?.trim().toLowerCase()) {\n case '0':\n case 'false':\n case 'off':\n case 'no':\n return false;\n case '1':\n case 'true':\n case 'on':\n case 'yes':\n return true;\n default:\n return undefined;\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/settings.ts","../src/cli/utils.ts","../src/settings-repository.ts","../src/settings-redaction.ts"],"sourcesContent":["import { z } from 'zod';\n\n/**\n * Permission rule: \"Tool\" or \"Tool(specifier)\" where specifier is a glob pattern\n * matched against the tool's primary argument (e.g. bash command, file path).\n */\nexport const permissionRuleSchema = z.string().min(1);\n\n// An object schema defaulted to `{}` uses `.prefault({})`, not `.default({})`: Zod 4's `.default()`\n// returns its value without parsing it, so the object's own field defaults would never be applied.\n// Records and arrays keep `.default({})` / `.default([])`, whose values are already final.\n\n/**\n * An integer setting with no ceiling of its own.\n *\n * Zod 4's `.int()` also rejects anything above `Number.MAX_SAFE_INTEGER`, which Zod 3 accepted. A\n * settings file that loaded before the upgrade, such as one holding an \"effectively unlimited\"\n * `maxTurns`, must keep loading: an invalid document stops Book from starting. So these settings\n * check integrality themselves, reporting the same `invalid_type` issue `.int()` does. Bounded\n * ones keep `.int()`, since their `.max()` is far lower.\n */\nexport function unboundedInt() {\n return z.number().check((ctx) => {\n if (!Number.isInteger(ctx.value)) {\n ctx.issues.push({ code: 'invalid_type', expected: 'int', input: ctx.value });\n }\n });\n}\n\n/**\n * Bash sandbox configuration (matches CC's sandbox.* keys).\n */\nexport const sandboxSchema = z.object({\n enabled: z.boolean().default(false),\n failIfUnavailable: z.boolean().default(false),\n autoAllowBashIfSandboxed: z.boolean().default(true),\n excludedCommands: z.array(z.string()).default([]),\n allowUnsandboxedCommands: z.boolean().default(true),\n filesystem: z\n .object({\n allowWrite: z.array(z.string()).default([]),\n denyWrite: z.array(z.string()).default([]),\n denyRead: z.array(z.string()).default([]),\n })\n .prefault({}),\n network: z\n .object({\n allowedDomains: z.array(z.string()).default([]),\n deniedDomains: z.array(z.string()).default([]),\n })\n .prefault({}),\n});\n\n/**\n * Permission configuration (matches CC's permissions key).\n */\n/**\n * Per-rule trust decisions for `permissions.allow` entries declared by the\n * checked-in project layer.\n *\n * Unlike an MCP server, a permission rule *is* its own identity: change the\n * rule and it is a different key, so no fingerprint is needed to detect drift.\n * Decisions are stored per workspace in `.book/settings.local.json`, which the\n * repository cannot write.\n */\nexport const projectAllowRuleChoiceSchema = z.enum(['approved', 'rejected']);\n\nexport type ProjectAllowRuleChoice = z.infer<typeof projectAllowRuleChoiceSchema>;\n\nexport const permissionsSchema = z.object({\n allow: z.array(permissionRuleSchema).default([]),\n ask: z.array(permissionRuleSchema).default([]),\n deny: z.array(permissionRuleSchema).default([]),\n /**\n * Decisions about `allow` rules the project layer declared. A project rule\n * with no `approved` entry here never reaches the resolved allow list.\n */\n projectAllowRules: z.record(z.string(), projectAllowRuleChoiceSchema).default({}),\n});\n\n/**\n * Hook entry: a shell command that runs at a lifecycle event.\n * Matches Claude Code's hook configuration format.\n */\nexport const hookEntrySchema = z.object({\n /** Tool(specifier) pattern to filter which events trigger this hook. */\n matcher: z.string().optional(),\n /** Shell command to run (passed through system shell). */\n command: z.string().min(1),\n /** Extra environment variables for this hook. */\n env: z.record(z.string(), z.string()).default({}),\n});\n\nexport type HookEntry = z.infer<typeof hookEntrySchema>;\n\n/**\n * Per-entry trust decisions for hook entries declared by the checked-in project\n * layer, keyed by a fingerprint of `{ event, matcher, command, env }`.\n *\n * A hook is a shell command Book executes at lifecycle events, so a project\n * hook with no `approved` entry here never reaches the resolved hooks. Unlike\n * an MCP server record, a hook carries no name — the fingerprint is the key.\n */\nexport const projectHookChoiceSchema = z.enum(['approved', 'rejected']);\n\nexport type ProjectHookChoice = z.infer<typeof projectHookChoiceSchema>;\n\n/** Hook events supported by book. */\nexport const HOOK_EVENTS = [\n 'SessionStart',\n 'SessionEnd',\n 'UserPromptSubmit',\n 'PreToolUse',\n 'PostToolUse',\n 'Stop',\n 'PreCompact',\n 'PostCompact',\n 'SubagentStart',\n 'SubagentStop',\n /**\n * Something a human should know about while nobody is watching: a run parked on\n * a rejected credential, a budget crossing its ceiling, a refused spawn because\n * the disk is nearly full. Wire ntfy/Slack/SMS here as an ordinary shell hook.\n *\n * Only `severity: 'alarm'` is meant to wake anyone; everything else is a line to\n * tail. `hooksSchema` is generated from this array, so the schema cost is one\n * entry.\n */\n 'Notification',\n] as const;\n\nexport type HookEvent = (typeof HOOK_EVENTS)[number];\n\n/**\n * Hooks configuration — a map from event name to an array of hook entries.\n */\nexport const hooksSchema = z.object({\n ...(Object.fromEntries(HOOK_EVENTS.map((e) => [e, z.array(hookEntrySchema).default([])])) as {\n [K in HookEvent]: z.ZodDefault<z.ZodArray<typeof hookEntrySchema>>;\n }),\n /**\n * Decisions about hook entries the checked-in project layer declared. An\n * entry whose fingerprint has no `approved` record here never reaches the\n * resolved hooks.\n */\n projectEntries: z.record(z.string(), projectHookChoiceSchema).default({}),\n});\n\nexport type HooksConfig = z.infer<typeof hooksSchema>;\n\n/**\n * Retry configuration schema — mirrors Claude Code's retry tunables.\n */\nexport const retrySettingsSchema = z.object({\n maxAttempts: z.number().int().min(0).max(15).default(10),\n baseDelayMs: z.number().int().min(100).max(60000).default(1000),\n maxDelayMs: z.number().int().min(100).max(300000).default(30000),\n totalBudgetMs: z.number().int().min(0).max(600000).default(0),\n requestTimeoutMs: z.number().int().min(5000).max(600000).default(600000),\n streamStallTimeoutMs: z.number().int().min(5000).max(120000).default(20000),\n /**\n * Stall ceiling while the model is thinking, which is a different regime.\n *\n * `streamStallTimeoutMs` is tuned for a chat: 20s of silence means something\n * broke. But with adaptive thinking on — the default for every Opus and Sonnet\n * model here, at `high` effort unless told otherwise — a long quiet stretch\n * before the first token is the model working, not a fault. Applying the chat\n * timeout to it cancels a healthy request and reports `stream_stall`.\n */\n thinkingStallTimeoutMs: z.number().int().min(10_000).max(1_800_000).default(900_000),\n toolRetries: z.number().int().min(0).max(3).default(1),\n watchdog: z.boolean().default(false),\n /**\n * How many times a turn may be re-sent after a transport fault that left the\n * work intact — a stalled stream, a dropped socket — and how many times an\n * empty completion may be re-asked. The two are counted separately per turn;\n * this only says how many of each a turn gets.\n *\n * `maxAttempts` covers connection setup only; once a 200 response is streaming\n * it is out of scope, so without this a single 20-second provider silence ended\n * the run. Set to 0 to restore that behavior exactly for transport faults. An\n * empty completion still gets one retry, the floor every earlier version spent,\n * so a run does not end on the first blank answer.\n */\n streamReissueAttempts: z.number().int().min(0).max(10).default(3),\n /**\n * Separate allowance for continuing after the provider's output cap. A large\n * generated file legitimately hits the cap on consecutive turns, and sharing\n * the transport budget would leave nothing for a real socket drop afterwards.\n */\n outputCapContinuations: z.number().int().min(0).max(50).default(10),\n});\n\nexport type RetrySettings = z.infer<typeof retrySettingsSchema>;\n\nexport const effortLevelSchema = z.enum(['low', 'medium', 'high', 'xhigh', 'max']);\n\nexport const providerModelSchema = z.object({\n label: z.string().optional(),\n contextWindow: unboundedInt().positive().optional(),\n maxOutputTokens: unboundedInt().positive().optional(),\n /** Whether this model accepts image input. Unknown models remain optimistic. */\n vision: z.boolean().optional(),\n /**\n * Entered by hand rather than returned by the endpoint's model-list API.\n * Refreshing the catalog keeps these, since they exist precisely because\n * discovery does not list them.\n */\n manual: z.boolean().optional(),\n /** Mutation-tool preference for this model; defaults to a family-level prior. */\n editFormat: z.enum(['patch', 'replace', 'whole']).optional(),\n effort: z\n .union([\n z.literal(false),\n z.object({\n default: effortLevelSchema.optional(),\n levels: z.array(effortLevelSchema).optional(),\n }),\n ])\n .optional(),\n});\n\nexport const providerConfigSchema = z.object({\n type: z.enum(['openai', 'anthropic']).default('openai'),\n baseURL: z.string().min(1).optional(),\n baseUrl: z.string().min(1).optional(),\n apiKey: z.string().min(1).optional(),\n models: z.record(z.string(), providerModelSchema).default({}),\n});\n\n/**\n * Continuation: whether a turn that stops with no tool calls may be re-driven.\n *\n * `runAgentLoop` ends as soon as a turn produces no tool calls, so one user\n * message is the whole run and a model that writes \"I've finished\" stops there.\n * Enabling this lets the loop append a host-authored user turn and keep going\n * while there is demonstrably work left.\n *\n * Off by default: it changes when a run ends, and continuation without the\n * no-progress brake below would let a stalled run spin and bill silently, where\n * today it stops and a human notices.\n */\nexport const continuationSettingsSchema = z.object({\n enabled: z.boolean().default(false),\n /** Consecutive host-authored continuations before the run stops regardless. */\n maxConsecutive: z.number().int().min(1).max(1000).default(50),\n /**\n * Identical progress witnesses in a row before the run stops as no-progress.\n * Minimum 1: a 0 here would fire on the first boundary, before any witness\n * exists, and label a run that never got a chance to spin as spinning.\n */\n noProgressLimit: z.number().int().min(1).max(20).default(3),\n /**\n * Consecutive turns in which EVERY tool call was refused before the run stops.\n *\n * Enforced even when `enabled` is false, deliberately. The spin it catches\n * predates continuation and needs no continuation to happen: a headless run in\n * the default permission mode answers every prompt `deny`, so the model can\n * re-issue a refused call forever while `toolCalls.length > 0` keeps the\n * turn-end gate from ever firing. None of the existing anti-loop signals stop it\n * either — the repeated-failure breaker only annotates a repeated call and never\n * sees the loop's own permission refusals, and `toolCallStats.failures` excludes\n * `blocked` by construction.\n *\n * 0 disables the guard.\n */\n blockedToolTurnLimit: z.number().int().min(0).max(100).default(3),\n /** Turns between host-authored work-state messages; 0 disables them. */\n planRefreshTurns: z.number().int().min(0).max(500).default(25),\n /** Wall-clock ceiling for one continued run; 0 means no ceiling. */\n maxWallClockMs: unboundedInt().min(0).default(0),\n});\n\nexport type ContinuationSettings = z.infer<typeof continuationSettingsSchema>;\n\nexport const memorySettingsSchema = z.object({\n enabled: z.boolean().default(true),\n autoSave: z.boolean().default(true),\n requireApproval: z.boolean().default(false),\n quarantineExternal: z.boolean().default(true),\n /**\n * Phase 1b: at the next session start, extract memories the model missed from idle sessions.\n * Off by default since 2026-10: on the owner's machine it wrote 8 of the 10 memories and all the\n * wrong ones, copied from delegated spec runs; it stays available as an opt-in.\n */\n extraction: z\n .object({\n enabled: z.boolean().default(false),\n idleHours: z.number().min(0).default(3),\n minMessages: unboundedInt().min(1).default(10),\n maxPerSession: z.number().int().min(1).max(20).default(5),\n maxSessionsPerRun: z.number().int().min(1).max(20).default(3),\n })\n .prefault({}),\n});\n\nexport const agentSettingsSchema = z.object({\n mode: z.enum(['adaptive', 'manual', 'off']).default('adaptive'),\n maxConcurrent: z.number().int().min(1).max(16).default(3),\n maxSpawned: z.number().int().min(1).max(64).default(8),\n maxDepth: z.literal(1).default(1),\n persist: z.boolean().default(true),\n includeUntrackedInSnapshot: z.boolean().default(true),\n telemetry: z.boolean().default(true),\n retentionDays: z.number().int().min(1).max(3650).default(30),\n checks: z\n .record(z.string(), z.union([z.string().min(1), z.array(z.string().min(1)).min(1)]))\n .default({}),\n /**\n * Wall-clock ceiling for one `Check` run. The 120s default suits a focused\n * suite; a full `npm test` on a large repository routinely exceeds it, and a\n * check that always times out is worse than no check because the timeout was\n * reported as a failing suite. Raise it per project rather than per invocation.\n */\n checkTimeoutMs: z.number().int().min(1_000).max(7_200_000).default(120_000),\n /** Ceiling on a foreground delegation via the Task tool; overrides the 1800s default. */\n taskTimeoutMs: unboundedInt().min(1000).optional(),\n /**\n * Simultaneous agent worktrees per repository; 0 disables the check.\n *\n * Nothing reclaims a worktree automatically outside the TUI, and the store's\n * retention sweep runs once at startup with a 30-day default — so on a long\n * unattended run the count only grows. Sized above `maxConcurrent` so ordinary\n * fan-out is unaffected and only accumulation trips it.\n */\n maxWorktrees: z.number().int().min(0).max(256).default(24),\n /**\n * Re-drive agents that were interrupted by process death on the next start.\n *\n * A restart otherwise converts the whole pending backlog into terminal records\n * nothing picks up, silently losing hours of child work on a wide fan-out. The\n * re-drive is contained: explorers are read-only and patchers/validators run in\n * their own worktree, so nothing reaches the parent workspace without the usual\n * evidence gate. Only `process_exit` interruptions qualify — a user stop stays\n * stopped.\n */\n resumeInterrupted: z.boolean().default(true),\n /**\n * Refuse a new worktree when free disk would fall below this; 0 disables.\n * Worktrees share the filesystem with the workspace, so exhausting it breaks\n * the root agent's own Edit and Bash, not just the child's.\n */\n minFreeDiskBytes: unboundedInt()\n .min(0)\n .default(2 * 1024 * 1024 * 1024),\n profiles: z\n .record(\n z.string(),\n z.object({\n model: z.string().min(1).optional(),\n effort: effortLevelSchema.optional(),\n maxTurns: unboundedInt().min(1).optional(),\n color: z.string().optional(),\n }),\n )\n .default({}),\n ui: z.object({ enabled: z.boolean().default(true) }).prefault({}),\n routing: z\n .object({\n inlineSearchBudget: z.number().int().min(1).max(20).default(3),\n exploreReminder: z.boolean().default(true),\n })\n .prefault({}),\n forwardTextEvents: z.boolean().default(false),\n});\n\nexport const toolDiscoverySettingsSchema = z.object({\n mode: z.enum(['auto', 'eager', 'deferred']).default('auto'),\n eagerToolCount: z.number().int().min(1).max(100).default(10),\n schemaTokenBudget: z.number().int().min(1000).max(100000).default(8000),\n maxLoadedTools: z.number().int().min(1).max(100).default(15),\n searchLimit: z.number().int().min(1).max(5).default(5),\n});\n\nexport type ToolDiscoverySettings = z.infer<typeof toolDiscoverySettingsSchema>;\n\nexport const toolExecutionSettingsSchema = z.object({\n /** Shared across the root loop and managed children for parallel-safe tools. */\n maxConcurrent: z.number().int().min(1).max(8).default(4),\n});\n\nexport type ToolExecutionSettings = z.infer<typeof toolExecutionSettingsSchema>;\n\nexport const observabilitySettingsSchema = z.object({\n /** Persist a per-tool-call JSONL record for `book tool-stats`. */\n toolTelemetry: z.boolean().default(true),\n /**\n * Default reporting window (in days) for `book tool-stats` and the target for\n * `book tool-stats --prune`. Disk use is bounded by size-based log rotation;\n * records are only deleted when they rotate out or are explicitly pruned.\n */\n toolTelemetryRetentionDays: z.number().int().min(1).max(3650).default(30),\n});\n\nexport type ObservabilitySettings = z.infer<typeof observabilitySettingsSchema>;\n\nexport const mcpProjectServerChoiceSchema = z.object({\n /** Hash of the server's command/args/env at decision time; a mismatch re-prompts. */\n fingerprint: z.string().min(1),\n choice: z.enum(['approved', 'rejected']),\n});\n\nexport type McpProjectServerChoice = z.infer<typeof mcpProjectServerChoiceSchema>;\n\nexport const mcpSettingsSchema = z.object({\n /**\n * Per-server trust decisions for workspace `.mcp.json` declarations.\n * User-global servers (<BOOK_HOME>/.book/mcp.json) never require approval.\n */\n projectServers: z.record(z.string(), mcpProjectServerChoiceSchema).default({}),\n});\n\nexport type McpSettings = z.infer<typeof mcpSettingsSchema>;\n\nexport const projectCommandChoiceSchema = z.object({\n /** Digest of the shell the body substitutes at decision time; a mismatch re-prompts. */\n fingerprint: z.string().min(1),\n choice: z.enum(['approved', 'rejected']),\n});\n\nexport type ProjectCommandChoice = z.infer<typeof projectCommandChoiceSchema>;\n\nexport const commandSettingsSchema = z.object({\n /**\n * Per-command trust decisions for `<workspace>/.book/commands/*.md` files\n * that substitute shell into their prompt. That substitution runs outside the\n * permission system and outside the sandbox, so a repository-controlled file\n * needs a decision before it runs. User-global commands\n * (<BOOK_HOME>/commands) were written by the user and never require one.\n */\n projectCommands: z.record(z.string(), projectCommandChoiceSchema).default({}),\n});\n\nexport type CommandSettings = z.infer<typeof commandSettingsSchema>;\n\n/**\n * Per-directory trust decisions for `additionalDirectories` declared by the checked-in project\n * layer, keyed by the directory's real path.\n *\n * `additionalDirectories` widens the set of roots the file tools serve, so honouring one from a\n * clone would hand that clone a read (and a write path) over whatever the path resolves to. A\n * project directory with no `approved` entry here is withheld, exactly as a project allow rule\n * is. Unlike a rule, the key cannot be the declared text: a repository controls that text and\n * could declare a link, so the decision is recorded against where the path really goes.\n */\nexport const projectDirectoryChoiceSchema = z.enum(['approved', 'rejected']);\n\nexport type ProjectDirectoryChoice = z.infer<typeof projectDirectoryChoiceSchema>;\n\nexport const uiSettingsSchema = z.object({\n /** Show provider-native and embedded model reasoning in the interactive transcript. */\n showThinking: z.boolean().default(true),\n /** Play the full-screen fire sequence on a newly created interactive startup session. */\n startupAnimation: z.boolean().default(true),\n});\n\nexport type UiSettings = z.infer<typeof uiSettingsSchema>;\n\nexport const skillActivationSchema = z.enum(['auto', 'name-only', 'manual', 'off']);\nexport type SkillActivation = z.infer<typeof skillActivationSchema>;\n\nexport const skillExecutionSchema = z.enum(['inherit', 'ask', 'deny']);\nexport type SkillExecution = z.infer<typeof skillExecutionSchema>;\n\nexport const skillSettingsSchema = z.object({\n /** Emergency switch that removes skill prompt and runtime effects without deleting packages. */\n enabled: z.boolean().default(true),\n /** Per-skill visibility overrides keyed by the skill's declared name. */\n overrides: z.record(z.string(), skillActivationSchema).default({}),\n /** Per-skill activation consent policy. Tool calls still use the normal permission system. */\n execution: z.record(z.string(), skillExecutionSchema).default({}),\n});\n\nexport type SkillSettings = z.infer<typeof skillSettingsSchema>;\n\nexport type ProviderModelConfig = z.infer<typeof providerModelSchema>;\nexport type ProviderConfig = z.infer<typeof providerConfigSchema>;\n\n/** The supported production strategy. */\nexport const compactStrategySchema = z.literal('summary');\nexport type CompactStrategy = z.infer<typeof compactStrategySchema>;\n\n/**\n * Full settings.json schema — all keys that book supports.\n * New keys added in later milestones extend this schema.\n */\nexport const bookSettingsSchema = z.object({\n model: z.string().optional(),\n /** Default strategy used to reduce historical conversation context. */\n compactStrategy: compactStrategySchema.optional(),\n /** Optional model used only to generate historical conversation checkpoints. */\n compactModel: z.string().min(1).optional(),\n /** Reasoning effort for the compaction reducer; defaults to the session effort capped at medium. */\n compactEffort: effortLevelSchema.optional(),\n /** Max agent turns per user message. Omit for unlimited. */\n maxTurns: unboundedInt().min(1).optional(),\n maxTokens: unboundedInt().min(1000).optional(),\n effort: effortLevelSchema.optional(),\n /** TUI color theme: rubric (default), folio, apple, or a custom theme filename. */\n theme: z.string().min(1).optional(),\n /**\n * The shell the Bash tool spawns: `bash`, `pwsh`, `powershell`, `cmd`, `sh`,\n * or an executable path. Honoured from the user-global layer, an explicit\n * `--settings` document, or `BOOK_SHELL` only: a workspace file cannot pick\n * the program every command is handed to.\n */\n shell: z.string().min(1).optional(),\n ui: uiSettingsSchema.prefault({}),\n skills: skillSettingsSchema.prefault({}),\n autoCompactEnabled: z.boolean().optional(),\n /** Permission mode used by each host when no invocation-specific mode is supplied. */\n defaultMode: z\n .enum(['default', 'acceptEdits', 'plan', 'auto', 'dontAsk', 'bypassPermissions'])\n .optional(),\n disableBypassPermissionsMode: z.boolean().optional(),\n /**\n * Extra roots the file tools serve, for paths outside the workspace. An honored entry is a root\n * for reads *and* writes: `Write`, `Edit`, `MultiEdit`, `ApplyPatch` and `NotebookEdit` accept an\n * absolute path inside one and treat the entry as they would the workspace, while a relative path\n * stays anchored to the workspace. A project-declared entry is withheld until `book trust dir`.\n */\n additionalDirectories: z.array(z.string()).default([]),\n /**\n * Decisions about `additionalDirectories` the project layer declared, keyed by the directory's\n * real path (the declared text is only what the repository wrote; the path is what the file\n * tools would serve). A project directory with no `approved` entry here is withheld, exactly\n * as a project allow rule with no entry is.\n */\n projectDirectories: z.record(z.string(), projectDirectoryChoiceSchema).default({}),\n env: z.record(z.string(), z.string()).default({}),\n provider: z.record(z.string(), providerConfigSchema).default({}),\n permissions: permissionsSchema.prefault({}),\n sandbox: sandboxSchema.prefault({}),\n hooks: hooksSchema.prefault({}),\n retry: retrySettingsSchema.prefault({}),\n memory: memorySettingsSchema.prefault({}),\n continuation: continuationSettingsSchema.prefault({}),\n agents: agentSettingsSchema.prefault({}),\n toolDiscovery: toolDiscoverySettingsSchema.prefault({}),\n toolExecution: toolExecutionSettingsSchema.prefault({}),\n observability: observabilitySettingsSchema.prefault({}),\n mcp: mcpSettingsSchema.prefault({}),\n commands: commandSettingsSchema.prefault({}),\n});\n\nexport type BookSettings = z.infer<typeof bookSettingsSchema>;\n\n/**\n * Resolved settings after layering (all defaults filled).\n */\nexport type ResolvedSettings = Required<\n Omit<\n BookSettings,\n | 'model'\n | 'compactModel'\n | 'compactEffort'\n | 'maxTurns'\n | 'maxTokens'\n | 'effort'\n | 'theme'\n | 'shell'\n | 'autoCompactEnabled'\n | 'defaultMode'\n | 'disableBypassPermissionsMode'\n >\n> &\n Pick<\n BookSettings,\n | 'model'\n | 'compactModel'\n | 'compactEffort'\n | 'maxTurns'\n | 'maxTokens'\n | 'effort'\n | 'theme'\n | 'shell'\n | 'autoCompactEnabled'\n | 'defaultMode'\n | 'disableBypassPermissionsMode'\n >;\n\n/**\n * Default settings used as the base layer before user/project/local override.\n */\nexport const DEFAULT_SETTINGS: ResolvedSettings = {\n compactStrategy: 'summary',\n permissions: { allow: [], ask: [], deny: [], projectAllowRules: {} },\n sandbox: {\n enabled: false,\n failIfUnavailable: false,\n autoAllowBashIfSandboxed: true,\n excludedCommands: [],\n allowUnsandboxedCommands: true,\n filesystem: { allowWrite: [], denyWrite: [], denyRead: [] },\n network: { allowedDomains: [], deniedDomains: [] },\n },\n hooks: {\n SessionStart: [],\n SessionEnd: [],\n UserPromptSubmit: [],\n PreToolUse: [],\n PostToolUse: [],\n Stop: [],\n PreCompact: [],\n PostCompact: [],\n SubagentStart: [],\n SubagentStop: [],\n Notification: [],\n projectEntries: {},\n },\n additionalDirectories: [],\n projectDirectories: {},\n env: {},\n provider: {},\n ui: { showThinking: true, startupAnimation: true },\n skills: { enabled: true, overrides: {}, execution: {} },\n retry: {\n maxAttempts: 10,\n baseDelayMs: 1000,\n maxDelayMs: 30000,\n totalBudgetMs: 0,\n requestTimeoutMs: 600000,\n streamStallTimeoutMs: 20000,\n thinkingStallTimeoutMs: 900_000,\n toolRetries: 1,\n watchdog: false,\n streamReissueAttempts: 3,\n outputCapContinuations: 10,\n },\n continuation: {\n enabled: false,\n maxConsecutive: 50,\n noProgressLimit: 3,\n blockedToolTurnLimit: 3,\n planRefreshTurns: 25,\n maxWallClockMs: 0,\n },\n memory: {\n enabled: true,\n autoSave: true,\n requireApproval: false,\n quarantineExternal: true,\n extraction: {\n enabled: false,\n idleHours: 3,\n minMessages: 10,\n maxPerSession: 5,\n maxSessionsPerRun: 3,\n },\n },\n agents: {\n mode: 'adaptive',\n maxConcurrent: 3,\n maxSpawned: 8,\n maxDepth: 1,\n persist: true,\n includeUntrackedInSnapshot: true,\n telemetry: true,\n retentionDays: 30,\n checks: {},\n checkTimeoutMs: 120_000,\n maxWorktrees: 24,\n resumeInterrupted: true,\n minFreeDiskBytes: 2 * 1024 * 1024 * 1024,\n profiles: {},\n ui: { enabled: true },\n routing: { inlineSearchBudget: 3, exploreReminder: true },\n forwardTextEvents: false,\n },\n toolDiscovery: {\n mode: 'auto',\n eagerToolCount: 10,\n schemaTokenBudget: 8000,\n maxLoadedTools: 15,\n searchLimit: 5,\n },\n toolExecution: {\n maxConcurrent: 4,\n },\n observability: {\n toolTelemetry: true,\n toolTelemetryRetentionDays: 30,\n },\n mcp: {\n projectServers: {},\n },\n commands: {\n projectCommands: {},\n },\n};\n","/** Deeply nested value access by dot-separated key path. */\nexport function getNestedValue(obj: Record<string, unknown>, path: string): unknown {\n const parts = path.split('.');\n let current: unknown = obj;\n for (const part of parts) {\n if (current === null || current === undefined || typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n return current;\n}\n\n/** Deeply nested value setter, creating intermediate objects as needed. */\nexport function setNestedValue(obj: Record<string, unknown>, path: string, value: unknown): void {\n const parts = path.split('.');\n let current = obj;\n for (let i = 0; i < parts.length - 1; i++) {\n if (\n !(parts[i] in current) ||\n typeof current[parts[i]] !== 'object' ||\n current[parts[i]] === null\n ) {\n current[parts[i]] = {};\n }\n current = current[parts[i]] as Record<string, unknown>;\n }\n current[parts[parts.length - 1]] = value;\n}\n\n/** Delete a dot-path and prune only empty ancestor objects on that path. */\nexport function deleteNestedValue(\n obj: Record<string, unknown>,\n path: string | readonly string[],\n): boolean {\n const parts = typeof path === 'string' ? path.split('.') : [...path];\n const ancestors: Array<{ parent: Record<string, unknown>; key: string }> = [];\n let current = obj;\n\n for (let i = 0; i < parts.length - 1; i++) {\n const key = parts[i];\n if (!Object.prototype.hasOwnProperty.call(current, key)) return false;\n const next = current[key];\n if (typeof next !== 'object' || next === null || Array.isArray(next)) return false;\n ancestors.push({ parent: current, key });\n current = next as Record<string, unknown>;\n }\n\n const leaf = parts[parts.length - 1];\n if (!Object.prototype.hasOwnProperty.call(current, leaf)) return false;\n delete current[leaf];\n\n for (let i = ancestors.length - 1; i >= 0; i--) {\n const { parent, key } = ancestors[i];\n const child = parent[key];\n if (\n typeof child === 'object' &&\n child !== null &&\n !Array.isArray(child) &&\n Object.keys(child).length === 0\n ) {\n delete parent[key];\n } else {\n break;\n }\n }\n return true;\n}\n\n/**\n * Parse a numeric CLI flag, refusing anything that is not a usable number.\n *\n * `parseInt('none', 10)` is `NaN`, and the old guard was truthiness — `'none'` is\n * truthy, so the typo sailed through. Downstream, `NaN` loses every comparison it\n * takes part in, which is the worst possible failure for a limit: `--max-turns`\n * ran zero turns and reported `completed`, and `--max-budget-usd` read as\n * *configured* while permitting unbounded spend. Reject it at the boundary, and\n * test presence with `!== undefined` so an explicit `0` is a real zero rather than\n * a missing flag.\n */\nexport function parseNumericFlag(\n raw: unknown,\n flag: string,\n opts: { integer?: boolean; min?: number } = {},\n): number | undefined {\n if (raw === undefined || raw === null || raw === '') return undefined;\n const text = String(raw).trim();\n const value = opts.integer ? Number.parseInt(text, 10) : Number.parseFloat(text);\n // Number.parse* stops at the first bad character, so '12abc' would otherwise\n // silently become 12.\n if (!Number.isFinite(value) || !/^[+-]?(\\d+\\.?\\d*|\\.\\d+)$/.test(text)) {\n throw new Error(`${flag} expects a number, but received \"${text}\".`);\n }\n // `Number.parseInt('2.5', 10)` is 2, so testing the parsed value would never\n // catch a fractional input for an integer flag - it would silently truncate.\n if (opts.integer && text.includes('.')) {\n throw new Error(`${flag} expects a whole number, but received \"${text}\".`);\n }\n const min = opts.min ?? 0;\n if (value < min) {\n throw new Error(`${flag} must be at least ${min}, but received \"${text}\".`);\n }\n return value;\n}\n\n/**\n * Merge the root positional argument into `-p/--print [prompt]`.\n *\n * `--print` carries its prompt as its own optional value, so a prompt written\n * after a later flag (`book -p --model m \"fix it\"`) is a stray positional that\n * commander rejected with \"too many arguments\" (#226). Claude Code accepts the\n * prompt in either place, and so does Book now: the positional is the prompt\n * when the flag has no value of its own. Both at once is an error rather than a\n * silent choice, and a positional without `--print` is an error too, because the\n * TUI has nowhere to put it — and it is the shape a mistyped subcommand takes.\n *\n * Returns the resolved `--print` value, or `{ error }` for commander to report.\n */\nexport function resolvePrintPrompt(\n print: string | boolean | undefined,\n positional: string | undefined,\n): { print: string | boolean | undefined } | { error: string } {\n if (positional === undefined) return { print };\n if (print === undefined) {\n return {\n error: `unexpected argument '${positional}'. A prompt on the command line needs -p/--print (book -p \"${positional}\"); run book --help for the subcommands.`,\n };\n }\n if (typeof print === 'string') {\n return {\n error: `the prompt was given twice: --print '${print}' and the argument '${positional}'. Pass it once, either as the --print value or as the argument.`,\n };\n }\n return { print: positional };\n}\n","import {\n closeSync,\n existsSync,\n fsyncSync,\n mkdirSync,\n openSync,\n readFileSync,\n renameSync,\n statSync,\n unlinkSync,\n writeSync,\n} from 'fs';\nimport { basename, dirname, join } from 'path';\nimport type { core } from 'zod';\nimport { deleteNestedValue, setNestedValue } from './cli/utils.js';\nimport { bookSettingsSchema } from './settings.js';\nimport { redactSettingValue } from './settings-redaction.js';\n\nexport type SettingsDocumentReadResult =\n | { status: 'absent'; path: string }\n | { status: 'valid'; path: string; document: Record<string, unknown> }\n | { status: 'malformed'; path: string; error: string }\n | { status: 'non-object'; path: string; error: string };\n\nexport interface SettingsDiagnostic {\n path: string;\n issuePath?: string;\n message: string;\n}\n\nexport type SettingsMutationResult =\n | {\n ok: true;\n path: string;\n document: Record<string, unknown>;\n values: Record<string, unknown>;\n changed: boolean;\n }\n | { ok: false; path: string; diagnostics: SettingsDiagnostic[] };\n\nexport interface SettingsRepositoryOptions {\n writeAtomic?: (path: string, contents: string) => void;\n}\n\nexport const SETTINGS_TOP_LEVEL_KEYS = Object.freeze(Object.keys(bookSettingsSchema.shape).sort());\n\nexport function formatSettingsKeyHelp(heading = 'Supported top-level settings:'): string {\n return `${heading}\\n${SETTINGS_TOP_LEVEL_KEYS.map((key) => ` ${key}`).join('\\n')}`;\n}\n\nfunction issueDiagnostic(path: string, issue: core.$ZodIssue): SettingsDiagnostic {\n return {\n path,\n issuePath: issue.path.join('.'),\n message: issue.message,\n };\n}\n\nfunction safeMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport function readSettingsDocument(path: string): SettingsDocumentReadResult {\n if (!existsSync(path)) return { status: 'absent', path };\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(path, 'utf-8'));\n } catch (error) {\n return { status: 'malformed', path, error: safeMessage(error) };\n }\n\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return { status: 'non-object', path, error: 'Settings must contain a JSON object.' };\n }\n\n return { status: 'valid', path, document: parsed as Record<string, unknown> };\n}\n\nexport function writeFileAtomic(path: string, contents: string): void {\n const directory = dirname(path);\n mkdirSync(directory, { recursive: true });\n const temporaryPath = join(\n directory,\n `.${basename(path)}.${process.pid}.${crypto.randomUUID()}.tmp`,\n );\n let descriptor: number | undefined;\n\n try {\n descriptor = openSync(temporaryPath, 'wx', 0o600);\n writeSync(descriptor, contents, undefined, 'utf-8');\n fsyncSync(descriptor);\n closeSync(descriptor);\n descriptor = undefined;\n renameSync(temporaryPath, path);\n } catch (error) {\n if (descriptor !== undefined) closeSync(descriptor);\n try {\n unlinkSync(temporaryPath);\n } catch {\n // The temporary file may not have been created.\n }\n throw error;\n }\n}\n\nfunction validateTopLevelKeys(\n path: string,\n document: Record<string, unknown>,\n): SettingsDiagnostic[] {\n const supported = new Set(SETTINGS_TOP_LEVEL_KEYS);\n return Object.keys(document)\n .filter((key) => !supported.has(key))\n .map((key) => ({\n path,\n issuePath: key,\n message: `Unknown top-level setting. Supported keys: ${SETTINGS_TOP_LEVEL_KEYS.join(', ')}`,\n }));\n}\n\nexport class SettingsRepository {\n private readonly writeAtomic: (path: string, contents: string) => void;\n\n constructor(\n readonly path: string,\n options: SettingsRepositoryOptions = {},\n ) {\n this.writeAtomic = options.writeAtomic ?? writeFileAtomic;\n }\n\n read(): SettingsDocumentReadResult {\n return readSettingsDocument(this.path);\n }\n\n set(values: Record<string, unknown>): SettingsMutationResult {\n return this.mutate((candidate) => {\n for (const [key, value] of Object.entries(values)) setNestedValue(candidate, key, value);\n }, values);\n }\n\n remove(paths: string[]): SettingsMutationResult {\n return this.mutate((candidate) => {\n for (const path of paths) deleteNestedValue(candidate, path.split('.'));\n }, {});\n }\n\n update(\n mutation: (candidate: Record<string, unknown>) => void,\n displayValues: Record<string, unknown> = {},\n ): SettingsMutationResult {\n return this.mutate(mutation, displayValues);\n }\n\n private mutate(\n mutation: (candidate: Record<string, unknown>) => void,\n values: Record<string, unknown>,\n ): SettingsMutationResult {\n let lock: number | undefined;\n try {\n lock = this.acquireLock();\n return this.mutateLocked(mutation, values);\n } catch (error) {\n return {\n ok: false,\n path: this.path,\n diagnostics: [{ path: this.path, message: safeMessage(error) }],\n };\n } finally {\n if (lock !== undefined) this.releaseLock(lock);\n }\n }\n\n private acquireLock(): number {\n mkdirSync(dirname(this.path), { recursive: true });\n const lockPath = `${this.path}.lock`;\n for (let attempt = 0; attempt < 50; attempt++) {\n try {\n return openSync(lockPath, 'wx', 0o600);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n try {\n if (Date.now() - statSync(lockPath).mtimeMs > 10_000) unlinkSync(lockPath);\n } catch {\n // Another process may have released or replaced the lock.\n }\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);\n }\n }\n throw new Error(`Timed out waiting for settings lock: ${lockPath}`);\n }\n\n private releaseLock(descriptor: number): void {\n closeSync(descriptor);\n try {\n unlinkSync(`${this.path}.lock`);\n } catch {\n // A stale-lock recovery may already have removed it.\n }\n }\n\n private mutateLocked(\n mutation: (candidate: Record<string, unknown>) => void,\n values: Record<string, unknown>,\n ): SettingsMutationResult {\n const source = this.read();\n if (source.status === 'malformed' || source.status === 'non-object') {\n return {\n ok: false,\n path: this.path,\n diagnostics: [{ path: this.path, message: source.error }],\n };\n }\n\n const candidate = structuredClone(source.status === 'valid' ? source.document : {});\n try {\n mutation(candidate);\n } catch (error) {\n return {\n ok: false,\n path: this.path,\n diagnostics: [{ path: this.path, message: safeMessage(error) }],\n };\n }\n\n const keyDiagnostics = validateTopLevelKeys(this.path, candidate);\n const validation = bookSettingsSchema.safeParse(candidate);\n const diagnostics = [\n ...keyDiagnostics,\n ...(validation.success\n ? []\n : validation.error.issues.map((issue) => issueDiagnostic(this.path, issue))),\n ];\n if (diagnostics.length > 0) return { ok: false, path: this.path, diagnostics };\n\n try {\n const changed =\n source.status !== 'valid' || JSON.stringify(source.document) !== JSON.stringify(candidate);\n if (changed) this.writeAtomic(this.path, JSON.stringify(candidate, null, 2) + '\\n');\n return {\n ok: true,\n path: this.path,\n document: candidate,\n changed,\n values: Object.fromEntries(\n Object.entries(values).map(([key, value]) => [key, redactSettingValue(key, value)]),\n ),\n };\n } catch (error) {\n return {\n ok: false,\n path: this.path,\n diagnostics: [{ path: this.path, message: safeMessage(error) }],\n };\n }\n }\n}\n\nexport function formatSettingsDiagnostics(diagnostics: SettingsDiagnostic[]): string {\n return diagnostics\n .map((diagnostic) => {\n const location = diagnostic.issuePath\n ? `${diagnostic.path} (${diagnostic.issuePath})`\n : diagnostic.path;\n return `${location}: ${diagnostic.message}`;\n })\n .join('\\n');\n}\n","const REDACTED_SECRET = '*** (stored)';\n\nfunction redactProviderRecord(value: unknown): unknown {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return value;\n const clone = structuredClone(value) as Record<string, unknown>;\n if ('apiKey' in clone) clone.apiKey = REDACTED_SECRET;\n return clone;\n}\n\nfunction redactProviderMap(value: unknown): unknown {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return value;\n const clone = structuredClone(value) as Record<string, unknown>;\n for (const [id, provider] of Object.entries(clone)) clone[id] = redactProviderRecord(provider);\n return clone;\n}\n\n/**\n * Removed blocks that may still carry a credential in a real settings file.\n *\n * Subscription authentication is gone, but `auth.profiles.<id>.clientSecret`\n * and the free-form `headers` that rode along with the bearer token are still\n * on disk wherever it was configured — and `book config list` prints the *raw*\n * document, not the validated one, so those keys survive into terminal\n * scrollback and pasted bug reports. Validation discarding a block is not the\n * same as a display path being safe, so the whole subtree is masked rather than\n * its individual leaves: nothing reads it any more, so there is nothing in it\n * worth showing.\n */\nconst REMOVED_SECRET_BEARING_BLOCKS = ['auth'];\n\nexport function redactSettingValue(path: string, value: unknown): unknown {\n const parts = path.split('.');\n if (parts[0] && REMOVED_SECRET_BEARING_BLOCKS.includes(parts[0])) return REDACTED_SECRET;\n if (parts[0] !== 'provider') return value;\n if (parts.length === 1) return redactProviderMap(value);\n if (parts.length === 2) return redactProviderRecord(value);\n return parts[parts.length - 1] === 'apiKey' ? REDACTED_SECRET : value;\n}\n\nexport function redactSettingsForDisplay<T>(value: T): T {\n if (!value || typeof value !== 'object') return value;\n const clone = structuredClone(value) as Record<string, unknown>;\n clone.provider = redactProviderMap(clone.provider);\n for (const key of REMOVED_SECRET_BEARING_BLOCKS) {\n if (clone[key] !== undefined) clone[key] = REDACTED_SECRET;\n }\n return clone as T;\n}\n"],"mappings":"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","names":[]}
@@ -1,137 +0,0 @@
1
- import {
2
- toolFailure
3
- } from "./chunk-224FJZLH.js";
4
-
5
- // src/tools/path-utils.ts
6
- import { existsSync, realpathSync } from "fs";
7
- import { dirname, isAbsolute, join, relative, resolve, sep } from "path";
8
- var REAL_PATH_COMPARISON = { relative, isAbsolute, sep };
9
- function isOutside(root, candidate, path = REAL_PATH_COMPARISON) {
10
- const rel = path.relative(root, candidate);
11
- return rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel);
12
- }
13
- function nearestExistingPath(inputPath) {
14
- let candidate = inputPath;
15
- while (!existsSync(candidate)) {
16
- const parent = dirname(candidate);
17
- if (parent === candidate) return null;
18
- candidate = parent;
19
- }
20
- return candidate;
21
- }
22
- function resolveWorkspacePath(workspaceRoot, inputPath) {
23
- const lexicalRoot = resolve(workspaceRoot);
24
- const filePath = resolve(isAbsolute(inputPath) ? inputPath : resolve(lexicalRoot, inputPath));
25
- const canonicalRoot = canonicalizePath(lexicalRoot);
26
- const canonicalPath = canonicalizePath(filePath);
27
- if (isOutside(canonicalRoot, canonicalPath)) return null;
28
- const asWrittenInside = !isOutside(lexicalRoot, filePath);
29
- const rel = asWrittenInside ? relative(lexicalRoot, filePath) : relative(canonicalRoot, canonicalPath);
30
- return { filePath, canonicalPath, relativePath: rel.replace(/\\/g, "/") };
31
- }
32
- function readOnlyEntries(roots) {
33
- return [...roots.readOnlyRoots ?? [], ...roots.additionalRoots ?? []];
34
- }
35
- function resolveReadablePathDetail(context, inputPath) {
36
- const entries = readOnlyEntries(context);
37
- const rejected = (match) => isExcludedPath(match.canonicalPath, entries) ? { reason: "excluded" } : { path: match };
38
- const wsMatch = resolveWorkspacePath(context.workspaceRoot, inputPath);
39
- if (wsMatch) {
40
- return rejected({ ...wsMatch, root: context.workspaceRoot, inWorkspace: true });
41
- }
42
- if (!isAbsolute(inputPath)) return { reason: "unreachable" };
43
- for (const entry of entries) {
44
- const root = typeof entry === "string" ? entry : entry.root;
45
- const match = resolveWorkspacePath(root, inputPath);
46
- if (match) return rejected({ ...match, root, inWorkspace: false });
47
- }
48
- return { reason: "unreachable" };
49
- }
50
- function resolveReadablePath(context, inputPath) {
51
- const detail = resolveReadablePathDetail(context, inputPath);
52
- return "path" in detail ? detail.path : null;
53
- }
54
- function resolveMutationPath(context, inputPath) {
55
- const inWorkspace = resolveWorkspacePath(context.workspaceRoot, inputPath);
56
- if (inWorkspace) return { ...inWorkspace, root: context.workspaceRoot, inWorkspace: true };
57
- if (!isAbsolute(inputPath)) return null;
58
- for (const root of context.additionalRoots ?? []) {
59
- const match = resolveWorkspacePath(root, inputPath);
60
- if (!match) continue;
61
- const rel = relative(resolve(context.workspaceRoot), match.filePath);
62
- return { ...match, relativePath: rel.replace(/\\/g, "/"), root, inWorkspace: false };
63
- }
64
- return null;
65
- }
66
- function isExcludedPath(canonicalPath, roots) {
67
- const fold = (p) => process.platform === "linux" ? p : p.toLowerCase();
68
- const target = fold(canonicalPath);
69
- for (const entry of roots ?? []) {
70
- if (typeof entry === "string" || !entry.exclude?.length) continue;
71
- const base = fold(canonicalizePath(entry.root));
72
- for (const sub of entry.exclude) {
73
- const dir = fold(join(base, sub));
74
- if (target === dir || target.startsWith(`${dir}${sep}`)) return true;
75
- }
76
- }
77
- return false;
78
- }
79
- function pathOutsideWorkspaceResult(inputPath) {
80
- return toolFailure(`Path outside workspace and additionalDirectories: ${inputPath}`, {
81
- code: "path_outside_workspace",
82
- remediation: "Add its directory to additionalDirectories, or start Book in a directory that contains it. No permission rule and no permission mode can change which roots a read may cross."
83
- });
84
- }
85
- function realWorkspaceRoot(root) {
86
- try {
87
- return realpathSync.native(root);
88
- } catch {
89
- return resolve(root);
90
- }
91
- }
92
- function canonicalizePath(inputPath) {
93
- const absolute = resolve(inputPath);
94
- const existing = nearestExistingPath(absolute);
95
- if (!existing) return absolute;
96
- try {
97
- return resolve(realpathSync.native(existing), relative(existing, absolute));
98
- } catch {
99
- return absolute;
100
- }
101
- }
102
- function isUnderRoot(canonicalPath, root) {
103
- return !isOutside(canonicalizePath(root), canonicalPath);
104
- }
105
- function hasBookLocalSettingsShape(canonicalPath, options) {
106
- const parts = canonicalPath.toLowerCase().split(/[\\/]/);
107
- if (parts[parts.length - 1] === ".book") return options.directory === true;
108
- return parts[parts.length - 2] === ".book" && parts[parts.length - 1] === "settings.local.json";
109
- }
110
- function isBookLocalSettingsPath(path, root, options = {}) {
111
- if (!hasBookLocalSettingsShape(path, options)) return false;
112
- const target = canonicalizePath(path).toLowerCase();
113
- const bookDir = join(canonicalizePath(root), ".book").toLowerCase();
114
- return target === join(bookDir, "settings.local.json") || options.directory === true && target === bookDir;
115
- }
116
- function isBookLocalSettingsUnderRoots(path, roots, options = {}) {
117
- if (!hasBookLocalSettingsShape(path, options)) return false;
118
- return [roots.workspaceRoot, ...readOnlyEntries(roots)].some((root) => {
119
- const entryRoot = typeof root === "string" ? root : root.root;
120
- return isBookLocalSettingsPath(path, entryRoot, options);
121
- });
122
- }
123
-
124
- export {
125
- isOutside,
126
- resolveWorkspacePath,
127
- resolveReadablePathDetail,
128
- resolveReadablePath,
129
- resolveMutationPath,
130
- pathOutsideWorkspaceResult,
131
- realWorkspaceRoot,
132
- canonicalizePath,
133
- isUnderRoot,
134
- isBookLocalSettingsPath,
135
- isBookLocalSettingsUnderRoots
136
- };
137
- //# sourceMappingURL=chunk-X66I2NQO.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/tools/path-utils.ts"],"sourcesContent":["import { existsSync, realpathSync } from 'fs';\nimport { dirname, isAbsolute, join, relative, resolve, sep } from 'path';\nimport type { ReadOnlyRoot, ToolResult } from '../types/tools.js';\nimport { toolFailure } from './result.js';\n\n/**\n * The path semantics a containment comparison needs.\n *\n * Injectable so a caller whose paths are spelled by another `path` module — the\n * sandbox builder resolves a *test* workspace through `path.win32` or\n * `path.posix` and must judge containment in the same spelling — compares in the\n * same terms it resolved in. `sep` is part of the contract because `..` is only\n * a parent step in the separator of the module that produced the relative path.\n */\nexport interface PathComparison {\n relative(from: string, to: string): string;\n isAbsolute(path: string): boolean;\n sep: string;\n}\n\nconst REAL_PATH_COMPARISON: PathComparison = { relative, isAbsolute, sep };\n\n/**\n * Whether `candidate` is `root` or lies outside it — the one containment test\n * the rest of the codebase makes.\n *\n * Note what it does *not* accept: a relative path that merely starts with `..`,\n * such as `..meta`. That is a name in the root's own directory, and treating it\n * as a parent step reads a directory called `..meta` as being outside the tree\n * it is in. `relative` can only produce a real parent step as `..` on its own or\n * as `../…` with the separator of the module that produced it, and the third\n * clause covers a relative path answering to two different drives.\n */\nexport function isOutside(\n root: string,\n candidate: string,\n path: PathComparison = REAL_PATH_COMPARISON,\n): boolean {\n const rel = path.relative(root, candidate);\n return rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel);\n}\n\nfunction nearestExistingPath(inputPath: string): string | null {\n let candidate = inputPath;\n while (!existsSync(candidate)) {\n const parent = dirname(candidate);\n if (parent === candidate) return null;\n candidate = parent;\n }\n return candidate;\n}\n\nexport function resolveWorkspacePath(\n workspaceRoot: string,\n inputPath: string,\n): { filePath: string; canonicalPath: string; relativePath: string } | null {\n const lexicalRoot = resolve(workspaceRoot);\n const filePath = resolve(isAbsolute(inputPath) ? inputPath : resolve(lexicalRoot, inputPath));\n // Containment is decided on the canonical pair — both sides after following links, and in one\n // separator — because one directory has more than one spelling. A root the user typed in 8.3\n // short form (`C:\\Users\\RUNNER~1\\…`) is a different string from the long form `realpath`,\n // a glob walk and ripgrep report, and reading \"outside\" between the two spellings of the *same*\n // directory is what let a `.book/settings.local.json` inside the workspace look like a file\n // somewhere else (#264). The canonical path is the one a read opens and a write lands on, so it\n // is the one that decides, and a link pointing out of a root still resolves outside it.\n const canonicalRoot = canonicalizePath(lexicalRoot);\n const canonicalPath = canonicalizePath(filePath);\n if (isOutside(canonicalRoot, canonicalPath)) return null;\n\n // The spelling shown to the model is relative to the root as written whenever the path was\n // written inside it, so a symlinked spelling keeps naming the file the way the caller reached it\n // (`link/notes.txt`) rather than by its real name.\n const asWrittenInside = !isOutside(lexicalRoot, filePath);\n const rel = asWrittenInside\n ? relative(lexicalRoot, filePath)\n : relative(canonicalRoot, canonicalPath);\n return { filePath, canonicalPath, relativePath: rel.replace(/\\\\/g, '/') };\n}\n\nexport interface ResolvedReadablePath {\n filePath: string;\n canonicalPath: string;\n relativePath: string;\n /**\n * The root that served this path, as the caller spelled it — the same string the file tools\n * glob and ripgrep from, and the one a result label should keep using, since it is the spelling\n * `Read` accepts back.\n */\n root: string;\n /**\n * Whether `root` is the workspace, which is what makes `relativePath` the workspace-relative\n * spelling rather than one relative to some other root.\n */\n inWorkspace: boolean;\n}\n\n/** The roots a file tool may serve, in the order it tries them. */\nexport interface PathRoots {\n workspaceRoot: string;\n readOnlyRoots?: readonly (string | ReadOnlyRoot)[];\n /**\n * Honored `additionalDirectories` (#300), as resolved real roots. Tried after the workspace, and\n * only for an absolute path: a relative path stays workspace-anchored.\n *\n * These are *read* roots. A write tool still resolves against the workspace alone\n * ({@link resolveWorkspacePath}), so widening this list never widens what Write, Edit,\n * MultiEdit, NotebookEdit or ApplyPatch may change.\n */\n additionalRoots?: readonly string[];\n}\n\n/** The entries of a read-only root list, which carry exclusions, as a plain array. */\nfunction readOnlyEntries(roots: PathRoots): Array<string | ReadOnlyRoot> {\n return [...(roots.readOnlyRoots ?? []), ...(roots.additionalRoots ?? [])];\n}\n\n/**\n * Why a path no tool may serve it: `excluded` when it fell under a root that hides it (Book's\n * memory inbox, `.book/settings.local.json`'s directory), or `unreachable` when no root contains\n * it at all. Only the second is a refusal whose remedy is to widen the roots; the first has a\n * narrower one, and a prompt is the right answer for it.\n */\nexport type UnreachablePathReason = 'excluded' | 'unreachable';\n\nexport type ReadablePathDetail = { path: ResolvedReadablePath } | { reason: UnreachablePathReason };\n\n/**\n * Which resolved file the read tools may serve, and why not when they may not.\n *\n * The workspace is tried first, then the read-only roots, then the honored `additionalDirectories`\n * — the same order {@link resolveReadablePath} has always used, with the extra roots appended.\n * Only an absolute path reaches the roots outside the workspace, so a relative path never\n * re-anchors to one.\n *\n * The distinction the single `null` used to hide matters now that a call the tool cannot serve is\n * refused instead of prompted for: a path under a hidden subpath keeps its prompt, while a path\n * no root contains is refused outright and the refusal names the remedy.\n */\nexport function resolveReadablePathDetail(\n context: PathRoots,\n inputPath: string,\n): ReadablePathDetail {\n const entries = readOnlyEntries(context);\n const rejected = (match: ResolvedReadablePath): ReadablePathDetail =>\n isExcludedPath(match.canonicalPath, entries) ? { reason: 'excluded' } : { path: match };\n\n // The exclusion applies whichever root matched: a memory directory inside the workspace\n // (running in $HOME) must not expose its inbox through the workspace branch. The root that\n // matched travels with the answer, so a caller labelling or re-searching the file does not have\n // to work out which of the roots it was found under by comparing paths again.\n const wsMatch = resolveWorkspacePath(context.workspaceRoot, inputPath);\n if (wsMatch) {\n return rejected({ ...wsMatch, root: context.workspaceRoot, inWorkspace: true });\n }\n\n if (!isAbsolute(inputPath)) return { reason: 'unreachable' };\n for (const entry of entries) {\n const root = typeof entry === 'string' ? entry : entry.root;\n const match = resolveWorkspacePath(root, inputPath);\n if (match) return rejected({ ...match, root, inWorkspace: false });\n }\n return { reason: 'unreachable' };\n}\n\nexport function resolveReadablePath(\n context: PathRoots,\n inputPath: string,\n): ResolvedReadablePath | null {\n const detail = resolveReadablePathDetail(context, inputPath);\n return 'path' in detail ? detail.path : null;\n}\n\n/**\n * Where a write tool may put a file: the workspace, then the honored directories.\n *\n * A write to an absolute path inside an honored directory is served exactly the way the same write\n * inside the workspace is served — same permission verdict, same `.book/settings.local.json` guard,\n * same fresh-observation requirement — and a relative path stays anchored to the workspace, so a\n * bare `./notes.txt` cannot be re-anchored to a root the caller never named. Read-only roots are\n * deliberately not consulted: they are roots a *read* may cross, not a write target.\n *\n * A match under an honored root reports the **workspace**-relative spelling of its path, not the\n * root-relative one its own resolution produced. That is the spelling the observation ledger is\n * keyed on (`observeFile` derives it from the absolute path, so a file in an honored root and a\n * workspace file of the same name can never collide), and the fresh-observation check looks the\n * write up under it. Reporting the root-relative form instead would make the Read that licensed\n * the write and the write itself consult two different keys, and every honored write would report\n * `file_not_observed`.\n */\nexport function resolveMutationPath(\n context: PathRoots,\n inputPath: string,\n): ResolvedReadablePath | null {\n const inWorkspace = resolveWorkspacePath(context.workspaceRoot, inputPath);\n if (inWorkspace) return { ...inWorkspace, root: context.workspaceRoot, inWorkspace: true };\n if (!isAbsolute(inputPath)) return null;\n for (const root of context.additionalRoots ?? []) {\n const match = resolveWorkspacePath(root, inputPath);\n if (!match) continue;\n const rel = relative(resolve(context.workspaceRoot), match.filePath);\n return { ...match, relativePath: rel.replace(/\\\\/g, '/'), root, inWorkspace: false };\n }\n return null;\n}\n\n/**\n * Whether a resolved path falls under an excluded subpath of a read-only root (the memory\n * inbox). Checked on the canonical path — after symlinks — and case-folded where the file\n * system folds case, so `.INBOX/` or a link into `.inbox` cannot slip past it.\n */\nfunction isExcludedPath(\n canonicalPath: string,\n roots: readonly (string | ReadOnlyRoot)[] | undefined,\n): boolean {\n const fold = (p: string) => (process.platform === 'linux' ? p : p.toLowerCase());\n const target = fold(canonicalPath);\n for (const entry of roots ?? []) {\n if (typeof entry === 'string' || !entry.exclude?.length) continue;\n const base = fold(canonicalizePath(entry.root));\n for (const sub of entry.exclude) {\n const dir = fold(join(base, sub));\n if (target === dir || target.startsWith(`${dir}${sep}`)) return true;\n }\n }\n return false;\n}\n\n/**\n * The refusal a read tool gives a path no root serves.\n *\n * The message names the directory list as well as the workspace: after #300 the workspace is not\n * the only root a read may cross, so \"outside workspace\" alone sent the model looking for a rule\n * that would never work. The code is unchanged, so a caller that classifies refusals by code\n * (the agent loop's streak remedy, the managed-child notice) is unaffected.\n */\nexport function pathOutsideWorkspaceResult(inputPath: unknown): ToolResult {\n return toolFailure(`Path outside workspace and additionalDirectories: ${inputPath}`, {\n code: 'path_outside_workspace',\n remediation:\n 'Add its directory to additionalDirectories, or start Book in a directory that contains it. ' +\n 'No permission rule and no permission mode can change which roots a read may cross.',\n });\n}\n\n/** The workspace root after following links, or the root as given when it cannot be resolved. */\nexport function realWorkspaceRoot(root: string): string {\n try {\n return realpathSync.native(root);\n } catch {\n return resolve(root);\n }\n}\n\n/**\n * A path with its links followed, the way {@link resolveWorkspacePath} canonicalises one: the\n * nearest existing ancestor is resolved and the rest of the path re-appended, so a path that\n * does not exist yet still comes back through a link above it.\n *\n * `realWorkspaceRoot` alone is not enough for a comparison against a *file* path — a read target\n * that has been deleted, or one named before it is written, would fall back to its lexical form\n * and quietly fail to see through the link that reaches it.\n */\nexport function canonicalizePath(inputPath: string): string {\n const absolute = resolve(inputPath);\n const existing = nearestExistingPath(absolute);\n if (!existing) return absolute;\n try {\n return resolve(realpathSync.native(existing), relative(existing, absolute));\n } catch {\n return absolute;\n }\n}\n\n/**\n * Whether a canonical path is a root or under it, both sides in one canonical form.\n *\n * The comparison every \"is this path inside that root\" test outside a resolution makes: which\n * root served a resolved file, whether a Glob label belongs to the workspace, which root a\n * guarded read was served by. The path arrives canonical — from `realpath`, a glob walk or\n * ripgrep, or from {@link resolveWorkspacePath} — so the root has to be put in that form before\n * the two are compared. An as-given root and a canonical path are two spellings of one directory at\n * best, and two unrelated ones when the root was typed in 8.3 short form.\n */\nexport function isUnderRoot(canonicalPath: string, root: string): boolean {\n return !isOutside(canonicalizePath(root), canonicalPath);\n}\n\n/**\n * Whether a path has the *shape* of a root's Book-local settings file or directory, whatever root\n * is being asked about: the last segment is `.book`, or `settings.local.json` under one.\n *\n * The cheap half of {@link isBookLocalSettingsPath}, and the half worth running for every file a\n * Grep lists, because the rest of that answer costs a `realpath` per root. Sound rather than\n * exact — a `.book` somewhere below a root has the shape without being that root's — which is\n * what a prefilter has to be.\n */\nfunction hasBookLocalSettingsShape(\n canonicalPath: string,\n options: { directory?: boolean },\n): boolean {\n const parts = canonicalPath.toLowerCase().split(/[\\\\/]/);\n if (parts[parts.length - 1] === '.book') return options.directory === true;\n return parts[parts.length - 2] === '.book' && parts[parts.length - 1] === 'settings.local.json';\n}\n\n/**\n * Whether a path is Book's project-local settings file, `<root>/.book/settings.local.json`, which\n * can hold an API key; with `directory`, also the `.book` directory that holds it.\n *\n * Both sides are put in one canonical form before they are compared, and compared\n * case-insensitively on every platform: on a case-sensitive file system that only makes the check\n * stricter, while a case-insensitive Linux mount (WSL on /mnt/c) serves any spelling from the same\n * file. The root is canonicalized because it is the side a caller spells freely — 8.3 short form,\n * a lowercase drive letter, forward slashes — and the path because this is the guard that leaked\n * an API key when a root's spelling and a resolved path's spelling disagreed, and a check that\n * silently stops matching when a caller hands it a path in another form is the same failure with\n * a smaller blast radius. Callers that check every file a Grep listed run\n * {@link hasBookLocalSettingsShape} first, which is what keeps this off the per-file path.\n */\nexport function isBookLocalSettingsPath(\n path: string,\n root: string,\n options: { directory?: boolean } = {},\n): boolean {\n if (!hasBookLocalSettingsShape(path, options)) return false;\n const target = canonicalizePath(path).toLowerCase();\n const bookDir = join(canonicalizePath(root), '.book').toLowerCase();\n return (\n target === join(bookDir, 'settings.local.json') ||\n (options.directory === true && target === bookDir)\n );\n}\n\n/**\n * Whether a path is Book's own local settings under *any* root a read may cross.\n *\n * After #300 a read can cross a directory the workspace does not contain, and that directory can\n * carry its own `.book/settings.local.json` with an API key in it — the same file, with the same\n * reason to stay closed, reached by a path the workspace-root check would not recognize. One\n * function so the read tools and the permission scope agree on which paths are guarded. The shape\n * test comes first, so the roots are only put in canonical form for a file that could be one,\n * which is what keeps this free for the per-file call a Grep makes.\n */\nexport function isBookLocalSettingsUnderRoots(\n path: string,\n roots: PathRoots,\n options: { directory?: boolean } = {},\n): boolean {\n if (!hasBookLocalSettingsShape(path, options)) return false;\n return [roots.workspaceRoot, ...readOnlyEntries(roots)].some((root) => {\n const entryRoot = typeof root === 'string' ? root : root.root;\n return isBookLocalSettingsPath(path, entryRoot, options);\n });\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/mcp-config.ts"],"sourcesContent":["import { readFileSync, existsSync } from 'fs';\nimport { join } from 'path';\nimport { resolveBookHome } from './book-home.js';\n\nexport type McpTransportType = 'stdio' | 'http' | 'sse';\n\nexport interface McpServerConfig {\n /** Explicit transport. Omitted means stdio when `command` is present. */\n type?: McpTransportType;\n /** Executable for stdio servers. */\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n /** Optional working directory for stdio servers. */\n cwd?: string;\n /** Endpoint for HTTP/SSE servers. */\n url?: string;\n /** Additional request headers for HTTP/SSE servers. Values are never shown in diagnostics. */\n headers?: Record<string, string>;\n}\n\nconst SAFE_SERVER_NAME = /^[A-Za-z0-9_-]+$/;\n\nexport function isValidMcpServerName(name: string): boolean {\n return SAFE_SERVER_NAME.test(name) && !name.includes('__');\n}\n\ninterface McpConfigDocument {\n mcpServers?: Record<string, unknown>;\n}\n\n/** Where a server declaration came from; project declarations require approval. */\nexport type McpServerSource = 'user' | 'project';\n\nexport interface ResolvedMcpServer {\n name: string;\n config: McpServerConfig;\n source: McpServerSource;\n /** Config file that defined (or, on a name collision, last overrode) this server. */\n path: string;\n}\n\nexport interface McpDiagnostic {\n level: 'warn';\n message: string;\n server?: string;\n}\n\nexport type McpDiagnosticSink = (diagnostic: McpDiagnostic) => void;\n\n/** Preserves the historical stderr warning behavior for headless/SDK hosts. */\nexport const consoleMcpDiagnosticSink: McpDiagnosticSink = (diagnostic) => {\n console.warn(`⚠ ${diagnostic.message}`);\n};\n\nexport interface ResolveMcpServersOptions {\n home?: string;\n onDiagnostic?: McpDiagnosticSink;\n /** Environment used for `${VAR}` interpolation; defaults to process.env. */\n env?: NodeJS.ProcessEnv;\n}\n\ninterface ExpandedValue {\n value: string;\n missing: string[];\n}\n\n/**\n * Expand the small interpolation language used by the de-facto mcpServers\n * config format. We deliberately support only `${NAME}` and `${NAME:-default}`\n * here; shell syntax is never evaluated.\n */\nfunction expandValue(raw: string, env: NodeJS.ProcessEnv): ExpandedValue {\n const missing = new Set<string>();\n const value = raw.replace(\n /\\$\\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\\}/g,\n (_match, name: string, fallback?: string) => {\n const configured = env[name];\n if (configured !== undefined) return configured;\n if (fallback !== undefined) return fallback;\n missing.add(name);\n return '';\n },\n );\n return { value, missing: [...missing] };\n}\n\nfunction expandRecord(\n raw: unknown,\n env: NodeJS.ProcessEnv,\n): { value: Record<string, string>; missing: string[]; invalid: string[] } {\n const value: Record<string, string> = {};\n const missing = new Set<string>();\n const invalid: string[] = [];\n if (raw === undefined) return { value, missing: [], invalid };\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n return { value, missing: [], invalid: ['expected an object'] };\n }\n for (const [key, item] of Object.entries(raw as Record<string, unknown>)) {\n if (typeof item !== 'string') {\n invalid.push(`${key} must be a string`);\n continue;\n }\n const expanded = expandValue(item, env);\n value[key] = expanded.value;\n for (const name of expanded.missing) missing.add(name);\n }\n return { value, missing: [...missing], invalid };\n}\n\nfunction normalizeServerConfig(\n name: string,\n raw: unknown,\n env: NodeJS.ProcessEnv,\n): { config?: McpServerConfig; error?: string } {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n return { error: 'expected an object' };\n }\n const source = raw as Record<string, unknown>;\n const requestedType = source.type ?? source.transport;\n const type =\n requestedType === undefined\n ? source.url !== undefined\n ? 'http'\n : 'stdio'\n : String(requestedType).toLowerCase();\n if (!['stdio', 'http', 'sse'].includes(type)) {\n return { error: `unsupported transport type \"${String(requestedType)}\"` };\n }\n\n const commandExpanded =\n typeof source.command === 'string' ? expandValue(source.command, env) : undefined;\n const urlExpanded = typeof source.url === 'string' ? expandValue(source.url, env) : undefined;\n const missing = new Set([...(commandExpanded?.missing ?? []), ...(urlExpanded?.missing ?? [])]);\n const args: string[] = [];\n if (source.args !== undefined) {\n if (!Array.isArray(source.args)) return { error: 'args must be an array' };\n for (const [index, item] of source.args.entries()) {\n if (typeof item !== 'string') return { error: `args[${index}] must be a string` };\n const expanded = expandValue(item, env);\n args.push(expanded.value);\n for (const variable of expanded.missing) missing.add(variable);\n }\n }\n const envValues = expandRecord(source.env, env);\n const headerValues = expandRecord(source.headers, env);\n for (const variable of [...envValues.missing, ...headerValues.missing]) missing.add(variable);\n if (envValues.invalid.length > 0) return { error: `env ${envValues.invalid.join(', ')}` };\n if (headerValues.invalid.length > 0)\n return { error: `headers ${headerValues.invalid.join(', ')}` };\n if (missing.size > 0)\n return { error: `missing environment variable(s): ${[...missing].join(', ')}` };\n\n if (type === 'stdio') {\n if (!commandExpanded?.value.trim())\n return { error: 'stdio server requires a non-empty command' };\n const cwd = typeof source.cwd === 'string' ? expandValue(source.cwd, env) : undefined;\n if (cwd?.missing.length)\n return { error: `missing environment variable(s): ${cwd.missing.join(', ')}` };\n return {\n config: {\n type: 'stdio',\n command: commandExpanded.value,\n args,\n env: envValues.value,\n ...(cwd?.value ? { cwd: cwd.value } : {}),\n },\n };\n }\n\n if (!urlExpanded?.value.trim()) return { error: `${type} server requires a non-empty url` };\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(urlExpanded.value);\n } catch {\n return { error: 'url must be a valid absolute URL' };\n }\n if (!['http:', 'https:'].includes(parsedUrl.protocol)) {\n return { error: 'url must use http:// or https://' };\n }\n if (parsedUrl.username || parsedUrl.password)\n return { error: 'url must not contain credentials' };\n return {\n config: {\n type: type as 'http' | 'sse',\n url: parsedUrl.toString(),\n headers: headerValues.value,\n },\n };\n}\n\n/**\n * Resolve the declared MCP servers with provenance.\n *\n * Sources, later wins on a name collision: <BOOK_HOME>/.book/mcp.json (user),\n * then <workspace>/.mcp.json (project). A project entry that overrides a\n * user-global name keeps `source: 'project'` so it cannot inherit the trust of\n * the name it shadows.\n */\nexport function resolveMcpServerList(\n workspace: string,\n options: ResolveMcpServersOptions = {},\n): ResolvedMcpServer[] {\n const onDiagnostic = options.onDiagnostic ?? consoleMcpDiagnosticSink;\n const env = options.env ?? process.env;\n const bookHome = options.home ? join(options.home, '.book') : resolveBookHome();\n const sources: Array<{ path: string; source: McpServerSource }> = [\n { path: join(bookHome, 'mcp.json'), source: 'user' },\n { path: join(workspace, '.mcp.json'), source: 'project' },\n ];\n const servers = new Map<string, ResolvedMcpServer>();\n\n for (const { path, source } of sources) {\n if (!existsSync(path)) continue;\n let parsed: McpConfigDocument;\n try {\n parsed = JSON.parse(readFileSync(path, 'utf-8')) as McpConfigDocument;\n } catch {\n onDiagnostic({ level: 'warn', message: `Failed to load MCP config: ${path}` });\n continue;\n }\n if (!parsed || typeof parsed !== 'object' || !parsed.mcpServers) continue;\n if (typeof parsed.mcpServers !== 'object' || Array.isArray(parsed.mcpServers)) {\n onDiagnostic({ level: 'warn', message: `Invalid mcpServers object: ${path}` });\n continue;\n }\n for (const [name, rawConfig] of Object.entries(parsed.mcpServers)) {\n if (!isValidMcpServerName(name)) {\n onDiagnostic({\n level: 'warn',\n message: `Ignoring invalid MCP server name: ${name} (${path}); use letters, numbers, hyphens, or underscores, without a double underscore`,\n server: name,\n });\n continue;\n }\n const normalized = normalizeServerConfig(name, rawConfig, env);\n if (!normalized.config) {\n onDiagnostic({\n level: 'warn',\n message: `Ignoring invalid MCP server config: ${name} (${path}): ${normalized.error ?? 'invalid configuration'}`,\n server: name,\n });\n continue;\n }\n servers.set(name, {\n name,\n source,\n path,\n config: normalized.config,\n });\n }\n }\n\n return [...servers.values()];\n}\n\n/** Merged name → config view used by doctor and ambient-run fingerprinting. */\nexport function loadMcpConfig(workspace: string, home?: string): Record<string, McpServerConfig> {\n const servers: Record<string, McpServerConfig> = {};\n for (const server of resolveMcpServerList(workspace, { home })) {\n servers[server.name] = server.config;\n }\n return servers;\n}\n\n/** Render one server as the exact command line a host is being asked to trust. */\nexport function formatMcpServerCommand(config: McpServerConfig): string {\n const sensitiveValues = [\n ...Object.values(config.env ?? {}),\n ...Object.values(config.headers ?? {}),\n ]\n .filter((value) => value.length >= 4)\n .sort((left, right) => right.length - left.length);\n const redact = (value: string): string => {\n let rendered = value;\n for (const sensitive of sensitiveValues)\n rendered = rendered.replaceAll(sensitive, '<redacted>');\n return rendered;\n };\n if (config.type === 'http' || config.type === 'sse' || (!config.command && config.url)) {\n const headers = Object.keys(config.headers ?? {}).sort();\n let endpoint = config.url ?? '';\n let queryConfigured = false;\n try {\n const url = new URL(endpoint);\n queryConfigured = Boolean(url.search);\n url.search = '';\n url.hash = '';\n endpoint = url.toString();\n } catch {\n endpoint = redact(endpoint);\n }\n const metadata = [\n ...(queryConfigured ? ['query configured'] : []),\n ...(headers.length ? [`headers: ${headers.join(', ')}`] : []),\n ];\n return `${config.type === 'sse' ? 'sse' : 'http'} ${endpoint}${metadata.length ? ` (${metadata.join('; ')})` : ''}`;\n }\n return [config.command ?? '', ...(config.args ?? [])].filter(Boolean).map(redact).join(' ');\n}\n"],"mappings":"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","names":[]}