@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.
- package/CHANGELOG.md +1024 -0
- package/README.md +6 -3
- package/dist/{additional-roots-H7UNAEHS.js → additional-roots-VAWRQ625.js} +8 -7
- package/dist/{app-GE4OHJOC.js → app-J7BVPSUH.js} +5397 -2953
- package/dist/app-J7BVPSUH.js.map +1 -0
- package/dist/chunk-32WBCUAN.js +660 -0
- package/dist/chunk-32WBCUAN.js.map +1 -0
- package/dist/{chunk-525CAZPY.js → chunk-4DKHLZRI.js} +6 -6
- package/dist/{chunk-4AS7LKKQ.js → chunk-62EEAUID.js} +2 -2
- package/dist/{chunk-AQEQX4FF.js → chunk-AHC7OU75.js} +385 -55
- package/dist/chunk-AHC7OU75.js.map +1 -0
- package/dist/{chunk-EFJ2NH6E.js → chunk-AJZWLKLQ.js} +63 -14
- package/dist/chunk-AJZWLKLQ.js.map +1 -0
- package/dist/chunk-B5FC3MYZ.js +395 -0
- package/dist/chunk-B5FC3MYZ.js.map +1 -0
- package/dist/{chunk-67UZEWWF.js → chunk-DNFQTSMJ.js} +53 -11
- package/dist/chunk-DNFQTSMJ.js.map +1 -0
- package/dist/{chunk-TLX3H2CL.js → chunk-E5WLQSKA.js} +231 -40
- package/dist/chunk-E5WLQSKA.js.map +1 -0
- package/dist/chunk-ECLC4CIJ.js +1181 -0
- package/dist/chunk-ECLC4CIJ.js.map +1 -0
- package/dist/chunk-EEX6QNXV.js +127 -0
- package/dist/chunk-EEX6QNXV.js.map +1 -0
- package/dist/chunk-EFXVSQ5J.js +931 -0
- package/dist/chunk-EFXVSQ5J.js.map +1 -0
- package/dist/{chunk-RFJ6Q6LE.js → chunk-ENLB7NYV.js} +577 -164
- package/dist/chunk-ENLB7NYV.js.map +1 -0
- package/dist/{chunk-IME7R2UI.js → chunk-FPR7SKIE.js} +1268 -883
- package/dist/chunk-FPR7SKIE.js.map +1 -0
- package/dist/{chunk-VPNLCMVR.js → chunk-HFB74CLM.js} +13 -5
- package/dist/chunk-HFB74CLM.js.map +1 -0
- package/dist/{chunk-5RDRAO4B.js → chunk-HOZXKOE3.js} +37 -1
- package/dist/chunk-HOZXKOE3.js.map +1 -0
- package/dist/chunk-IDYWN37T.js +36 -0
- package/dist/chunk-IDYWN37T.js.map +1 -0
- package/dist/{chunk-YC6W4EJH.js → chunk-JGF5HLSI.js} +193 -75
- package/dist/chunk-JGF5HLSI.js.map +1 -0
- package/dist/{chunk-5YHTKWJN.js → chunk-KDNWMKXV.js} +2447 -907
- package/dist/chunk-KDNWMKXV.js.map +1 -0
- package/dist/{chunk-Y2VHN7TC.js → chunk-MEOB2DZA.js} +11 -1
- package/dist/chunk-MEOB2DZA.js.map +1 -0
- package/dist/{chunk-KBQS35OV.js → chunk-MV5PQNIA.js} +2 -2
- package/dist/chunk-N2G4Q7ME.js +2672 -0
- package/dist/chunk-N2G4Q7ME.js.map +1 -0
- package/dist/{chunk-62QL5QUP.js → chunk-OJBMDM5H.js} +17 -1
- package/dist/chunk-OJBMDM5H.js.map +1 -0
- package/dist/{chunk-56SIEMDX.js → chunk-PTDC7ZUP.js} +2 -2
- package/dist/{chunk-X4NJT436.js → chunk-REASC2QG.js} +67 -28
- package/dist/chunk-REASC2QG.js.map +1 -0
- package/dist/chunk-SBTU5BEC.js +114 -0
- package/dist/chunk-SBTU5BEC.js.map +1 -0
- package/dist/{chunk-HQI7NYGX.js → chunk-SWPDKLNM.js} +2 -2
- package/dist/{chunk-W3XFHEF4.js → chunk-UCFJ43OS.js} +140 -28
- package/dist/chunk-UCFJ43OS.js.map +1 -0
- package/dist/{chunk-QYBD22H5.js → chunk-W7SLYS3M.js} +80 -16
- package/dist/chunk-W7SLYS3M.js.map +1 -0
- package/dist/{chunk-SOW4WRQP.js → chunk-WV3O5PGX.js} +3 -3
- package/dist/{chunk-JTPOI3KU.js → chunk-XJRJVXJR.js} +262 -38
- package/dist/chunk-XJRJVXJR.js.map +1 -0
- package/dist/{chunk-224FJZLH.js → chunk-YIL6HTJX.js} +228 -5
- package/dist/chunk-YIL6HTJX.js.map +1 -0
- package/dist/{command-approvals-VBIU6BLM.js → command-approvals-ZRY66LCW.js} +5 -4
- package/dist/context-C3RAP6K5.js +41 -0
- package/dist/{hook-approvals-ZVT4V7IM.js → hook-approvals-DPQD5H65.js} +5 -4
- package/dist/index.js +657 -754
- package/dist/index.js.map +1 -1
- package/dist/{interactive-assets-T4I4SEHU.js → interactive-assets-73AQO5WP.js} +11 -6
- package/dist/{interactive-assets-T4I4SEHU.js.map → interactive-assets-73AQO5WP.js.map} +1 -1
- package/dist/job-runner.js +10 -3
- package/dist/job-runner.js.map +1 -1
- package/dist/job-supervisor.js +13 -4
- package/dist/job-supervisor.js.map +1 -1
- package/dist/loader-DQNPDKOX.js +27 -0
- package/dist/{mcp-approvals-HJ2VDYMC.js → mcp-approvals-27LJYADQ.js} +5 -4
- package/dist/{mcp-config-RNB5LEVE.js → mcp-config-GXSODNCM.js} +3 -2
- package/dist/memory-store-I6Z54VUN.js +51 -0
- package/dist/{model-window-store-MTSQ5JDJ.js → model-window-store-6VKXXTPJ.js} +4 -3
- package/dist/{openai-compatible-2OB54YST.js → openai-compatible-IPUNBJ53.js} +5 -3
- package/dist/{permission-approvals-YLV6UC5W.js → permission-approvals-Q5IMFEZI.js} +5 -4
- package/dist/{permissions-DHOKF3W2.js → permissions-7Y35X6HZ.js} +26 -9
- package/dist/provider-approvals-5AGH3RVT.js +76 -0
- package/dist/{sandbox-WRYTZH3D.js → sandbox-N2RY72GN.js} +6 -5
- package/dist/{scrollback-35ME2GFX.js → scrollback-GD76GNM4.js} +36 -28
- package/dist/scrollback-GD76GNM4.js.map +1 -0
- package/dist/sdk.d.ts +876 -64
- package/dist/sdk.js +57 -37
- package/dist/sdk.js.map +1 -1
- package/dist/settings-loader-KDBH45AQ.js +43 -0
- package/dist/{settings-removed-JWWPT4WA.js → settings-removed-3ATWJEF3.js} +2 -2
- package/dist/shell-selection-Z5WORZU4.js +31 -0
- package/dist/shell-selection-Z5WORZU4.js.map +1 -0
- package/package.json +5 -5
- package/dist/app-GE4OHJOC.js.map +0 -1
- package/dist/chunk-224FJZLH.js.map +0 -1
- package/dist/chunk-5RDRAO4B.js.map +0 -1
- package/dist/chunk-5YHTKWJN.js.map +0 -1
- package/dist/chunk-62QL5QUP.js.map +0 -1
- package/dist/chunk-67UZEWWF.js.map +0 -1
- package/dist/chunk-6OQQOMZR.js +0 -785
- package/dist/chunk-6OQQOMZR.js.map +0 -1
- package/dist/chunk-AQEQX4FF.js.map +0 -1
- package/dist/chunk-C7ADUNPR.js +0 -273
- package/dist/chunk-C7ADUNPR.js.map +0 -1
- package/dist/chunk-EFJ2NH6E.js.map +0 -1
- package/dist/chunk-IME7R2UI.js.map +0 -1
- package/dist/chunk-J62VZYOB.js +0 -1339
- package/dist/chunk-J62VZYOB.js.map +0 -1
- package/dist/chunk-JTPOI3KU.js.map +0 -1
- package/dist/chunk-QYBD22H5.js.map +0 -1
- package/dist/chunk-RFJ6Q6LE.js.map +0 -1
- package/dist/chunk-S4JMID4G.js +0 -259
- package/dist/chunk-S4JMID4G.js.map +0 -1
- package/dist/chunk-TLX3H2CL.js.map +0 -1
- package/dist/chunk-VPNLCMVR.js.map +0 -1
- package/dist/chunk-W3XFHEF4.js.map +0 -1
- package/dist/chunk-X4NJT436.js.map +0 -1
- package/dist/chunk-X66I2NQO.js +0 -137
- package/dist/chunk-X66I2NQO.js.map +0 -1
- package/dist/chunk-Y2VHN7TC.js.map +0 -1
- package/dist/chunk-YC6W4EJH.js.map +0 -1
- package/dist/context-CBOBDY5Q.js +0 -38
- package/dist/loader-S77OVTHH.js +0 -18
- package/dist/memory-store-DZUT5HUH.js +0 -75
- package/dist/scrollback-35ME2GFX.js.map +0 -1
- package/dist/settings-loader-JUNPGAAM.js +0 -35
- package/dist/shell-selection-CDHGJ4GC.js +0 -17
- /package/dist/{additional-roots-H7UNAEHS.js.map → additional-roots-VAWRQ625.js.map} +0 -0
- /package/dist/{chunk-525CAZPY.js.map → chunk-4DKHLZRI.js.map} +0 -0
- /package/dist/{chunk-4AS7LKKQ.js.map → chunk-62EEAUID.js.map} +0 -0
- /package/dist/{chunk-KBQS35OV.js.map → chunk-MV5PQNIA.js.map} +0 -0
- /package/dist/{chunk-56SIEMDX.js.map → chunk-PTDC7ZUP.js.map} +0 -0
- /package/dist/{chunk-HQI7NYGX.js.map → chunk-SWPDKLNM.js.map} +0 -0
- /package/dist/{chunk-SOW4WRQP.js.map → chunk-WV3O5PGX.js.map} +0 -0
- /package/dist/{command-approvals-VBIU6BLM.js.map → command-approvals-ZRY66LCW.js.map} +0 -0
- /package/dist/{context-CBOBDY5Q.js.map → context-C3RAP6K5.js.map} +0 -0
- /package/dist/{hook-approvals-ZVT4V7IM.js.map → hook-approvals-DPQD5H65.js.map} +0 -0
- /package/dist/{loader-S77OVTHH.js.map → loader-DQNPDKOX.js.map} +0 -0
- /package/dist/{mcp-approvals-HJ2VDYMC.js.map → mcp-approvals-27LJYADQ.js.map} +0 -0
- /package/dist/{mcp-config-RNB5LEVE.js.map → mcp-config-GXSODNCM.js.map} +0 -0
- /package/dist/{memory-store-DZUT5HUH.js.map → memory-store-I6Z54VUN.js.map} +0 -0
- /package/dist/{model-window-store-MTSQ5JDJ.js.map → model-window-store-6VKXXTPJ.js.map} +0 -0
- /package/dist/{openai-compatible-2OB54YST.js.map → openai-compatible-IPUNBJ53.js.map} +0 -0
- /package/dist/{permission-approvals-YLV6UC5W.js.map → permission-approvals-Q5IMFEZI.js.map} +0 -0
- /package/dist/{permissions-DHOKF3W2.js.map → permissions-7Y35X6HZ.js.map} +0 -0
- /package/dist/{sandbox-WRYTZH3D.js.map → provider-approvals-5AGH3RVT.js.map} +0 -0
- /package/dist/{settings-loader-JUNPGAAM.js.map → sandbox-N2RY72GN.js.map} +0 -0
- /package/dist/{settings-removed-JWWPT4WA.js.map → settings-loader-KDBH45AQ.js.map} +0 -0
- /package/dist/{shell-selection-CDHGJ4GC.js.map → settings-removed-3ATWJEF3.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sandbox.ts","../src/tools/glob-regex.ts"],"sourcesContent":["import {\n accessSync,\n closeSync,\n constants,\n existsSync,\n lstatSync,\n openSync,\n readdirSync,\n readlinkSync,\n readSync,\n realpathSync,\n statSync,\n} from 'fs';\nimport { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from 'path';\nimport { homedir, platform } from 'os';\nimport type { ResolvedSettings } from './settings.js';\nimport { isOutside, resolveWorkspacePath, type PathComparison } from './tools/path-utils.js';\nimport type { CommandExecution } from './types/runtime.js';\nimport { globToRegex } from './tools/glob-regex.js';\n\nexport interface Sandbox {\n /**\n * Resolve a shell command into a sandboxed argv spawn.\n *\n * `workspaceRoot` is the only directory bound writable by default, and it is\n * deliberately not the command's working directory: `workdir` is a\n * model-supplied tool argument, and binding it would let the model widen its\n * own sandbox to any path — `workdir: \"/\"` would shadow every other mount and\n * hand back the whole host filesystem, read-write. Callers must keep the\n * working directory inside the workspace; extra paths go through\n * `sandbox.filesystem.allowWrite`.\n *\n * Returns null if the sandbox is unavailable.\n */\n wrap(command: string, workspaceRoot: string): CommandExecution | null;\n /** One-line description of the policy actually enforced, for diagnostics. */\n describe(): string;\n}\n\n/**\n * Try to detect bubblewrap (bwrap) on the system PATH.\n * Returns the bwrap binary path or null if not found.\n */\nfunction isExecutableFile(path: string): boolean {\n try {\n if (!statSync(path).isFile()) return false;\n accessSync(path, constants.X_OK);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction detectBwrap(): string | null {\n const candidates = [\n ...(process.env.PATH ?? '')\n .split(delimiter)\n .filter(Boolean)\n .map((dir) => join(dir, 'bwrap')),\n '/usr/local/bin/bwrap',\n '/opt/homebrew/bin/bwrap',\n '/usr/bin/bwrap',\n ];\n return candidates.find(isExecutableFile) ?? null;\n}\n\n/**\n * Why a specific command will not run inside a real sandbox boundary.\n *\n * These are the only three ways a Bash command escapes the sandbox, and both\n * settings that depend on \"is this command actually sandboxed?\"\n * (`allowUnsandboxedCommands` and `autoAllowBashIfSandboxed`) are decided from\n * this one enumeration so they can never disagree about a given command.\n */\nexport type SandboxSkipReason = 'disabled' | 'excluded' | 'unavailable';\n\nexport interface SandboxCoverage {\n /** True only when this exact command will execute inside a bubblewrap namespace. */\n sandboxed: boolean;\n /** Set whenever `sandboxed` is false. */\n reason?: SandboxSkipReason;\n}\n\n/**\n * Does a command match one of the `sandbox.excludedCommands` escape patterns?\n *\n * The whole command string is matched, not just its first line: the permission\n * path and the execution path must agree about the same text, and\n * `getPrimaryArg` truncates a multi-line command to its first line.\n */\nexport function matchesExcludedCommand(command: string, patterns: string[] = []): boolean {\n return patterns.some((pattern) => globToRegex(pattern).test(command));\n}\n\n/**\n * Is the bubblewrap backend usable on this machine at all? Independent of\n * settings, so callers can distinguish \"turned off\" from \"cannot be turned on\".\n */\nexport function sandboxBackendAvailable(): boolean {\n return platform() !== 'win32' && detectBwrap() !== null;\n}\n\n/**\n * Decide whether one specific command genuinely executes inside the sandbox.\n *\n * Ordering is deliberate. `enabled` is checked first because \"sandboxing is\n * off\" is the actionable diagnosis even on a host with no bwrap installed, and\n * because it is false by default — the common path never touches the\n * filesystem. `excludedCommands` is checked before the backend probe for the\n * same reason: an excluded command is unsandboxed regardless of what is\n * installed, so probing PATH for it would be wasted work.\n *\n * `backendAvailable` is injectable purely so tests can exercise the\n * missing-bwrap branch on a host that has bwrap installed.\n */\nexport function sandboxCoverage(\n command: string,\n settings: ResolvedSettings['sandbox'],\n backendAvailable: () => boolean = sandboxBackendAvailable,\n): SandboxCoverage {\n if (!settings.enabled) return { sandboxed: false, reason: 'disabled' };\n if (matchesExcludedCommand(command, settings.excludedCommands))\n return { sandboxed: false, reason: 'excluded' };\n if (!backendAvailable()) return { sandboxed: false, reason: 'unavailable' };\n return { sandboxed: true };\n}\n\nconst SKIP_REASON_DETAIL: Record<SandboxSkipReason, string> = {\n disabled: 'sandboxing is turned off (sandbox.enabled is false)',\n excluded: 'the command matches a sandbox.excludedCommands pattern, which runs it unsandboxed',\n unavailable:\n 'the bubblewrap (bwrap) sandbox backend is unavailable on this platform or is not installed',\n};\n\nconst SKIP_REASON_REMEDY: Record<SandboxSkipReason, string> = {\n disabled: 'Set sandbox.enabled to true',\n excluded: 'Remove the matching pattern from sandbox.excludedCommands',\n unavailable: 'Install bubblewrap (bwrap)',\n};\n\n/**\n * Where a `sandbox.*` key has to be written to be honoured.\n *\n * Named in every refusal because the obvious fix does not work: from\n * `.book/settings.json` or `.book/settings.local.json` the loader honours these\n * keys only in the tightening direction, so a user who \"solved\" the refusal by\n * editing the project's own settings would see it reported as successful and stay\n * refused (#373).\n */\nconst SANDBOX_SETTINGS_LOCATION = 'in ~/.book/settings.json, or with a --settings file';\n\n/**\n * The refusal shown when `sandbox.allowUnsandboxedCommands` is false and a\n * command would otherwise have run outside the sandbox. It names the setting\n * that caused the refusal, the specific reason the command could not be\n * sandboxed, and what to do about it.\n *\n * The settings location is attached to the *alternative* rather than to every\n * reason, because it is only part of that alternative: `allowUnsandboxedCommands`\n * and `excludedCommands` are loader-ignored in a workspace file, so \"set it\n * there\" is the instruction for them. A missing `bwrap` is fixed by installing\n * bubblewrap, and telling a user to edit `~/.book/settings.json` about it sends\n * them to look for a package name that is not in the file.\n */\nexport function unsandboxedRefusalMessage(reason: SandboxSkipReason): string {\n const remedy =\n reason === 'unavailable'\n ? 'Install bubblewrap (bwrap) to enable sandboxing.'\n : `${SKIP_REASON_REMEDY[reason]} ${SANDBOX_SETTINGS_LOCATION}, or set ` +\n 'sandbox.allowUnsandboxedCommands to true there to allow this command to run unsandboxed.';\n return [\n 'Refused to run this command outside the sandbox:',\n `${SKIP_REASON_DETAIL[reason]}.`,\n 'sandbox.allowUnsandboxedCommands is false, so unsandboxed commands are not permitted.',\n remedy,\n ].join(' ');\n}\n\n/**\n * Everything outside `sandbox.*` that decides whether the two policy switches\n * can actually bite. Passed as one object rather than as trailing booleans so a\n * new condition cannot be added to the permission path and forgotten here —\n * which is how `book doctor` starts reporting a policy stronger than the\n * enforced one.\n */\nexport interface SandboxPolicyState {\n /** A sandbox was really created, so some command can genuinely be confined. */\n sandboxActive: boolean;\n /**\n * The user configured `permissions.deny` / `permissions.ask` rules. See\n * `sandboxAutoAllows` in permissions.ts: any such rule keeps the default ask,\n * because a shell line evades a glob far too easily for the matched rules to\n * be the whole protection.\n */\n adjudicationConfigured: boolean;\n}\n\n/**\n * Effective (not merely configured) state of the two policy switches, for\n * `book doctor`. A setting whose configured value cannot bite is reported as\n * inert rather than as enabled, so the reported policy matches the enforced one.\n */\nexport function sandboxPolicySummary(\n settings: ResolvedSettings['sandbox'],\n state: SandboxPolicyState,\n): { unsandboxedCommands: string; autoAllowBash: string } {\n return {\n unsandboxedCommands: settings.allowUnsandboxedCommands\n ? 'allowed (sandbox.allowUnsandboxedCommands=true)'\n : 'refused (sandbox.allowUnsandboxedCommands=false)',\n autoAllowBash: !settings.autoAllowBashIfSandboxed\n ? 'off — every Bash call goes through the normal permission path (sandbox.autoAllowBashIfSandboxed=false)'\n : !state.sandboxActive\n ? 'inert — no command is sandboxed here, so nothing is auto-allowed (sandbox.autoAllowBashIfSandboxed=true)'\n : state.adjudicationConfigured\n ? 'inert — permissions.deny/ask rules are configured, so every Bash call still goes through the normal permission path (sandbox.autoAllowBashIfSandboxed=true)'\n : 'on for genuinely sandboxed commands (sandbox.autoAllowBashIfSandboxed=true)',\n };\n}\n\n/**\n * The host facts the argv builder depends on: which paths exist, whether they\n * are directories, what a small file says, and the path semantics used to\n * resolve configured entries.\n *\n * Injectable so the generated argv can be pinned by tests on any platform.\n * The unit suite runs on Windows CI, where probing the real filesystem finds\n * none of the POSIX system mounts (`resolve('/usr')` lands on `C:\\usr`), so a\n * test against the real host would be asserting on binds that were never\n * emitted. Production always uses the real host below.\n */\nexport interface SandboxHost {\n /** Path semantics for configured entries; tests inject path.win32/path.posix. */\n path: PathComparison & {\n resolve(...segments: string[]): string;\n join(...segments: string[]): string;\n dirname(path: string): string;\n };\n exists(path: string): boolean;\n isDirectory(path: string): boolean;\n /**\n * File contents, or null when the file is missing or unreadable. The read is\n * capped at `maxBytes` — a workspace-controlled file must not be able to make\n * the host read without bound — and the cap is per call because the files\n * differ by orders of magnitude: a `gitdir:` pointer is one line, a repository\n * config is a page or more and a `hooksPath` can sit anywhere in it.\n */\n readFile(path: string, maxBytes?: number): string | null;\n /** The size of a regular file in bytes, or null for anything else. */\n fileSize(path: string): number | null;\n /** Entry names of a directory; empty when it is absent, unreadable, or not one. */\n readDir(path: string): string[];\n /** What a path is *without* following a final symlink, or null when nothing is there. */\n entryKind(path: string): SandboxEntryKind | null;\n /** The path a symlink names, as written; null when the path is not a symlink. */\n linkTarget(path: string): string | null;\n /** A path with its links followed, or null when it cannot be resolved. */\n realpath(path: string): string | null;\n homedir(): string;\n}\n\n/** What a path is, as `lstat` sees it: a symlink is not yet what it points at. */\nexport type SandboxEntryKind = 'file' | 'directory' | 'symlink' | 'other';\n\n/**\n * The most bytes read out of a workspace-controlled control file. These files\n * carry one line — a `gitdir:` pointer, a `commondir` — and a workspace that can\n * make the host read anything at all can make it read gigabytes, so the read is\n * bounded rather than the file trusted to be small.\n */\nconst MAX_CONTROL_FILE_BYTES = 4096;\n\n/**\n * The cap for a repository config, which is the exception: it is a document, not\n * a pointer, and a busy one is a few hundred kilobytes of remotes before the\n * `[core]` stanza that says where the hooks live. At 4 KiB a `hooksPath` declared\n * late in a real config was simply not seen, and the directory it named stayed\n * writable. A config past *this* cap is refused instead of truncated — a\n * half-read config reads as one with no `hooksPath`, which is the unsafe answer.\n */\nconst MAX_GIT_CONFIG_BYTES = 1024 * 1024;\n\n/**\n * How deep the two workspace-controlled walks below go: `modules/<a>/<b>/…` for a\n * submodule path with segments, and `include` chains. Both are bounded by the\n * filesystem rather than by anything the walk can check, and both are\n * cycle-guarded by the sets they record what they have already visited.\n */\nconst MAX_WALK_DEPTH = 8;\n\nfunction entryKind(path: string): SandboxEntryKind | null {\n try {\n const stats = lstatSync(path);\n if (stats.isSymbolicLink()) return 'symlink';\n if (stats.isDirectory()) return 'directory';\n if (stats.isFile()) return 'file';\n return 'other';\n } catch {\n return null;\n }\n}\n\n/**\n * The production host: the real filesystem and the real path module.\n *\n * Exported so the contract it has to keep — a bounded read of a regular file,\n * and no throw for a path that is not there — is tested directly rather than\n * inferred from the argv it happens to produce.\n */\nexport const realSandboxHost: SandboxHost = {\n path: { resolve, join, relative, isAbsolute, sep, dirname },\n exists: existsSync,\n // A path that vanished between the probe and the spawn is absent as far as the\n // mount list is concerned. `statSync` throws for it, and a throw here would\n // abort the whole bwrap invocation over a race, not a protection failure.\n isDirectory: (path) => {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n },\n readFile: (path, maxBytes = MAX_CONTROL_FILE_BYTES) => {\n // `lstat` first, and only a regular file. An unguarded readFileSync blocks\n // for ever on a FIFO (a workspace that plants one at `.git` would hang\n // every later sandboxed command) and never ends on a character device such\n // as `/dev/zero`.\n if (entryKind(path) !== 'file') return null;\n let fd: number | undefined;\n try {\n fd = openSync(path, 'r');\n const buffer = Buffer.alloc(maxBytes);\n const bytes = readSync(fd, buffer, 0, maxBytes, 0);\n return buffer.subarray(0, bytes).toString('utf-8');\n } catch {\n return null;\n } finally {\n if (fd !== undefined) {\n try {\n closeSync(fd);\n } catch {\n // Nothing to do: the read result stands either way.\n }\n }\n }\n },\n fileSize: (path) => {\n // A size is only meaningful for a regular file. A directory, a link and a\n // path that is not there are all \"no size to compare against the cap\".\n if (entryKind(path) !== 'file') return null;\n try {\n return statSync(path).size;\n } catch {\n return null;\n }\n },\n readDir: (path) => {\n try {\n return readdirSync(path);\n } catch {\n return [];\n }\n },\n entryKind,\n linkTarget: (path) => {\n try {\n return readlinkSync(path);\n } catch {\n return null;\n }\n },\n realpath: (path) => {\n try {\n return realpathSync(path);\n } catch {\n return null;\n }\n },\n homedir,\n};\n\n/**\n * Resolve a configured sandbox path. `~` is expanded the same way the rest of\n * the codebase expands it; a relative entry stays relative to the process cwd,\n * which `unbindablePaths` reports so it does not pass silently.\n */\nfunction expandPath(path: string, host: SandboxHost): string {\n if (path === '~') return host.homedir();\n if (path.startsWith('~/')) return host.path.join(host.homedir(), path.slice(2));\n return host.path.resolve(path);\n}\n\n/** bwrap fails the whole invocation if a bind source does not exist. */\nfunction bindIfPresent(args: string[], flag: string, path: string, host: SandboxHost): void {\n const target = expandPath(path, host);\n if (host.exists(target)) args.push(flag, target, target);\n}\n\n/**\n * Configured filesystem paths that cannot be applied because nothing exists at\n * them. bwrap aborts the whole invocation on a missing bind source, so these\n * have to be skipped — but a skipped rule is unenforced policy the user\n * believes is active, so it is reported rather than dropped quietly.\n */\nexport function unbindablePaths(\n settings: ResolvedSettings['sandbox'],\n host: SandboxHost = realSandboxHost,\n): string[] {\n return [\n ...settings.filesystem.allowWrite,\n ...settings.filesystem.denyWrite,\n ...settings.filesystem.denyRead,\n ].filter((path) => !host.exists(expandPath(path, host)));\n}\n\n/**\n * True when the declared network policy asks for something finer than\n * all-or-nothing. bwrap has no DNS or domain awareness, so a per-domain policy\n * cannot be honoured as written.\n */\nexport function hasDomainPolicy(settings: ResolvedSettings['sandbox']): boolean {\n return settings.network.allowedDomains.length > 0 || settings.network.deniedDomains.length > 0;\n}\n\n/**\n * A path with its links followed, in the caller's own path semantics.\n *\n * The nearest existing ancestor is what gets resolved and the rest of the path\n * re-appended, so a path that does not exist yet — an absent `.book/`, the\n * `hooks/` of a repository that has none — still comes back through a link\n * above it. Containment is decided on the canonical pair for the same reason\n * `resolveWorkspacePath` decides it there (#264): one directory has more than\n * one spelling, and a lexical comparison reads a path inside the workspace as\n * being outside it, or the reverse.\n */\nfunction canonicalPath(path: string, host: SandboxHost): string {\n const absolute = host.path.resolve(path);\n let existing = absolute;\n for (;;) {\n const real = host.realpath(existing);\n if (real !== null) return host.path.resolve(real, host.path.relative(existing, absolute));\n const parent = host.path.dirname(existing);\n if (parent === existing) return absolute;\n existing = parent;\n }\n}\n\n/**\n * True when `candidate` is the workspace root or something below it.\n *\n * Both sides are canonicalised first, and the comparison itself is\n * {@link isOutside} — the same predicate the file tools use — so a directory\n * called `..meta` is inside the tree it is in rather than outside it on the\n * strength of its first two characters.\n *\n * A path outside the root is not bound writable — it is not bound at all — so\n * there is nothing to protect there, and mounting it would hand the command a\n * view of a repository the user never opened.\n */\nfunction insideWorkspace(candidate: string, workspaceRoot: string, host: SandboxHost): boolean {\n return !isOutside(canonicalPath(workspaceRoot, host), canonicalPath(candidate, host), host.path);\n}\n\n/** A git directory git itself would act on, and the work tree it serves. */\ninterface GitDirRef {\n dir: string;\n /**\n * The work tree a relative `core.hooksPath` in this directory's config is\n * resolved against — the root git resolves it against, which is the work tree\n * root and not the git dir.\n */\n workTree: string;\n}\n\n/**\n * The git directories a workspace's `.git` names, in the order they apply.\n *\n * `.git` is usually the directory itself, but a worktree or submodule carries a\n * one-line `.git` file (`gitdir: <path>`; relative to the workspace when it is\n * not absolute), and that directory can itself name a `commondir` — the shared\n * part of the repository, where `hooks/` and `config` actually live for a\n * linked worktree. Under a repository sit `<gitdir>/worktrees/<name>` for every\n * linked worktree and `<gitdir>/modules/<path>` for every submodule, and a\n * submodule of a submodule nests again, so the walk is a breadth-first one over\n * directories already accepted.\n *\n * Only directories inside the workspace are returned, and only ones that are\n * directories at all: `gitdir:` and `commondir` are workspace-controlled text\n * and can name a plain file, which `<dir>/hooks` is not a directory bubblewrap\n * can mount.\n */\nfunction workspaceGitDirs(workspaceRoot: string, host: SandboxHost): GitDirRef[] {\n const root = host.path.resolve(workspaceRoot);\n const found: GitDirRef[] = [];\n const add = (candidate: string, workTree: string): void => {\n const dir = host.path.resolve(candidate);\n if (found.some((entry) => entry.dir === dir)) return;\n if (!host.isDirectory(dir) || !insideWorkspace(dir, root, host)) return;\n found.push({ dir, workTree: host.path.resolve(workTree) });\n };\n const dotGit = host.path.join(root, '.git');\n if (!host.exists(dotGit)) return found;\n if (host.isDirectory(dotGit)) {\n add(dotGit, root);\n } else {\n const target = /^gitdir:\\s*(.+)$/m.exec(host.readFile(dotGit) ?? '')?.[1]?.trim();\n if (!target) return found;\n add(host.path.isAbsolute(target) ? target : host.path.join(root, target), root);\n }\n // A linked worktree's own dir holds only `config.worktree`, `HEAD` and the\n // `gitdir` naming its work tree; the hooks and the shared config live in the\n // common dir.\n for (let index = 0; index < found.length; index++) {\n const { dir, workTree } = found[index];\n const common = host.readFile(host.path.join(dir, 'commondir'))?.trim();\n if (common) add(host.path.isAbsolute(common) ? common : host.path.join(dir, common), workTree);\n for (const name of host.readDir(host.path.join(dir, 'worktrees'))) {\n const linked = host.path.join(dir, 'worktrees', name);\n // `gitdir` inside a linked worktree's dir names the work tree's own\n // `.git` file, so its directory is the work tree root — what a relative\n // `core.hooksPath` in that worktree's config is resolved against.\n const workTreeGitdir = host.readFile(host.path.join(linked, 'gitdir'))?.trim();\n add(linked, workTreeGitdir ? host.path.dirname(workTreeGitdir) : root);\n }\n // `<gitdir>/modules/<path>` holds the git dir of the submodule at `<path>`,\n // and `<path>` is the submodule's own path in the work tree — which can have\n // segments, so `git submodule add url libs/deep` puts the git dir at\n // `modules/libs/deep` and `modules/libs` holds no `HEAD` and no `config`. It\n // is a container, and its own entries are the candidates. `HEAD` or `config`\n // is what tells the two apart, and the walk is depth-bounded because the tree\n // is workspace-controlled.\n const addModules = (modulesRoot: string, prefix: string, depth: number): void => {\n for (const name of host.readDir(modulesRoot)) {\n const moduleDir = host.path.join(modulesRoot, name);\n const workTreePath = prefix ? `${prefix}/${name}` : name;\n const isGitDir = ['HEAD', 'config'].some((file) =>\n host.exists(host.path.join(moduleDir, file)),\n );\n if (isGitDir) add(moduleDir, host.path.join(root, workTreePath));\n else if (depth < MAX_WALK_DEPTH) addModules(moduleDir, workTreePath, depth + 1);\n }\n };\n addModules(host.path.join(dir, 'modules'), '', 0);\n }\n // Every work tree the walk found carries its own `.git` pointer file, and the\n // pointer is what the host reads to decide *which* git dir those control files\n // are: `sub/.git` says `gitdir: ../.git/modules/sub`, which resolves against\n // the work tree holding it and not against the workspace root.\n for (const { workTree } of [...found]) {\n if (!insideWorkspace(workTree, root, host)) continue;\n const pointer = host.path.join(workTree, '.git');\n if (host.isDirectory(pointer)) continue;\n const target = /^gitdir:\\s*(.+)$/m.exec(host.readFile(pointer) ?? '')?.[1]?.trim();\n if (!target) continue;\n add(host.path.isAbsolute(target) ? target : host.path.join(workTree, target), workTree);\n }\n return found;\n}\n\n/**\n * The part of a git config line after the `=`, as git reads it.\n *\n * Two rules the raw text does not show: a `;` or `#` outside quotes ends the\n * value (so `hooksPath = .husky ; note` is `.husky`, and the directory named by\n * a path that kept the comment never exists), and a quoted value carries\n * backslash escapes. Without the first, the protection lands at a path the\n * repository chose and never fires; the second is rare in a path and cheap.\n */\nfunction parseConfigValue(raw: string): string {\n const text = raw.trim();\n if (!text.startsWith('\"')) {\n let quoted = false;\n for (let index = 0; index < text.length; index += 1) {\n const character = text[index];\n if (character === '\"') quoted = !quoted;\n else if (!quoted && (character === ';' || character === '#'))\n return text.slice(0, index).trim();\n }\n return text;\n }\n let value = '';\n for (let index = 1; index < text.length; index += 1) {\n const character = text[index];\n if (character === '\"') break;\n if (character !== '\\\\') {\n value += character;\n continue;\n }\n const escaped = text[index + 1];\n index += 1;\n value +=\n escaped === 'n' ? '\\n' : escaped === 't' ? '\\t' : escaped === 'b' ? '\\b' : (escaped ?? '');\n }\n return value;\n}\n\n/**\n * The section a `[header]` line names, or undefined when the line carries a\n * subsection. `[core \"x\"]` is not `[core]`, and counting it as one protects a\n * directory git never reads hooks from.\n */\nfunction configSectionName(line: string): string | undefined {\n const header = /^\\[\\s*([A-Za-z0-9.-]+)(\\s[^\\]]*)?\\]$/.exec(line.trim());\n if (!header || header[2]) return undefined;\n return header[1].toLowerCase();\n}\n\n/**\n * Every `core.hooksPath` a git config file states, or none.\n *\n * A deliberately small `[core]` parser rather than a git-config dependency: the\n * key is the one setting that moves `hooks/` out of the git dir, and it is read\n * from a file a workspace can rewrite. Git takes the *last* value for a key, so\n * a parser that stops at the first is protecting a directory git ignores; every\n * value is returned instead, which also makes the answer independent of which\n * one git would have chosen — the safer of the two mistakes to make.\n */\nexport function readCoreHooksPaths(configText: string): string[] {\n const found: string[] = [];\n let inCore = false;\n for (const line of configText.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed.startsWith('[')) {\n inCore = configSectionName(trimmed) === 'core';\n continue;\n }\n if (!inCore) continue;\n const separator = trimmed.indexOf('=');\n if (separator === -1) continue;\n if (trimmed.slice(0, separator).trim().toLowerCase() !== 'hookspath') continue;\n const value = parseConfigValue(trimmed.slice(separator + 1));\n if (value && !found.includes(value)) found.push(value);\n }\n return found;\n}\n\n/**\n * The files a git config pulls in, from `[include]` and `[includeIf \"…\"]`.\n *\n * An included file is part of the same config as far as git is concerned: a\n * `core.hooksPath` set in one is the directory git runs hooks from, and one\n * left writable inside the workspace is a control file the host reads on its next\n * commit. An `includeIf` condition is not evaluated here — only a file that\n * exists can be included, and a file the sandbox creates later is the case this\n * cannot reach.\n */\nexport function readGitConfigIncludes(configText: string): string[] {\n const found: string[] = [];\n let include = false;\n for (const line of configText.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed.startsWith('[')) {\n // An `includeIf` header carries the condition as its subsection, so the\n // section name is matched on the text up to it rather than exactly.\n include = configSectionName(trimmed) === 'include' || /^\\[includeif\\b/i.test(trimmed);\n continue;\n }\n if (!include) continue;\n const separator = trimmed.indexOf('=');\n if (separator === -1) continue;\n if (trimmed.slice(0, separator).trim().toLowerCase() !== 'path') continue;\n const value = parseConfigValue(trimmed.slice(separator + 1));\n if (value) found.push(value);\n }\n return found;\n}\n\n/**\n * One workspace path the namespace must shield, and how.\n *\n * - `directory` — a directory the host reads after the command exits. Bound\n * read-only when it exists, masked with an empty read-only tmpfs when it does\n * not, which both hides any contents the host grows there later and makes\n * creating it fail.\n * - `file` — a file the host reads after the command exits. Bound read-only\n * when it exists: bubblewrap aborts the whole invocation on a tmpfs mounted\n * over a file path (\"Not a directory\"), so an absent file cannot be masked at\n * all.\n * - `hidden-file` — a file whose *contents* must not reach the command, bound\n * over with `/dev/null`. A read-only bind of `.book/` still serves what is in\n * it, and the namespace shares the host network, so a key the user stored\n * there would be one `cat` away from a sandboxed command.\n */\nexport type ProtectedMountKind = 'directory' | 'file' | 'hidden-file';\n\nexport interface ProtectedMount {\n /** Already resolved through a symlink: the path the host reads the control file at. */\n path: string;\n kind: ProtectedMountKind;\n /** False when nothing exists at it, which for a directory becomes a mask. */\n present: boolean;\n}\n\nexport interface ProtectedWorkspacePaths {\n /**\n * Directories the namespace must pin as mount points in their own right.\n *\n * A read-only child mount is not enough: the directory that holds it can still\n * be renamed away and replaced, and rename(2) on a path that is not a mount\n * point succeeds. `--bind <dir> <dir>` pins it, which is what makes\n * `mv .git .git.old` fail with EBUSY instead of redirecting the host's git.\n *\n * Only the workspace's own top-level `.git` is pinned. Pinning every git dir\n * the walk found — a linked worktree's and a submodule's admin directory —\n * pinned those too, and a sandboxed `git worktree remove` then failed with\n * EBUSY halfway through, leaving the worktree gone and its admin directory\n * behind. Their control files are read-only on their own, which is what this\n * relies on instead.\n */\n pinnedDirectories: string[];\n mounts: ProtectedMount[];\n /**\n * Control paths this namespace cannot protect at all. A refusal rather than a\n * mount: bwrap aborts the whole invocation on the shapes that could be\n * mistaken for one, and guessing would leave a control file writable while\n * reporting a sandbox.\n */\n refusals: ControlPathRefusal[];\n}\n\n/**\n * Why a workspace cannot be sandboxed, and which path says so.\n *\n * Both are shapes a repository controls and no mount can cover. The run is\n * refused with the path named, because the fix — replace the link, trim the\n * config — is a change to the workspace, not to a Book setting.\n */\nexport type ControlPathRefusalReason = 'symlinked-control-path' | 'oversized-git-config';\n\nexport interface ControlPathRefusal {\n path: string;\n reason: ControlPathRefusalReason;\n}\n\n/** A refusal in the words the model is told, which name the path to change. */\nexport function describeControlPathRefusal(refusal: ControlPathRefusal): string {\n if (refusal.reason === 'oversized-git-config') {\n return (\n `Refused to run this command in the sandbox: ${refusal.path} is larger than the ` +\n `${MAX_GIT_CONFIG_BYTES / 1024 / 1024} MiB read limit for a git config file, so the ` +\n 'core.hooksPath it may declare cannot be found and the hooks directory it names cannot ' +\n 'be made read-only. Trim the config, or run this command without the sandbox.'\n );\n }\n return (\n `Refused to run this command in the sandbox: ${refusal.path} is a symlink, and the ` +\n 'sandbox cannot protect a symlinked control path. The link itself sits in the writable ' +\n 'workspace, so a command could remove it, put a directory or file in its place, and write ' +\n 'the settings, hooks or repository config the host then reads through it. Replace the ' +\n 'symlink with the file or directory it names, or run this command without the sandbox.'\n );\n}\n\n/**\n * The workspace control files a sandboxed command must not be able to rewrite.\n *\n * Each one is read by the *host* after the command exits, which is what makes a\n * write to it an escape rather than a scratch file: `.book/settings.local.json`\n * is resolved on the host from the next session (it can switch the sandbox off\n * or add `Bash(*)`), a `.git/hooks/*` script runs on the next host `git commit`,\n * and `core.hooksPath` / `core.fsmonitor` in `.git/config` redirect both. The\n * permission layer asks before a file tool touches `.book/`, but a prompt is one\n * approval the model can ask for — and a shell command is not a file tool (#373).\n *\n * What this cannot cover is stated in the docs rather than implied here: a\n * sandboxed command can still *create* a pointer file a plain repository has\n * none of (`.git/commondir`), because closing that needs the whole git\n * directory read-only, which would make sandboxed `git commit`, `checkout` and\n * `fetch` fail.\n */\nexport function protectedWorkspacePaths(\n workspaceRoot: string,\n host: SandboxHost = realSandboxHost,\n): ProtectedWorkspacePaths {\n const root = host.path.resolve(workspaceRoot);\n const result: ProtectedWorkspacePaths = { pinnedDirectories: [], mounts: [], refusals: [] };\n const seen = new Set<string>();\n\n /**\n * The mount one control path needs, or null when it needs none.\n *\n * A symlink is refused rather than resolved. Protecting the link's *target* is\n * not protection, because the link itself sits in the writable workspace:\n * `rm .book && mkdir .book && …` replaces it, and the target protection then\n * guards a file nothing reads any more. There is no mount that pins a symlink,\n * and a mount shaped for one aborts the whole bwrap invocation, so a repository\n * shipping one is a workspace Book declines to sandbox.\n *\n * A path that is neither a regular file nor a directory — a FIFO, a socket, a\n * device — is skipped instead: nothing reads it as a control file, and there is\n * no mount bwrap can make over it.\n */\n const target = (path: string, kind: ProtectedMountKind): ProtectedMount | null => {\n const resolved = host.path.resolve(path);\n if (seen.has(resolved)) return null;\n const describe = (present: boolean): ProtectedMount => {\n seen.add(resolved);\n return { path: resolved, kind, present };\n };\n const first = host.entryKind(resolved);\n if (first === 'symlink') {\n if (!result.refusals.some((entry) => entry.path === resolved)) {\n result.refusals.push({ path: resolved, reason: 'symlinked-control-path' });\n }\n return null;\n }\n if (first === null) {\n // Absent: only a directory has a mask, and only a directory needs one —\n // an absent file has nothing to protect and nothing to create that git or\n // the settings resolver would read as one.\n return kind === 'directory' ? describe(false) : null;\n }\n if (first === 'directory') {\n // A mask is `--ro-bind /dev/null <dir>`, which bwrap rejects as \"Is a\n // directory\" — and one aborts the whole invocation, so a repository that\n // ships `.book/settings.local.json` as a directory would otherwise break\n // every sandboxed command in the workspace, not only its own.\n return kind === 'hidden-file' ? null : describe(true);\n }\n return first === 'file' ? describe(true) : null;\n };\n\n const addDirectory = (path: string): void => {\n const mount = target(path, 'directory');\n if (mount) result.mounts.push(mount);\n };\n const addFile = (path: string): void => {\n const mount = target(path, 'file');\n if (mount) result.mounts.push(mount);\n };\n const addMaskedFile = (path: string): void => {\n const mount = target(path, 'hidden-file');\n if (mount) result.mounts.push(mount);\n };\n\n // The settings directory, which holds both workspace layers plus the trust\n // store writes and `migrations.json`.\n addDirectory(host.path.join(root, '.book'));\n // ...and the one file in it that can hold a provider credential. Read-only is\n // not enough: the namespace shares the host network, so the value has to be\n // unreadable inside it, not merely unwritable.\n addMaskedFile(host.path.join(root, '.book', 'settings.local.json'));\n // The legacy config file the host reads on the next launch, where its\n // `baseUrl` wins over every settings layer (src/config.ts).\n addFile(host.path.join(root, '.bookrc.json'));\n\n const dotGit = host.path.join(root, '.git');\n // Only a real directory inside the workspace is pinned: a symlinked one is\n // refused above, and a pin for anything else is a mount over a path the\n // namespace cannot build one for.\n if (host.entryKind(dotGit) === 'directory') {\n // The git dir is bound onto itself, writable, so it is a mount point: the\n // read-only children below can then be swapped for a fresh writable\n // directory only by renaming this one away, which EBUSY refuses.\n result.pinnedDirectories.push(dotGit);\n } else {\n // A worktree's `.git` is a file naming its git dir. Read-only, so the\n // pointer itself cannot be rewritten at another git directory.\n addFile(dotGit);\n }\n\n // Config files already read, so an `include` chain that names one of them ends\n // at the file rather than descending for ever.\n const configsRead = new Set<string>();\n const readConfig = (file: string, workTree: string, depth: number): void => {\n const at = host.path.resolve(file);\n if (configsRead.has(at)) return;\n configsRead.add(at);\n const size = host.fileSize(at);\n if (size !== null && size > MAX_GIT_CONFIG_BYTES) {\n // Truncating a config reads as one with no `hooksPath` in it, which is the\n // answer that leaves the hooks directory writable. Refusing says so instead.\n result.refusals.push({ path: at, reason: 'oversized-git-config' });\n return;\n }\n const text = host.readFile(at, MAX_GIT_CONFIG_BYTES);\n if (text === null) return;\n addFile(at);\n for (const hooksPath of readCoreHooksPaths(text)) {\n const directory = host.path.isAbsolute(hooksPath)\n ? hooksPath\n : host.path.join(workTree, hooksPath);\n if (insideWorkspace(directory, root, host)) addDirectory(directory);\n }\n if (depth >= MAX_WALK_DEPTH) return;\n for (const included of readGitConfigIncludes(text)) {\n const target = included.startsWith('~/')\n ? host.path.join(host.homedir(), included.slice(2))\n : host.path.join(host.path.dirname(at), included);\n readConfig(target, workTree, depth + 1);\n }\n };\n\n for (const gitDir of workspaceGitDirs(root, host)) {\n addDirectory(host.path.join(gitDir.dir, 'hooks'));\n for (const name of ['config', 'config.worktree', 'commondir', 'gitdir']) {\n addFile(host.path.join(gitDir.dir, name));\n }\n // The work tree's own `.git`: a directory is the git dir itself, already\n // pinned, and a file is the pointer the host reads to find the git dir. It is\n // read-only for the same reason as the one at the workspace root — rewriting\n // it to `gitdir: ../evil` moves every control path above onto a repository\n // the command built.\n const workTreePointer = host.path.join(gitDir.workTree, '.git');\n if (host.entryKind(workTreePointer) === 'file') addFile(workTreePointer);\n // `core.hooksPath` moves the hook directory out of the git dir entirely —\n // husky v9 points it at `.husky/_` — so a config this namespace leaves\n // writable is a hooks directory it never looked at. An `[include]`d file is\n // part of that config, and is followed to the same depth.\n for (const name of ['config', 'config.worktree']) {\n readConfig(host.path.join(gitDir.dir, name), gitDir.workTree, 0);\n }\n }\n\n return result;\n}\n\nfunction mountProtected(args: string[], mount: ProtectedMount, host: SandboxHost): void {\n const target = host.path.resolve(mount.path);\n if (mount.kind === 'hidden-file') {\n if (mount.present) args.push('--ro-bind', '/dev/null', target);\n return;\n }\n if (mount.kind === 'file') {\n if (mount.present) args.push('--ro-bind', target, target);\n return;\n }\n if (mount.present) {\n args.push('--ro-bind', target, target);\n return;\n }\n // `--tmpfs` creates the mount point, and bwrap does that *through* the\n // workspace bind: a sandboxed command in a workspace with no `.book/` leaves\n // an empty `.book/` directory on the host as a side effect. It is recorded in\n // docs/guide/configuration.md; the alternative would be a writable hole.\n args.push('--tmpfs', target, '--remount-ro', target);\n}\n\n/**\n * Build the bubblewrap argument vector for one command.\n *\n * Mount order is significant: bwrap applies operations in sequence and a later\n * mount shadows an earlier one covering the same path. The workspace bind\n * therefore comes *after* the system read-only binds and the /tmp tmpfs (a\n * workspace under /usr/local or /tmp would otherwise be silently shadowed), and\n * explicit filesystem policy comes after the workspace so it can override it.\n *\n * The workspace control files are the exception to \"policy comes after the\n * workspace\": their read-only mounts come after the workspace bind, so the\n * workspace does not shadow them, and before `denyWrite`/`denyRead`, which stay\n * free to be stricter still. The one `allowWrite` entry that is applied after\n * them is one that names a protected path or something under it, which is the\n * user asking for that path to be writable again; a broader root stays before,\n * so it cannot reopen what the protections just closed.\n *\n * Exported for testing: it does not require bwrap to be installed, and with an\n * injected `host` its output is fully determined by its arguments.\n */\nexport function buildSandboxExecution(\n bwrapPath: string,\n command: string,\n workspaceRoot: string,\n settings: ResolvedSettings['sandbox'],\n host: SandboxHost = realSandboxHost,\n): CommandExecution {\n const args: string[] = [];\n\n // Fresh PID/IPC/UTS namespaces, and the sandbox dies if the spawning process\n // does.\n //\n // Deliberately NOT --new-session: it makes bwrap call setsid(), which moves\n // the sandboxed tree into its own process group. Every teardown path here\n // (KillShell, foreground timeout, Ctrl-C) signals the group Node created with\n // `detached: true` and confirms death with `kill(-pgid, 0)`, so the group\n // would read as empty while the command kept running — a kill that reports\n // success and does nothing. The TIOCSTI hardening --new-session buys is moot\n // anyway: all three spawn sites pipe stdio and never hand over a tty.\n args.push('--unshare-pid', '--unshare-ipc', '--unshare-uts', '--die-with-parent');\n args.push('--proc', '/proc');\n args.push('--dev', '/dev');\n\n // Minimal system directories, read-only.\n for (const dir of ['/usr', '/lib', '/lib64', '/bin', '/sbin', '/etc', '/opt']) {\n bindIfPresent(args, '--ro-bind', dir, host);\n }\n\n args.push('--tmpfs', '/tmp');\n\n // The workspace is writable. Bound after /tmp so a workspace inside /tmp\n // (every mkdtemp-based test run, among others) survives the tmpfs.\n bindIfPresent(args, '--bind', workspaceRoot, host);\n\n // Declared filesystem policy overrides the defaults above.\n //\n // The control files are computed first, because where an `allowWrite` entry\n // lands in the sequence depends on them. An entry *at or under* a protected\n // path is the user asking for exactly that path to be writable again — a hook\n // installer that has to write `.git/hooks/pre-commit`, a cache under\n // `.book/cache` — so it is applied after the protected mounts and wins. An\n // entry *above* them (a parent directory, `/home`, the workspace itself) stays\n // before, because a broad root that reopened `.book/` or a hooks directory on\n // its way down would not be an opt-in but the default back the protections\n // exist to remove. Only a trusted layer can hold one: a workspace layer's\n // `allowWrite` entries are dropped by the loader (#373).\n const protectedPaths = protectedWorkspacePaths(workspaceRoot, host);\n const isProtectedArea = (path: string): boolean => {\n const canonical = canonicalPath(expandPath(path, host), host);\n return protectedPaths.mounts.some(\n (mount) => !isOutside(canonicalPath(mount.path, host), canonical, host.path),\n );\n };\n\n // Control files the host acts on after this command exits come after the\n // workspace bind, so the workspace does not shadow them.\n //\n // The pins come before the opt-in binds as well: a pin is a writable self-bind\n // of the git dir, so an `allowWrite` entry *under* it emitted earlier is\n // shadowed by it and silently does nothing — the entry the user wrote, dropped\n // without a word.\n for (const dir of protectedPaths.pinnedDirectories) {\n args.push('--bind', host.path.resolve(dir), host.path.resolve(dir));\n }\n for (const path of settings.filesystem.allowWrite) {\n if (isProtectedArea(path)) continue;\n bindIfPresent(args, '--bind', path, host);\n }\n for (const mount of protectedPaths.mounts) mountProtected(args, mount, host);\n // ...and only now the opt-ins for the protected areas themselves.\n for (const path of settings.filesystem.allowWrite) {\n if (!isProtectedArea(path)) continue;\n bindIfPresent(args, '--bind', path, host);\n }\n // A mask hides a file's *contents*, and an opt-in bind for the directory that\n // holds it undoes that: `allowWrite: [\"<workspace>/.book\"]` puts the whole\n // directory back in reach, provider credential included. The masks are\n // therefore emitted again after every opt-in, so the last mount of a masked\n // path is `/dev/null` whichever entry named its parent.\n for (const mount of protectedPaths.mounts) {\n if (mount.kind === 'hidden-file') mountProtected(args, mount, host);\n }\n\n for (const path of settings.filesystem.denyWrite) bindIfPresent(args, '--ro-bind', path, host);\n // bwrap cannot unmount a subpath, so a denied path is masked instead. The\n // mask has to match the kind: --tmpfs needs to mkdir its target, so pointing\n // it at a file aborts the entire invocation with \"Not a directory\" — and a\n // credentials *file* is the most natural thing to deny. Files are masked with\n // /dev/null instead, which makes reads fail outright.\n for (const path of settings.filesystem.denyRead) {\n const target = expandPath(path, host);\n if (!host.exists(target)) continue;\n if (host.isDirectory(target)) args.push('--tmpfs', target);\n else args.push('--ro-bind', '/dev/null', target);\n }\n\n // bwrap can only share or unshare the network wholesale. A declared\n // per-domain policy cannot be enforced as written, so fail closed rather\n // than hand out the unrestricted host network the caller did not ask for.\n args.push(hasDomainPolicy(settings) ? '--unshare-net' : '--share-net');\n\n args.push('--cap-drop', 'ALL');\n\n // The command string is a single argv element: bash inside the sandbox\n // parses it, and nothing outside the sandbox ever does.\n args.push('--', '/bin/bash', '-c', command);\n\n return { file: bwrapPath, args };\n}\n\n/**\n * The context facts the sandbox decision reads. Structural rather than\n * `ToolContext` so this stays free of the tool layer: `Bash` and the `Check`\n * tool both pass one, and neither type is imported here.\n */\nexport interface SandboxDecisionContext {\n sandbox?: ResolvedSettings['sandbox'];\n workspaceRoot: string;\n /** The session's sandbox, built once per distinct settings object. */\n runtime?: { sandbox(settings: ResolvedSettings['sandbox']): Sandbox | null };\n}\n\n/**\n * How one command is to be run, or why it may not run at all.\n *\n * `exec` is set only on the sandboxed outcome, and is what the caller spawns\n * instead of the command. `reason` is set when a sandbox was asked for and\n * skipped, which is what `allowUnsandboxedCommands` governs; it is absent when\n * no sandbox was configured at all. `error` means do not run anything.\n */\nexport interface SandboxDecision {\n sandboxed: boolean;\n exec?: CommandExecution;\n reason?: SandboxSkipReason;\n error?: string;\n}\n\n/**\n * Decide how `command` is to be run, and refuse it when it must not run at all.\n *\n * One function, because the order of these checks is the policy:\n *\n * 1. no sandbox configured, or the command is excluded → run unsandboxed,\n * unless `allowUnsandboxedCommands` is false, which refuses it;\n * 2. a workdir outside the workspace → refused, since the sandbox binds the\n * workspace and the command would run somewhere the caller did not ask for;\n * 3. a control path the namespace cannot protect → refused, since every mount\n * that could stand in for one either aborts bwrap or leaves the control file\n * writable;\n * 4. otherwise wrap it, and report the wrapped argv.\n *\n * A path that ends with the command running outside a bubblewrap namespace all\n * funnels through here, so `allowUnsandboxedCommands: false` cannot be enforced\n * on one escape and quietly missed on another. `Bash` and the `Check` tool both\n * call this, so a check cannot run unsandboxed in a session that refuses\n * unsandboxed commands (#373).\n *\n * The refusal and failure messages are the ones `Bash` has always returned; they\n * are part of the tool contract, not an implementation detail.\n */\nexport function decideSandboxExecution(\n ctx: SandboxDecisionContext,\n command: string,\n workdir: string,\n): SandboxDecision {\n // Every path that ends with the command running outside a bubblewrap\n // namespace funnels through here.\n const unsandboxed = (reason: SandboxSkipReason): SandboxDecision =>\n ctx.sandbox && !ctx.sandbox.allowUnsandboxedCommands\n ? { sandboxed: false, reason, error: unsandboxedRefusalMessage(reason) }\n : { sandboxed: false, reason };\n\n if (!ctx.sandbox?.enabled) return unsandboxed('disabled');\n if (matchesExcludedCommand(command, ctx.sandbox.excludedCommands)) return unsandboxed('excluded');\n\n // The sandbox binds the workspace, not this workdir. A workdir outside it\n // would leave the command with no working directory inside the namespace,\n // and silently running it against the workspace root instead would execute\n // somewhere the caller did not ask for.\n if (!resolveWorkspacePath(ctx.workspaceRoot, workdir)) {\n return {\n sandboxed: false,\n error: `workdir is outside the sandboxed workspace: ${workdir}. Add it to sandbox.filesystem.allowWrite, or run without the sandbox.`,\n };\n }\n // createSandbox emits one-time diagnostics, so reuse the session's instance\n // rather than rebuilding it per command.\n //\n // Its `failIfUnavailable` refusal is a throw, and this decision is the only\n // thing between that throw and a tool exception worded in terms of `Bash`\n // whichever tool happened to be running. It is caught here and returned as the\n // decision's own error, so `Check` and `Bash` report it the same way and the\n // message is the one the settings author has to act on.\n let sandbox: Sandbox | null;\n try {\n sandbox = ctx.runtime ? ctx.runtime.sandbox(ctx.sandbox) : createSandbox(ctx.sandbox);\n } catch (error) {\n return { sandboxed: false, error: error instanceof Error ? error.message : String(error) };\n }\n // A control path the namespace cannot protect is refused, not worked around,\n // and only once a sandbox is actually in hand: with none, the command runs\n // unsandboxed under the policy the settings author chose, which is not this\n // decision to tighten.\n if (sandbox) {\n const refusal = protectedWorkspacePaths(ctx.workspaceRoot).refusals[0];\n if (refusal) return { sandboxed: false, error: describeControlPathRefusal(refusal) };\n }\n const exec = sandbox?.wrap(command, ctx.workspaceRoot);\n if (exec) return { sandboxed: true, exec };\n if (ctx.sandbox.failIfUnavailable) {\n return { sandboxed: false, error: 'Sandbox unavailable and failIfUnavailable is set' };\n }\n return unsandboxed('unavailable');\n}\n\n/**\n * The line a git command needs when it could not update the repository config.\n *\n * Inside the sandbox a git dir's `config` is bound read-only (#373), which is\n * what stops a sandboxed command from repointing `core.hooksPath` at a script it\n * plants. It is also why `git checkout -b`, `git push -u`, `git remote add`,\n * `git branch --set-upstream-to` and `git config` fail there with\n * \"could not write config file\" / \"could not lock config file\" — an error that\n * says nothing about the read-only bind, so the model retries the same command\n * or rewrites the command to avoid the config instead of running it outside.\n *\n * Returned as a line the caller appends to the output that carried the error, and\n * only for a sandboxed command: unsandboxed git has no read-only config, and an\n * unrelated file that happens to be called `config` is not this.\n *\n * `command` is what rules out `git config --global`. That writes `~/.gitconfig`,\n * which the namespace binds read-only only if the user listed it in\n * `denyWrite` — a different remedy, and pointing this note at `.git/config` for\n * it would send the model after the wrong file.\n */\nexport function withGitConfigReadOnlyNotice(\n text: string,\n output: string,\n sandboxed: boolean,\n command = '',\n): string {\n if (!sandboxed || !/could not (?:write|lock) config file/.test(output)) return text;\n if (writesAGlobalConfig(command)) return text;\n return (\n `${text}\\n` +\n 'Note: .git/config is read-only inside the sandbox, so git cannot update repository ' +\n 'configuration there. Run this command outside the sandbox.'\n );\n}\n\n/** Whether a `git config` invocation targets the user's or the system's config. */\nfunction writesAGlobalConfig(command: string): boolean {\n return /^\\s*(?:\\S+\\s+)*git\\s+config\\b[^|;&]*\\s--(?:global|system)\\b/.test(command);\n}\n\n/**\n * Create a sandbox wrapper if bwrap is available on this platform.\n * On Windows, returns null with a warning (sandbox not supported).\n *\n * @param settings - Resolved sandbox settings\n * @returns Sandbox instance or null\n */\nexport function createSandbox(settings: ResolvedSettings['sandbox']): Sandbox | null {\n if (!settings.enabled) return null;\n\n const os = platform();\n if (os === 'win32') {\n if (settings.failIfUnavailable) {\n throw new Error(\n 'Bash sandbox is not available on Windows. Disable sandbox.enabled or set failIfUnavailable to false.',\n );\n }\n console.warn('⚠ Bash sandbox is not available on Windows. Commands will run unsandboxed.');\n return null;\n }\n\n const bwrap = detectBwrap();\n if (!bwrap) {\n if (settings.failIfUnavailable) {\n throw new Error(\n 'bubblewrap (bwrap) not found. Install it to enable bash sandboxing, or set failIfUnavailable to false.',\n );\n }\n console.warn(\n '⚠ bubblewrap (bwrap) not found — install it for bash sandboxing. Commands will run unsandboxed.',\n );\n return null;\n }\n\n // Diagnostics belong to whoever builds the sandbox, and callers are expected\n // to build it once per session — `createSandbox` runs per Bash call would\n // repeat these on every command.\n if (hasDomainPolicy(settings)) {\n console.warn(\n '⚠ sandbox.network domain rules cannot be enforced by bubblewrap, which has no per-domain filtering. Network access is disabled entirely for sandboxed commands.',\n );\n }\n const unbindable = unbindablePaths(settings);\n if (unbindable.length > 0) {\n console.warn(\n `⚠ sandbox.filesystem rules skipped — nothing exists at: ${unbindable.join(', ')}. These paths are not protected.`,\n );\n }\n\n return {\n wrap(command: string, workspaceRoot: string): CommandExecution {\n return buildSandboxExecution(bwrap, command, workspaceRoot, settings);\n },\n describe(): string {\n return [\n `bubblewrap (${bwrap})`,\n hasDomainPolicy(settings) ? 'network disabled' : 'host network shared',\n `${settings.filesystem.allowWrite.length} extra writable path(s)`,\n `${settings.filesystem.denyWrite.length} read-only path(s)`,\n `${settings.filesystem.denyRead.length} masked path(s)`,\n unbindable.length > 0 ? `${unbindable.length} skipped (missing)` : 'all paths resolved',\n ].join('; ');\n },\n };\n}\n","import { posix, win32 } from 'node:path';\nimport picomatch from 'picomatch';\nimport { convertPathToPattern } from 'tinyglobby';\nimport { createDebugLogger } from '../debug-log.js';\n\n/**\n * Convert a glob pattern to a regex. Supports * (any chars) and ** (same as *).\n * The pattern is anchored at both ends (^...$).\n */\nexport function globToRegex(pattern: string): RegExp {\n const escaped = pattern.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*\\*|\\*/g, '.*');\n return new RegExp('^' + escaped + '$');\n}\n\nconst log = createDebugLogger('tools:glob');\n\n/**\n * The longest pattern handed to picomatch, the matcher tinyglobby walks with.\n *\n * The limit is the one `micromatch` enforced before this walk (`Input length (40000), exceeds max\n * characters (10000)`), so a pattern that was answered then is still answered. It is not what keeps\n * the process alive, though — see {@link MAX_GLOB_GROUP_DEPTH}.\n */\nexport const MAX_GLOB_PATTERN_LENGTH = 10_000;\n\n/**\n * How deeply one pattern may nest a group, and how many it may open.\n *\n * The length limit is not the guard that matters. Two patterns well inside it take the process\n * down, measured against this walk (`glob('…', { expandDirectories: false })`):\n *\n * - `'!('.repeat(2500) + ')'.repeat(2500)` — 7500 characters, refused by nothing: picomatch\n * compiles it in 201 ms and then *matching* one path against it aborts Node with\n * `FATAL ERROR: RegExpCompiler Allocation failed`, which is below the reach of any `catch`.\n * - `'+('.repeat(3300) + ')'.repeat(3300)` — 9900 characters: picomatch's own parse takes 145 s,\n * so the call blocks the event loop for minutes. It is a cubic cost in the nesting depth — 32\n * deep compiles in 5 ms, 256 in 74 ms, 2000 in 38 s — which is what makes a depth bound the\n * bound that matters.\n *\n * So the shape of a pattern is refused as well as its length: a nesting 32 deep, or 256 groups of\n * any kind. No real pattern is near either. A brace group is no help to an attacker here —\n * picomatch folds `{a,b}` into a regex alternation and compiles `{a,b}` × 200 (400 groups) in 2 ms —\n * so the count is a backstop for the shapes above, not a brace-expansion budget.\n */\nexport const MAX_GLOB_GROUP_DEPTH = 32;\nexport const MAX_GLOB_GROUPS = 256;\n\n/**\n * Why picomatch cannot be handed this pattern, in the words the tools report, or null when it can.\n *\n * Applied to a Glob pattern, to Grep's include pattern and to every entry of a walk's `ignore`\n * list alike, because a walk compiles all of them through the same matcher. A pattern that reaches\n * a walk has to be judged in the spelling the walk compiles — see {@link globWalkPlan}: `\\` is an\n * escape to picomatch and a separator to the walk, so the two spellings of `x\\{a,b\\}` open and close\n * a different number of groups.\n */\nexport function globPatternRefusal(pattern: string): string | null {\n if (pattern.length > MAX_GLOB_PATTERN_LENGTH) {\n return `Pattern is too long: a glob pattern may be at most ${MAX_GLOB_PATTERN_LENGTH} characters (this one is ${pattern.length})`;\n }\n let depth = 0;\n let deepest = 0;\n let groups = 0;\n for (let index = 0; index < pattern.length; index++) {\n const char = pattern[index];\n // An escaped character is a literal: `\\(`, `\\{` and `\\[` open nothing.\n if (char === '\\\\') {\n index++;\n continue;\n }\n if (char === '(' || char === '{' || char === '[') {\n groups++;\n depth++;\n if (depth > deepest) deepest = depth;\n if (groups > MAX_GLOB_GROUPS) {\n return `Pattern has too many groups: a glob pattern may open at most ${MAX_GLOB_GROUPS} of them (this one opens more)`;\n }\n if (depth > MAX_GLOB_GROUP_DEPTH) {\n return `Pattern nests groups too deeply: a glob pattern may nest at most ${MAX_GLOB_GROUP_DEPTH} deep (this one nests ${deepest})`;\n }\n } else if (char === ')' || char === '}' || char === ']') {\n depth = Math.max(0, depth - 1);\n }\n }\n return null;\n}\n\n/** Whether picomatch can be handed this pattern at all. */\nexport function globPatternWithinLimit(pattern: string): boolean {\n return globPatternRefusal(pattern) === null;\n}\n\n/** Told that an entry was dropped, with the entry itself and why. */\nexport type IgnoreDropReporter = (entry: string, reason: string) => void;\n\n/**\n * The default {@link IgnoreDropReporter}: one line per dropped entry through the debug logger, which\n * prints only when `BOOK_DEBUG=1`. It names the entry, because the entry is the whole of what is\n * being reported — `.gitignore` line 12, say — and the walk it no longer obeys is on every tool that\n * reads the file, so it is logged per walk and not once per entry list.\n */\nconst logIgnoreDrop: IgnoreDropReporter = (entry, reason) => {\n log.warn(\n `glob: dropping a .gitignore entry the walk cannot obey — ${reason}: ${JSON.stringify(entry)}`,\n );\n};\n\n/**\n * The ignore list a walk can be handed: entries the matcher is able to compile, spelled the way it\n * reads them.\n *\n * A walk compiles every entry of its `ignore` option exactly as it compiles the pattern, and those\n * entries are the repository's own `.gitignore` — `loadGitignore` hands them straight to the Glob\n * tool, to Grep's include walk and to the file-mention walk. One line of ten thousand nested braces\n * takes the process down there exactly as it does in a pattern, so it is dropped here, before any\n * walk is handed a list. Dropping one widens the walk by whatever it ignored, so each one is named\n * to the {@link IgnoreDropReporter}: the `.gitignore` the matcher cannot read is walked around\n * rather than obeyed, and that is worth being able to see.\n *\n * Three more entries are dropped for the same reason — not because they cannot be compiled, but\n * because the walk reads them as something other than what git means:\n *\n * - An entry whose walk form opens with a `..` hop (`../x`, `/../x`, `a/../..`) moves the crawl root\n * out of the workspace, so the walk lists that subtree on the way, and it still ignores nothing: the\n * ignore list is matched against `cwd`-relative paths, and `../x` cannot name one. `posix.normalize`\n * collapses `/..` the same way it collapses `a/..`, so a root-anchored entry is checked in the\n * spelling the respelling below would hand the walk.\n * - `!(x)` is an extglob to picomatch, so as an ignore entry it matches everything *except* `x` — the\n * reverse of the negated literal git reads it as, and it can empty a walk (measured: a two-entry\n * workspace went from two results to none).\n * - `/` and `/.` name the repository root, which is the walk's `cwd` itself; respelling them to `.`\n * would have the walk ignore its own root and answer nothing.\n *\n * A root-anchored entry (`/build`, `/build/`, `/build/**`) is also respelled, because the walk\n * reads it as an absolute path: tinyglobby normalizes `/build` against the walk's `cwd` into\n * `../../../build`, which moves the crawl root to `/` — the walk then lists every directory from\n * the filesystem root down to the workspace, and the line still ignores nothing. Git means the\n * entry relative to the repository root, which is the directory the walk starts in, so it becomes\n * `build` plus `build/**` (measured: the crawl root stays at `cwd`, and `build` is pruned with\n * everything under it). Only the walk boundary rewrites an entry; `loadGitignore` still returns the\n * file's own lines to everything else that reads them.\n *\n * A directory-only entry (`build/`, `/build/`) means what git means — that directory and everything\n * under it, but not a *file* named `build` — and the walk cannot be told that: it strips the trailing\n * `/` off an entry before matching it, so `build/` and `build` are the same entry. It is spelled\n * `build/**` plus one more globstar, which matches only what is *inside* the directory and not the\n * name itself. The walk cannot prune with that spelling: the ignored directory is read once and its\n * entries are dropped as it goes (measured on a tree of 1600 directories: three `readdir` calls\n * instead of two). The other spelling that covers the inside, `build/**`, also matches the bare name\n * — the file git keeps.\n */\nexport function globWalkIgnore(\n patterns: readonly string[],\n report: IgnoreDropReporter = logIgnoreDrop,\n): string[] {\n const walkable: string[] = [];\n for (const pattern of patterns) {\n const refusal = globPatternRefusal(pattern);\n if (refusal) {\n report(pattern, `${refusal}, so the walk covers what it ignored`);\n continue;\n }\n if (pattern.startsWith('!(')) {\n report(\n pattern,\n 'picomatch reads `!(` as an extglob, so as an ignore entry it matches everything but what it names',\n );\n continue;\n }\n const climb = climbOutOfWalk(pattern);\n if (climb) {\n report(\n pattern,\n `${climb}, so the walk would read outside the workspace and still ignore nothing`,\n );\n continue;\n }\n walkable.push(...walkIgnoreEntry(pattern));\n }\n return walkable;\n}\n\n/**\n * Why an entry would move a walk's crawl root out of the directory it starts in, or null when it\n * would not.\n *\n * Checked on the entry as the walk would read it, which is the entry with a root-anchoring `/`\n * dropped — the repository root is the directory the walk starts in, so `/../x` is `../x` — and then\n * lexically collapsed the way `posix.normalize` collapses it in `normalizePattern`. `..foo` and\n * `..c` are directories that begin with two dots, not hops, and are kept.\n */\nfunction climbOutOfWalk(entry: string): string | null {\n const spelling = entry.replace(/^!/, '').replace(/^\\/+/, '');\n const normalized = posix.normalize(spelling).split('/');\n return normalized[0] === '..' ? 'the walk reads it as a climb out of its own root' : null;\n}\n\n/**\n * One `.gitignore` entry in the spelling a walk reads, as the entries that make it work.\n *\n * A leading `!` (a negation, other than the extglob `!(`, which is dropped before this) is kept, and\n * a glob character anywhere in the entry is left for the matcher. An entry that is not anchored to\n * the walk's root is already one entry the matcher reads where it stands; a directory-only one is the\n * exception, because the walk strips the trailing `/` off it and reads it as the bare name.\n */\nfunction walkIgnoreEntry(entry: string): string[] {\n const negated = entry.startsWith('!') && !entry.startsWith('!(');\n const bang = negated ? '!' : '';\n const body = negated ? entry.slice(1) : entry;\n const relative = body.replace(/^\\/+/, '').replace(/\\/+$/, '');\n const dirOnly = body.endsWith('/') && !relative.endsWith('/**');\n if (!body.startsWith('/')) return dirOnly ? [`${bang}${relative}/**/*`] : [entry];\n // `/` and `/.` alone name the repository root, which is the walk's `cwd` itself: nothing to ignore.\n if (!relative || relative === '.') return [];\n const anchored = `${bang}${relative}`;\n if (dirOnly) return [`${anchored}/**/*`];\n return relative.endsWith('/**') ? [anchored] : [anchored, `${anchored}/**`];\n}\n\n/**\n * The pattern a walk is handed, and whether picomatch can be handed it at all.\n */\nexport interface GlobWalkPlan {\n /** The pattern in the spelling the walk reads — the string picomatch compiles. */\n pattern: string;\n /** Why the matcher cannot be handed that pattern, in the words the tools report, or null. */\n refusal: string | null;\n /**\n * Every directory the walk of {@link pattern} can return a result from — one per alternative of a\n * leading group, all of which have to be judged — or null when the pattern is refused.\n */\n scopes: string[] | null;\n}\n\n/**\n * What to hand a walk of this pattern from this directory, and whether it can be handed at all.\n *\n * The judgment and the walk are made from one pattern, so they cannot disagree: whatever the walk is\n * handed is the string that is judged, and both read it as the walk does. A refusal is reported\n * before the walk rather than by it, so the words the caller sees name the shape of the pattern\n * instead of a permission refusal that misfiled it (see `readToolTarget`).\n */\nexport function globWalkPlan(\n pattern: string,\n cwd: string,\n platform: NodeJS.Platform = process.platform,\n): GlobWalkPlan {\n const walked = globWalkPattern(pattern, cwd, platform);\n const refusal = globPatternRefusal(walked);\n return {\n pattern: walked,\n refusal,\n scopes: refusal ? null : confinedDirectoriesOfPattern(walked, cwd, platform),\n };\n}\n\n/**\n * The path module the platform being judged for reads its paths with.\n *\n * `win32` reads `D:\\ws\\src`, `D:/ws/src` and `/ws/src` (the root of the current drive) as absolute\n * paths of one filesystem; `posix` reads all three as relative segment lists. That is not a\n * spelling difference: a Windows pattern judged with the POSIX module came back as the *relative*\n * scope `../../../tmp/ws/src`, which the permission layer resolves against the workspace and so\n * judges as a place the pattern never named.\n */\ntype PathModule = Pick<\n typeof posix,\n 'isAbsolute' | 'join' | 'normalize' | 'parse' | 'relative' | 'resolve'\n>;\n\n/** {@link PathModule} for the platform a caller passed, not the one this process runs on. */\nfunction pathsOf(platform: NodeJS.Platform): PathModule {\n return platform === 'win32' ? win32 : posix;\n}\n\n/** A path as a walk spells it: resolved by the platform, and in the one separator a pattern uses. */\nfunction walkPath(value: string): string {\n return value.replaceAll('\\\\', '/');\n}\n\n/**\n * An absolute pattern as the walk reads it from `walkCwd`: the platform's relative spelling, which\n * is the one the walk is handed and the one every scope is judged against.\n *\n * The pattern is resolved against the *root* of the walk directory first, which is what fills in\n * the drive of a Windows pattern that names none: `/ws/src/*.ts` is the root of the current drive,\n * so it is `C:\\ws\\src\\*.ts` under a workspace on `C:`. Skipping that step, `win32.relative` reads\n * the two as two filesystems and hands the target back untouched — and a rooted pattern would then\n * be judged as `ws/src` *inside* the workspace instead of at the root of the drive.\n */\nfunction relativePatternOf(platformPaths: PathModule, walkCwd: string, body: string): string {\n const absolute = platformPaths.resolve(platformPaths.parse(walkCwd).root || '/', body);\n return walkPath(platformPaths.relative(walkCwd, absolute));\n}\n\n/**\n * A pattern in the form tinyglobby can read, which on Windows is not the form a caller writes.\n *\n * tinyglobby treats `\\` as an escape character, so a Windows pattern spelled with backslashes\n * (`C:\\ws\\**\\*.ts`) parses as a single escaped token: the walk matches nothing. An absolute Glob\n * therefore found no files at all, and a relative one (`src\\*.ts`) escaped the directory it named.\n * The separators are converted first, then the leading base is re-escaped by `convertPathToPattern`\n * and spliced back onto the dynamic tail.\n *\n * Only on Windows: a backslash in a POSIX pattern is a real escape, and converting it would read\n * `\\*` as a star.\n *\n * An absolute pattern inside the walk's own directory is handed to the walk as the part of it below\n * that directory. tinyglobby makes an absolute pattern relative by comparing it against an\n * *escaped* `cwd` (`escapePath`), so a workspace whose path holds a glob character — `project (2)`,\n * `repo[x]`, anything checked out under one — reads every absolute pattern as a climb into its\n * parent and then answers nothing at all: measured, a `src/a.ts` behind `project (2)` came back\n * empty for `<workspace>/src/*.ts` and one file for `src/*.ts`. Relative to the real `cwd` there is\n * nothing to climb, so this is the same walk with the metacharacters out of the comparison.\n */\nexport function globWalkPattern(\n pattern: string,\n cwd: string,\n platform: NodeJS.Platform = process.platform,\n): string {\n const platformPaths = pathsOf(platform);\n const spelled = posixPattern(pattern, platform);\n const body = spelled.endsWith('/') ? spelled.slice(0, -1) : spelled;\n const relative = platformPaths.isAbsolute(body.replace(ESCAPING_BACKSLASHES, ''))\n ? relativePatternOf(platformPaths, walkCwdOf(cwd, platformPaths), body)\n : '';\n if (relative) return relative;\n // The base is built from the pattern's own parts, which are `/`-separated on every platform.\n const base = staticBaseOfPattern(body);\n // A base that is not a prefix of the pattern cannot be spliced onto one: `**/*` starts where it\n // stands, and there is nothing in the pattern for a base to replace.\n if (!base || !body.startsWith(base)) return body;\n return `${convertPathToPattern(base)}${body.slice(base.length)}`;\n}\n\n/**\n * A pattern with the separators tinyglobby reads, which on Windows is not the form a caller writes.\n *\n * Only on Windows: a backslash in a POSIX pattern is a real escape, and converting it would read\n * `\\*` as a star. The platform is a parameter so that the Windows spelling is judged on any platform\n * (the judgement does not have a platform-dependent code path of its own — it reads what\n * `globWalkPattern` handed the walk).\n */\nfunction posixPattern(pattern: string, platform: NodeJS.Platform): string {\n return platform === 'win32' ? pattern.replaceAll('\\\\', '/') : pattern;\n}\n\n/** The `cwd` as the walk reads it: resolved by the platform, and in one separator. */\nfunction walkCwdOf(cwd: string, platformPaths: PathModule): string {\n return walkPath(platformPaths.resolve(cwd));\n}\n\n/** The leading `..` run of a normalized pattern, however many are in it. */\nconst PARENT_DIRECTORY = /^(\\/?\\.\\.)+/;\n/** The backslashes that only escape a glob character, which do not hide an absolute path. */\nconst ESCAPING_BACKSLASHES = /\\\\(?=[()[\\]{}!*+?@|])/g;\n/** A drive letter alone, which `posix.join` would read as a path segment rather than a root. */\nconst DRIVE_RELATIVE_PATH = /^[A-Za-z]:$/;\n\nfunction ensureNonDriveRelativePath(path: string): string {\n return path.replace(DRIVE_RELATIVE_PATH, (match) => `${match}/`);\n}\n\n/**\n * A scope as a path: joined and normalized by the platform, so a caller is handed `/ws/..c/b` rather\n * than the `/ws/c/../..c/b` it was built from. Normalizing cannot lose a hop — a `join` steps a `..`\n * only when a whole segment is one, which leaves `..c` (a name the walk matches) where it was. The\n * result is absolute wherever the walk directory is, which is what a caller resolving a scope\n * against its own roots needs: a relative one would be re-anchored to somewhere the walk never was.\n */\nfunction scopePath(platformPaths: PathModule, cwd: string, ...segments: readonly string[]): string {\n return ensureNonDriveRelativePath(\n walkPath(platformPaths.normalize(platformPaths.join(cwd, ...segments))),\n );\n}\n\n/**\n * One pattern split into the parts tinyglobby walks it part by part.\n *\n * Its own `splitPattern`, which is what decides where a walk stops descending: a pattern with no\n * usable part list is walked as the single part it spells, and that part contains a `/` — which is\n * what makes a leading globstar start where it stands rather than at a named directory.\n */\nfunction splitPatternParts(pattern: string): string[] {\n const parts = picomatch.scan(pattern, { parts: true }).parts;\n return parts?.length ? parts : [pattern];\n}\n\n/** Whether a segment has to be matched rather than looked for by name. */\nfunction isDynamicPart(part: string): boolean {\n const scan = picomatch.scan(part);\n return scan.isGlob || scan.negated;\n}\n\n/** The most alternatives a leading group is broken into before the run stops naming them. */\nconst MAX_LEADING_ALTERNATIVES = 64;\n\n/**\n * The named directories a pattern's parts open with: everything but its last segment, up to the\n * first segment that has to be matched.\n *\n * This is tinyglobby's own `commonPath` for a single pattern (`{ popTrailingGlobstar: true }`), and it\n * is where a walk *searches* from, which is why a brace group in the first segment (`{a,b}` then a\n * separator) walks the workspace while `src/*.ts` walks `src`: the group is a segment that has to be\n * matched, so the named run ends before it. A pattern ending in a globstar drops that globstar and\n * the segment before it, which is why `src/**` searches the workspace and not `src`.\n */\nfunction leadingPartsOfParts(parts: readonly string[], popTrailingGlobstar: boolean): string[] {\n const leading: string[] = [];\n for (let index = 0; index < parts.length; index++) {\n const part = parts[index];\n if (popTrailingGlobstar && part === '**' && !parts[index + 1]) {\n leading.pop();\n break;\n }\n if (index === parts.length - 1 || isDynamicPart(part)) break;\n leading.push(part);\n }\n return leading;\n}\n\n/**\n * The named leading directories of a pattern, as a path: the part of it the walk never has to\n * match. Empty when the walk starts where the pattern stands (a globstar, `*.ts`).\n */\nfunction staticBaseOfPattern(pattern: string): string {\n const leadingParts = leadingPartsOfParts(splitPatternParts(pattern), false);\n return leadingParts.length ? posix.join(...leadingParts) : '';\n}\n\n/** Whether a directory is inside another, or is it. */\nfunction holds(platformPaths: PathModule, root: string, path: string): boolean {\n const relativePath = walkPath(platformPaths.relative(root, path));\n return (\n relativePath === '' ||\n (!relativePath.startsWith('../') &&\n relativePath !== '..' &&\n !platformPaths.isAbsolute(relativePath))\n );\n}\n\n/**\n * Every directory a walk of this pattern can answer from, one run per alternative of the first\n * group it names, as the walk itself computes it.\n *\n * `normalizePattern` first, then the choice `buildCrawler` makes from what is left. Getting this\n * wrong is a permission bypass rather than a wrong answer, so every step here is measured against the\n * walk (see {@link globWalkScopes} and its tests):\n *\n * - The pattern is normalized first, which is a purely *lexical* collapse of `.` and `..`. It is\n * what makes a globstar followed by two parent hops a climb even though the first segment is a\n * glob: `normalize` drops the `*` and the `..` beside it, and the walk runs as a leading\n * `../`. A brace group is not touched — the segment `..}}` is not `..` — so a `..` spelled inside\n * one keeps the walk inside the directory in front of it.\n * - An absolute pattern is made relative to the `cwd` first, so one naming a directory elsewhere\n * arrives as a leading `..` run, and an absolute path the `cwd` itself ends in arrives without\n * one: `<workspace>/src/*.ts` walks `src`, not the workspace. Both readings are the platform's\n * ({@link pathsOf}), so `C:\\ws\\src\\*.ts` is made relative to `C:\\ws` on any platform the caller\n * asks this for.\n * - That run is a climb, and it cancels only against the tail of the `cwd`: `../ws/src/*.ts` from\n * `/a/ws` is the same directory as `src/*.ts`, and both walk `/a/ws/src`.\n * - What is left of the climb that did not cancel, plus the named run of the pattern's remaining\n * segments, is a directory the walk answers from: entries are matched against the `cwd`-relative\n * path they have, and no matcher can turn the named run into something else.\n *\n * The named run of a *search* drops the segment in front of a trailing globstar — that is the\n * directory the walk reads from on its way, which it may answer from anything under. The run a\n * judgment needs does not: `src/**` answers from `src` and not from the workspace (measured, with a\n * symlinked `src`, and through the permission layer's `guarded` answer).\n *\n * Every run here is inside the directory the walk searches from, and a run that is not — a group\n * spelling a `..` hop, `{..,src}/*` — is dropped, because no entry can have it: the walk formats\n * every entry as a `cwd`-relative path of a file *inside* the tree it searched.\n */\nfunction confinedDirectoriesOfPattern(\n pattern: string,\n cwd: string,\n platform: NodeJS.Platform,\n): string[] {\n const platformPaths = pathsOf(platform);\n const walkCwd = walkCwdOf(cwd, platformPaths);\n const body = pattern.endsWith('/') ? pattern.slice(0, -1) : pattern;\n const normalized = platformPaths.isAbsolute(body.replace(ESCAPING_BACKSLASHES, ''))\n ? relativePatternOf(platformPaths, walkCwd, body)\n : walkPath(platformPaths.normalize(body));\n\n const parts = splitPatternParts(normalized);\n const parentDirectory = PARENT_DIRECTORY.exec(normalized)?.[0];\n // The count of hops the walk itself works from. It is read off the leading `..` run of the\n // *pattern*, which is where the run really is: a segment that begins with two dots and then does\n // not stop (`....`, `..c`) is a directory, not one and a half hops, and counting it as one used to\n // hand `new Array(1.6666666666666667)` to a walk — a RangeError that took the run down — and to\n // answer for `../..c/b/c/**` with a directory the walk never reads.\n const climb = leadingClimbOfParts(parts);\n const cwdParts = walkCwd.split('/');\n // The count of hops the walk itself works from, read off the leading `..` run of the *pattern*,\n // which is where the run really is: a segment that begins with two dots and then does not stop\n // (`....`, `..c`, `..foo`) is a name the walk climbs one hop toward and then matches, not one and\n // a half hops to step over. It is the walk's own count, so it is fractional for those — and it is\n // this number, read as a whole, that used to hand `new Array(1.6666666666666667)` to a hop list\n // and take the run down with a RangeError.\n const walkedClimb = ((parentDirectory?.length ?? 0) + 1) / 3;\n let cancelled = 0;\n while (\n Number.isInteger(walkedClimb) &&\n cancelled < walkedClimb &&\n parts[cancelled + walkedClimb] !== undefined &&\n parts[cancelled + walkedClimb] === cwdParts[cwdParts.length + cancelled - walkedClimb]\n ) {\n cancelled++;\n }\n // Where the walk reads from, which holds every entry it can return. It is one of two directories,\n // and which one is the walk's own choice: the directory the climb lands on (which it also makes\n // its search directory when the climb leaves the `cwd`), or the named run of the pattern as the\n // caller wrote it, which is where a walk starts once it has cancelled its way back in.\n // The climb as the walk spells it *after* it has cancelled: the hops that cancelled against the\n // tail of the `cwd` are gone from it, which is why `../ws/src/*.ts` does not climb at all.\n const potentialRoot = scopePath(\n platformPaths,\n walkCwd,\n parentDirectory?.slice(cancelled * 3) ?? '',\n );\n const climbedOutOfCwd = potentialRoot[0] !== '.' && walkCwd.length > potentialRoot.length;\n const searchesFrom = climbedOutOfCwd\n ? potentialRoot\n : scopePath(platformPaths, walkCwd, ...leadingPartsOfParts(parts, true));\n\n if (!parentDirectory) {\n // No climb: the directory the walk searches from is the directory it answers from.\n return confinedRunsWithin(platformPaths, searchesFrom, leadingRunsOfParts(parts), walkCwd);\n }\n if (cancelled > 0) {\n // The walk rewrites the pattern as it cancels a hop against the tail of the `cwd`, so what it\n // filters with no longer spells what the caller wrote — `../..c/b/c/**` from a `cwd` ending in\n // `b/c` becomes `**`, matched from `<cwd>/../..c/b` — and a matcher that has lost its named run\n // can match anything under the directory the walk reads. Nothing narrower than that directory\n // can be said to hold every entry.\n return [searchesFrom];\n }\n // The climb is still in the pattern, so its hops are in front of the run and the entries sit under\n // the pattern's named segments measured from the directory it lands on.\n if (!Number.isInteger(walkedClimb)) {\n // A run that is only partly a hop names the directory after the hop (`..foo/*` reads the parent\n // and matches `..foo` in it), so nothing inside that directory is named and the directory the\n // walk reads is all there is. Counting it as a hop rather than as a name is the walk's reading,\n // and it is the reason this is not answered from the workspace: the walk lists the parent.\n return [searchesFrom];\n }\n const hops = new Array<string>(walkedClimb).fill('..');\n return confinedRunsWithin(\n platformPaths,\n searchesFrom,\n leadingRunsOfParts(parts.slice(climb)).map((run) => [...hops, ...run]),\n walkCwd,\n );\n}\n\n/** The leading `..` run of a split pattern: one hop per segment that is exactly `..`. */\nfunction leadingClimbOfParts(parts: readonly string[]): number {\n let climb = 0;\n while (parts[climb] === '..') climb++;\n return climb;\n}\n\n/**\n * The named directories a pattern's parts open with, one run per alternative of the first group it\n * names: `['src']` for a pattern under `src`, `['link']` and `['src']` for `{link,src}` followed by\n * a globstar, `[]` for a leading globstar. The last segment is never part of a run: `src/notes.txt`\n * answers from `src`.\n *\n * The alternatives are what a group *in the first segment* means to a permission judgment: the group\n * is matched, so the walk answers from wherever the alternative names, and both have to be judged.\n * They are enumerated with the walk's own brace expansion and dropped past\n * {@link MAX_LEADING_ALTERNATIVES} — an unenumerated group names nothing, so the run stops in front\n * of it, which is the wider and so the safer answer.\n */\nfunction leadingRunsOfParts(parts: readonly string[]): string[][] {\n let runs: string[][] = [[]];\n for (let index = 0; index < parts.length - 1; index++) {\n const part = parts[index];\n const alternatives = alternativesOfGroup(part);\n if (alternatives) {\n if (runs.length * alternatives.length > MAX_LEADING_ALTERNATIVES) break;\n runs = runs.flatMap((run) => alternatives.map((alternative) => [...run, alternative]));\n continue;\n }\n // A segment that has to be matched rather than looked for by name ends the run: the walk never\n // starts inside it, so nothing in it can be named as a directory the results are held in.\n if (isDynamicPart(part)) break;\n for (const run of runs) run.push(part);\n }\n return runs;\n}\n\n/**\n * The alternatives of a segment spelled as one plain brace group, or null when it is not one.\n *\n * A group that holds a range (`{1..12}`) or a nested group is not enumerated: either way the\n * alternative count is not bounded by what is written, and the caller's answer is then the run in\n * front of the group.\n */\nfunction alternativesOfGroup(part: string): string[] | null {\n if (part.length < 5 || !part.startsWith('{') || !part.endsWith('}')) return null;\n const body = part.slice(1, -1);\n if (!body.includes(',') || /\\.\\./.test(body)) return null;\n const alternatives: string[] = [];\n let nested = 0;\n let current = '';\n for (const char of body) {\n if (char === '{' || char === '(' || char === '[') nested++;\n else if (char === '}' || char === ')' || char === ']') nested--;\n if (char === ',' && nested === 0) {\n alternatives.push(current);\n current = '';\n continue;\n }\n current += char;\n }\n alternatives.push(current);\n // An alternative the walk has to match names no directory, so a group of nothing but those is a\n // group to stop in front of.\n const named = alternatives.filter(\n (alternative) => !alternative.includes('/') && !isDynamicPart(alternative),\n );\n return named.length ? named : null;\n}\n\n/** The runs as directories, keeping the ones the walk's own root holds, or that root when none is. */\nfunction confinedRunsWithin(\n platformPaths: PathModule,\n root: string,\n runs: readonly string[][],\n walkCwd: string,\n): string[] {\n const held = [\n ...new Set(\n runs\n .map((run) => scopePath(platformPaths, walkCwd, ...run))\n .filter((directory) => holds(platformPaths, root, directory)),\n ),\n ];\n return held.length ? held : [root];\n}\n\n/**\n * Every directory a walk of this pattern can answer from — or null when picomatch cannot be handed\n * the pattern at all.\n *\n * The walk's own answer to that question (see {@link confinedDirectoriesOfPattern}), so a caller\n * judging what a Glob reaches is judging the directory the walk really uses rather than how the\n * pattern reads. Every entry it returns is inside one of these, which is what a permission judgment\n * needs: it judges each, and the worst one decides. They are absolute — in the platform's own\n * reading of an absolute path, spelled the way a walk spells it — so a caller can resolve them\n * against the roots it serves rather than re-anchoring a `..` to a root the pattern never named.\n *\n * `platform` is the platform whose paths are being judged, which is not necessarily the one this\n * process runs on: it is what decides how an absolute pattern is recognized and a relative one\n * resolved (`pathsOf`), so the Windows reading of a Windows path is measurable on any host.\n *\n * Where the walk *searches* from is one of them or an ancestor of them, so a pattern that climbs\n * makes tinyglobby start from the directory the climb lands on and list that directory's subtree on\n * the way to the one it answers from; only what the pattern matches comes back (measured; the\n * `globWalkScopes` tests walk both).\n *\n * Assumes the options the Glob tool walks with: `expandDirectories: false` (a pattern naming a\n * directory is not walked as a subtree) and a single pattern. Both only ever narrow the walk to a\n * subtree of what is reported here.\n */\nexport function globWalkScopes(\n pattern: string,\n cwd: string,\n platform: NodeJS.Platform = process.platform,\n): string[] | null {\n return globWalkPlan(pattern, cwd, platform).scopes;\n}\n"],"mappings":"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","names":["relative","platform","target"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shell-selection.ts"],"sourcesContent":["/**\n * Which shell the Bash tool spawns.\n *\n * Node's `shell: true` means `/bin/sh` on POSIX and `%ComSpec%` on Windows, so\n * for a long time a tool called `Bash` ran cmd.exe on Windows: one line per\n * command, `%VAR%` quoting, no heredocs, and the shell every model writes\n * worst. Every other native-Windows coding agent resolved this the same way,\n * with PowerShell 7 first and Windows PowerShell 5.1 next; Claude Code keeps\n * Git Bash as its POSIX shell; OpenCode honours the shell Book was launched\n * from before either. This module is that ladder, resolved once per session:\n *\n * 1. `BOOK_SHELL` in the environment, then `shell` in a trusted settings\n * layer - a kind name (`bash`, `pwsh`, `powershell`, `cmd`, `sh`) or an\n * executable path. A request that cannot be found is reported and the\n * automatic ladder continues, rather than failing every command.\n * 2. Git Bash, when Book was launched from one (MSYSTEM or a POSIX $SHELL is\n * set), so the syntax the user sees in their own terminal is the syntax\n * the model writes.\n * 3. PowerShell 7 (`pwsh`), then Windows PowerShell 5.1.\n * 4. Git Bash if it is merely installed.\n * 5. cmd.exe, which still works but is never chosen over a real shell.\n *\n * Off Windows the platform default (`/bin/sh` through `shell: true`) is kept.\n *\n * Pure: every probe (platform, environment, filesystem) is injected, so the\n * ladder is testable on any host. Nothing here is cached at module level;\n * `loadConfig` resolves once and the result rides on `AgentConfig.shell`.\n */\nimport { existsSync as fsExistsSync } from 'node:fs';\nimport { posix, win32 } from 'node:path';\nimport type { CommandExecution, ResolvedShell, ShellKind, ShellSource } from './types/runtime.js';\n\nexport interface ShellSelectionInput {\n platform?: NodeJS.Platform;\n env?: NodeJS.ProcessEnv;\n /** `settings.shell`: a shell kind name or an executable path. `BOOK_SHELL` wins over it. */\n requested?: string;\n existsSync?: (path: string) => boolean;\n}\n\nconst KIND_BY_NAME: Record<string, ShellKind> = {\n bash: 'bash',\n 'git-bash': 'bash',\n gitbash: 'bash',\n pwsh: 'pwsh',\n powershell: 'powershell',\n cmd: 'cmd',\n 'cmd.exe': 'cmd',\n sh: 'sh',\n};\n\nconst LABELS: Record<ShellKind, string> = {\n bash: 'Git Bash',\n pwsh: 'PowerShell 7',\n powershell: 'Windows PowerShell 5.1',\n cmd: 'cmd.exe',\n sh: '/bin/sh',\n};\n\ninterface Probe {\n which: (name: string) => string | undefined;\n exists: (path: string) => boolean;\n pwsh: () => string | undefined;\n powershell: () => string | undefined;\n gitBash: () => string | undefined;\n cmd: () => string | undefined;\n}\n\nfunction createProbe(\n platform: NodeJS.Platform,\n env: NodeJS.ProcessEnv,\n exists: (path: string) => boolean,\n): Probe {\n const path = platform === 'win32' ? win32 : posix;\n const separator = platform === 'win32' ? ';' : ':';\n const pathDirs = (env.PATH ?? env.Path ?? '').split(separator).filter(Boolean);\n const which = (name: string): string | undefined => {\n for (const dir of pathDirs) {\n const candidate = path.join(dir, name);\n if (exists(candidate)) return candidate;\n }\n return undefined;\n };\n const first = (candidates: Array<string | undefined>): string | undefined => {\n for (const candidate of candidates) {\n if (candidate && exists(candidate)) return candidate;\n }\n return undefined;\n };\n const programFiles = env.ProgramFiles || env.PROGRAMFILES || 'C:\\\\Program Files';\n const programFilesX86 = env['ProgramFiles(x86)'] || 'C:\\\\Program Files (x86)';\n const systemRoot = env.SystemRoot || env.windir || 'C:\\\\Windows';\n const localAppData = env.LOCALAPPDATA;\n return {\n which,\n exists,\n pwsh: () =>\n first([\n which('pwsh.exe'),\n path.join(programFiles, 'PowerShell', '7', 'pwsh.exe'),\n localAppData ? path.join(localAppData, 'Microsoft', 'WindowsApps', 'pwsh.exe') : undefined,\n ]),\n // Windows PowerShell lives one directory below System32, where the\n // `system32Executable` helper does not look, so the path is built here.\n powershell: () =>\n first([\n path.join(systemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'),\n which('powershell.exe'),\n ]),\n gitBash: () => {\n const git = which('git.exe');\n return first([\n // Git Bash exports EXEPATH as the Git for Windows root.\n env.EXEPATH ? path.join(env.EXEPATH, 'bin', 'bash.exe') : undefined,\n // `Git\\cmd\\git.exe`, `Git\\bin\\git.exe`, and `Git\\mingw64\\bin\\git.exe`\n // all sit two levels below the root that holds `bin\\bash.exe`.\n git ? path.join(path.dirname(path.dirname(git)), 'bin', 'bash.exe') : undefined,\n path.join(programFiles, 'Git', 'bin', 'bash.exe'),\n path.join(programFilesX86, 'Git', 'bin', 'bash.exe'),\n localAppData ? path.join(localAppData, 'Programs', 'Git', 'bin', 'bash.exe') : undefined,\n ]);\n },\n // ComSpec is what Node's `shell: true` spawns verbatim, so it is reported\n // as-is; the System32 path is only a guess for a display name.\n cmd: () =>\n env.ComSpec ||\n env.COMSPEC ||\n first([path.join(systemRoot, 'System32', 'cmd.exe')]) ||\n 'cmd.exe',\n };\n}\n\nfunction shell(\n kind: ShellKind,\n source: ShellSource,\n file?: string,\n warning?: string,\n): ResolvedShell {\n const resolved: ResolvedShell = { kind, label: LABELS[kind], source };\n if (file) resolved.file = file;\n if (warning) resolved.warning = warning;\n return resolved;\n}\n\nfunction kindFromExecutable(file: string, platform: NodeJS.Platform): ShellKind {\n const path = platform === 'win32' ? win32 : posix;\n const name = path\n .basename(file)\n .toLowerCase()\n .replace(/\\.exe$/, '');\n if (name === 'pwsh') return 'pwsh';\n if (name === 'powershell') return 'powershell';\n if (name === 'cmd') return 'cmd';\n if (name.includes('bash')) return 'bash';\n return 'sh';\n}\n\nfunction resolveRequested(\n requested: string,\n platform: NodeJS.Platform,\n probe: Probe,\n): ResolvedShell | undefined {\n const byName = KIND_BY_NAME[requested.toLowerCase()];\n if (byName) {\n switch (byName) {\n case 'bash': {\n const file = platform === 'win32' ? probe.gitBash() : probe.which('bash');\n return file ? shell('bash', 'setting', file) : undefined;\n }\n case 'pwsh': {\n const file = probe.pwsh() ?? (platform === 'win32' ? undefined : probe.which('pwsh'));\n return file ? shell('pwsh', 'setting', file) : undefined;\n }\n case 'powershell': {\n const file = probe.powershell();\n return file ? shell('powershell', 'setting', file) : undefined;\n }\n case 'cmd':\n return platform === 'win32' ? shell('cmd', 'setting', probe.cmd()) : undefined;\n case 'sh': {\n if (platform === 'win32') return undefined;\n return shell('sh', 'setting', probe.which('sh') ?? '/bin/sh');\n }\n }\n }\n if (probe.exists(requested)) {\n return shell(kindFromExecutable(requested, platform), 'setting', requested);\n }\n return undefined;\n}\n\nfunction launchedFromPosixShell(env: NodeJS.ProcessEnv): boolean {\n if (env.MSYSTEM) return true;\n const launcher = env.SHELL ?? '';\n return /(^|[\\\\/])(ba|z|da)?sh(\\.exe)?$/i.test(launcher);\n}\n\nfunction resolveWindows(env: NodeJS.ProcessEnv, probe: Probe): ResolvedShell {\n if (launchedFromPosixShell(env)) {\n const bash = probe.gitBash();\n if (bash) return shell('bash', 'launching-shell', bash);\n }\n const pwsh = probe.pwsh();\n if (pwsh) return shell('pwsh', 'detected', pwsh);\n const powershell = probe.powershell();\n if (powershell) return shell('powershell', 'detected', powershell);\n const bash = probe.gitBash();\n if (bash) return shell('bash', 'detected', bash);\n return shell('cmd', 'fallback', probe.cmd());\n}\n\n/** Resolve the shell the Bash tool should spawn. See the module comment for the ladder. */\nexport function resolveShell(input: ShellSelectionInput = {}): ResolvedShell {\n const platform = input.platform ?? process.platform;\n const env = input.env ?? process.env;\n const probe = createProbe(platform, env, input.existsSync ?? fsExistsSync);\n const requested = (env.BOOK_SHELL || input.requested || '').trim();\n\n let warning: string | undefined;\n if (requested) {\n const chosen = resolveRequested(requested, platform, probe);\n if (chosen) return chosen;\n warning = `Requested shell \"${requested}\" was not found; Book chose one automatically instead.`;\n }\n // POSIX keeps Node's default: `/bin/sh` through `shell: true`, exactly as before.\n const automatic = platform === 'win32' ? resolveWindows(env, probe) : shell('sh', 'detected');\n return warning ? { ...automatic, warning } : automatic;\n}\n\n/**\n * Silences progress records (which a redirected stderr would otherwise carry\n * as CLIXML) and sets UTF-8 on both pipes so non-ASCII output survives.\n */\nconst POWERSHELL_PRELUDE =\n \"$ProgressPreference = 'SilentlyContinue'; \" +\n '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ' +\n '$OutputEncoding = [System.Text.Encoding]::UTF8\\n';\n\n/**\n * Mirrors bash: the process exits with the last statement's status, using the\n * native command's code when there is one. `$?` refers to the statement\n * before the `if`, i.e. the last line of the user's command.\n */\nconst POWERSHELL_EPILOGUE =\n '\\nif (-not $?) { if ($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }';\n\n/**\n * Windows PowerShell 5.1 serializes anything it writes to a redirected stderr\n * as CLIXML, so a plain `Get-Item missing` hands the model an XML document\n * instead of \"Cannot find path\". Merging the error stream is not enough on\n * its own: an ErrorRecord that reaches the end of the pipeline is still\n * rendered to the host's error stream. Each record is turned into its message\n * text inside the pipeline instead, and the failure is recorded in a flag\n * because `$?` after the `ForEach-Object` describes the pipeline, not the\n * user's last statement. Verified on this exact script: text errors on\n * stdout, exit codes 0/1/3/7 preserved, table formatting intact.\n */\nfunction wrapForWindowsPowerShell(command: string): string {\n return (\n POWERSHELL_PRELUDE +\n '$global:__bookErr = $false\\n' +\n '& {\\n' +\n command +\n '\\nif (-not $?) { $global:__bookErr = $true }\\n' +\n '} 2>&1 | ForEach-Object { if ($_ -is [System.Management.Automation.ErrorRecord]) { \"$_\" } else { $_ } }\\n' +\n 'if ($global:__bookErr) { if ($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }'\n );\n}\n\n/**\n * Base64 of UTF-16LE, the encoding `-EncodedCommand` reads. Chosen over\n * `-Command` because Windows PowerShell 5.1 re-parses a `-Command` argument\n * and silently strips embedded double quotes from it; the encoded form is\n * delivered byte for byte. The cost is roughly 2.7x the command length on the\n * command line, which stays inside CreateProcess's 32k limit for anything a\n * model writes as a single tool call.\n */\nexport function encodePowerShellCommand(\n command: string,\n kind: 'pwsh' | 'powershell' = 'pwsh',\n): string {\n const script =\n kind === 'powershell'\n ? wrapForWindowsPowerShell(command)\n : POWERSHELL_PRELUDE + command + POWERSHELL_EPILOGUE;\n return Buffer.from(script, 'utf16le').toString('base64');\n}\n\n/** Decode what `encodePowerShellCommand` produced, for tests and diagnostics. */\nexport function decodePowerShellCommand(encoded: string): string {\n return Buffer.from(encoded, 'base64').toString('utf16le');\n}\n\n/**\n * The argv that runs `command` under `shell`, or undefined when the command\n * should go to Node's `shell: true` (cmd.exe and the POSIX default).\n */\nexport function shellExecution(\n resolved: ResolvedShell,\n command: string,\n): CommandExecution | undefined {\n switch (resolved.kind) {\n case 'bash':\n case 'sh':\n return resolved.file ? { file: resolved.file, args: ['-c', command] } : undefined;\n case 'pwsh':\n case 'powershell':\n return resolved.file\n ? {\n file: resolved.file,\n args: [\n '-NoLogo',\n '-NoProfile',\n '-NonInteractive',\n '-EncodedCommand',\n encodePowerShellCommand(command, resolved.kind),\n ],\n }\n : undefined;\n case 'cmd':\n // cmd.exe quoting cannot be reproduced from an argv; Node's `shell: true`\n // builds the `/d /s /c \"...\"` line itself.\n return undefined;\n }\n}\n\n/** `label (file)` for doctor and the system prompt. */\nexport function describeShell(resolved: ResolvedShell): string {\n return resolved.file ? `${resolved.label} (${resolved.file})` : resolved.label;\n}\n\n/**\n * The Harness bullet that tells the model which syntax to write. The shell is\n * fixed for the session, so this belongs in the cached prompt prefix.\n */\nexport function shellPromptLine(resolved: ResolvedShell): string {\n const where = resolved.file ? ` at \\`${resolved.file}\\`` : '';\n switch (resolved.kind) {\n case 'bash':\n return (\n `- The Bash tool runs Git Bash (POSIX sh)${where}, not cmd.exe or PowerShell. Use Unix shell ` +\n 'syntax: `/dev/null` not `NUL`, forward slashes, `$VAR` not `%VAR%` or `$env:VAR`. Windows ' +\n 'drive paths such as `C:/dir` and MSYS paths such as `/c/dir` both work. The bash sandbox ' +\n 'requires bubblewrap and is unavailable on Windows.'\n );\n case 'pwsh':\n return (\n `- The Bash tool runs PowerShell 7 (pwsh)${where}, not bash or cmd.exe. Use PowerShell ` +\n 'syntax: `&&` and `||` work, `;` sequences commands, `$env:VAR` reads a variable, and ' +\n '`Get-ChildItem`, `Select-String`, `Get-Content` replace `ls`, `grep`, `cat` unless those ' +\n 'tools are on PATH. There are no heredocs: use a single-quoted here-string with the closing ' +\n \"`'@` at column 0. The bash sandbox is unavailable on Windows.\"\n );\n case 'powershell':\n return (\n `- The Bash tool runs Windows PowerShell 5.1${where}, not bash or cmd.exe. ` +\n '`&&` and `||` are NOT available: use `;` to sequence, or `cmd1; if ($?) { cmd2 }`. Avoid ' +\n 'PowerShell 7-only syntax (`??`, `?.`). `$env:VAR` reads a variable; `Get-ChildItem`, ' +\n '`Select-String`, `Get-Content` replace `ls`, `grep`, `cat` unless those tools are on PATH. ' +\n \"There are no heredocs: use a single-quoted here-string with the closing `'@` at column 0. \" +\n 'The bash sandbox is unavailable on Windows.'\n );\n case 'cmd':\n return (\n '- The Bash tool runs cmd.exe because no PowerShell or Git Bash was found (set `shell` in ' +\n '`~/.book/settings.json` or `BOOK_SHELL` to change this). Each command must be a single ' +\n 'line; chain with `&` or `&&`; `%VAR%` reads a variable; `dir`, `findstr`, `type` replace ' +\n '`ls`, `grep`, `cat`; quoting follows cmd.exe rules, not POSIX. The bash sandbox is ' +\n 'unavailable on Windows.'\n );\n case 'sh':\n return (\n `- The Bash tool runs commands through the platform's default shell, \\`/bin/sh\\`${where}. ` +\n 'The bash sandbox requires bubblewrap.'\n );\n }\n}\n"],"mappings":";AA4BA,SAAS,cAAc,oBAAoB;AAC3C,SAAS,OAAO,aAAa;AAW7B,IAAM,eAA0C;AAAA,EAC9C,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,KAAK;AAAA,EACL,WAAW;AAAA,EACX,IAAI;AACN;AAEA,IAAM,SAAoC;AAAA,EACxC,MAAM;AAAA,EACN,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,KAAK;AAAA,EACL,IAAI;AACN;AAWA,SAAS,YACP,UACA,KACA,QACO;AACP,QAAM,OAAO,aAAa,UAAU,QAAQ;AAC5C,QAAM,YAAY,aAAa,UAAU,MAAM;AAC/C,QAAM,YAAY,IAAI,QAAQ,IAAI,QAAQ,IAAI,MAAM,SAAS,EAAE,OAAO,OAAO;AAC7E,QAAM,QAAQ,CAAC,SAAqC;AAClD,eAAW,OAAO,UAAU;AAC1B,YAAM,YAAY,KAAK,KAAK,KAAK,IAAI;AACrC,UAAI,OAAO,SAAS,EAAG,QAAO;AAAA,IAChC;AACA,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,CAAC,eAA8D;AAC3E,eAAW,aAAa,YAAY;AAClC,UAAI,aAAa,OAAO,SAAS,EAAG,QAAO;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AACA,QAAM,eAAe,IAAI,gBAAgB,IAAI,gBAAgB;AAC7D,QAAM,kBAAkB,IAAI,mBAAmB,KAAK;AACpD,QAAM,aAAa,IAAI,cAAc,IAAI,UAAU;AACnD,QAAM,eAAe,IAAI;AACzB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM,MACJ,MAAM;AAAA,MACJ,MAAM,UAAU;AAAA,MAChB,KAAK,KAAK,cAAc,cAAc,KAAK,UAAU;AAAA,MACrD,eAAe,KAAK,KAAK,cAAc,aAAa,eAAe,UAAU,IAAI;AAAA,IACnF,CAAC;AAAA;AAAA;AAAA,IAGH,YAAY,MACV,MAAM;AAAA,MACJ,KAAK,KAAK,YAAY,YAAY,qBAAqB,QAAQ,gBAAgB;AAAA,MAC/E,MAAM,gBAAgB;AAAA,IACxB,CAAC;AAAA,IACH,SAAS,MAAM;AACb,YAAM,MAAM,MAAM,SAAS;AAC3B,aAAO,MAAM;AAAA;AAAA,QAEX,IAAI,UAAU,KAAK,KAAK,IAAI,SAAS,OAAO,UAAU,IAAI;AAAA;AAAA;AAAA,QAG1D,MAAM,KAAK,KAAK,KAAK,QAAQ,KAAK,QAAQ,GAAG,CAAC,GAAG,OAAO,UAAU,IAAI;AAAA,QACtE,KAAK,KAAK,cAAc,OAAO,OAAO,UAAU;AAAA,QAChD,KAAK,KAAK,iBAAiB,OAAO,OAAO,UAAU;AAAA,QACnD,eAAe,KAAK,KAAK,cAAc,YAAY,OAAO,OAAO,UAAU,IAAI;AAAA,MACjF,CAAC;AAAA,IACH;AAAA;AAAA;AAAA,IAGA,KAAK,MACH,IAAI,WACJ,IAAI,WACJ,MAAM,CAAC,KAAK,KAAK,YAAY,YAAY,SAAS,CAAC,CAAC,KACpD;AAAA,EACJ;AACF;AAEA,SAAS,MACP,MACA,QACA,MACA,SACe;AACf,QAAM,WAA0B,EAAE,MAAM,OAAO,OAAO,IAAI,GAAG,OAAO;AACpE,MAAI,KAAM,UAAS,OAAO;AAC1B,MAAI,QAAS,UAAS,UAAU;AAChC,SAAO;AACT;AAEA,SAAS,mBAAmB,MAAc,UAAsC;AAC9E,QAAM,OAAO,aAAa,UAAU,QAAQ;AAC5C,QAAM,OAAO,KACV,SAAS,IAAI,EACb,YAAY,EACZ,QAAQ,UAAU,EAAE;AACvB,MAAI,SAAS,OAAQ,QAAO;AAC5B,MAAI,SAAS,aAAc,QAAO;AAClC,MAAI,SAAS,MAAO,QAAO;AAC3B,MAAI,KAAK,SAAS,MAAM,EAAG,QAAO;AAClC,SAAO;AACT;AAEA,SAAS,iBACP,WACA,UACA,OAC2B;AAC3B,QAAM,SAAS,aAAa,UAAU,YAAY,CAAC;AACnD,MAAI,QAAQ;AACV,YAAQ,QAAQ;AAAA,MACd,KAAK,QAAQ;AACX,cAAM,OAAO,aAAa,UAAU,MAAM,QAAQ,IAAI,MAAM,MAAM,MAAM;AACxE,eAAO,OAAO,MAAM,QAAQ,WAAW,IAAI,IAAI;AAAA,MACjD;AAAA,MACA,KAAK,QAAQ;AACX,cAAM,OAAO,MAAM,KAAK,MAAM,aAAa,UAAU,SAAY,MAAM,MAAM,MAAM;AACnF,eAAO,OAAO,MAAM,QAAQ,WAAW,IAAI,IAAI;AAAA,MACjD;AAAA,MACA,KAAK,cAAc;AACjB,cAAM,OAAO,MAAM,WAAW;AAC9B,eAAO,OAAO,MAAM,cAAc,WAAW,IAAI,IAAI;AAAA,MACvD;AAAA,MACA,KAAK;AACH,eAAO,aAAa,UAAU,MAAM,OAAO,WAAW,MAAM,IAAI,CAAC,IAAI;AAAA,MACvE,KAAK,MAAM;AACT,YAAI,aAAa,QAAS,QAAO;AACjC,eAAO,MAAM,MAAM,WAAW,MAAM,MAAM,IAAI,KAAK,SAAS;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AACA,MAAI,MAAM,OAAO,SAAS,GAAG;AAC3B,WAAO,MAAM,mBAAmB,WAAW,QAAQ,GAAG,WAAW,SAAS;AAAA,EAC5E;AACA,SAAO;AACT;AAEA,SAAS,uBAAuB,KAAiC;AAC/D,MAAI,IAAI,QAAS,QAAO;AACxB,QAAM,WAAW,IAAI,SAAS;AAC9B,SAAO,kCAAkC,KAAK,QAAQ;AACxD;AAEA,SAAS,eAAe,KAAwB,OAA6B;AAC3E,MAAI,uBAAuB,GAAG,GAAG;AAC/B,UAAMA,QAAO,MAAM,QAAQ;AAC3B,QAAIA,MAAM,QAAO,MAAM,QAAQ,mBAAmBA,KAAI;AAAA,EACxD;AACA,QAAM,OAAO,MAAM,KAAK;AACxB,MAAI,KAAM,QAAO,MAAM,QAAQ,YAAY,IAAI;AAC/C,QAAM,aAAa,MAAM,WAAW;AACpC,MAAI,WAAY,QAAO,MAAM,cAAc,YAAY,UAAU;AACjE,QAAM,OAAO,MAAM,QAAQ;AAC3B,MAAI,KAAM,QAAO,MAAM,QAAQ,YAAY,IAAI;AAC/C,SAAO,MAAM,OAAO,YAAY,MAAM,IAAI,CAAC;AAC7C;AAGO,SAAS,aAAa,QAA6B,CAAC,GAAkB;AAC3E,QAAM,WAAW,MAAM,YAAY,QAAQ;AAC3C,QAAM,MAAM,MAAM,OAAO,QAAQ;AACjC,QAAM,QAAQ,YAAY,UAAU,KAAK,MAAM,cAAc,YAAY;AACzE,QAAM,aAAa,IAAI,cAAc,MAAM,aAAa,IAAI,KAAK;AAEjE,MAAI;AACJ,MAAI,WAAW;AACb,UAAM,SAAS,iBAAiB,WAAW,UAAU,KAAK;AAC1D,QAAI,OAAQ,QAAO;AACnB,cAAU,oBAAoB,SAAS;AAAA,EACzC;AAEA,QAAM,YAAY,aAAa,UAAU,eAAe,KAAK,KAAK,IAAI,MAAM,MAAM,UAAU;AAC5F,SAAO,UAAU,EAAE,GAAG,WAAW,QAAQ,IAAI;AAC/C;AAMA,IAAM,qBACJ;AASF,IAAM,sBACJ;AAaF,SAAS,yBAAyB,SAAyB;AACzD,SACE,qBACA,sCAEA,UACA;AAIJ;AAUO,SAAS,wBACd,SACA,OAA8B,QACtB;AACR,QAAM,SACJ,SAAS,eACL,yBAAyB,OAAO,IAChC,qBAAqB,UAAU;AACrC,SAAO,OAAO,KAAK,QAAQ,SAAS,EAAE,SAAS,QAAQ;AACzD;AAGO,SAAS,wBAAwB,SAAyB;AAC/D,SAAO,OAAO,KAAK,SAAS,QAAQ,EAAE,SAAS,SAAS;AAC1D;AAMO,SAAS,eACd,UACA,SAC8B;AAC9B,UAAQ,SAAS,MAAM;AAAA,IACrB,KAAK;AAAA,IACL,KAAK;AACH,aAAO,SAAS,OAAO,EAAE,MAAM,SAAS,MAAM,MAAM,CAAC,MAAM,OAAO,EAAE,IAAI;AAAA,IAC1E,KAAK;AAAA,IACL,KAAK;AACH,aAAO,SAAS,OACZ;AAAA,QACE,MAAM,SAAS;AAAA,QACf,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,wBAAwB,SAAS,SAAS,IAAI;AAAA,QAChD;AAAA,MACF,IACA;AAAA,IACN,KAAK;AAGH,aAAO;AAAA,EACX;AACF;AAGO,SAAS,cAAc,UAAiC;AAC7D,SAAO,SAAS,OAAO,GAAG,SAAS,KAAK,KAAK,SAAS,IAAI,MAAM,SAAS;AAC3E;AAMO,SAAS,gBAAgB,UAAiC;AAC/D,QAAM,QAAQ,SAAS,OAAO,SAAS,SAAS,IAAI,OAAO;AAC3D,UAAQ,SAAS,MAAM;AAAA,IACrB,KAAK;AACH,aACE,2CAA2C,KAAK;AAAA,IAKpD,KAAK;AACH,aACE,2CAA2C,KAAK;AAAA,IAMpD,KAAK;AACH,aACE,8CAA8C,KAAK;AAAA,IAOvD,KAAK;AACH,aACE;AAAA,IAMJ,KAAK;AACH,aACE,kFAAkF,KAAK;AAAA,EAG7F;AACF;","names":["bash"]}
|