@letrquan/book 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2504 -0
- package/LICENSE +123 -0
- package/README.md +1383 -0
- package/dist/app-MOXXWEOY.js +18945 -0
- package/dist/app-MOXXWEOY.js.map +1 -0
- package/dist/chunk-24AXE6SP.js +12 -0
- package/dist/chunk-24AXE6SP.js.map +1 -0
- package/dist/chunk-3U5IBM24.js +213 -0
- package/dist/chunk-3U5IBM24.js.map +1 -0
- package/dist/chunk-574C6MQR.js +743 -0
- package/dist/chunk-574C6MQR.js.map +1 -0
- package/dist/chunk-5GDZ22YP.js +449 -0
- package/dist/chunk-5GDZ22YP.js.map +1 -0
- package/dist/chunk-5KLVH3PY.js +167 -0
- package/dist/chunk-5KLVH3PY.js.map +1 -0
- package/dist/chunk-5RDRAO4B.js +92 -0
- package/dist/chunk-5RDRAO4B.js.map +1 -0
- package/dist/chunk-7N4J557C.js +10 -0
- package/dist/chunk-7N4J557C.js.map +1 -0
- package/dist/chunk-A7JLBK2Y.js +3506 -0
- package/dist/chunk-A7JLBK2Y.js.map +1 -0
- package/dist/chunk-ANSJPYGI.js +84 -0
- package/dist/chunk-ANSJPYGI.js.map +1 -0
- package/dist/chunk-BTZZUCLI.js +264 -0
- package/dist/chunk-BTZZUCLI.js.map +1 -0
- package/dist/chunk-C7D4NJ2D.js +217 -0
- package/dist/chunk-C7D4NJ2D.js.map +1 -0
- package/dist/chunk-CAGOB2N4.js +208 -0
- package/dist/chunk-CAGOB2N4.js.map +1 -0
- package/dist/chunk-DGMQEI6Y.js +98 -0
- package/dist/chunk-DGMQEI6Y.js.map +1 -0
- package/dist/chunk-E75IPST5.js +289 -0
- package/dist/chunk-E75IPST5.js.map +1 -0
- package/dist/chunk-EYGD6X2T.js +180 -0
- package/dist/chunk-EYGD6X2T.js.map +1 -0
- package/dist/chunk-GTVB4HEM.js +765 -0
- package/dist/chunk-GTVB4HEM.js.map +1 -0
- package/dist/chunk-J7MIZ5CA.js +1907 -0
- package/dist/chunk-J7MIZ5CA.js.map +1 -0
- package/dist/chunk-KWHQ2XXL.js +626 -0
- package/dist/chunk-KWHQ2XXL.js.map +1 -0
- package/dist/chunk-L6LK2QIQ.js +657 -0
- package/dist/chunk-L6LK2QIQ.js.map +1 -0
- package/dist/chunk-MAQXCUR4.js +1007 -0
- package/dist/chunk-MAQXCUR4.js.map +1 -0
- package/dist/chunk-S4XL7HOM.js +402 -0
- package/dist/chunk-S4XL7HOM.js.map +1 -0
- package/dist/chunk-URLQAXTR.js +60 -0
- package/dist/chunk-URLQAXTR.js.map +1 -0
- package/dist/chunk-V4BOYR52.js +3504 -0
- package/dist/chunk-V4BOYR52.js.map +1 -0
- package/dist/chunk-VZQXX3WW.js +210 -0
- package/dist/chunk-VZQXX3WW.js.map +1 -0
- package/dist/chunk-WKBO5R4O.js +34 -0
- package/dist/chunk-WKBO5R4O.js.map +1 -0
- package/dist/chunk-XIFFTATU.js +15753 -0
- package/dist/chunk-XIFFTATU.js.map +1 -0
- package/dist/command-approvals-6Y57VCWZ.js +28 -0
- package/dist/command-approvals-6Y57VCWZ.js.map +1 -0
- package/dist/context-ZC5IFEAX.js +25 -0
- package/dist/context-ZC5IFEAX.js.map +1 -0
- package/dist/hook-approvals-CTUFKEJT.js +22 -0
- package/dist/hook-approvals-CTUFKEJT.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2582 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive-assets-CCAAWVKH.js +31 -0
- package/dist/interactive-assets-CCAAWVKH.js.map +1 -0
- package/dist/job-runner.d.ts +2 -0
- package/dist/job-runner.js +235 -0
- package/dist/job-runner.js.map +1 -0
- package/dist/loader-4IDS2RIE.js +15 -0
- package/dist/loader-4IDS2RIE.js.map +1 -0
- package/dist/mcp-approvals-WNPH65BK.js +18 -0
- package/dist/mcp-approvals-WNPH65BK.js.map +1 -0
- package/dist/mcp-config-YIJIES2C.js +16 -0
- package/dist/mcp-config-YIJIES2C.js.map +1 -0
- package/dist/model-window-store-YFVNXCIP.js +27 -0
- package/dist/model-window-store-YFVNXCIP.js.map +1 -0
- package/dist/openai-compatible-RHZUBHV6.js +10 -0
- package/dist/openai-compatible-RHZUBHV6.js.map +1 -0
- package/dist/permission-approvals-76PVREFD.js +14 -0
- package/dist/permission-approvals-76PVREFD.js.map +1 -0
- package/dist/permissions-CHCKTFQQ.js +28 -0
- package/dist/permissions-CHCKTFQQ.js.map +1 -0
- package/dist/sandbox-CNVAQDA7.js +23 -0
- package/dist/sandbox-CNVAQDA7.js.map +1 -0
- package/dist/scrollback-LUKLIBVI.js +136 -0
- package/dist/scrollback-LUKLIBVI.js.map +1 -0
- package/dist/sdk.d.ts +4574 -0
- package/dist/sdk.js +665 -0
- package/dist/sdk.js.map +1 -0
- package/dist/settings-loader-M2QP5TX2.js +21 -0
- package/dist/settings-loader-M2QP5TX2.js.map +1 -0
- package/dist/settings-removed-JWWPT4WA.js +19 -0
- package/dist/settings-removed-JWWPT4WA.js.map +1 -0
- package/dist/shell-selection-V6QAYXPX.js +17 -0
- package/dist/shell-selection-V6QAYXPX.js.map +1 -0
- package/package.json +143 -0
- package/patches/ink+6.8.0.patch +13 -0
- package/scripts/apply-ink-patch.mjs +29 -0
- package/scripts/ink-patch.mjs +59 -0
- package/scripts/verify-ink-patch.mjs +11 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// src/shell-selection.ts
|
|
2
|
+
import { existsSync as fsExistsSync } from "fs";
|
|
3
|
+
import { posix, win32 } from "path";
|
|
4
|
+
var KIND_BY_NAME = {
|
|
5
|
+
bash: "bash",
|
|
6
|
+
"git-bash": "bash",
|
|
7
|
+
gitbash: "bash",
|
|
8
|
+
pwsh: "pwsh",
|
|
9
|
+
powershell: "powershell",
|
|
10
|
+
cmd: "cmd",
|
|
11
|
+
"cmd.exe": "cmd",
|
|
12
|
+
sh: "sh"
|
|
13
|
+
};
|
|
14
|
+
var LABELS = {
|
|
15
|
+
bash: "Git Bash",
|
|
16
|
+
pwsh: "PowerShell 7",
|
|
17
|
+
powershell: "Windows PowerShell 5.1",
|
|
18
|
+
cmd: "cmd.exe",
|
|
19
|
+
sh: "/bin/sh"
|
|
20
|
+
};
|
|
21
|
+
function createProbe(platform, env, exists) {
|
|
22
|
+
const path = platform === "win32" ? win32 : posix;
|
|
23
|
+
const separator = platform === "win32" ? ";" : ":";
|
|
24
|
+
const pathDirs = (env.PATH ?? env.Path ?? "").split(separator).filter(Boolean);
|
|
25
|
+
const which = (name) => {
|
|
26
|
+
for (const dir of pathDirs) {
|
|
27
|
+
const candidate = path.join(dir, name);
|
|
28
|
+
if (exists(candidate)) return candidate;
|
|
29
|
+
}
|
|
30
|
+
return void 0;
|
|
31
|
+
};
|
|
32
|
+
const first = (candidates) => {
|
|
33
|
+
for (const candidate of candidates) {
|
|
34
|
+
if (candidate && exists(candidate)) return candidate;
|
|
35
|
+
}
|
|
36
|
+
return void 0;
|
|
37
|
+
};
|
|
38
|
+
const programFiles = env.ProgramFiles || env.PROGRAMFILES || "C:\\Program Files";
|
|
39
|
+
const programFilesX86 = env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
|
40
|
+
const systemRoot = env.SystemRoot || env.windir || "C:\\Windows";
|
|
41
|
+
const localAppData = env.LOCALAPPDATA;
|
|
42
|
+
return {
|
|
43
|
+
which,
|
|
44
|
+
exists,
|
|
45
|
+
pwsh: () => first([
|
|
46
|
+
which("pwsh.exe"),
|
|
47
|
+
path.join(programFiles, "PowerShell", "7", "pwsh.exe"),
|
|
48
|
+
localAppData ? path.join(localAppData, "Microsoft", "WindowsApps", "pwsh.exe") : void 0
|
|
49
|
+
]),
|
|
50
|
+
// Windows PowerShell lives one directory below System32, where the
|
|
51
|
+
// `system32Executable` helper does not look, so the path is built here.
|
|
52
|
+
powershell: () => first([
|
|
53
|
+
path.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"),
|
|
54
|
+
which("powershell.exe")
|
|
55
|
+
]),
|
|
56
|
+
gitBash: () => {
|
|
57
|
+
const git = which("git.exe");
|
|
58
|
+
return first([
|
|
59
|
+
// Git Bash exports EXEPATH as the Git for Windows root.
|
|
60
|
+
env.EXEPATH ? path.join(env.EXEPATH, "bin", "bash.exe") : void 0,
|
|
61
|
+
// `Git\cmd\git.exe`, `Git\bin\git.exe`, and `Git\mingw64\bin\git.exe`
|
|
62
|
+
// all sit two levels below the root that holds `bin\bash.exe`.
|
|
63
|
+
git ? path.join(path.dirname(path.dirname(git)), "bin", "bash.exe") : void 0,
|
|
64
|
+
path.join(programFiles, "Git", "bin", "bash.exe"),
|
|
65
|
+
path.join(programFilesX86, "Git", "bin", "bash.exe"),
|
|
66
|
+
localAppData ? path.join(localAppData, "Programs", "Git", "bin", "bash.exe") : void 0
|
|
67
|
+
]);
|
|
68
|
+
},
|
|
69
|
+
// ComSpec is what Node's `shell: true` spawns verbatim, so it is reported
|
|
70
|
+
// as-is; the System32 path is only a guess for a display name.
|
|
71
|
+
cmd: () => env.ComSpec || env.COMSPEC || first([path.join(systemRoot, "System32", "cmd.exe")]) || "cmd.exe"
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function shell(kind, source, file, warning) {
|
|
75
|
+
const resolved = { kind, label: LABELS[kind], source };
|
|
76
|
+
if (file) resolved.file = file;
|
|
77
|
+
if (warning) resolved.warning = warning;
|
|
78
|
+
return resolved;
|
|
79
|
+
}
|
|
80
|
+
function kindFromExecutable(file, platform) {
|
|
81
|
+
const path = platform === "win32" ? win32 : posix;
|
|
82
|
+
const name = path.basename(file).toLowerCase().replace(/\.exe$/, "");
|
|
83
|
+
if (name === "pwsh") return "pwsh";
|
|
84
|
+
if (name === "powershell") return "powershell";
|
|
85
|
+
if (name === "cmd") return "cmd";
|
|
86
|
+
if (name.includes("bash")) return "bash";
|
|
87
|
+
return "sh";
|
|
88
|
+
}
|
|
89
|
+
function resolveRequested(requested, platform, probe) {
|
|
90
|
+
const byName = KIND_BY_NAME[requested.toLowerCase()];
|
|
91
|
+
if (byName) {
|
|
92
|
+
switch (byName) {
|
|
93
|
+
case "bash": {
|
|
94
|
+
const file = platform === "win32" ? probe.gitBash() : probe.which("bash");
|
|
95
|
+
return file ? shell("bash", "setting", file) : void 0;
|
|
96
|
+
}
|
|
97
|
+
case "pwsh": {
|
|
98
|
+
const file = probe.pwsh() ?? (platform === "win32" ? void 0 : probe.which("pwsh"));
|
|
99
|
+
return file ? shell("pwsh", "setting", file) : void 0;
|
|
100
|
+
}
|
|
101
|
+
case "powershell": {
|
|
102
|
+
const file = probe.powershell();
|
|
103
|
+
return file ? shell("powershell", "setting", file) : void 0;
|
|
104
|
+
}
|
|
105
|
+
case "cmd":
|
|
106
|
+
return platform === "win32" ? shell("cmd", "setting", probe.cmd()) : void 0;
|
|
107
|
+
case "sh": {
|
|
108
|
+
if (platform === "win32") return void 0;
|
|
109
|
+
return shell("sh", "setting", probe.which("sh") ?? "/bin/sh");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (probe.exists(requested)) {
|
|
114
|
+
return shell(kindFromExecutable(requested, platform), "setting", requested);
|
|
115
|
+
}
|
|
116
|
+
return void 0;
|
|
117
|
+
}
|
|
118
|
+
function launchedFromPosixShell(env) {
|
|
119
|
+
if (env.MSYSTEM) return true;
|
|
120
|
+
const launcher = env.SHELL ?? "";
|
|
121
|
+
return /(^|[\\/])(ba|z|da)?sh(\.exe)?$/i.test(launcher);
|
|
122
|
+
}
|
|
123
|
+
function resolveWindows(env, probe) {
|
|
124
|
+
if (launchedFromPosixShell(env)) {
|
|
125
|
+
const bash2 = probe.gitBash();
|
|
126
|
+
if (bash2) return shell("bash", "launching-shell", bash2);
|
|
127
|
+
}
|
|
128
|
+
const pwsh = probe.pwsh();
|
|
129
|
+
if (pwsh) return shell("pwsh", "detected", pwsh);
|
|
130
|
+
const powershell = probe.powershell();
|
|
131
|
+
if (powershell) return shell("powershell", "detected", powershell);
|
|
132
|
+
const bash = probe.gitBash();
|
|
133
|
+
if (bash) return shell("bash", "detected", bash);
|
|
134
|
+
return shell("cmd", "fallback", probe.cmd());
|
|
135
|
+
}
|
|
136
|
+
function resolveShell(input = {}) {
|
|
137
|
+
const platform = input.platform ?? process.platform;
|
|
138
|
+
const env = input.env ?? process.env;
|
|
139
|
+
const probe = createProbe(platform, env, input.existsSync ?? fsExistsSync);
|
|
140
|
+
const requested = (env.BOOK_SHELL || input.requested || "").trim();
|
|
141
|
+
let warning;
|
|
142
|
+
if (requested) {
|
|
143
|
+
const chosen = resolveRequested(requested, platform, probe);
|
|
144
|
+
if (chosen) return chosen;
|
|
145
|
+
warning = `Requested shell "${requested}" was not found; Book chose one automatically instead.`;
|
|
146
|
+
}
|
|
147
|
+
const automatic = platform === "win32" ? resolveWindows(env, probe) : shell("sh", "detected");
|
|
148
|
+
return warning ? { ...automatic, warning } : automatic;
|
|
149
|
+
}
|
|
150
|
+
var POWERSHELL_PRELUDE = "$ProgressPreference = 'SilentlyContinue'; [Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $OutputEncoding = [System.Text.Encoding]::UTF8\n";
|
|
151
|
+
var POWERSHELL_EPILOGUE = "\nif (-not $?) { if ($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }";
|
|
152
|
+
function wrapForWindowsPowerShell(command) {
|
|
153
|
+
return POWERSHELL_PRELUDE + "$global:__bookErr = $false\n& {\n" + command + '\nif (-not $?) { $global:__bookErr = $true }\n} 2>&1 | ForEach-Object { if ($_ -is [System.Management.Automation.ErrorRecord]) { "$_" } else { $_ } }\nif ($global:__bookErr) { if ($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }';
|
|
154
|
+
}
|
|
155
|
+
function encodePowerShellCommand(command, kind = "pwsh") {
|
|
156
|
+
const script = kind === "powershell" ? wrapForWindowsPowerShell(command) : POWERSHELL_PRELUDE + command + POWERSHELL_EPILOGUE;
|
|
157
|
+
return Buffer.from(script, "utf16le").toString("base64");
|
|
158
|
+
}
|
|
159
|
+
function decodePowerShellCommand(encoded) {
|
|
160
|
+
return Buffer.from(encoded, "base64").toString("utf16le");
|
|
161
|
+
}
|
|
162
|
+
function shellExecution(resolved, command) {
|
|
163
|
+
switch (resolved.kind) {
|
|
164
|
+
case "bash":
|
|
165
|
+
case "sh":
|
|
166
|
+
return resolved.file ? { file: resolved.file, args: ["-c", command] } : void 0;
|
|
167
|
+
case "pwsh":
|
|
168
|
+
case "powershell":
|
|
169
|
+
return resolved.file ? {
|
|
170
|
+
file: resolved.file,
|
|
171
|
+
args: [
|
|
172
|
+
"-NoLogo",
|
|
173
|
+
"-NoProfile",
|
|
174
|
+
"-NonInteractive",
|
|
175
|
+
"-EncodedCommand",
|
|
176
|
+
encodePowerShellCommand(command, resolved.kind)
|
|
177
|
+
]
|
|
178
|
+
} : void 0;
|
|
179
|
+
case "cmd":
|
|
180
|
+
return void 0;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function describeShell(resolved) {
|
|
184
|
+
return resolved.file ? `${resolved.label} (${resolved.file})` : resolved.label;
|
|
185
|
+
}
|
|
186
|
+
function shellPromptLine(resolved) {
|
|
187
|
+
const where = resolved.file ? ` at \`${resolved.file}\`` : "";
|
|
188
|
+
switch (resolved.kind) {
|
|
189
|
+
case "bash":
|
|
190
|
+
return `- The Bash tool runs Git Bash (POSIX sh)${where}, not cmd.exe or PowerShell. Use Unix shell syntax: \`/dev/null\` not \`NUL\`, forward slashes, \`$VAR\` not \`%VAR%\` or \`$env:VAR\`. Windows drive paths such as \`C:/dir\` and MSYS paths such as \`/c/dir\` both work. The bash sandbox requires bubblewrap and is unavailable on Windows.`;
|
|
191
|
+
case "pwsh":
|
|
192
|
+
return `- The Bash tool runs PowerShell 7 (pwsh)${where}, not bash or cmd.exe. Use PowerShell syntax: \`&&\` and \`||\` work, \`;\` sequences commands, \`$env:VAR\` reads a variable, and \`Get-ChildItem\`, \`Select-String\`, \`Get-Content\` replace \`ls\`, \`grep\`, \`cat\` unless those tools are on PATH. There are no heredocs: use a single-quoted here-string with the closing \`'@\` at column 0. The bash sandbox is unavailable on Windows.`;
|
|
193
|
+
case "powershell":
|
|
194
|
+
return `- The Bash tool runs Windows PowerShell 5.1${where}, not bash or cmd.exe. \`&&\` and \`||\` are NOT available: use \`;\` to sequence, or \`cmd1; if ($?) { cmd2 }\`. Avoid PowerShell 7-only syntax (\`??\`, \`?.\`). \`$env:VAR\` reads a variable; \`Get-ChildItem\`, \`Select-String\`, \`Get-Content\` replace \`ls\`, \`grep\`, \`cat\` unless those tools are on PATH. There are no heredocs: use a single-quoted here-string with the closing \`'@\` at column 0. The bash sandbox is unavailable on Windows.`;
|
|
195
|
+
case "cmd":
|
|
196
|
+
return "- The Bash tool runs cmd.exe because no PowerShell or Git Bash was found (set `shell` in `~/.book/settings.json` or `BOOK_SHELL` to change this). Each command must be a single line; chain with `&` or `&&`; `%VAR%` reads a variable; `dir`, `findstr`, `type` replace `ls`, `grep`, `cat`; quoting follows cmd.exe rules, not POSIX. The bash sandbox is unavailable on Windows.";
|
|
197
|
+
case "sh":
|
|
198
|
+
return `- The Bash tool runs commands through the platform's default shell, \`/bin/sh\`${where}. The bash sandbox requires bubblewrap.`;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export {
|
|
203
|
+
resolveShell,
|
|
204
|
+
encodePowerShellCommand,
|
|
205
|
+
decodePowerShellCommand,
|
|
206
|
+
shellExecution,
|
|
207
|
+
describeShell,
|
|
208
|
+
shellPromptLine
|
|
209
|
+
};
|
|
210
|
+
//# sourceMappingURL=chunk-VZQXX3WW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shell-selection.ts"],"sourcesContent":["/**\r\n * Which shell the Bash tool spawns.\r\n *\r\n * Node's `shell: true` means `/bin/sh` on POSIX and `%ComSpec%` on Windows, so\r\n * for a long time a tool called `Bash` ran cmd.exe on Windows: one line per\r\n * command, `%VAR%` quoting, no heredocs, and the shell every model writes\r\n * worst. Every other native-Windows coding agent resolved this the same way,\r\n * with PowerShell 7 first and Windows PowerShell 5.1 next; Claude Code keeps\r\n * Git Bash as its POSIX shell; OpenCode honours the shell Book was launched\r\n * from before either. This module is that ladder, resolved once per session:\r\n *\r\n * 1. `BOOK_SHELL` in the environment, then `shell` in a trusted settings\r\n * layer - a kind name (`bash`, `pwsh`, `powershell`, `cmd`, `sh`) or an\r\n * executable path. A request that cannot be found is reported and the\r\n * automatic ladder continues, rather than failing every command.\r\n * 2. Git Bash, when Book was launched from one (MSYSTEM or a POSIX $SHELL is\r\n * set), so the syntax the user sees in their own terminal is the syntax\r\n * the model writes.\r\n * 3. PowerShell 7 (`pwsh`), then Windows PowerShell 5.1.\r\n * 4. Git Bash if it is merely installed.\r\n * 5. cmd.exe, which still works but is never chosen over a real shell.\r\n *\r\n * Off Windows the platform default (`/bin/sh` through `shell: true`) is kept.\r\n *\r\n * Pure: every probe (platform, environment, filesystem) is injected, so the\r\n * ladder is testable on any host. Nothing here is cached at module level;\r\n * `loadConfig` resolves once and the result rides on `AgentConfig.shell`.\r\n */\r\nimport { existsSync as fsExistsSync } from 'node:fs';\r\nimport { posix, win32 } from 'node:path';\r\nimport type { CommandExecution, ResolvedShell, ShellKind, ShellSource } from './types/runtime.js';\r\n\r\nexport interface ShellSelectionInput {\r\n platform?: NodeJS.Platform;\r\n env?: NodeJS.ProcessEnv;\r\n /** `settings.shell`: a shell kind name or an executable path. `BOOK_SHELL` wins over it. */\r\n requested?: string;\r\n existsSync?: (path: string) => boolean;\r\n}\r\n\r\nconst KIND_BY_NAME: Record<string, ShellKind> = {\r\n bash: 'bash',\r\n 'git-bash': 'bash',\r\n gitbash: 'bash',\r\n pwsh: 'pwsh',\r\n powershell: 'powershell',\r\n cmd: 'cmd',\r\n 'cmd.exe': 'cmd',\r\n sh: 'sh',\r\n};\r\n\r\nconst LABELS: Record<ShellKind, string> = {\r\n bash: 'Git Bash',\r\n pwsh: 'PowerShell 7',\r\n powershell: 'Windows PowerShell 5.1',\r\n cmd: 'cmd.exe',\r\n sh: '/bin/sh',\r\n};\r\n\r\ninterface Probe {\r\n which: (name: string) => string | undefined;\r\n exists: (path: string) => boolean;\r\n pwsh: () => string | undefined;\r\n powershell: () => string | undefined;\r\n gitBash: () => string | undefined;\r\n cmd: () => string | undefined;\r\n}\r\n\r\nfunction createProbe(\r\n platform: NodeJS.Platform,\r\n env: NodeJS.ProcessEnv,\r\n exists: (path: string) => boolean,\r\n): Probe {\r\n const path = platform === 'win32' ? win32 : posix;\r\n const separator = platform === 'win32' ? ';' : ':';\r\n const pathDirs = (env.PATH ?? env.Path ?? '').split(separator).filter(Boolean);\r\n const which = (name: string): string | undefined => {\r\n for (const dir of pathDirs) {\r\n const candidate = path.join(dir, name);\r\n if (exists(candidate)) return candidate;\r\n }\r\n return undefined;\r\n };\r\n const first = (candidates: Array<string | undefined>): string | undefined => {\r\n for (const candidate of candidates) {\r\n if (candidate && exists(candidate)) return candidate;\r\n }\r\n return undefined;\r\n };\r\n const programFiles = env.ProgramFiles || env.PROGRAMFILES || 'C:\\\\Program Files';\r\n const programFilesX86 = env['ProgramFiles(x86)'] || 'C:\\\\Program Files (x86)';\r\n const systemRoot = env.SystemRoot || env.windir || 'C:\\\\Windows';\r\n const localAppData = env.LOCALAPPDATA;\r\n return {\r\n which,\r\n exists,\r\n pwsh: () =>\r\n first([\r\n which('pwsh.exe'),\r\n path.join(programFiles, 'PowerShell', '7', 'pwsh.exe'),\r\n localAppData ? path.join(localAppData, 'Microsoft', 'WindowsApps', 'pwsh.exe') : undefined,\r\n ]),\r\n // Windows PowerShell lives one directory below System32, where the\r\n // `system32Executable` helper does not look, so the path is built here.\r\n powershell: () =>\r\n first([\r\n path.join(systemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'),\r\n which('powershell.exe'),\r\n ]),\r\n gitBash: () => {\r\n const git = which('git.exe');\r\n return first([\r\n // Git Bash exports EXEPATH as the Git for Windows root.\r\n env.EXEPATH ? path.join(env.EXEPATH, 'bin', 'bash.exe') : undefined,\r\n // `Git\\cmd\\git.exe`, `Git\\bin\\git.exe`, and `Git\\mingw64\\bin\\git.exe`\r\n // all sit two levels below the root that holds `bin\\bash.exe`.\r\n git ? path.join(path.dirname(path.dirname(git)), 'bin', 'bash.exe') : undefined,\r\n path.join(programFiles, 'Git', 'bin', 'bash.exe'),\r\n path.join(programFilesX86, 'Git', 'bin', 'bash.exe'),\r\n localAppData ? path.join(localAppData, 'Programs', 'Git', 'bin', 'bash.exe') : undefined,\r\n ]);\r\n },\r\n // ComSpec is what Node's `shell: true` spawns verbatim, so it is reported\r\n // as-is; the System32 path is only a guess for a display name.\r\n cmd: () =>\r\n env.ComSpec ||\r\n env.COMSPEC ||\r\n first([path.join(systemRoot, 'System32', 'cmd.exe')]) ||\r\n 'cmd.exe',\r\n };\r\n}\r\n\r\nfunction shell(\r\n kind: ShellKind,\r\n source: ShellSource,\r\n file?: string,\r\n warning?: string,\r\n): ResolvedShell {\r\n const resolved: ResolvedShell = { kind, label: LABELS[kind], source };\r\n if (file) resolved.file = file;\r\n if (warning) resolved.warning = warning;\r\n return resolved;\r\n}\r\n\r\nfunction kindFromExecutable(file: string, platform: NodeJS.Platform): ShellKind {\r\n const path = platform === 'win32' ? win32 : posix;\r\n const name = path\r\n .basename(file)\r\n .toLowerCase()\r\n .replace(/\\.exe$/, '');\r\n if (name === 'pwsh') return 'pwsh';\r\n if (name === 'powershell') return 'powershell';\r\n if (name === 'cmd') return 'cmd';\r\n if (name.includes('bash')) return 'bash';\r\n return 'sh';\r\n}\r\n\r\nfunction resolveRequested(\r\n requested: string,\r\n platform: NodeJS.Platform,\r\n probe: Probe,\r\n): ResolvedShell | undefined {\r\n const byName = KIND_BY_NAME[requested.toLowerCase()];\r\n if (byName) {\r\n switch (byName) {\r\n case 'bash': {\r\n const file = platform === 'win32' ? probe.gitBash() : probe.which('bash');\r\n return file ? shell('bash', 'setting', file) : undefined;\r\n }\r\n case 'pwsh': {\r\n const file = probe.pwsh() ?? (platform === 'win32' ? undefined : probe.which('pwsh'));\r\n return file ? shell('pwsh', 'setting', file) : undefined;\r\n }\r\n case 'powershell': {\r\n const file = probe.powershell();\r\n return file ? shell('powershell', 'setting', file) : undefined;\r\n }\r\n case 'cmd':\r\n return platform === 'win32' ? shell('cmd', 'setting', probe.cmd()) : undefined;\r\n case 'sh': {\r\n if (platform === 'win32') return undefined;\r\n return shell('sh', 'setting', probe.which('sh') ?? '/bin/sh');\r\n }\r\n }\r\n }\r\n if (probe.exists(requested)) {\r\n return shell(kindFromExecutable(requested, platform), 'setting', requested);\r\n }\r\n return undefined;\r\n}\r\n\r\nfunction launchedFromPosixShell(env: NodeJS.ProcessEnv): boolean {\r\n if (env.MSYSTEM) return true;\r\n const launcher = env.SHELL ?? '';\r\n return /(^|[\\\\/])(ba|z|da)?sh(\\.exe)?$/i.test(launcher);\r\n}\r\n\r\nfunction resolveWindows(env: NodeJS.ProcessEnv, probe: Probe): ResolvedShell {\r\n if (launchedFromPosixShell(env)) {\r\n const bash = probe.gitBash();\r\n if (bash) return shell('bash', 'launching-shell', bash);\r\n }\r\n const pwsh = probe.pwsh();\r\n if (pwsh) return shell('pwsh', 'detected', pwsh);\r\n const powershell = probe.powershell();\r\n if (powershell) return shell('powershell', 'detected', powershell);\r\n const bash = probe.gitBash();\r\n if (bash) return shell('bash', 'detected', bash);\r\n return shell('cmd', 'fallback', probe.cmd());\r\n}\r\n\r\n/** Resolve the shell the Bash tool should spawn. See the module comment for the ladder. */\r\nexport function resolveShell(input: ShellSelectionInput = {}): ResolvedShell {\r\n const platform = input.platform ?? process.platform;\r\n const env = input.env ?? process.env;\r\n const probe = createProbe(platform, env, input.existsSync ?? fsExistsSync);\r\n const requested = (env.BOOK_SHELL || input.requested || '').trim();\r\n\r\n let warning: string | undefined;\r\n if (requested) {\r\n const chosen = resolveRequested(requested, platform, probe);\r\n if (chosen) return chosen;\r\n warning = `Requested shell \"${requested}\" was not found; Book chose one automatically instead.`;\r\n }\r\n // POSIX keeps Node's default: `/bin/sh` through `shell: true`, exactly as before.\r\n const automatic = platform === 'win32' ? resolveWindows(env, probe) : shell('sh', 'detected');\r\n return warning ? { ...automatic, warning } : automatic;\r\n}\r\n\r\n/**\r\n * Silences progress records (which a redirected stderr would otherwise carry\r\n * as CLIXML) and sets UTF-8 on both pipes so non-ASCII output survives.\r\n */\r\nconst POWERSHELL_PRELUDE =\r\n \"$ProgressPreference = 'SilentlyContinue'; \" +\r\n '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ' +\r\n '$OutputEncoding = [System.Text.Encoding]::UTF8\\n';\r\n\r\n/**\r\n * Mirrors bash: the process exits with the last statement's status, using the\r\n * native command's code when there is one. `$?` refers to the statement\r\n * before the `if`, i.e. the last line of the user's command.\r\n */\r\nconst POWERSHELL_EPILOGUE =\r\n '\\nif (-not $?) { if ($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }';\r\n\r\n/**\r\n * Windows PowerShell 5.1 serializes anything it writes to a redirected stderr\r\n * as CLIXML, so a plain `Get-Item missing` hands the model an XML document\r\n * instead of \"Cannot find path\". Merging the error stream is not enough on\r\n * its own: an ErrorRecord that reaches the end of the pipeline is still\r\n * rendered to the host's error stream. Each record is turned into its message\r\n * text inside the pipeline instead, and the failure is recorded in a flag\r\n * because `$?` after the `ForEach-Object` describes the pipeline, not the\r\n * user's last statement. Verified on this exact script: text errors on\r\n * stdout, exit codes 0/1/3/7 preserved, table formatting intact.\r\n */\r\nfunction wrapForWindowsPowerShell(command: string): string {\r\n return (\r\n POWERSHELL_PRELUDE +\r\n '$global:__bookErr = $false\\n' +\r\n '& {\\n' +\r\n command +\r\n '\\nif (-not $?) { $global:__bookErr = $true }\\n' +\r\n '} 2>&1 | ForEach-Object { if ($_ -is [System.Management.Automation.ErrorRecord]) { \"$_\" } else { $_ } }\\n' +\r\n 'if ($global:__bookErr) { if ($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }'\r\n );\r\n}\r\n\r\n/**\r\n * Base64 of UTF-16LE, the encoding `-EncodedCommand` reads. Chosen over\r\n * `-Command` because Windows PowerShell 5.1 re-parses a `-Command` argument\r\n * and silently strips embedded double quotes from it; the encoded form is\r\n * delivered byte for byte. The cost is roughly 2.7x the command length on the\r\n * command line, which stays inside CreateProcess's 32k limit for anything a\r\n * model writes as a single tool call.\r\n */\r\nexport function encodePowerShellCommand(\r\n command: string,\r\n kind: 'pwsh' | 'powershell' = 'pwsh',\r\n): string {\r\n const script =\r\n kind === 'powershell'\r\n ? wrapForWindowsPowerShell(command)\r\n : POWERSHELL_PRELUDE + command + POWERSHELL_EPILOGUE;\r\n return Buffer.from(script, 'utf16le').toString('base64');\r\n}\r\n\r\n/** Decode what `encodePowerShellCommand` produced, for tests and diagnostics. */\r\nexport function decodePowerShellCommand(encoded: string): string {\r\n return Buffer.from(encoded, 'base64').toString('utf16le');\r\n}\r\n\r\n/**\r\n * The argv that runs `command` under `shell`, or undefined when the command\r\n * should go to Node's `shell: true` (cmd.exe and the POSIX default).\r\n */\r\nexport function shellExecution(\r\n resolved: ResolvedShell,\r\n command: string,\r\n): CommandExecution | undefined {\r\n switch (resolved.kind) {\r\n case 'bash':\r\n case 'sh':\r\n return resolved.file ? { file: resolved.file, args: ['-c', command] } : undefined;\r\n case 'pwsh':\r\n case 'powershell':\r\n return resolved.file\r\n ? {\r\n file: resolved.file,\r\n args: [\r\n '-NoLogo',\r\n '-NoProfile',\r\n '-NonInteractive',\r\n '-EncodedCommand',\r\n encodePowerShellCommand(command, resolved.kind),\r\n ],\r\n }\r\n : undefined;\r\n case 'cmd':\r\n // cmd.exe quoting cannot be reproduced from an argv; Node's `shell: true`\r\n // builds the `/d /s /c \"...\"` line itself.\r\n return undefined;\r\n }\r\n}\r\n\r\n/** `label (file)` for doctor and the system prompt. */\r\nexport function describeShell(resolved: ResolvedShell): string {\r\n return resolved.file ? `${resolved.label} (${resolved.file})` : resolved.label;\r\n}\r\n\r\n/**\r\n * The Harness bullet that tells the model which syntax to write. The shell is\r\n * fixed for the session, so this belongs in the cached prompt prefix.\r\n */\r\nexport function shellPromptLine(resolved: ResolvedShell): string {\r\n const where = resolved.file ? ` at \\`${resolved.file}\\`` : '';\r\n switch (resolved.kind) {\r\n case 'bash':\r\n return (\r\n `- The Bash tool runs Git Bash (POSIX sh)${where}, not cmd.exe or PowerShell. Use Unix shell ` +\r\n 'syntax: `/dev/null` not `NUL`, forward slashes, `$VAR` not `%VAR%` or `$env:VAR`. Windows ' +\r\n 'drive paths such as `C:/dir` and MSYS paths such as `/c/dir` both work. The bash sandbox ' +\r\n 'requires bubblewrap and is unavailable on Windows.'\r\n );\r\n case 'pwsh':\r\n return (\r\n `- The Bash tool runs PowerShell 7 (pwsh)${where}, not bash or cmd.exe. Use PowerShell ` +\r\n 'syntax: `&&` and `||` work, `;` sequences commands, `$env:VAR` reads a variable, and ' +\r\n '`Get-ChildItem`, `Select-String`, `Get-Content` replace `ls`, `grep`, `cat` unless those ' +\r\n 'tools are on PATH. There are no heredocs: use a single-quoted here-string with the closing ' +\r\n \"`'@` at column 0. The bash sandbox is unavailable on Windows.\"\r\n );\r\n case 'powershell':\r\n return (\r\n `- The Bash tool runs Windows PowerShell 5.1${where}, not bash or cmd.exe. ` +\r\n '`&&` and `||` are NOT available: use `;` to sequence, or `cmd1; if ($?) { cmd2 }`. Avoid ' +\r\n 'PowerShell 7-only syntax (`??`, `?.`). `$env:VAR` reads a variable; `Get-ChildItem`, ' +\r\n '`Select-String`, `Get-Content` replace `ls`, `grep`, `cat` unless those tools are on PATH. ' +\r\n \"There are no heredocs: use a single-quoted here-string with the closing `'@` at column 0. \" +\r\n 'The bash sandbox is unavailable on Windows.'\r\n );\r\n case 'cmd':\r\n return (\r\n '- The Bash tool runs cmd.exe because no PowerShell or Git Bash was found (set `shell` in ' +\r\n '`~/.book/settings.json` or `BOOK_SHELL` to change this). Each command must be a single ' +\r\n 'line; chain with `&` or `&&`; `%VAR%` reads a variable; `dir`, `findstr`, `type` replace ' +\r\n '`ls`, `grep`, `cat`; quoting follows cmd.exe rules, not POSIX. The bash sandbox is ' +\r\n 'unavailable on Windows.'\r\n );\r\n case 'sh':\r\n return (\r\n `- The Bash tool runs commands through the platform's default shell, \\`/bin/sh\\`${where}. ` +\r\n 'The bash sandbox requires bubblewrap.'\r\n );\r\n }\r\n}\r\n"],"mappings":"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","names":["bash"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
updateWorkspaceTrust
|
|
3
|
+
} from "./chunk-DGMQEI6Y.js";
|
|
4
|
+
|
|
5
|
+
// src/permission-approvals.ts
|
|
6
|
+
function evaluateProjectAllowRule(store, rule) {
|
|
7
|
+
return store?.[rule] ?? "unknown";
|
|
8
|
+
}
|
|
9
|
+
function partitionProjectAllowRules(rules, store) {
|
|
10
|
+
const partition = { approved: [], pending: [], rejected: [] };
|
|
11
|
+
for (const rule of rules) {
|
|
12
|
+
const state = evaluateProjectAllowRule(store, rule);
|
|
13
|
+
if (state === "approved") partition.approved.push(rule);
|
|
14
|
+
else if (state === "rejected") partition.rejected.push(rule);
|
|
15
|
+
else partition.pending.push(rule);
|
|
16
|
+
}
|
|
17
|
+
return partition;
|
|
18
|
+
}
|
|
19
|
+
function persistProjectAllowRuleChoice(workspace, rule, choice, options = {}) {
|
|
20
|
+
return updateWorkspaceTrust(
|
|
21
|
+
workspace,
|
|
22
|
+
(trust) => {
|
|
23
|
+
trust.permissionAllowRules[rule] = choice;
|
|
24
|
+
},
|
|
25
|
+
options.trustStorePath
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
evaluateProjectAllowRule,
|
|
31
|
+
partitionProjectAllowRules,
|
|
32
|
+
persistProjectAllowRuleChoice
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=chunk-WKBO5R4O.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/permission-approvals.ts"],"sourcesContent":["/**\r\n * Trust decisions for permission allow-rules declared by a repository.\r\n *\r\n * `permissions.allow` entries accumulate across settings layers, so a rule in a\r\n * cloned repository's checked-in `.book/settings.json` would otherwise widen the\r\n * effective allow list — reaching the same outcome as the `bypassPermissions`\r\n * default mode that project layers are already forbidden to select. Once merged\r\n * a rule carries no provenance, so nothing downstream can tell a repository's\r\n * grant from the user's own.\r\n *\r\n * Project-declared allow rules therefore require a one-time per-workspace\r\n * decision, keyed by the exact rule text and recorded in the user-global trust\r\n * store, outside the workspace, where nothing the repository ships can reach it\r\n * (see `workspace-trust.ts`).\r\n *\r\n * `ask` and `deny` need no gate: they only ever restrict.\r\n */\r\nimport type { ProjectAllowRuleChoice } from './settings.js';\r\nimport { updateWorkspaceTrust } from './workspace-trust.js';\r\n\r\n/** Recorded decisions, keyed by the exact rule text. */\r\nexport type ProjectAllowRuleStore = Record<string, ProjectAllowRuleChoice>;\r\n\r\nexport type ProjectAllowRuleState = ProjectAllowRuleChoice | 'unknown';\r\n\r\nexport function evaluateProjectAllowRule(\r\n store: ProjectAllowRuleStore | undefined,\r\n rule: string,\r\n): ProjectAllowRuleState {\r\n return store?.[rule] ?? 'unknown';\r\n}\r\n\r\nexport interface ProjectAllowRulePartition {\r\n /** Approved by the user: these join the resolved allow list. */\r\n approved: string[];\r\n /** No decision recorded: withheld until the user makes one. */\r\n pending: string[];\r\n /** Explicitly refused: withheld, and not re-offered. */\r\n rejected: string[];\r\n}\r\n\r\nexport function partitionProjectAllowRules(\r\n rules: readonly string[],\r\n store: ProjectAllowRuleStore | undefined,\r\n): ProjectAllowRulePartition {\r\n const partition: ProjectAllowRulePartition = { approved: [], pending: [], rejected: [] };\r\n for (const rule of rules) {\r\n const state = evaluateProjectAllowRule(store, rule);\r\n if (state === 'approved') partition.approved.push(rule);\r\n else if (state === 'rejected') partition.rejected.push(rule);\r\n else partition.pending.push(rule);\r\n }\r\n return partition;\r\n}\r\n\r\n/**\r\n * Record an approve/reject decision for one rule, leaving every other recorded\r\n * decision — in this workspace and in every other — untouched.\r\n */\r\nexport function persistProjectAllowRuleChoice(\r\n workspace: string,\r\n rule: string,\r\n choice: ProjectAllowRuleChoice,\r\n options: { trustStorePath?: string } = {},\r\n): { ok: boolean; error?: string } {\r\n return updateWorkspaceTrust(\r\n workspace,\r\n (trust) => {\r\n trust.permissionAllowRules[rule] = choice;\r\n },\r\n options.trustStorePath,\r\n );\r\n}\r\n"],"mappings":";;;;;AAyBO,SAAS,yBACd,OACA,MACuB;AACvB,SAAO,QAAQ,IAAI,KAAK;AAC1B;AAWO,SAAS,2BACd,OACA,OAC2B;AAC3B,QAAM,YAAuC,EAAE,UAAU,CAAC,GAAG,SAAS,CAAC,GAAG,UAAU,CAAC,EAAE;AACvF,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,yBAAyB,OAAO,IAAI;AAClD,QAAI,UAAU,WAAY,WAAU,SAAS,KAAK,IAAI;AAAA,aAC7C,UAAU,WAAY,WAAU,SAAS,KAAK,IAAI;AAAA,QACtD,WAAU,QAAQ,KAAK,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AAMO,SAAS,8BACd,WACA,MACA,QACA,UAAuC,CAAC,GACP;AACjC,SAAO;AAAA,IACL;AAAA,IACA,CAAC,UAAU;AACT,YAAM,qBAAqB,IAAI,IAAI;AAAA,IACrC;AAAA,IACA,QAAQ;AAAA,EACV;AACF;","names":[]}
|