@warlock.js/ai-workspace 5.44.0 → 5.46.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 +13 -0
- package/cjs/index.cjs +101 -35
- package/cjs/index.cjs.map +1 -1
- package/esm/contracts/shell-approval.type.d.mts +11 -1
- package/esm/contracts/shell-approval.type.d.mts.map +1 -1
- package/esm/contracts/tool-io.type.d.mts +10 -0
- package/esm/contracts/tool-io.type.d.mts.map +1 -1
- package/esm/contracts/workspace.contract.d.mts +12 -1
- package/esm/contracts/workspace.contract.d.mts.map +1 -1
- package/esm/index.d.mts +2 -2
- package/esm/ops.d.mts.map +1 -1
- package/esm/ops.mjs +27 -10
- package/esm/ops.mjs.map +1 -1
- package/esm/output/spill.mjs +4 -1
- package/esm/output/spill.mjs.map +1 -1
- package/esm/policy/approval-helpers.mjs +22 -0
- package/esm/policy/approval-helpers.mjs.map +1 -0
- package/esm/policy/powershell-parser.mjs +6 -4
- package/esm/policy/powershell-parser.mjs.map +1 -1
- package/esm/policy/shell-check.d.mts.map +1 -1
- package/esm/policy/shell-check.mjs +4 -4
- package/esm/policy/shell-check.mjs.map +1 -1
- package/esm/policy/shell-interpreter.d.mts +20 -2
- package/esm/policy/shell-interpreter.d.mts.map +1 -1
- package/esm/policy/shell-interpreter.mjs +26 -12
- package/esm/policy/shell-interpreter.mjs.map +1 -1
- package/esm/policy/shell-parser.d.mts +3 -1
- package/esm/policy/shell-parser.d.mts.map +1 -1
- package/esm/policy/shell-parser.mjs +1 -1
- package/esm/policy/shell-parser.mjs.map +1 -1
- package/esm/tools/run-shell.d.mts +1 -1
- package/esm/tools/run-shell.d.mts.map +1 -1
- package/esm/tools/run-shell.mjs +2 -1
- package/esm/tools/run-shell.mjs.map +1 -1
- package/esm/tools/run-tests.d.mts +1 -1
- package/esm/tools/run-tests.mjs +1 -1
- package/esm/tools/run-tests.mjs.map +1 -1
- package/esm/workspace.d.mts.map +1 -1
- package/esm/workspace.mjs +12 -1
- package/esm/workspace.mjs.map +1 -1
- package/llms-full.txt +23 -17
- package/package.json +3 -3
- package/skills/use-a-workspace/SKILL.md +23 -17
|
@@ -12,13 +12,28 @@ export interface ShellHost {
|
|
|
12
12
|
pathEnv: string | undefined;
|
|
13
13
|
/** Whether `file` is an existing executable file. */
|
|
14
14
|
isExecutable(file: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* The version of the PowerShell at `file` (for example `"7.4.6"`), when the
|
|
17
|
+
* host can tell. Optional: the real host does not start the shell to ask, so
|
|
18
|
+
* `version` is then known only for Windows PowerShell (always `"5.1"`).
|
|
19
|
+
*/
|
|
20
|
+
powershellVersion?(file: string): string | undefined;
|
|
15
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Which PowerShell a `powershell` interpreter is: `"desktop"` is Windows
|
|
24
|
+
* PowerShell 5.1 (`powershell.exe`), `"core"` is PowerShell 7+ (`pwsh`).
|
|
25
|
+
*/
|
|
26
|
+
export type PowerShellEdition = "desktop" | "core";
|
|
16
27
|
/** The shell a policy's `interpreter` resolves to on a host. */
|
|
17
28
|
export interface ResolvedShell {
|
|
18
29
|
/** The concrete interpreter (never `"auto"`). */
|
|
19
30
|
interpreter: ResolvedShellInterpreter;
|
|
20
31
|
/** The grammar its command lines are parsed with. */
|
|
21
32
|
dialect: ShellDialect;
|
|
33
|
+
/** For `"powershell"`: which edition runs the lines. Absent when the executable was not found. */
|
|
34
|
+
edition?: PowerShellEdition;
|
|
35
|
+
/** For `"powershell"`: the shell's version, when known (`"5.1"` for Windows PowerShell). */
|
|
36
|
+
version?: string;
|
|
22
37
|
/**
|
|
23
38
|
* The executable to spawn: `interpreterPath`, or the shell found on `PATH`.
|
|
24
39
|
* Absent when the shell was not found (the spawn then reports the failure)
|
|
@@ -32,8 +47,11 @@ export interface ResolvedShell {
|
|
|
32
47
|
* Resolve a shell policy's `interpreter` (and `interpreterPath`) to the
|
|
33
48
|
* concrete shell that will run lines, and the grammar they are checked with.
|
|
34
49
|
*
|
|
35
|
-
* `"auto"` picks `pwsh
|
|
36
|
-
* `git`), then
|
|
50
|
+
* `"auto"` picks, on Windows, `pwsh` (PowerShell 7+), then Git Bash (found next
|
|
51
|
+
* to `git`), then Windows PowerShell 5.1, then `cmd.exe`; elsewhere `bash`,
|
|
52
|
+
* then `sh`. Windows PowerShell 5.1 gets its own dialect (`"powershell5"`)
|
|
53
|
+
* because it cannot run `&&` / `||`; `edition` and `version` say which one
|
|
54
|
+
* was found. An explicit interpreter whose executable
|
|
37
55
|
* is not on `PATH` keeps its name and has no `file`, so the spawn reports the
|
|
38
56
|
* missing shell instead of quietly running another one.
|
|
39
57
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-interpreter.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/policy/shell-interpreter.ts"],"mappings":";;;;;YAMY,2BAA2B,QAAQ;;iBAG9B;;EAEf,UAAU,OAAO;;EAEjB;;EAEA,aAAa
|
|
1
|
+
{"version":3,"file":"shell-interpreter.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/policy/shell-interpreter.ts"],"mappings":";;;;;YAMY,2BAA2B,QAAQ;;iBAG9B;;EAEf,UAAU,OAAO;;EAEjB;;EAEA,aAAa;;;;;;EAMb,mBAAmB;;;;;;YAOT;;iBAGK;;EAEf,aAAa;;EAEb,SAAS;;EAET,UAAU;;EAEV;;;;;;EAMA;;EAEA;;;;;;;;;;;;;;;;;wBAsHc,aACd,OAAO,KAAK,sEACZ,OAAM,YACL"}
|
|
@@ -18,8 +18,8 @@ function currentShellHost() {
|
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
/** The grammar a concrete interpreter reads on `platform`. */
|
|
21
|
-
function dialectOf(interpreter, platform = process.platform) {
|
|
22
|
-
if (interpreter === "powershell") return "powershell";
|
|
21
|
+
function dialectOf(interpreter, platform = process.platform, edition) {
|
|
22
|
+
if (interpreter === "powershell") return edition === "desktop" ? "powershell5" : "powershell";
|
|
23
23
|
if (interpreter === "cmd") return "cmd";
|
|
24
24
|
return interpreter === "sh" && platform === "win32" ? "cmd" : "posix";
|
|
25
25
|
}
|
|
@@ -46,15 +46,23 @@ function findGitBash(host) {
|
|
|
46
46
|
function powershellNames(host) {
|
|
47
47
|
return host.platform === "win32" ? ["pwsh", "powershell"] : ["pwsh"];
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
/** The edition of the PowerShell executable `file`: `powershell.exe` is Windows PowerShell, anything else (`pwsh`) is PowerShell 7+. */
|
|
50
|
+
function editionOf(file) {
|
|
51
|
+
return /(^|[\\/])powershell(\.exe)?$/i.test(file) ? "desktop" : "core";
|
|
52
|
+
}
|
|
53
|
+
function powershellLabel(edition) {
|
|
54
|
+
if (edition === "desktop") return "Windows PowerShell 5.1 (powershell)";
|
|
55
|
+
return edition === "core" ? "PowerShell 7 (pwsh)" : "PowerShell";
|
|
51
56
|
}
|
|
52
57
|
/**
|
|
53
58
|
* Resolve a shell policy's `interpreter` (and `interpreterPath`) to the
|
|
54
59
|
* concrete shell that will run lines, and the grammar they are checked with.
|
|
55
60
|
*
|
|
56
|
-
* `"auto"` picks `pwsh
|
|
57
|
-
* `git`), then
|
|
61
|
+
* `"auto"` picks, on Windows, `pwsh` (PowerShell 7+), then Git Bash (found next
|
|
62
|
+
* to `git`), then Windows PowerShell 5.1, then `cmd.exe`; elsewhere `bash`,
|
|
63
|
+
* then `sh`. Windows PowerShell 5.1 gets its own dialect (`"powershell5"`)
|
|
64
|
+
* because it cannot run `&&` / `||`; `edition` and `version` say which one
|
|
65
|
+
* was found. An explicit interpreter whose executable
|
|
58
66
|
* is not on `PATH` keeps its name and has no `file`, so the spawn reports the
|
|
59
67
|
* missing shell instead of quietly running another one.
|
|
60
68
|
*
|
|
@@ -74,10 +82,12 @@ function resolveShell(shell, host = currentShellHost()) {
|
|
|
74
82
|
if (requested === "auto") {
|
|
75
83
|
if (explicit !== void 0) interpreter = /(^|[\\/])(pwsh|powershell)(\.exe)?$/i.test(explicit) ? "powershell" : /(^|[\\/])cmd(\.exe)?$/i.test(explicit) ? "cmd" : /(^|[\\/])bash(\.exe)?$/i.test(explicit) ? "bash" : "sh";
|
|
76
84
|
else if (host.platform === "win32") {
|
|
77
|
-
const
|
|
78
|
-
const gitBash =
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
const core = findOnPath(["pwsh"], host);
|
|
86
|
+
const gitBash = core === void 0 ? findGitBash(host) : void 0;
|
|
87
|
+
const desktop = core === void 0 && gitBash === void 0 ? findOnPath(["powershell"], host) : void 0;
|
|
88
|
+
const found = core ?? gitBash ?? desktop;
|
|
89
|
+
interpreter = core !== void 0 || desktop !== void 0 ? "powershell" : gitBash !== void 0 ? "bash" : "cmd";
|
|
90
|
+
file = found;
|
|
81
91
|
} else {
|
|
82
92
|
const bash = findOnPath(["bash"], host);
|
|
83
93
|
interpreter = bash !== void 0 ? "bash" : "sh";
|
|
@@ -89,16 +99,20 @@ function resolveShell(shell, host = currentShellHost()) {
|
|
|
89
99
|
else if (interpreter === "bash") file = findOnPath(["bash"], host) ?? (host.platform === "win32" ? findGitBash(host) : void 0);
|
|
90
100
|
else if (interpreter === "cmd" && host.platform === "win32") file = process.env.ComSpec ?? "cmd.exe";
|
|
91
101
|
}
|
|
102
|
+
const edition = interpreter === "powershell" && file !== void 0 ? editionOf(file) : void 0;
|
|
103
|
+
const version = edition === "desktop" ? "5.1" : edition === "core" && file !== void 0 ? host.powershellVersion?.(file) : void 0;
|
|
92
104
|
const labels = {
|
|
93
105
|
none: "none",
|
|
94
106
|
sh: host.platform === "win32" ? "cmd.exe" : "sh",
|
|
95
107
|
bash: "bash",
|
|
96
108
|
cmd: "cmd.exe",
|
|
97
|
-
powershell: powershellLabel(
|
|
109
|
+
powershell: powershellLabel(edition)
|
|
98
110
|
};
|
|
99
111
|
return {
|
|
100
112
|
interpreter,
|
|
101
|
-
dialect: dialectOf(interpreter, host.platform),
|
|
113
|
+
dialect: dialectOf(interpreter, host.platform, edition),
|
|
114
|
+
...edition !== void 0 ? { edition } : {},
|
|
115
|
+
...version !== void 0 ? { version } : {},
|
|
102
116
|
...file !== void 0 ? { file } : {},
|
|
103
117
|
label: labels[interpreter]
|
|
104
118
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-interpreter.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/policy/shell-interpreter.ts"],"sourcesContent":["import { accessSync, constants } from \"node:fs\";\nimport path from \"node:path\";\nimport type { WorkspaceShellInterpreter, WorkspaceShellPolicy } from \"../contracts\";\nimport type { ShellDialect } from \"./shell-parser\";\n\n/** An interpreter after `\"auto\"` has been resolved: what actually runs the line. */\nexport type ResolvedShellInterpreter = Exclude<WorkspaceShellInterpreter, \"auto\">;\n\n/** What the resolver reads from the host, injectable so every platform is testable anywhere. */\nexport interface ShellHost {\n /** `process.platform`. */\n platform: NodeJS.Platform;\n /** The host's `PATH` value (not the workspace's isolated environment). */\n pathEnv: string | undefined;\n /** Whether `file` is an existing executable file. */\n isExecutable(file: string): boolean;\n}\n\n/** The shell a policy's `interpreter` resolves to on a host. */\nexport interface ResolvedShell {\n /** The concrete interpreter (never `\"auto\"`). */\n interpreter: ResolvedShellInterpreter;\n /** The grammar its command lines are parsed with. */\n dialect: ShellDialect;\n /**\n * The executable to spawn: `interpreterPath`, or the shell found on `PATH`.\n * Absent when the shell was not found (the spawn then reports the failure)\n * or for `\"none\"`.\n */\n file?: string;\n /** A human-readable name for descriptions, e.g. `bash`, `PowerShell 7 (pwsh)`. */\n label: string;\n}\n\n/** The real host: the running platform, `process.env.PATH`, and the filesystem. */\nexport function currentShellHost(): ShellHost {\n return {\n platform: process.platform,\n pathEnv: process.env.PATH ?? process.env.Path,\n isExecutable: (file) => {\n try {\n accessSync(file, process.platform === \"win32\" ? constants.F_OK : constants.X_OK);\n\n return true;\n } catch {\n return false;\n }\n },\n };\n}\n\n/** The grammar a concrete interpreter reads on `platform`. */\nexport function dialectOf(\n interpreter: ResolvedShellInterpreter,\n platform: NodeJS.Platform = process.platform,\n): ShellDialect {\n if (interpreter === \"powershell\") {\n return \"powershell\";\n }\n\n if (interpreter === \"cmd\") {\n return \"cmd\";\n }\n\n // `sh` is cmd.exe on Windows, where there is no /bin/sh.\n return interpreter === \"sh\" && platform === \"win32\" ? \"cmd\" : \"posix\";\n}\n\n/** The first of `names` found in the host's `PATH` (Windows: with `.exe`). */\nfunction findOnPath(names: string[], host: ShellHost): string | undefined {\n const windows = host.platform === \"win32\";\n const separator = windows ? \";\" : \":\";\n const join = windows ? path.win32.join : path.posix.join;\n const directories = (host.pathEnv ?? \"\").split(separator).filter((entry) => entry !== \"\");\n\n for (const name of names) {\n for (const directory of directories) {\n const candidate = join(directory, windows ? `${name}.exe` : name);\n\n if (host.isExecutable(candidate)) {\n return candidate;\n }\n }\n }\n\n return undefined;\n}\n\n/** Git for Windows' `bash.exe`, derived from the `git` found on `PATH`. */\nfunction findGitBash(host: ShellHost): string | undefined {\n const git = findOnPath([\"git\"], host);\n\n if (git === undefined) {\n return undefined;\n }\n\n // …\\Git\\cmd\\git.exe, …\\Git\\bin\\git.exe or …\\Git\\mingw64\\bin\\git.exe\n const win = path.win32;\n const roots = [win.dirname(win.dirname(git)), win.dirname(win.dirname(win.dirname(git)))];\n\n for (const root of roots) {\n for (const candidate of [\n win.join(root, \"bin\", \"bash.exe\"),\n win.join(root, \"usr\", \"bin\", \"bash.exe\"),\n ]) {\n if (host.isExecutable(candidate)) {\n return candidate;\n }\n }\n }\n\n return undefined;\n}\n\n/** The PowerShell executable names, newest first. */\nfunction powershellNames(host: ShellHost): string[] {\n return host.platform === \"win32\" ? [\"pwsh\", \"powershell\"] : [\"pwsh\"];\n}\n\nfunction powershellLabel(file: string | undefined): string {\n return file !== undefined && /pwsh(\\.exe)?$/i.test(file) ? \"PowerShell 7 (pwsh)\" : \"PowerShell\";\n}\n\n/**\n * Resolve a shell policy's `interpreter` (and `interpreterPath`) to the\n * concrete shell that will run lines, and the grammar they are checked with.\n *\n * `\"auto\"` picks `pwsh`, then Windows PowerShell, then Git Bash (found next to\n * `git`), then `cmd.exe` on Windows and `bash`, then `sh` elsewhere. An explicit interpreter whose executable\n * is not on `PATH` keeps its name and has no `file`, so the spawn reports the\n * missing shell instead of quietly running another one.\n *\n * @example\n * resolveShell({ interpreter: \"auto\" }, { platform: \"win32\", pathEnv: \"C:\\\\bin\", isExecutable })\n */\nexport function resolveShell(\n shell: Pick<WorkspaceShellPolicy, \"interpreter\" | \"interpreterPath\"> | undefined,\n host: ShellHost = currentShellHost(),\n): ResolvedShell {\n const requested = shell?.interpreter ?? \"none\";\n const explicit = shell?.interpreterPath;\n\n if (requested === \"none\") {\n return { interpreter: \"none\", dialect: \"posix\", label: \"none\" };\n }\n\n let interpreter: ResolvedShellInterpreter;\n let file: string | undefined = explicit;\n\n if (requested === \"auto\") {\n if (explicit !== undefined) {\n interpreter = /(^|[\\\\/])(pwsh|powershell)(\\.exe)?$/i.test(explicit)\n ? \"powershell\"\n : /(^|[\\\\/])cmd(\\.exe)?$/i.test(explicit)\n ? \"cmd\"\n : /(^|[\\\\/])bash(\\.exe)?$/i.test(explicit)\n ? \"bash\"\n : \"sh\";\n } else if (host.platform === \"win32\") {\n const powershell = findOnPath(powershellNames(host), host);\n const gitBash = powershell === undefined ? findGitBash(host) : undefined;\n\n interpreter =\n powershell !== undefined ? \"powershell\" : gitBash !== undefined ? \"bash\" : \"cmd\";\n file = powershell ?? gitBash;\n } else {\n const bash = findOnPath([\"bash\"], host);\n\n interpreter = bash !== undefined ? \"bash\" : \"sh\";\n file = bash;\n }\n } else {\n interpreter = requested;\n }\n\n if (file === undefined) {\n if (interpreter === \"powershell\") {\n file = findOnPath(powershellNames(host), host);\n } else if (interpreter === \"bash\") {\n file =\n findOnPath([\"bash\"], host) ?? (host.platform === \"win32\" ? findGitBash(host) : undefined);\n } else if (interpreter === \"cmd\" && host.platform === \"win32\") {\n file = process.env.ComSpec ?? \"cmd.exe\";\n }\n }\n\n const labels: Record<ResolvedShellInterpreter, string> = {\n none: \"none\",\n sh: host.platform === \"win32\" ? \"cmd.exe\" : \"sh\",\n bash: \"bash\",\n cmd: \"cmd.exe\",\n powershell: powershellLabel(file),\n };\n\n return {\n interpreter,\n dialect: dialectOf(interpreter, host.platform),\n ...(file !== undefined ? { file } : {}),\n label: labels[interpreter],\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"shell-interpreter.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/policy/shell-interpreter.ts"],"sourcesContent":["import { accessSync, constants } from \"node:fs\";\nimport path from \"node:path\";\nimport type { WorkspaceShellInterpreter, WorkspaceShellPolicy } from \"../contracts\";\nimport type { ShellDialect } from \"./shell-parser\";\n\n/** An interpreter after `\"auto\"` has been resolved: what actually runs the line. */\nexport type ResolvedShellInterpreter = Exclude<WorkspaceShellInterpreter, \"auto\">;\n\n/** What the resolver reads from the host, injectable so every platform is testable anywhere. */\nexport interface ShellHost {\n /** `process.platform`. */\n platform: NodeJS.Platform;\n /** The host's `PATH` value (not the workspace's isolated environment). */\n pathEnv: string | undefined;\n /** Whether `file` is an existing executable file. */\n isExecutable(file: string): boolean;\n /**\n * The version of the PowerShell at `file` (for example `\"7.4.6\"`), when the\n * host can tell. Optional: the real host does not start the shell to ask, so\n * `version` is then known only for Windows PowerShell (always `\"5.1\"`).\n */\n powershellVersion?(file: string): string | undefined;\n}\n\n/**\n * Which PowerShell a `powershell` interpreter is: `\"desktop\"` is Windows\n * PowerShell 5.1 (`powershell.exe`), `\"core\"` is PowerShell 7+ (`pwsh`).\n */\nexport type PowerShellEdition = \"desktop\" | \"core\";\n\n/** The shell a policy's `interpreter` resolves to on a host. */\nexport interface ResolvedShell {\n /** The concrete interpreter (never `\"auto\"`). */\n interpreter: ResolvedShellInterpreter;\n /** The grammar its command lines are parsed with. */\n dialect: ShellDialect;\n /** For `\"powershell\"`: which edition runs the lines. Absent when the executable was not found. */\n edition?: PowerShellEdition;\n /** For `\"powershell\"`: the shell's version, when known (`\"5.1\"` for Windows PowerShell). */\n version?: string;\n /**\n * The executable to spawn: `interpreterPath`, or the shell found on `PATH`.\n * Absent when the shell was not found (the spawn then reports the failure)\n * or for `\"none\"`.\n */\n file?: string;\n /** A human-readable name for descriptions, e.g. `bash`, `PowerShell 7 (pwsh)`. */\n label: string;\n}\n\n/** The real host: the running platform, `process.env.PATH`, and the filesystem. */\nexport function currentShellHost(): ShellHost {\n return {\n platform: process.platform,\n pathEnv: process.env.PATH ?? process.env.Path,\n isExecutable: (file) => {\n try {\n accessSync(file, process.platform === \"win32\" ? constants.F_OK : constants.X_OK);\n\n return true;\n } catch {\n return false;\n }\n },\n };\n}\n\n/** The grammar a concrete interpreter reads on `platform`. */\nexport function dialectOf(\n interpreter: ResolvedShellInterpreter,\n platform: NodeJS.Platform = process.platform,\n edition?: PowerShellEdition,\n): ShellDialect {\n if (interpreter === \"powershell\") {\n // Windows PowerShell 5.1 has no `&&` / `||`, so it reads its own grammar.\n return edition === \"desktop\" ? \"powershell5\" : \"powershell\";\n }\n\n if (interpreter === \"cmd\") {\n return \"cmd\";\n }\n\n // `sh` is cmd.exe on Windows, where there is no /bin/sh.\n return interpreter === \"sh\" && platform === \"win32\" ? \"cmd\" : \"posix\";\n}\n\n/** The first of `names` found in the host's `PATH` (Windows: with `.exe`). */\nfunction findOnPath(names: string[], host: ShellHost): string | undefined {\n const windows = host.platform === \"win32\";\n const separator = windows ? \";\" : \":\";\n const join = windows ? path.win32.join : path.posix.join;\n const directories = (host.pathEnv ?? \"\").split(separator).filter((entry) => entry !== \"\");\n\n for (const name of names) {\n for (const directory of directories) {\n const candidate = join(directory, windows ? `${name}.exe` : name);\n\n if (host.isExecutable(candidate)) {\n return candidate;\n }\n }\n }\n\n return undefined;\n}\n\n/** Git for Windows' `bash.exe`, derived from the `git` found on `PATH`. */\nfunction findGitBash(host: ShellHost): string | undefined {\n const git = findOnPath([\"git\"], host);\n\n if (git === undefined) {\n return undefined;\n }\n\n // …\\Git\\cmd\\git.exe, …\\Git\\bin\\git.exe or …\\Git\\mingw64\\bin\\git.exe\n const win = path.win32;\n const roots = [win.dirname(win.dirname(git)), win.dirname(win.dirname(win.dirname(git)))];\n\n for (const root of roots) {\n for (const candidate of [\n win.join(root, \"bin\", \"bash.exe\"),\n win.join(root, \"usr\", \"bin\", \"bash.exe\"),\n ]) {\n if (host.isExecutable(candidate)) {\n return candidate;\n }\n }\n }\n\n return undefined;\n}\n\n/** The PowerShell executable names, newest first. */\nfunction powershellNames(host: ShellHost): string[] {\n return host.platform === \"win32\" ? [\"pwsh\", \"powershell\"] : [\"pwsh\"];\n}\n\n/** The edition of the PowerShell executable `file`: `powershell.exe` is Windows PowerShell, anything else (`pwsh`) is PowerShell 7+. */\nfunction editionOf(file: string): PowerShellEdition {\n return /(^|[\\\\/])powershell(\\.exe)?$/i.test(file) ? \"desktop\" : \"core\";\n}\n\nfunction powershellLabel(edition: PowerShellEdition | undefined): string {\n if (edition === \"desktop\") {\n return \"Windows PowerShell 5.1 (powershell)\";\n }\n\n return edition === \"core\" ? \"PowerShell 7 (pwsh)\" : \"PowerShell\";\n}\n\n/**\n * Resolve a shell policy's `interpreter` (and `interpreterPath`) to the\n * concrete shell that will run lines, and the grammar they are checked with.\n *\n * `\"auto\"` picks, on Windows, `pwsh` (PowerShell 7+), then Git Bash (found next\n * to `git`), then Windows PowerShell 5.1, then `cmd.exe`; elsewhere `bash`,\n * then `sh`. Windows PowerShell 5.1 gets its own dialect (`\"powershell5\"`)\n * because it cannot run `&&` / `||`; `edition` and `version` say which one\n * was found. An explicit interpreter whose executable\n * is not on `PATH` keeps its name and has no `file`, so the spawn reports the\n * missing shell instead of quietly running another one.\n *\n * @example\n * resolveShell({ interpreter: \"auto\" }, { platform: \"win32\", pathEnv: \"C:\\\\bin\", isExecutable })\n */\nexport function resolveShell(\n shell: Pick<WorkspaceShellPolicy, \"interpreter\" | \"interpreterPath\"> | undefined,\n host: ShellHost = currentShellHost(),\n): ResolvedShell {\n const requested = shell?.interpreter ?? \"none\";\n const explicit = shell?.interpreterPath;\n\n if (requested === \"none\") {\n return { interpreter: \"none\", dialect: \"posix\", label: \"none\" };\n }\n\n let interpreter: ResolvedShellInterpreter;\n let file: string | undefined = explicit;\n\n if (requested === \"auto\") {\n if (explicit !== undefined) {\n interpreter = /(^|[\\\\/])(pwsh|powershell)(\\.exe)?$/i.test(explicit)\n ? \"powershell\"\n : /(^|[\\\\/])cmd(\\.exe)?$/i.test(explicit)\n ? \"cmd\"\n : /(^|[\\\\/])bash(\\.exe)?$/i.test(explicit)\n ? \"bash\"\n : \"sh\";\n } else if (host.platform === \"win32\") {\n const core = findOnPath([\"pwsh\"], host);\n const gitBash = core === undefined ? findGitBash(host) : undefined;\n const desktop =\n core === undefined && gitBash === undefined ? findOnPath([\"powershell\"], host) : undefined;\n const found = core ?? gitBash ?? desktop;\n\n interpreter =\n core !== undefined || desktop !== undefined\n ? \"powershell\"\n : gitBash !== undefined\n ? \"bash\"\n : \"cmd\";\n file = found;\n } else {\n const bash = findOnPath([\"bash\"], host);\n\n interpreter = bash !== undefined ? \"bash\" : \"sh\";\n file = bash;\n }\n } else {\n interpreter = requested;\n }\n\n if (file === undefined) {\n if (interpreter === \"powershell\") {\n file = findOnPath(powershellNames(host), host);\n } else if (interpreter === \"bash\") {\n file =\n findOnPath([\"bash\"], host) ?? (host.platform === \"win32\" ? findGitBash(host) : undefined);\n } else if (interpreter === \"cmd\" && host.platform === \"win32\") {\n file = process.env.ComSpec ?? \"cmd.exe\";\n }\n }\n\n const edition = interpreter === \"powershell\" && file !== undefined ? editionOf(file) : undefined;\n const version =\n edition === \"desktop\"\n ? \"5.1\"\n : edition === \"core\" && file !== undefined\n ? host.powershellVersion?.(file)\n : undefined;\n\n const labels: Record<ResolvedShellInterpreter, string> = {\n none: \"none\",\n sh: host.platform === \"win32\" ? \"cmd.exe\" : \"sh\",\n bash: \"bash\",\n cmd: \"cmd.exe\",\n powershell: powershellLabel(edition),\n };\n\n return {\n interpreter,\n dialect: dialectOf(interpreter, host.platform, edition),\n ...(edition !== undefined ? { edition } : {}),\n ...(version !== undefined ? { version } : {}),\n ...(file !== undefined ? { file } : {}),\n label: labels[interpreter],\n };\n}\n"],"mappings":"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"}
|
|
@@ -24,8 +24,10 @@
|
|
|
24
24
|
* parentheses, `;` and `,` are refused.
|
|
25
25
|
* - `"powershell"` — what `powershell -Command` / `pwsh -Command` reads, restricted
|
|
26
26
|
* the same way (see `powershell-parser.ts`).
|
|
27
|
+
* - `"powershell5"` — Windows PowerShell 5.1: the `"powershell"` grammar
|
|
28
|
+
* without `&&` and `||`, which 5.1 cannot run; a line using them is refused.
|
|
27
29
|
*/
|
|
28
30
|
/** Which shell grammar to parse. */
|
|
29
|
-
export type ShellDialect = "posix" | "cmd" | "powershell";
|
|
31
|
+
export type ShellDialect = "posix" | "cmd" | "powershell" | "powershell5";
|
|
30
32
|
//#endregion
|
|
31
33
|
//# sourceMappingURL=shell-parser.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-parser.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/policy/shell-parser.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"shell-parser.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/policy/shell-parser.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAgCY"}
|
|
@@ -766,7 +766,7 @@ var CmdParser = class {
|
|
|
766
766
|
*/
|
|
767
767
|
function parseShellLine(line, dialect) {
|
|
768
768
|
const segments = [];
|
|
769
|
-
if (dialect === "powershell") return parsePowerShellLine(line);
|
|
769
|
+
if (dialect === "powershell" || dialect === "powershell5") return parsePowerShellLine(line, { chaining: dialect === "powershell" });
|
|
770
770
|
try {
|
|
771
771
|
if (dialect === "cmd") {
|
|
772
772
|
const commands = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-parser.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/policy/shell-parser.ts"],"sourcesContent":["import { parsePowerShellLine } from \"./powershell-parser\";\n\n/**\n * A deliberately conservative parser for the command lines `run_shell`\n * hands to a shell when the policy opts in to `interpreter: \"sh\"`.\n *\n * Its only job is to find **every simple command** a line would run — so\n * the policy can check each one — plus every redirection target. It is not\n * a shell: it never expands anything. What it cannot read with confidence\n * it refuses, because a construct the parser misreads is a construct a\n * denied command can hide in. `/bin/sh` is bash on some systems, dash or\n * busybox ash on others, so constructs that those shells read differently\n * (bash-only syntax such as `<<<`, `&>`, `$'…'`, brace expansion, `[[`,\n * arithmetic) are refused as well.\n *\n * Three dialects:\n * - `\"posix\"` — what `/bin/sh -c` reads: `;`, `&&`, `||`, `|`, `&`,\n * newlines, comments, `( … )` subshells, `$( … )` and backtick command\n * substitution (parsed recursively), `$name` / `${name…}` parameter\n * expansion (a restricted set of forms), single and double quotes,\n * backslash escapes, redirections and here-documents.\n * - `\"cmd\"` — what `cmd.exe /d /v:off /s /c` reads, restricted to the\n * subset whose meaning is unambiguous: `&&`, `||`, `|`, `&` (sequential\n * in cmd), double quotes and simple redirections. `%`, `!`, `^`,\n * parentheses, `;` and `,` are refused.\n * - `\"powershell\"` — what `powershell -Command` / `pwsh -Command` reads, restricted\n * the same way (see `powershell-parser.ts`).\n */\n\n/** Which shell grammar to parse. */\nexport type ShellDialect = \"posix\" | \"cmd\" | \"powershell\";\n\n/** One word of a simple command, after quote removal. */\nexport interface ShellWord {\n /** The word exactly as written. */\n raw: string;\n /**\n * The word's text after quote removal and escapes. Expansions\n * (`$HOME`, `$(…)`, backticks) are kept as their source text — their real\n * value is only known when the shell runs.\n */\n value: string;\n /**\n * True when the shell decides part of the word at run time: a parameter\n * expansion, a command substitution, an unquoted glob character\n * (`*`, `?`, `[`) or a leading `~`.\n */\n dynamic: boolean;\n /** True when any part of the word was quoted or escaped. */\n quoted: boolean;\n /** For a `NAME=value` assignment word, the variable name. */\n assignmentName?: string;\n}\n\n/** A redirection attached to a simple command (or a subshell). */\nexport interface ShellRedirect {\n /** The operator: `>`, `>>`, `>|`, `<`, `<>`, `>&`, `<&`, `<<` or `<<-`. */\n op: string;\n /** Explicit file descriptor (`2>`), when written. */\n fd?: number;\n /** The target word (file, descriptor number or here-doc delimiter). */\n target: ShellWord;\n /**\n * `\"read\"` / `\"write\"` — a file target; `\"dup\"` — a descriptor copy or\n * close (`2>&1`, `>&-`); `\"heredoc\"` — inline input.\n */\n type: \"read\" | \"write\" | \"dup\" | \"heredoc\";\n}\n\n/** A simple command: assignments, words and redirections. */\nexport interface ShellSimpleCommand {\n /** Leading `NAME=value` words. */\n assignments: ShellWord[];\n /** The command's words — `words[0]` is the command name. Empty for a redirect- or assignment-only command. */\n words: ShellWord[];\n /** Redirections, in order. */\n redirects: ShellRedirect[];\n /** The command's source text (for messages). */\n source: string;\n}\n\n/** A top-level piece of the line and the operator that follows it. */\nexport interface ShellSegment {\n /** Source text of the pipeline. */\n source: string;\n /** The list operator after it (`&&`, `||`, `;`, `&`, a newline), if any. */\n operator?: string;\n}\n\n/** Outcome of {@link parseShellLine}. */\nexport type ShellParseResult =\n | {\n ok: true;\n /** Every simple command, in the order the shell meets them (substitutions first). */\n commands: ShellSimpleCommand[];\n /** The top-level pipelines and the operators joining them. */\n segments: ShellSegment[];\n /** True when the line backgrounds anything with `&` (POSIX dialect). */\n background: boolean;\n }\n | {\n ok: false;\n /** Why the line was refused. */\n reason: \"unbalanced-quotes\" | \"unsupported-syntax\";\n /** What was refused and why, for the model. */\n message: string;\n };\n\n/** Deepest nesting of subshells and substitutions the parser accepts. */\nconst MAX_NESTING = 16;\n\n/**\n * Words that start a compound command (or negate a pipeline) when they\n * appear unquoted in command position. The parser does not read compound\n * commands, so a line using one is refused.\n */\nconst RESERVED_WORDS = new Set([\n \"!\",\n \"{\",\n \"}\",\n \"[[\",\n \"]]\",\n \"case\",\n \"coproc\",\n \"do\",\n \"done\",\n \"elif\",\n \"else\",\n \"esac\",\n \"fi\",\n \"for\",\n \"function\",\n \"if\",\n \"in\",\n \"select\",\n \"then\",\n \"until\",\n \"while\",\n]);\n\n/** cmd.exe keywords the `\"cmd\"` dialect refuses in command position. */\nconst CMD_RESERVED_WORDS = new Set([\"if\", \"for\", \"goto\", \"setlocal\", \"endlocal\"]);\n\n/** A parameter name inside `${…}`: a variable, a positional or a special parameter. */\nconst PARAMETER_NAME = String.raw`(?:[A-Za-z_][A-Za-z0-9_]*|[0-9]+|[@*#?$!-])`;\n\n/**\n * The default/pattern word of `${name:-word}` / `${name%word}`: no quotes,\n * no nested expansion or substitution, no braces or parentheses, no `@`.\n */\nconst PARAMETER_WORD = \"[^`$'\\\"\\\\\\\\{}()@\\\\n]*\";\n\n/**\n * Allowed `${…}` forms: `${name}`, `${#name}`, `${name-word}`,\n * `${name:-word}` (also `=`, `+`, `?`), `${name%word}`, `${name%%word}`,\n * `${name#word}`, `${name##word}`. Everything else — substrings and\n * subscripts (arithmetic in bash), `${!name}`, `${name@op}`, pattern\n * substitution — is refused.\n */\nconst PARAMETER_EXPANSION = new RegExp(\n `^(?:#${PARAMETER_NAME}|${PARAMETER_NAME}(?:(?::?[-=+?]|%%?|##?)${PARAMETER_WORD})?)$`,\n);\n\n/** A refusal raised while parsing; caught by {@link parseShellLine}. */\nclass ShellSyntaxRefusal extends Error {\n public constructor(\n public readonly reason: \"unbalanced-quotes\" | \"unsupported-syntax\",\n message: string,\n ) {\n super(message);\n }\n}\n\n/** Refuse a construct the parser does not read with confidence. */\nfunction unsupported(message: string): ShellSyntaxRefusal {\n return new ShellSyntaxRefusal(\"unsupported-syntax\", message);\n}\n\n/** Refuse an unterminated quote / substitution. */\nfunction unbalanced(message: string): ShellSyntaxRefusal {\n return new ShellSyntaxRefusal(\"unbalanced-quotes\", message);\n}\n\n/** State shared by a parser and the nested parsers it starts (backticks, here-docs). */\ninterface SharedState {\n commands: ShellSimpleCommand[];\n background: boolean;\n nesting: number;\n}\n\n/** A here-document whose body starts after the next newline. */\ninterface PendingHeredoc {\n delimiter: string;\n stripTabs: boolean;\n expand: boolean;\n}\n\n/** Parser for the POSIX `sh` dialect. */\nclass PosixParser {\n private pos = 0;\n\n private pendingHeredocs: PendingHeredoc[] = [];\n\n public constructor(\n private readonly src: string,\n private readonly state: SharedState,\n ) {}\n\n /** Parse the whole line as a command list. */\n public parseProgram(segments: ShellSegment[]): void {\n this.parseList(null, segments);\n this.assertNoPendingHeredocs();\n }\n\n /** Parse the contents of a backtick substitution (a full list until EOF). */\n public parseNestedProgram(): void {\n this.parseList(null, undefined);\n this.assertNoPendingHeredocs();\n }\n\n /**\n * Scan an unquoted-delimiter here-document body: `$…`, `${…}`, `$(…)` and\n * backticks expand there exactly as inside double quotes (but `\"` is an\n * ordinary character), so any command substitution in it runs.\n */\n public parseExpandingText(): void {\n while (!this.atEnd()) {\n const char = this.peek();\n\n if (char === \"\\\\\") {\n this.pos += 2;\n\n continue;\n }\n\n if (char === \"$\") {\n this.parseDollar(\"heredoc\");\n\n continue;\n }\n\n if (char === \"`\") {\n this.parseBackticks(false);\n\n continue;\n }\n\n this.pos++;\n }\n }\n\n private atEnd(): boolean {\n return this.pos >= this.src.length;\n }\n\n private peek(offset = 0): string {\n return this.src.charAt(this.pos + offset);\n }\n\n private assertNoPendingHeredocs(): void {\n if (this.pendingHeredocs.length > 0) {\n throw unsupported(\n `The here-document delimited by \\`${this.pendingHeredocs[0]?.delimiter ?? \"\"}\\` is never terminated.`,\n );\n }\n }\n\n private enterNesting(): void {\n this.state.nesting++;\n\n if (this.state.nesting > MAX_NESTING) {\n throw unsupported(\"The line nests subshells or substitutions too deeply.\");\n }\n }\n\n private exitNesting(): void {\n this.state.nesting--;\n }\n\n /** Skip spaces, tabs and backslash-newline continuations. */\n private skipBlanks(): void {\n while (!this.atEnd()) {\n const char = this.peek();\n\n if (char === \" \" || char === \"\\t\") {\n this.pos++;\n\n continue;\n }\n\n if (char === \"\\\\\" && this.peek(1) === \"\\n\") {\n this.pos += 2;\n\n continue;\n }\n\n break;\n }\n }\n\n /** Skip a `#` comment up to (not including) the newline. */\n private skipComment(): void {\n const newline = this.src.indexOf(\"\\n\", this.pos);\n\n this.pos = newline === -1 ? this.src.length : newline;\n }\n\n /** Consume one newline and read any here-document bodies queued before it. */\n private consumeNewline(): void {\n this.pos++;\n\n if (this.pendingHeredocs.length === 0) {\n return;\n }\n\n if (this.state.nesting > 0) {\n throw unsupported(\"Here-documents inside subshells or substitutions are not supported.\");\n }\n\n const pending = this.pendingHeredocs;\n\n this.pendingHeredocs = [];\n\n for (const heredoc of pending) {\n this.readHeredocBody(heredoc);\n }\n }\n\n /** Skip blanks, comments and newlines (separators between commands). */\n private skipBlanksAndNewlines(): void {\n while (!this.atEnd()) {\n this.skipBlanks();\n\n if (this.peek() === \"#\") {\n this.skipComment();\n\n continue;\n }\n\n if (this.peek() === \"\\n\") {\n this.consumeNewline();\n\n continue;\n }\n\n break;\n }\n }\n\n private readHeredocBody(heredoc: PendingHeredoc): void {\n const bodyLines: string[] = [];\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (this.atEnd()) {\n throw unsupported(\n `The here-document delimited by \\`${heredoc.delimiter}\\` is never terminated.`,\n );\n }\n\n const lineEnd = this.src.indexOf(\"\\n\", this.pos);\n const line = lineEnd === -1 ? this.src.slice(this.pos) : this.src.slice(this.pos, lineEnd);\n const compared = heredoc.stripTabs ? line.replace(/^\\t+/, \"\") : line;\n\n this.pos = lineEnd === -1 ? this.src.length : lineEnd + 1;\n\n if (compared === heredoc.delimiter) {\n break;\n }\n\n bodyLines.push(compared);\n }\n\n if (heredoc.expand) {\n new PosixParser(bodyLines.join(\"\\n\"), this.state).parseExpandingText();\n }\n }\n\n /**\n * Parse a command list until `closing` (`\")\"`) or the end of the input.\n * `segments`, when given, collects the top-level pipelines.\n */\n private parseList(closing: string | null, segments: ShellSegment[] | undefined): void {\n let sawCommand = false;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanksAndNewlines();\n\n if (this.atEnd()) {\n if (closing !== null) {\n throw unbalanced(`A \\`(\\` or \\`$(\\` is never closed with \\`${closing}\\`.`);\n }\n\n break;\n }\n\n if (closing !== null && this.peek() === closing) {\n break;\n }\n\n const start = this.pos;\n\n this.parsePipeline(closing);\n\n const end = this.pos;\n\n sawCommand = true;\n this.skipBlanks();\n\n if (this.peek() === \"#\") {\n this.skipComment();\n }\n\n const operator = this.readListOperator();\n\n segments?.push({\n source: this.src.slice(start, end).trim(),\n ...(operator !== null ? { operator } : {}),\n });\n\n if (operator === \"&&\" || operator === \"||\") {\n this.skipBlanksAndNewlines();\n\n if (this.atEnd() || (closing !== null && this.peek() === closing)) {\n throw unsupported(`The line ends with \\`${operator}\\` but no command follows it.`);\n }\n\n continue;\n }\n\n if (operator === \"&\") {\n this.state.background = true;\n }\n\n if (operator === null) {\n if (this.atEnd() || (closing !== null && this.peek() === closing)) {\n break;\n }\n\n throw unsupported(`Unexpected \\`${this.peek()}\\`.`);\n }\n }\n\n if (!sawCommand) {\n throw unsupported(closing === null ? \"The command is empty.\" : \"A `( )` or `$( )` is empty.\");\n }\n }\n\n /** Read `&&`, `||`, `;`, `&` or a newline after a pipeline. */\n private readListOperator(): string | null {\n const char = this.peek();\n const next = this.peek(1);\n\n if (char === \"&\") {\n if (next === \"&\") {\n this.pos += 2;\n\n return \"&&\";\n }\n\n if (next === \">\") {\n throw unsupported(\n \"`&>` (redirect both streams) is not portable; write `> file 2>&1` instead.\",\n );\n }\n\n this.pos++;\n\n return \"&\";\n }\n\n if (char === \"|\" && next === \"|\") {\n this.pos += 2;\n\n return \"||\";\n }\n\n if (char === \";\") {\n if (next === \";\") {\n throw unsupported(\"`;;` (a `case` clause) is not supported.\");\n }\n\n this.pos++;\n\n return \";\";\n }\n\n if (char === \"\\n\") {\n this.consumeNewline();\n\n return \"\\n\";\n }\n\n return null;\n }\n\n private parsePipeline(closing: string | null): void {\n this.parseCommand(closing);\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (this.peek() !== \"|\" || this.peek(1) === \"|\") {\n return;\n }\n\n if (this.peek(1) === \"&\") {\n throw unsupported(\"`|&` is not portable; write `2>&1 |` instead.\");\n }\n\n this.pos++;\n this.skipBlanksAndNewlines();\n\n if (this.atEnd() || (closing !== null && this.peek() === closing)) {\n throw unsupported(\"The line ends with `|` but no command follows it.\");\n }\n\n this.parseCommand(closing);\n }\n }\n\n private parseCommand(closing: string | null): void {\n this.skipBlanks();\n\n if (this.peek() === \"(\") {\n if (this.peek(1) === \"(\") {\n throw unsupported(\"`((…))` (arithmetic) is not supported.\");\n }\n\n this.parseSubshell();\n\n return;\n }\n\n if (this.peek() === \")\") {\n throw unsupported(closing === null ? \"Unexpected `)`.\" : \"A `( )` or `$( )` is empty.\");\n }\n\n this.parseSimpleCommand();\n }\n\n private parseSubshell(): void {\n const start = this.pos;\n\n this.pos++;\n this.enterNesting();\n this.parseList(\")\", undefined);\n\n if (this.peek() !== \")\") {\n throw unbalanced(\"A `(` is never closed with `)`.\");\n }\n\n this.pos++;\n this.exitNesting();\n\n // Redirections may follow the closing parenthesis; nothing else may.\n const redirects: ShellRedirect[] = [];\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (!this.atRedirectStart()) {\n break;\n }\n\n redirects.push(this.parseRedirect());\n }\n\n if (redirects.length > 0) {\n this.state.commands.push({\n assignments: [],\n words: [],\n redirects,\n source: this.src.slice(start, this.pos).trim(),\n });\n }\n\n if (!this.atEnd() && !this.atOperatorBoundary()) {\n throw unsupported(\"Only an operator or a redirection may follow `)`.\");\n }\n }\n\n /** Whether the next character ends a simple command. */\n private atOperatorBoundary(): boolean {\n const char = this.peek();\n\n return (\n char === \"\\n\" ||\n char === \";\" ||\n char === \"&\" ||\n char === \"|\" ||\n char === \")\" ||\n char === \"#\" ||\n char === \" \" ||\n char === \"\\t\"\n );\n }\n\n /** Whether a redirection starts here (`>`, `<`, or digits glued to one). */\n private atRedirectStart(): boolean {\n let index = this.pos;\n\n while (index < this.src.length && /[0-9]/.test(this.src.charAt(index))) {\n index++;\n }\n\n const char = this.src.charAt(index);\n\n return char === \"<\" || char === \">\";\n }\n\n private parseSimpleCommand(): void {\n const start = this.pos;\n const command: ShellSimpleCommand = { assignments: [], words: [], redirects: [], source: \"\" };\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (this.atEnd()) {\n break;\n }\n\n const char = this.peek();\n\n if (char === \"\\n\" || char === \";\" || char === \"&\" || char === \"|\" || char === \")\") {\n break;\n }\n\n if (char === \"(\") {\n throw unsupported(\n \"`(` is only supported at the start of a command (a subshell); quote it to pass it as text.\",\n );\n }\n\n if (char === \"#\") {\n this.skipComment();\n\n break;\n }\n\n if (this.atRedirectStart()) {\n command.redirects.push(this.parseRedirect());\n\n continue;\n }\n\n const word = this.parseWord();\n\n if (command.words.length === 0 && word.assignmentName !== undefined) {\n command.assignments.push(word);\n\n continue;\n }\n\n command.words.push(word);\n\n if (command.words.length === 1 && !word.quoted && RESERVED_WORDS.has(word.value)) {\n throw unsupported(\n `\\`${word.value}\\` starts a shell compound command, which is not supported; run the commands one at a time.`,\n );\n }\n }\n\n if (\n command.words.length === 0 &&\n command.assignments.length === 0 &&\n command.redirects.length === 0\n ) {\n throw unsupported(\"The line has an empty command (two operators in a row?).\");\n }\n\n command.source = this.src.slice(start, this.pos).trim();\n this.state.commands.push(command);\n }\n\n private parseRedirect(): ShellRedirect {\n let digits = \"\";\n\n while (/[0-9]/.test(this.peek())) {\n digits += this.peek();\n this.pos++;\n }\n\n const rest = this.src.slice(this.pos, this.pos + 3);\n let op: string;\n\n if (rest.startsWith(\"<<<\")) {\n throw unsupported(\"`<<<` (a here-string) is not portable; use a here-document.\");\n } else if (rest.startsWith(\"<<-\")) {\n op = \"<<-\";\n } else if (rest.startsWith(\"<<\")) {\n op = \"<<\";\n } else if (rest.startsWith(\"<>\")) {\n op = \"<>\";\n } else if (rest.startsWith(\"<&\")) {\n op = \"<&\";\n } else if (rest.startsWith(\"<(\") || rest.startsWith(\">(\")) {\n throw unsupported(\"Process substitution (`<(…)`, `>(…)`) is not supported.\");\n } else if (rest.startsWith(\">>\")) {\n op = \">>\";\n } else if (rest.startsWith(\">|\")) {\n op = \">|\";\n } else if (rest.startsWith(\">&\")) {\n op = \">&\";\n } else {\n op = this.peek();\n }\n\n this.pos += op.length;\n\n const fd = digits === \"\" ? undefined : Number(digits);\n\n this.skipBlanks();\n\n if (this.atEnd() || /[\\n;&|<>()]/.test(this.peek())) {\n throw unsupported(`The redirection \\`${op}\\` has no target.`);\n }\n\n const target = this.parseWord();\n\n if (op === \"<<\" || op === \"<<-\") {\n if (this.state.nesting > 0) {\n throw unsupported(\"Here-documents inside subshells or substitutions are not supported.\");\n }\n\n if (target.dynamic) {\n throw unsupported(\"A here-document delimiter must be literal text.\");\n }\n\n this.pendingHeredocs.push({\n delimiter: target.value,\n stripTabs: op === \"<<-\",\n expand: !target.quoted,\n });\n\n return { op, ...(fd !== undefined ? { fd } : {}), target, type: \"heredoc\" };\n }\n\n if (op === \">&\" || op === \"<&\") {\n if (target.dynamic || !/^(?:[0-9]+|-)$/.test(target.value)) {\n throw unsupported(\n `\\`${op}${target.raw}\\` is not portable; write \\`> file 2>&1\\` to send both streams to a file.`,\n );\n }\n\n return { op, ...(fd !== undefined ? { fd } : {}), target, type: \"dup\" };\n }\n\n return {\n op,\n ...(fd !== undefined ? { fd } : {}),\n target,\n type: op === \"<\" ? \"read\" : \"write\",\n };\n }\n\n /** Parse one unquoted-context word. */\n private parseWord(): ShellWord {\n const start = this.pos;\n let value = \"\";\n let dynamic = false;\n let quoted = false;\n let braces = false;\n let nameCandidate = true;\n let assignmentName: string | undefined;\n\n while (!this.atEnd()) {\n const char = this.peek();\n\n if (\n char === \" \" ||\n char === \"\\t\" ||\n char === \"\\n\" ||\n char === \";\" ||\n char === \"&\" ||\n char === \"|\" ||\n char === \"<\" ||\n char === \">\" ||\n char === \"(\" ||\n char === \")\"\n ) {\n break;\n }\n\n if (char === \"\\r\" || char === \"\\0\") {\n throw unsupported(\"Carriage returns and NUL bytes are not supported in a command line.\");\n }\n\n if (char === \"\\\\\") {\n const next = this.peek(1);\n\n if (next === \"\") {\n throw unsupported(\"The line ends with a backslash.\");\n }\n\n this.pos += 2;\n\n if (next !== \"\\n\") {\n value += next;\n quoted = true;\n nameCandidate = false;\n }\n\n continue;\n }\n\n if (char === \"'\") {\n const closing = this.src.indexOf(\"'\", this.pos + 1);\n\n if (closing === -1) {\n throw unbalanced(\"A `'` quote is never closed.\");\n }\n\n value += this.src.slice(this.pos + 1, closing);\n quoted = true;\n nameCandidate = false;\n this.pos = closing + 1;\n\n continue;\n }\n\n if (char === '\"') {\n const part = this.parseDoubleQuoted();\n\n value += part.value;\n dynamic = dynamic || part.dynamic;\n quoted = true;\n nameCandidate = false;\n\n continue;\n }\n\n if (char === \"$\") {\n const part = this.parseDollar(\"unquoted\");\n\n value += part.text;\n dynamic = dynamic || part.dynamic;\n nameCandidate = false;\n\n continue;\n }\n\n if (char === \"`\") {\n value += this.parseBackticks(false);\n dynamic = true;\n nameCandidate = false;\n\n continue;\n }\n\n if (char === \"=\" && nameCandidate && assignmentName === undefined) {\n if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {\n assignmentName = value;\n }\n\n nameCandidate = false;\n } else if (!/[A-Za-z0-9_]/.test(char)) {\n nameCandidate = false;\n }\n\n if (char === \"*\" || char === \"?\" || char === \"[\") {\n dynamic = true;\n }\n\n if (char === \"~\") {\n const previous = this.src.charAt(this.pos - 1);\n\n if (\n this.pos === start ||\n (assignmentName !== undefined && (previous === \"=\" || previous === \":\"))\n ) {\n dynamic = true;\n }\n }\n\n if (char === \"{\" || char === \"}\") {\n braces = true;\n }\n\n value += char;\n this.pos++;\n }\n\n const raw = this.src.slice(start, this.pos);\n\n if (braces && raw !== \"{}\") {\n throw unsupported(\n `Unquoted \\`{\\` or \\`}\\` in \\`${raw}\\` (brace expansion or a command group) is not supported; quote it.`,\n );\n }\n\n // A lone `[` or `]` is never a glob (it is the `test` command or its end).\n if (raw === \"[\" || raw === \"]\") {\n dynamic = false;\n }\n\n return {\n raw,\n value,\n dynamic,\n quoted,\n ...(assignmentName !== undefined ? { assignmentName } : {}),\n };\n }\n\n /** Parse a `\"…\"` span (cursor on the opening quote). */\n private parseDoubleQuoted(): { value: string; dynamic: boolean } {\n this.pos++;\n\n let value = \"\";\n let dynamic = false;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (this.atEnd()) {\n throw unbalanced('A `\"` quote is never closed.');\n }\n\n const char = this.peek();\n\n if (char === '\"') {\n this.pos++;\n\n return { value, dynamic };\n }\n\n if (char === \"\\0\") {\n throw unsupported(\"NUL bytes are not supported in a command line.\");\n }\n\n if (char === \"\\\\\") {\n const next = this.peek(1);\n\n if (next === \"\") {\n throw unbalanced('A `\"` quote is never closed.');\n }\n\n if (next === \"$\" || next === \"`\" || next === '\"' || next === \"\\\\\") {\n value += next;\n this.pos += 2;\n\n continue;\n }\n\n if (next === \"\\n\") {\n this.pos += 2;\n\n continue;\n }\n\n value += char;\n this.pos++;\n\n continue;\n }\n\n if (char === \"$\") {\n const part = this.parseDollar(\"dquote\");\n\n value += part.text;\n dynamic = dynamic || part.dynamic;\n\n continue;\n }\n\n if (char === \"`\") {\n value += this.parseBackticks(true);\n dynamic = true;\n\n continue;\n }\n\n value += char;\n this.pos++;\n }\n }\n\n /** Parse a `$…` expansion (cursor on the `$`). */\n private parseDollar(context: \"unquoted\" | \"dquote\" | \"heredoc\"): {\n text: string;\n dynamic: boolean;\n } {\n const start = this.pos;\n const next = this.peek(1);\n\n if (next === \"(\") {\n if (this.peek(2) === \"(\") {\n throw unsupported(\"`$((…))` (arithmetic expansion) is not supported.\");\n }\n\n this.pos += 2;\n this.enterNesting();\n this.parseList(\")\", undefined);\n\n if (this.peek() !== \")\") {\n throw unbalanced(\"A `$(` is never closed with `)`.\");\n }\n\n this.pos++;\n this.exitNesting();\n\n return { text: this.src.slice(start, this.pos), dynamic: true };\n }\n\n if (next === \"{\") {\n const closing = this.src.indexOf(\"}\", this.pos + 2);\n\n if (closing === -1) {\n throw unbalanced(\"A `${` is never closed with `}`.\");\n }\n\n const body = this.src.slice(this.pos + 2, closing);\n\n if (!PARAMETER_EXPANSION.test(body)) {\n throw unsupported(\n `\\`\\${${body}}\\` is not a supported parameter expansion; use \\`$name\\`, \\`\\${name}\\` or \\`\\${name:-default}\\`.`,\n );\n }\n\n this.pos = closing + 1;\n\n return { text: this.src.slice(start, this.pos), dynamic: true };\n }\n\n if (next === \"[\") {\n throw unsupported(\"`$[…]` (arithmetic expansion) is not supported.\");\n }\n\n if (context === \"unquoted\" && (next === \"'\" || next === '\"')) {\n throw unsupported(`\\`$${next}…${next}\\` quoting is not portable; use plain quotes.`);\n }\n\n if (/[A-Za-z_]/.test(next)) {\n this.pos += 2;\n\n while (/[A-Za-z0-9_]/.test(this.peek())) {\n this.pos++;\n }\n\n return { text: this.src.slice(start, this.pos), dynamic: true };\n }\n\n if (/[0-9@*#?$!-]/.test(next) && next !== \"\") {\n this.pos += 2;\n\n return { text: this.src.slice(start, this.pos), dynamic: true };\n }\n\n // A `$` followed by anything else is an ordinary character.\n this.pos++;\n\n return { text: \"$\", dynamic: false };\n }\n\n /** Parse a backtick substitution (cursor on the opening backtick); returns its source. */\n private parseBackticks(insideDoubleQuotes: boolean): string {\n const start = this.pos;\n let content = \"\";\n\n this.pos++;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (this.atEnd()) {\n throw unbalanced(\"A backtick substitution is never closed.\");\n }\n\n const char = this.peek();\n\n if (char === \"`\") {\n this.pos++;\n\n break;\n }\n\n if (char === \"\\\\\") {\n const next = this.peek(1);\n\n if (next === \"\") {\n throw unbalanced(\"A backtick substitution is never closed.\");\n }\n\n if (next === \"`\" || next === \"\\\\\" || next === \"$\") {\n content += next;\n this.pos += 2;\n\n continue;\n }\n\n if (next === '\"' && insideDoubleQuotes) {\n throw unsupported(\n 'An escaped `\\\\\"` inside backticks inside double quotes is not portable; use `$(…)`.',\n );\n }\n\n content += char + next;\n this.pos += 2;\n\n continue;\n }\n\n content += char;\n this.pos++;\n }\n\n this.enterNesting();\n new PosixParser(content, this.state).parseNestedProgram();\n this.exitNesting();\n\n return this.src.slice(start, this.pos);\n }\n}\n\n/** Parser for the restricted `cmd.exe` dialect. */\nclass CmdParser {\n private pos = 0;\n\n public constructor(\n private readonly src: string,\n private readonly commands: ShellSimpleCommand[],\n ) {}\n\n public parseProgram(segments: ShellSegment[]): void {\n if (/[\\r\\n\\0]/.test(this.src)) {\n throw unsupported(\"Line breaks and NUL bytes are not supported in a cmd.exe command line.\");\n }\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (this.atEnd()) {\n throw unsupported(\n segments.length === 0\n ? \"The command is empty.\"\n : \"The line ends with an operator but no command follows it.\",\n );\n }\n\n const start = this.pos;\n\n this.parsePipeline();\n\n const end = this.pos;\n\n this.skipBlanks();\n\n const operator = this.readListOperator();\n\n segments.push({\n source: this.src.slice(start, end).trim(),\n ...(operator !== null ? { operator } : {}),\n });\n\n if (operator === null) {\n if (!this.atEnd()) {\n throw unsupported(`Unexpected \\`${this.peek()}\\`.`);\n }\n\n return;\n }\n }\n }\n\n private atEnd(): boolean {\n return this.pos >= this.src.length;\n }\n\n private peek(offset = 0): string {\n return this.src.charAt(this.pos + offset);\n }\n\n private skipBlanks(): void {\n while (this.peek() === \" \" || this.peek() === \"\\t\") {\n this.pos++;\n }\n }\n\n private readListOperator(): string | null {\n if (this.peek() === \"&\") {\n if (this.peek(1) === \"&\") {\n this.pos += 2;\n\n return \"&&\";\n }\n\n this.pos++;\n\n return \"&\";\n }\n\n if (this.peek() === \"|\" && this.peek(1) === \"|\") {\n this.pos += 2;\n\n return \"||\";\n }\n\n return null;\n }\n\n private parsePipeline(): void {\n this.parseSimpleCommand();\n\n while (this.peek() === \"|\" && this.peek(1) !== \"|\") {\n this.pos++;\n this.skipBlanks();\n\n if (this.atEnd()) {\n throw unsupported(\"The line ends with `|` but no command follows it.\");\n }\n\n this.parseSimpleCommand();\n }\n }\n\n private atRedirectStart(): boolean {\n const char = this.peek();\n\n if (char === \"<\" || char === \">\") {\n return true;\n }\n\n return /[0-9]/.test(char) && (this.peek(1) === \">\" || this.peek(1) === \"<\");\n }\n\n private parseSimpleCommand(): void {\n const start = this.pos;\n const command: ShellSimpleCommand = { assignments: [], words: [], redirects: [], source: \"\" };\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (this.atEnd() || this.peek() === \"&\" || this.peek() === \"|\") {\n break;\n }\n\n if (this.atRedirectStart()) {\n command.redirects.push(this.parseRedirect());\n\n continue;\n }\n\n const word = this.parseWord();\n\n command.words.push(word);\n\n if (command.words.length === 1) {\n this.checkCommandWord(word);\n }\n }\n\n if (command.words.length === 0 && command.redirects.length === 0) {\n throw unsupported(\"The line has an empty command (two operators in a row?).\");\n }\n\n command.source = this.src.slice(start, this.pos).trim();\n this.commands.push(command);\n }\n\n private checkCommandWord(word: ShellWord): void {\n if (word.raw.startsWith(\"@\") || word.raw.startsWith(\":\")) {\n throw unsupported(`\\`${word.raw}\\` is not supported as a cmd.exe command name.`);\n }\n\n if (/[/=]/.test(word.value)) {\n throw unsupported(\n `A cmd.exe command name may not contain \\`/\\` or \\`=\\` (\\`${word.value}\\`); use \\`\\\\\\` in paths.`,\n );\n }\n\n if (!word.quoted && CMD_RESERVED_WORDS.has(word.value.toLowerCase())) {\n throw unsupported(\n `\\`${word.value}\\` starts a cmd.exe compound command, which is not supported.`,\n );\n }\n }\n\n private parseRedirect(): ShellRedirect {\n let fd: number | undefined;\n\n if (/[0-9]/.test(this.peek())) {\n fd = Number(this.peek());\n this.pos++;\n }\n\n let op: string;\n\n if (this.src.startsWith(\">>\", this.pos)) {\n op = \">>\";\n } else if (this.src.startsWith(\">&\", this.pos)) {\n op = \">&\";\n } else if (this.src.startsWith(\"<&\", this.pos) || this.src.startsWith(\"<<\", this.pos)) {\n throw unsupported(\n `\\`${this.src.slice(this.pos, this.pos + 2)}\\` is not supported by cmd.exe.`,\n );\n } else {\n op = this.peek();\n }\n\n this.pos += op.length;\n this.skipBlanks();\n\n if (this.atEnd() || /[&|<>]/.test(this.peek())) {\n throw unsupported(`The redirection \\`${op}\\` has no target.`);\n }\n\n const target = this.parseWord();\n\n if (op === \">&\") {\n if (!/^[0-9]$/.test(target.value)) {\n throw unsupported(`\\`>&${target.raw}\\` is not supported; write \\`> file 2>&1\\`.`);\n }\n\n return { op, ...(fd !== undefined ? { fd } : {}), target, type: \"dup\" };\n }\n\n return { op, ...(fd !== undefined ? { fd } : {}), target, type: op === \"<\" ? \"read\" : \"write\" };\n }\n\n private parseWord(): ShellWord {\n const start = this.pos;\n let value = \"\";\n let dynamic = false;\n let quoted = false;\n\n while (!this.atEnd()) {\n const char = this.peek();\n\n if (\n char === \" \" ||\n char === \"\\t\" ||\n char === \"&\" ||\n char === \"|\" ||\n char === \"<\" ||\n char === \">\"\n ) {\n break;\n }\n\n if (char === '\"') {\n const closing = this.src.indexOf('\"', this.pos + 1);\n\n if (closing === -1) {\n throw unbalanced('A `\"` quote is never closed.');\n }\n\n const inner = this.src.slice(this.pos + 1, closing);\n\n if (/[%!]/.test(inner)) {\n throw unsupported(\n \"`%` and `!` expand even inside quotes in cmd.exe; they are not supported.\",\n );\n }\n\n if (value.endsWith(\"\\\\\") || inner.endsWith(\"\\\\\")) {\n throw unsupported(\n 'A backslash before a `\"` is read differently by cmd.exe and programs.',\n );\n }\n\n value += inner;\n quoted = true;\n this.pos = closing + 1;\n\n continue;\n }\n\n if (\n char === \"%\" ||\n char === \"!\" ||\n char === \"^\" ||\n char === \"(\" ||\n char === \")\" ||\n char === \";\" ||\n char === \",\"\n ) {\n throw unsupported(\n `Unquoted \\`${char}\\` has a special meaning in cmd.exe and is not supported.`,\n );\n }\n\n if (char === \"\\\\\" && this.peek(1) === '\"') {\n throw unsupported('A backslash before a `\"` is read differently by cmd.exe and programs.');\n }\n\n if (char === \"*\" || char === \"?\") {\n dynamic = true;\n }\n\n value += char;\n this.pos++;\n }\n\n return { raw: this.src.slice(start, this.pos), value, dynamic, quoted };\n }\n}\n\n/**\n * Parse a command line for the shell policy. Never throws: a construct the\n * parser does not read with confidence comes back as `{ ok: false }` with a\n * message naming it.\n *\n * @example\n * const parsed = parseShellLine(\"git add -A && git commit -m 'wip'\", \"posix\");\n * // parsed.ok === true; parsed.commands.map((c) => c.words.map((w) => w.value))\n * // → [[\"git\", \"add\", \"-A\"], [\"git\", \"commit\", \"-m\", \"wip\"]]\n */\nexport function parseShellLine(line: string, dialect: ShellDialect): ShellParseResult {\n const segments: ShellSegment[] = [];\n\n if (dialect === \"powershell\") {\n return parsePowerShellLine(line);\n }\n\n try {\n if (dialect === \"cmd\") {\n const commands: ShellSimpleCommand[] = [];\n\n new CmdParser(line, commands).parseProgram(segments);\n\n return { ok: true, commands, segments, background: false };\n }\n\n const state: SharedState = { commands: [], background: false, nesting: 0 };\n\n new PosixParser(line, state).parseProgram(segments);\n\n return { ok: true, commands: state.commands, segments, background: state.background };\n } catch (error) {\n if (error instanceof ShellSyntaxRefusal) {\n return { ok: false, reason: error.reason, message: error.message };\n }\n\n throw error;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"shell-parser.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/policy/shell-parser.ts"],"sourcesContent":["import { parsePowerShellLine } from \"./powershell-parser\";\n\n/**\n * A deliberately conservative parser for the command lines `run_shell`\n * hands to a shell when the policy opts in to `interpreter: \"sh\"`.\n *\n * Its only job is to find **every simple command** a line would run — so\n * the policy can check each one — plus every redirection target. It is not\n * a shell: it never expands anything. What it cannot read with confidence\n * it refuses, because a construct the parser misreads is a construct a\n * denied command can hide in. `/bin/sh` is bash on some systems, dash or\n * busybox ash on others, so constructs that those shells read differently\n * (bash-only syntax such as `<<<`, `&>`, `$'…'`, brace expansion, `[[`,\n * arithmetic) are refused as well.\n *\n * Three dialects:\n * - `\"posix\"` — what `/bin/sh -c` reads: `;`, `&&`, `||`, `|`, `&`,\n * newlines, comments, `( … )` subshells, `$( … )` and backtick command\n * substitution (parsed recursively), `$name` / `${name…}` parameter\n * expansion (a restricted set of forms), single and double quotes,\n * backslash escapes, redirections and here-documents.\n * - `\"cmd\"` — what `cmd.exe /d /v:off /s /c` reads, restricted to the\n * subset whose meaning is unambiguous: `&&`, `||`, `|`, `&` (sequential\n * in cmd), double quotes and simple redirections. `%`, `!`, `^`,\n * parentheses, `;` and `,` are refused.\n * - `\"powershell\"` — what `powershell -Command` / `pwsh -Command` reads, restricted\n * the same way (see `powershell-parser.ts`).\n * - `\"powershell5\"` — Windows PowerShell 5.1: the `\"powershell\"` grammar\n * without `&&` and `||`, which 5.1 cannot run; a line using them is refused.\n */\n\n/** Which shell grammar to parse. */\nexport type ShellDialect = \"posix\" | \"cmd\" | \"powershell\" | \"powershell5\";\n\n/** One word of a simple command, after quote removal. */\nexport interface ShellWord {\n /** The word exactly as written. */\n raw: string;\n /**\n * The word's text after quote removal and escapes. Expansions\n * (`$HOME`, `$(…)`, backticks) are kept as their source text — their real\n * value is only known when the shell runs.\n */\n value: string;\n /**\n * True when the shell decides part of the word at run time: a parameter\n * expansion, a command substitution, an unquoted glob character\n * (`*`, `?`, `[`) or a leading `~`.\n */\n dynamic: boolean;\n /** True when any part of the word was quoted or escaped. */\n quoted: boolean;\n /** For a `NAME=value` assignment word, the variable name. */\n assignmentName?: string;\n}\n\n/** A redirection attached to a simple command (or a subshell). */\nexport interface ShellRedirect {\n /** The operator: `>`, `>>`, `>|`, `<`, `<>`, `>&`, `<&`, `<<` or `<<-`. */\n op: string;\n /** Explicit file descriptor (`2>`), when written. */\n fd?: number;\n /** The target word (file, descriptor number or here-doc delimiter). */\n target: ShellWord;\n /**\n * `\"read\"` / `\"write\"` — a file target; `\"dup\"` — a descriptor copy or\n * close (`2>&1`, `>&-`); `\"heredoc\"` — inline input.\n */\n type: \"read\" | \"write\" | \"dup\" | \"heredoc\";\n}\n\n/** A simple command: assignments, words and redirections. */\nexport interface ShellSimpleCommand {\n /** Leading `NAME=value` words. */\n assignments: ShellWord[];\n /** The command's words — `words[0]` is the command name. Empty for a redirect- or assignment-only command. */\n words: ShellWord[];\n /** Redirections, in order. */\n redirects: ShellRedirect[];\n /** The command's source text (for messages). */\n source: string;\n}\n\n/** A top-level piece of the line and the operator that follows it. */\nexport interface ShellSegment {\n /** Source text of the pipeline. */\n source: string;\n /** The list operator after it (`&&`, `||`, `;`, `&`, a newline), if any. */\n operator?: string;\n}\n\n/** Outcome of {@link parseShellLine}. */\nexport type ShellParseResult =\n | {\n ok: true;\n /** Every simple command, in the order the shell meets them (substitutions first). */\n commands: ShellSimpleCommand[];\n /** The top-level pipelines and the operators joining them. */\n segments: ShellSegment[];\n /** True when the line backgrounds anything with `&` (POSIX dialect). */\n background: boolean;\n }\n | {\n ok: false;\n /** Why the line was refused. */\n reason: \"unbalanced-quotes\" | \"unsupported-syntax\";\n /** What was refused and why, for the model. */\n message: string;\n };\n\n/** Deepest nesting of subshells and substitutions the parser accepts. */\nconst MAX_NESTING = 16;\n\n/**\n * Words that start a compound command (or negate a pipeline) when they\n * appear unquoted in command position. The parser does not read compound\n * commands, so a line using one is refused.\n */\nconst RESERVED_WORDS = new Set([\n \"!\",\n \"{\",\n \"}\",\n \"[[\",\n \"]]\",\n \"case\",\n \"coproc\",\n \"do\",\n \"done\",\n \"elif\",\n \"else\",\n \"esac\",\n \"fi\",\n \"for\",\n \"function\",\n \"if\",\n \"in\",\n \"select\",\n \"then\",\n \"until\",\n \"while\",\n]);\n\n/** cmd.exe keywords the `\"cmd\"` dialect refuses in command position. */\nconst CMD_RESERVED_WORDS = new Set([\"if\", \"for\", \"goto\", \"setlocal\", \"endlocal\"]);\n\n/** A parameter name inside `${…}`: a variable, a positional or a special parameter. */\nconst PARAMETER_NAME = String.raw`(?:[A-Za-z_][A-Za-z0-9_]*|[0-9]+|[@*#?$!-])`;\n\n/**\n * The default/pattern word of `${name:-word}` / `${name%word}`: no quotes,\n * no nested expansion or substitution, no braces or parentheses, no `@`.\n */\nconst PARAMETER_WORD = \"[^`$'\\\"\\\\\\\\{}()@\\\\n]*\";\n\n/**\n * Allowed `${…}` forms: `${name}`, `${#name}`, `${name-word}`,\n * `${name:-word}` (also `=`, `+`, `?`), `${name%word}`, `${name%%word}`,\n * `${name#word}`, `${name##word}`. Everything else — substrings and\n * subscripts (arithmetic in bash), `${!name}`, `${name@op}`, pattern\n * substitution — is refused.\n */\nconst PARAMETER_EXPANSION = new RegExp(\n `^(?:#${PARAMETER_NAME}|${PARAMETER_NAME}(?:(?::?[-=+?]|%%?|##?)${PARAMETER_WORD})?)$`,\n);\n\n/** A refusal raised while parsing; caught by {@link parseShellLine}. */\nclass ShellSyntaxRefusal extends Error {\n public constructor(\n public readonly reason: \"unbalanced-quotes\" | \"unsupported-syntax\",\n message: string,\n ) {\n super(message);\n }\n}\n\n/** Refuse a construct the parser does not read with confidence. */\nfunction unsupported(message: string): ShellSyntaxRefusal {\n return new ShellSyntaxRefusal(\"unsupported-syntax\", message);\n}\n\n/** Refuse an unterminated quote / substitution. */\nfunction unbalanced(message: string): ShellSyntaxRefusal {\n return new ShellSyntaxRefusal(\"unbalanced-quotes\", message);\n}\n\n/** State shared by a parser and the nested parsers it starts (backticks, here-docs). */\ninterface SharedState {\n commands: ShellSimpleCommand[];\n background: boolean;\n nesting: number;\n}\n\n/** A here-document whose body starts after the next newline. */\ninterface PendingHeredoc {\n delimiter: string;\n stripTabs: boolean;\n expand: boolean;\n}\n\n/** Parser for the POSIX `sh` dialect. */\nclass PosixParser {\n private pos = 0;\n\n private pendingHeredocs: PendingHeredoc[] = [];\n\n public constructor(\n private readonly src: string,\n private readonly state: SharedState,\n ) {}\n\n /** Parse the whole line as a command list. */\n public parseProgram(segments: ShellSegment[]): void {\n this.parseList(null, segments);\n this.assertNoPendingHeredocs();\n }\n\n /** Parse the contents of a backtick substitution (a full list until EOF). */\n public parseNestedProgram(): void {\n this.parseList(null, undefined);\n this.assertNoPendingHeredocs();\n }\n\n /**\n * Scan an unquoted-delimiter here-document body: `$…`, `${…}`, `$(…)` and\n * backticks expand there exactly as inside double quotes (but `\"` is an\n * ordinary character), so any command substitution in it runs.\n */\n public parseExpandingText(): void {\n while (!this.atEnd()) {\n const char = this.peek();\n\n if (char === \"\\\\\") {\n this.pos += 2;\n\n continue;\n }\n\n if (char === \"$\") {\n this.parseDollar(\"heredoc\");\n\n continue;\n }\n\n if (char === \"`\") {\n this.parseBackticks(false);\n\n continue;\n }\n\n this.pos++;\n }\n }\n\n private atEnd(): boolean {\n return this.pos >= this.src.length;\n }\n\n private peek(offset = 0): string {\n return this.src.charAt(this.pos + offset);\n }\n\n private assertNoPendingHeredocs(): void {\n if (this.pendingHeredocs.length > 0) {\n throw unsupported(\n `The here-document delimited by \\`${this.pendingHeredocs[0]?.delimiter ?? \"\"}\\` is never terminated.`,\n );\n }\n }\n\n private enterNesting(): void {\n this.state.nesting++;\n\n if (this.state.nesting > MAX_NESTING) {\n throw unsupported(\"The line nests subshells or substitutions too deeply.\");\n }\n }\n\n private exitNesting(): void {\n this.state.nesting--;\n }\n\n /** Skip spaces, tabs and backslash-newline continuations. */\n private skipBlanks(): void {\n while (!this.atEnd()) {\n const char = this.peek();\n\n if (char === \" \" || char === \"\\t\") {\n this.pos++;\n\n continue;\n }\n\n if (char === \"\\\\\" && this.peek(1) === \"\\n\") {\n this.pos += 2;\n\n continue;\n }\n\n break;\n }\n }\n\n /** Skip a `#` comment up to (not including) the newline. */\n private skipComment(): void {\n const newline = this.src.indexOf(\"\\n\", this.pos);\n\n this.pos = newline === -1 ? this.src.length : newline;\n }\n\n /** Consume one newline and read any here-document bodies queued before it. */\n private consumeNewline(): void {\n this.pos++;\n\n if (this.pendingHeredocs.length === 0) {\n return;\n }\n\n if (this.state.nesting > 0) {\n throw unsupported(\"Here-documents inside subshells or substitutions are not supported.\");\n }\n\n const pending = this.pendingHeredocs;\n\n this.pendingHeredocs = [];\n\n for (const heredoc of pending) {\n this.readHeredocBody(heredoc);\n }\n }\n\n /** Skip blanks, comments and newlines (separators between commands). */\n private skipBlanksAndNewlines(): void {\n while (!this.atEnd()) {\n this.skipBlanks();\n\n if (this.peek() === \"#\") {\n this.skipComment();\n\n continue;\n }\n\n if (this.peek() === \"\\n\") {\n this.consumeNewline();\n\n continue;\n }\n\n break;\n }\n }\n\n private readHeredocBody(heredoc: PendingHeredoc): void {\n const bodyLines: string[] = [];\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (this.atEnd()) {\n throw unsupported(\n `The here-document delimited by \\`${heredoc.delimiter}\\` is never terminated.`,\n );\n }\n\n const lineEnd = this.src.indexOf(\"\\n\", this.pos);\n const line = lineEnd === -1 ? this.src.slice(this.pos) : this.src.slice(this.pos, lineEnd);\n const compared = heredoc.stripTabs ? line.replace(/^\\t+/, \"\") : line;\n\n this.pos = lineEnd === -1 ? this.src.length : lineEnd + 1;\n\n if (compared === heredoc.delimiter) {\n break;\n }\n\n bodyLines.push(compared);\n }\n\n if (heredoc.expand) {\n new PosixParser(bodyLines.join(\"\\n\"), this.state).parseExpandingText();\n }\n }\n\n /**\n * Parse a command list until `closing` (`\")\"`) or the end of the input.\n * `segments`, when given, collects the top-level pipelines.\n */\n private parseList(closing: string | null, segments: ShellSegment[] | undefined): void {\n let sawCommand = false;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanksAndNewlines();\n\n if (this.atEnd()) {\n if (closing !== null) {\n throw unbalanced(`A \\`(\\` or \\`$(\\` is never closed with \\`${closing}\\`.`);\n }\n\n break;\n }\n\n if (closing !== null && this.peek() === closing) {\n break;\n }\n\n const start = this.pos;\n\n this.parsePipeline(closing);\n\n const end = this.pos;\n\n sawCommand = true;\n this.skipBlanks();\n\n if (this.peek() === \"#\") {\n this.skipComment();\n }\n\n const operator = this.readListOperator();\n\n segments?.push({\n source: this.src.slice(start, end).trim(),\n ...(operator !== null ? { operator } : {}),\n });\n\n if (operator === \"&&\" || operator === \"||\") {\n this.skipBlanksAndNewlines();\n\n if (this.atEnd() || (closing !== null && this.peek() === closing)) {\n throw unsupported(`The line ends with \\`${operator}\\` but no command follows it.`);\n }\n\n continue;\n }\n\n if (operator === \"&\") {\n this.state.background = true;\n }\n\n if (operator === null) {\n if (this.atEnd() || (closing !== null && this.peek() === closing)) {\n break;\n }\n\n throw unsupported(`Unexpected \\`${this.peek()}\\`.`);\n }\n }\n\n if (!sawCommand) {\n throw unsupported(closing === null ? \"The command is empty.\" : \"A `( )` or `$( )` is empty.\");\n }\n }\n\n /** Read `&&`, `||`, `;`, `&` or a newline after a pipeline. */\n private readListOperator(): string | null {\n const char = this.peek();\n const next = this.peek(1);\n\n if (char === \"&\") {\n if (next === \"&\") {\n this.pos += 2;\n\n return \"&&\";\n }\n\n if (next === \">\") {\n throw unsupported(\n \"`&>` (redirect both streams) is not portable; write `> file 2>&1` instead.\",\n );\n }\n\n this.pos++;\n\n return \"&\";\n }\n\n if (char === \"|\" && next === \"|\") {\n this.pos += 2;\n\n return \"||\";\n }\n\n if (char === \";\") {\n if (next === \";\") {\n throw unsupported(\"`;;` (a `case` clause) is not supported.\");\n }\n\n this.pos++;\n\n return \";\";\n }\n\n if (char === \"\\n\") {\n this.consumeNewline();\n\n return \"\\n\";\n }\n\n return null;\n }\n\n private parsePipeline(closing: string | null): void {\n this.parseCommand(closing);\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (this.peek() !== \"|\" || this.peek(1) === \"|\") {\n return;\n }\n\n if (this.peek(1) === \"&\") {\n throw unsupported(\"`|&` is not portable; write `2>&1 |` instead.\");\n }\n\n this.pos++;\n this.skipBlanksAndNewlines();\n\n if (this.atEnd() || (closing !== null && this.peek() === closing)) {\n throw unsupported(\"The line ends with `|` but no command follows it.\");\n }\n\n this.parseCommand(closing);\n }\n }\n\n private parseCommand(closing: string | null): void {\n this.skipBlanks();\n\n if (this.peek() === \"(\") {\n if (this.peek(1) === \"(\") {\n throw unsupported(\"`((…))` (arithmetic) is not supported.\");\n }\n\n this.parseSubshell();\n\n return;\n }\n\n if (this.peek() === \")\") {\n throw unsupported(closing === null ? \"Unexpected `)`.\" : \"A `( )` or `$( )` is empty.\");\n }\n\n this.parseSimpleCommand();\n }\n\n private parseSubshell(): void {\n const start = this.pos;\n\n this.pos++;\n this.enterNesting();\n this.parseList(\")\", undefined);\n\n if (this.peek() !== \")\") {\n throw unbalanced(\"A `(` is never closed with `)`.\");\n }\n\n this.pos++;\n this.exitNesting();\n\n // Redirections may follow the closing parenthesis; nothing else may.\n const redirects: ShellRedirect[] = [];\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (!this.atRedirectStart()) {\n break;\n }\n\n redirects.push(this.parseRedirect());\n }\n\n if (redirects.length > 0) {\n this.state.commands.push({\n assignments: [],\n words: [],\n redirects,\n source: this.src.slice(start, this.pos).trim(),\n });\n }\n\n if (!this.atEnd() && !this.atOperatorBoundary()) {\n throw unsupported(\"Only an operator or a redirection may follow `)`.\");\n }\n }\n\n /** Whether the next character ends a simple command. */\n private atOperatorBoundary(): boolean {\n const char = this.peek();\n\n return (\n char === \"\\n\" ||\n char === \";\" ||\n char === \"&\" ||\n char === \"|\" ||\n char === \")\" ||\n char === \"#\" ||\n char === \" \" ||\n char === \"\\t\"\n );\n }\n\n /** Whether a redirection starts here (`>`, `<`, or digits glued to one). */\n private atRedirectStart(): boolean {\n let index = this.pos;\n\n while (index < this.src.length && /[0-9]/.test(this.src.charAt(index))) {\n index++;\n }\n\n const char = this.src.charAt(index);\n\n return char === \"<\" || char === \">\";\n }\n\n private parseSimpleCommand(): void {\n const start = this.pos;\n const command: ShellSimpleCommand = { assignments: [], words: [], redirects: [], source: \"\" };\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (this.atEnd()) {\n break;\n }\n\n const char = this.peek();\n\n if (char === \"\\n\" || char === \";\" || char === \"&\" || char === \"|\" || char === \")\") {\n break;\n }\n\n if (char === \"(\") {\n throw unsupported(\n \"`(` is only supported at the start of a command (a subshell); quote it to pass it as text.\",\n );\n }\n\n if (char === \"#\") {\n this.skipComment();\n\n break;\n }\n\n if (this.atRedirectStart()) {\n command.redirects.push(this.parseRedirect());\n\n continue;\n }\n\n const word = this.parseWord();\n\n if (command.words.length === 0 && word.assignmentName !== undefined) {\n command.assignments.push(word);\n\n continue;\n }\n\n command.words.push(word);\n\n if (command.words.length === 1 && !word.quoted && RESERVED_WORDS.has(word.value)) {\n throw unsupported(\n `\\`${word.value}\\` starts a shell compound command, which is not supported; run the commands one at a time.`,\n );\n }\n }\n\n if (\n command.words.length === 0 &&\n command.assignments.length === 0 &&\n command.redirects.length === 0\n ) {\n throw unsupported(\"The line has an empty command (two operators in a row?).\");\n }\n\n command.source = this.src.slice(start, this.pos).trim();\n this.state.commands.push(command);\n }\n\n private parseRedirect(): ShellRedirect {\n let digits = \"\";\n\n while (/[0-9]/.test(this.peek())) {\n digits += this.peek();\n this.pos++;\n }\n\n const rest = this.src.slice(this.pos, this.pos + 3);\n let op: string;\n\n if (rest.startsWith(\"<<<\")) {\n throw unsupported(\"`<<<` (a here-string) is not portable; use a here-document.\");\n } else if (rest.startsWith(\"<<-\")) {\n op = \"<<-\";\n } else if (rest.startsWith(\"<<\")) {\n op = \"<<\";\n } else if (rest.startsWith(\"<>\")) {\n op = \"<>\";\n } else if (rest.startsWith(\"<&\")) {\n op = \"<&\";\n } else if (rest.startsWith(\"<(\") || rest.startsWith(\">(\")) {\n throw unsupported(\"Process substitution (`<(…)`, `>(…)`) is not supported.\");\n } else if (rest.startsWith(\">>\")) {\n op = \">>\";\n } else if (rest.startsWith(\">|\")) {\n op = \">|\";\n } else if (rest.startsWith(\">&\")) {\n op = \">&\";\n } else {\n op = this.peek();\n }\n\n this.pos += op.length;\n\n const fd = digits === \"\" ? undefined : Number(digits);\n\n this.skipBlanks();\n\n if (this.atEnd() || /[\\n;&|<>()]/.test(this.peek())) {\n throw unsupported(`The redirection \\`${op}\\` has no target.`);\n }\n\n const target = this.parseWord();\n\n if (op === \"<<\" || op === \"<<-\") {\n if (this.state.nesting > 0) {\n throw unsupported(\"Here-documents inside subshells or substitutions are not supported.\");\n }\n\n if (target.dynamic) {\n throw unsupported(\"A here-document delimiter must be literal text.\");\n }\n\n this.pendingHeredocs.push({\n delimiter: target.value,\n stripTabs: op === \"<<-\",\n expand: !target.quoted,\n });\n\n return { op, ...(fd !== undefined ? { fd } : {}), target, type: \"heredoc\" };\n }\n\n if (op === \">&\" || op === \"<&\") {\n if (target.dynamic || !/^(?:[0-9]+|-)$/.test(target.value)) {\n throw unsupported(\n `\\`${op}${target.raw}\\` is not portable; write \\`> file 2>&1\\` to send both streams to a file.`,\n );\n }\n\n return { op, ...(fd !== undefined ? { fd } : {}), target, type: \"dup\" };\n }\n\n return {\n op,\n ...(fd !== undefined ? { fd } : {}),\n target,\n type: op === \"<\" ? \"read\" : \"write\",\n };\n }\n\n /** Parse one unquoted-context word. */\n private parseWord(): ShellWord {\n const start = this.pos;\n let value = \"\";\n let dynamic = false;\n let quoted = false;\n let braces = false;\n let nameCandidate = true;\n let assignmentName: string | undefined;\n\n while (!this.atEnd()) {\n const char = this.peek();\n\n if (\n char === \" \" ||\n char === \"\\t\" ||\n char === \"\\n\" ||\n char === \";\" ||\n char === \"&\" ||\n char === \"|\" ||\n char === \"<\" ||\n char === \">\" ||\n char === \"(\" ||\n char === \")\"\n ) {\n break;\n }\n\n if (char === \"\\r\" || char === \"\\0\") {\n throw unsupported(\"Carriage returns and NUL bytes are not supported in a command line.\");\n }\n\n if (char === \"\\\\\") {\n const next = this.peek(1);\n\n if (next === \"\") {\n throw unsupported(\"The line ends with a backslash.\");\n }\n\n this.pos += 2;\n\n if (next !== \"\\n\") {\n value += next;\n quoted = true;\n nameCandidate = false;\n }\n\n continue;\n }\n\n if (char === \"'\") {\n const closing = this.src.indexOf(\"'\", this.pos + 1);\n\n if (closing === -1) {\n throw unbalanced(\"A `'` quote is never closed.\");\n }\n\n value += this.src.slice(this.pos + 1, closing);\n quoted = true;\n nameCandidate = false;\n this.pos = closing + 1;\n\n continue;\n }\n\n if (char === '\"') {\n const part = this.parseDoubleQuoted();\n\n value += part.value;\n dynamic = dynamic || part.dynamic;\n quoted = true;\n nameCandidate = false;\n\n continue;\n }\n\n if (char === \"$\") {\n const part = this.parseDollar(\"unquoted\");\n\n value += part.text;\n dynamic = dynamic || part.dynamic;\n nameCandidate = false;\n\n continue;\n }\n\n if (char === \"`\") {\n value += this.parseBackticks(false);\n dynamic = true;\n nameCandidate = false;\n\n continue;\n }\n\n if (char === \"=\" && nameCandidate && assignmentName === undefined) {\n if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {\n assignmentName = value;\n }\n\n nameCandidate = false;\n } else if (!/[A-Za-z0-9_]/.test(char)) {\n nameCandidate = false;\n }\n\n if (char === \"*\" || char === \"?\" || char === \"[\") {\n dynamic = true;\n }\n\n if (char === \"~\") {\n const previous = this.src.charAt(this.pos - 1);\n\n if (\n this.pos === start ||\n (assignmentName !== undefined && (previous === \"=\" || previous === \":\"))\n ) {\n dynamic = true;\n }\n }\n\n if (char === \"{\" || char === \"}\") {\n braces = true;\n }\n\n value += char;\n this.pos++;\n }\n\n const raw = this.src.slice(start, this.pos);\n\n if (braces && raw !== \"{}\") {\n throw unsupported(\n `Unquoted \\`{\\` or \\`}\\` in \\`${raw}\\` (brace expansion or a command group) is not supported; quote it.`,\n );\n }\n\n // A lone `[` or `]` is never a glob (it is the `test` command or its end).\n if (raw === \"[\" || raw === \"]\") {\n dynamic = false;\n }\n\n return {\n raw,\n value,\n dynamic,\n quoted,\n ...(assignmentName !== undefined ? { assignmentName } : {}),\n };\n }\n\n /** Parse a `\"…\"` span (cursor on the opening quote). */\n private parseDoubleQuoted(): { value: string; dynamic: boolean } {\n this.pos++;\n\n let value = \"\";\n let dynamic = false;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (this.atEnd()) {\n throw unbalanced('A `\"` quote is never closed.');\n }\n\n const char = this.peek();\n\n if (char === '\"') {\n this.pos++;\n\n return { value, dynamic };\n }\n\n if (char === \"\\0\") {\n throw unsupported(\"NUL bytes are not supported in a command line.\");\n }\n\n if (char === \"\\\\\") {\n const next = this.peek(1);\n\n if (next === \"\") {\n throw unbalanced('A `\"` quote is never closed.');\n }\n\n if (next === \"$\" || next === \"`\" || next === '\"' || next === \"\\\\\") {\n value += next;\n this.pos += 2;\n\n continue;\n }\n\n if (next === \"\\n\") {\n this.pos += 2;\n\n continue;\n }\n\n value += char;\n this.pos++;\n\n continue;\n }\n\n if (char === \"$\") {\n const part = this.parseDollar(\"dquote\");\n\n value += part.text;\n dynamic = dynamic || part.dynamic;\n\n continue;\n }\n\n if (char === \"`\") {\n value += this.parseBackticks(true);\n dynamic = true;\n\n continue;\n }\n\n value += char;\n this.pos++;\n }\n }\n\n /** Parse a `$…` expansion (cursor on the `$`). */\n private parseDollar(context: \"unquoted\" | \"dquote\" | \"heredoc\"): {\n text: string;\n dynamic: boolean;\n } {\n const start = this.pos;\n const next = this.peek(1);\n\n if (next === \"(\") {\n if (this.peek(2) === \"(\") {\n throw unsupported(\"`$((…))` (arithmetic expansion) is not supported.\");\n }\n\n this.pos += 2;\n this.enterNesting();\n this.parseList(\")\", undefined);\n\n if (this.peek() !== \")\") {\n throw unbalanced(\"A `$(` is never closed with `)`.\");\n }\n\n this.pos++;\n this.exitNesting();\n\n return { text: this.src.slice(start, this.pos), dynamic: true };\n }\n\n if (next === \"{\") {\n const closing = this.src.indexOf(\"}\", this.pos + 2);\n\n if (closing === -1) {\n throw unbalanced(\"A `${` is never closed with `}`.\");\n }\n\n const body = this.src.slice(this.pos + 2, closing);\n\n if (!PARAMETER_EXPANSION.test(body)) {\n throw unsupported(\n `\\`\\${${body}}\\` is not a supported parameter expansion; use \\`$name\\`, \\`\\${name}\\` or \\`\\${name:-default}\\`.`,\n );\n }\n\n this.pos = closing + 1;\n\n return { text: this.src.slice(start, this.pos), dynamic: true };\n }\n\n if (next === \"[\") {\n throw unsupported(\"`$[…]` (arithmetic expansion) is not supported.\");\n }\n\n if (context === \"unquoted\" && (next === \"'\" || next === '\"')) {\n throw unsupported(`\\`$${next}…${next}\\` quoting is not portable; use plain quotes.`);\n }\n\n if (/[A-Za-z_]/.test(next)) {\n this.pos += 2;\n\n while (/[A-Za-z0-9_]/.test(this.peek())) {\n this.pos++;\n }\n\n return { text: this.src.slice(start, this.pos), dynamic: true };\n }\n\n if (/[0-9@*#?$!-]/.test(next) && next !== \"\") {\n this.pos += 2;\n\n return { text: this.src.slice(start, this.pos), dynamic: true };\n }\n\n // A `$` followed by anything else is an ordinary character.\n this.pos++;\n\n return { text: \"$\", dynamic: false };\n }\n\n /** Parse a backtick substitution (cursor on the opening backtick); returns its source. */\n private parseBackticks(insideDoubleQuotes: boolean): string {\n const start = this.pos;\n let content = \"\";\n\n this.pos++;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (this.atEnd()) {\n throw unbalanced(\"A backtick substitution is never closed.\");\n }\n\n const char = this.peek();\n\n if (char === \"`\") {\n this.pos++;\n\n break;\n }\n\n if (char === \"\\\\\") {\n const next = this.peek(1);\n\n if (next === \"\") {\n throw unbalanced(\"A backtick substitution is never closed.\");\n }\n\n if (next === \"`\" || next === \"\\\\\" || next === \"$\") {\n content += next;\n this.pos += 2;\n\n continue;\n }\n\n if (next === '\"' && insideDoubleQuotes) {\n throw unsupported(\n 'An escaped `\\\\\"` inside backticks inside double quotes is not portable; use `$(…)`.',\n );\n }\n\n content += char + next;\n this.pos += 2;\n\n continue;\n }\n\n content += char;\n this.pos++;\n }\n\n this.enterNesting();\n new PosixParser(content, this.state).parseNestedProgram();\n this.exitNesting();\n\n return this.src.slice(start, this.pos);\n }\n}\n\n/** Parser for the restricted `cmd.exe` dialect. */\nclass CmdParser {\n private pos = 0;\n\n public constructor(\n private readonly src: string,\n private readonly commands: ShellSimpleCommand[],\n ) {}\n\n public parseProgram(segments: ShellSegment[]): void {\n if (/[\\r\\n\\0]/.test(this.src)) {\n throw unsupported(\"Line breaks and NUL bytes are not supported in a cmd.exe command line.\");\n }\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (this.atEnd()) {\n throw unsupported(\n segments.length === 0\n ? \"The command is empty.\"\n : \"The line ends with an operator but no command follows it.\",\n );\n }\n\n const start = this.pos;\n\n this.parsePipeline();\n\n const end = this.pos;\n\n this.skipBlanks();\n\n const operator = this.readListOperator();\n\n segments.push({\n source: this.src.slice(start, end).trim(),\n ...(operator !== null ? { operator } : {}),\n });\n\n if (operator === null) {\n if (!this.atEnd()) {\n throw unsupported(`Unexpected \\`${this.peek()}\\`.`);\n }\n\n return;\n }\n }\n }\n\n private atEnd(): boolean {\n return this.pos >= this.src.length;\n }\n\n private peek(offset = 0): string {\n return this.src.charAt(this.pos + offset);\n }\n\n private skipBlanks(): void {\n while (this.peek() === \" \" || this.peek() === \"\\t\") {\n this.pos++;\n }\n }\n\n private readListOperator(): string | null {\n if (this.peek() === \"&\") {\n if (this.peek(1) === \"&\") {\n this.pos += 2;\n\n return \"&&\";\n }\n\n this.pos++;\n\n return \"&\";\n }\n\n if (this.peek() === \"|\" && this.peek(1) === \"|\") {\n this.pos += 2;\n\n return \"||\";\n }\n\n return null;\n }\n\n private parsePipeline(): void {\n this.parseSimpleCommand();\n\n while (this.peek() === \"|\" && this.peek(1) !== \"|\") {\n this.pos++;\n this.skipBlanks();\n\n if (this.atEnd()) {\n throw unsupported(\"The line ends with `|` but no command follows it.\");\n }\n\n this.parseSimpleCommand();\n }\n }\n\n private atRedirectStart(): boolean {\n const char = this.peek();\n\n if (char === \"<\" || char === \">\") {\n return true;\n }\n\n return /[0-9]/.test(char) && (this.peek(1) === \">\" || this.peek(1) === \"<\");\n }\n\n private parseSimpleCommand(): void {\n const start = this.pos;\n const command: ShellSimpleCommand = { assignments: [], words: [], redirects: [], source: \"\" };\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (this.atEnd() || this.peek() === \"&\" || this.peek() === \"|\") {\n break;\n }\n\n if (this.atRedirectStart()) {\n command.redirects.push(this.parseRedirect());\n\n continue;\n }\n\n const word = this.parseWord();\n\n command.words.push(word);\n\n if (command.words.length === 1) {\n this.checkCommandWord(word);\n }\n }\n\n if (command.words.length === 0 && command.redirects.length === 0) {\n throw unsupported(\"The line has an empty command (two operators in a row?).\");\n }\n\n command.source = this.src.slice(start, this.pos).trim();\n this.commands.push(command);\n }\n\n private checkCommandWord(word: ShellWord): void {\n if (word.raw.startsWith(\"@\") || word.raw.startsWith(\":\")) {\n throw unsupported(`\\`${word.raw}\\` is not supported as a cmd.exe command name.`);\n }\n\n if (/[/=]/.test(word.value)) {\n throw unsupported(\n `A cmd.exe command name may not contain \\`/\\` or \\`=\\` (\\`${word.value}\\`); use \\`\\\\\\` in paths.`,\n );\n }\n\n if (!word.quoted && CMD_RESERVED_WORDS.has(word.value.toLowerCase())) {\n throw unsupported(\n `\\`${word.value}\\` starts a cmd.exe compound command, which is not supported.`,\n );\n }\n }\n\n private parseRedirect(): ShellRedirect {\n let fd: number | undefined;\n\n if (/[0-9]/.test(this.peek())) {\n fd = Number(this.peek());\n this.pos++;\n }\n\n let op: string;\n\n if (this.src.startsWith(\">>\", this.pos)) {\n op = \">>\";\n } else if (this.src.startsWith(\">&\", this.pos)) {\n op = \">&\";\n } else if (this.src.startsWith(\"<&\", this.pos) || this.src.startsWith(\"<<\", this.pos)) {\n throw unsupported(\n `\\`${this.src.slice(this.pos, this.pos + 2)}\\` is not supported by cmd.exe.`,\n );\n } else {\n op = this.peek();\n }\n\n this.pos += op.length;\n this.skipBlanks();\n\n if (this.atEnd() || /[&|<>]/.test(this.peek())) {\n throw unsupported(`The redirection \\`${op}\\` has no target.`);\n }\n\n const target = this.parseWord();\n\n if (op === \">&\") {\n if (!/^[0-9]$/.test(target.value)) {\n throw unsupported(`\\`>&${target.raw}\\` is not supported; write \\`> file 2>&1\\`.`);\n }\n\n return { op, ...(fd !== undefined ? { fd } : {}), target, type: \"dup\" };\n }\n\n return { op, ...(fd !== undefined ? { fd } : {}), target, type: op === \"<\" ? \"read\" : \"write\" };\n }\n\n private parseWord(): ShellWord {\n const start = this.pos;\n let value = \"\";\n let dynamic = false;\n let quoted = false;\n\n while (!this.atEnd()) {\n const char = this.peek();\n\n if (\n char === \" \" ||\n char === \"\\t\" ||\n char === \"&\" ||\n char === \"|\" ||\n char === \"<\" ||\n char === \">\"\n ) {\n break;\n }\n\n if (char === '\"') {\n const closing = this.src.indexOf('\"', this.pos + 1);\n\n if (closing === -1) {\n throw unbalanced('A `\"` quote is never closed.');\n }\n\n const inner = this.src.slice(this.pos + 1, closing);\n\n if (/[%!]/.test(inner)) {\n throw unsupported(\n \"`%` and `!` expand even inside quotes in cmd.exe; they are not supported.\",\n );\n }\n\n if (value.endsWith(\"\\\\\") || inner.endsWith(\"\\\\\")) {\n throw unsupported(\n 'A backslash before a `\"` is read differently by cmd.exe and programs.',\n );\n }\n\n value += inner;\n quoted = true;\n this.pos = closing + 1;\n\n continue;\n }\n\n if (\n char === \"%\" ||\n char === \"!\" ||\n char === \"^\" ||\n char === \"(\" ||\n char === \")\" ||\n char === \";\" ||\n char === \",\"\n ) {\n throw unsupported(\n `Unquoted \\`${char}\\` has a special meaning in cmd.exe and is not supported.`,\n );\n }\n\n if (char === \"\\\\\" && this.peek(1) === '\"') {\n throw unsupported('A backslash before a `\"` is read differently by cmd.exe and programs.');\n }\n\n if (char === \"*\" || char === \"?\") {\n dynamic = true;\n }\n\n value += char;\n this.pos++;\n }\n\n return { raw: this.src.slice(start, this.pos), value, dynamic, quoted };\n }\n}\n\n/**\n * Parse a command line for the shell policy. Never throws: a construct the\n * parser does not read with confidence comes back as `{ ok: false }` with a\n * message naming it.\n *\n * @example\n * const parsed = parseShellLine(\"git add -A && git commit -m 'wip'\", \"posix\");\n * // parsed.ok === true; parsed.commands.map((c) => c.words.map((w) => w.value))\n * // → [[\"git\", \"add\", \"-A\"], [\"git\", \"commit\", \"-m\", \"wip\"]]\n */\nexport function parseShellLine(line: string, dialect: ShellDialect): ShellParseResult {\n const segments: ShellSegment[] = [];\n\n if (dialect === \"powershell\" || dialect === \"powershell5\") {\n return parsePowerShellLine(line, { chaining: dialect === \"powershell\" });\n }\n\n try {\n if (dialect === \"cmd\") {\n const commands: ShellSimpleCommand[] = [];\n\n new CmdParser(line, commands).parseProgram(segments);\n\n return { ok: true, commands, segments, background: false };\n }\n\n const state: SharedState = { commands: [], background: false, nesting: 0 };\n\n new PosixParser(line, state).parseProgram(segments);\n\n return { ok: true, commands: state.commands, segments, background: state.background };\n } catch (error) {\n if (error instanceof ShellSyntaxRefusal) {\n return { ok: false, reason: error.reason, message: error.message };\n }\n\n throw error;\n }\n}\n"],"mappings":"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"}
|
|
@@ -27,7 +27,7 @@ export interface MakeRunShellToolOptions {
|
|
|
27
27
|
*/
|
|
28
28
|
shellLabel?: string;
|
|
29
29
|
/** The grammar of that shell; selects the syntax rules in the description. */
|
|
30
|
-
shellDialect?: "posix" | "cmd" | "powershell" | "none";
|
|
30
|
+
shellDialect?: "posix" | "cmd" | "powershell" | "powershell5" | "none";
|
|
31
31
|
/** The operating system named in the description. Default: the running one. */
|
|
32
32
|
platform?: NodeJS.Platform;
|
|
33
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-shell.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/tools/run-shell.ts"],"mappings":";;;;;;iBA2EiB;;EAEf;;;;;;EAMA;;;;;;;EAOA;;;;;;EAMA;;EAEA;;EAEA,WAAW,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"run-shell.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/tools/run-shell.ts"],"mappings":";;;;;;iBA2EiB;;EAEf;;;;;;EAMA;;;;;;;EAOA;;;;;;EAMA;;EAEA;;EAEA,WAAW,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAmFJ,iBACd,KAAK,cACL,UAAU;EAA4B;IACrC,aAAa,eAAe;wBACf,iBACd,KAAK,cACL,SAAS;EAA4B;IACpC,aAAa,eAAe,iBAAiB;wBAChC,iBACd,KAAK,cACL,UAAU,0BACT,aAAa,eAAe,iBAAiB"}
|
package/esm/tools/run-shell.mjs
CHANGED
|
@@ -43,7 +43,8 @@ function runShellInputSchema(background) {
|
|
|
43
43
|
const DIALECT_RULES = {
|
|
44
44
|
posix: "Use POSIX shell syntax: `;`, `&&`, `||`, pipes, quotes, `$( )` and redirections are supported, and every command in the line is checked. Compound statements (`if`, `for`), brace expansion, arithmetic and `&` are not.",
|
|
45
45
|
cmd: "Use cmd.exe syntax: `&&`, `||`, `&`, `|` and simple redirections are supported, and every command in the line is checked. `%` and `!` expansion, `^`, parentheses and `if` / `for` are not.",
|
|
46
|
-
powershell: "Use PowerShell syntax: `;`, `&&`, `||`, pipes, `$( )`, the call operator `&` and the redirections `>`, `>>`, `2>` and `2>&1` are supported, and every command in the line is checked (aliases such as `rm` and `Remove-Item` are treated alike). Script blocks, `Invoke-Expression`, here-strings, splatting and `if` / `foreach` are not."
|
|
46
|
+
powershell: "Use PowerShell syntax: `;`, `&&`, `||`, pipes, `$( )`, the call operator `&` and the redirections `>`, `>>`, `2>` and `2>&1` are supported, and every command in the line is checked (aliases such as `rm` and `Remove-Item` are treated alike). Script blocks, `Invoke-Expression`, here-strings, splatting and `if` / `foreach` are not.",
|
|
47
|
+
powershell5: "Use Windows PowerShell 5.1 syntax: `;`, pipes, `$( )`, the call operator `&` and the redirections `>`, `>>`, `2>` and `2>&1` are supported, and every command in the line is checked (aliases such as `rm` and `Remove-Item` are treated alike). This version cannot chain with `&&` or `||`: put one command per call, or join with `;` (which runs the next command whatever the exit code). Script blocks, `Invoke-Expression`, here-strings, splatting and `if` / `foreach` are not supported."
|
|
47
48
|
};
|
|
48
49
|
const PLATFORM_NAMES = {
|
|
49
50
|
win32: "Windows",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-shell.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/tools/run-shell.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { WorkspacePolicyError } from \"../errors\";\nimport type {\n RunShellBackgroundResult,\n RunShellInput,\n RunShellResult,\n WorkspaceOps,\n} from \"../contracts\";\nimport { runShellJsonSchema } from \"./json-schemas\";\nimport { withJsonSchema } from \"./schema\";\n\n/** The default tool name `run_shell` is exposed to the LLM under. */\nconst DEFAULT_RUN_SHELL_TOOL_NAME = \"run_shell\";\n\n/**\n * Hand-rolled Standard Schema for {@link RunShellInput}. We validate the\n * model's arguments without a runtime schema dependency: `command` must be\n * a non-empty string, `timeoutMs` (when present) a positive number and\n * `background` (when present) a boolean. Invalid args surface as a\n * `SchemaValidationError` in the tool result's `error` field rather than\n * reaching `ops.exec`.\n */\nfunction runShellInputSchema(background: boolean): StandardSchemaV1<RunShellInput> {\n const schema: StandardSchemaV1<RunShellInput> = {\n \"~standard\": {\n version: 1,\n vendor: \"@warlock.js/ai-workspace\",\n validate: (value) => {\n if (typeof value !== \"object\" || value === null) {\n return { issues: [{ message: \"expected an object\" }] };\n }\n\n const candidate = value as Record<string, unknown>;\n\n if (typeof candidate.command !== \"string\" || candidate.command.length === 0) {\n return {\n issues: [{ message: \"command must be a non-empty string\", path: [\"command\"] }],\n };\n }\n\n if (\n candidate.timeoutMs !== undefined &&\n (typeof candidate.timeoutMs !== \"number\" || candidate.timeoutMs <= 0)\n ) {\n return {\n issues: [{ message: \"timeoutMs must be a positive number\", path: [\"timeoutMs\"] }],\n };\n }\n\n if (candidate.background !== undefined && typeof candidate.background !== \"boolean\") {\n return {\n issues: [{ message: \"background must be a boolean\", path: [\"background\"] }],\n };\n }\n\n const result: RunShellInput = { command: candidate.command };\n\n if (typeof candidate.timeoutMs === \"number\") {\n result.timeoutMs = candidate.timeoutMs;\n }\n\n if (candidate.background === true) {\n result.background = true;\n }\n\n return { value: result };\n },\n },\n };\n\n return withJsonSchema(schema, runShellJsonSchema(background));\n}\n\n/** Options for {@link makeRunShellTool}. */\nexport interface MakeRunShellToolOptions {\n /** Override the tool name exposed to the LLM (default `\"run_shell\"`). */\n name?: string;\n /**\n * Upper bound for a model-supplied `timeoutMs` on a foreground command —\n * the model can lower the timeout, never raise it past this. The\n * workspace passes its `shell.timeoutMs`.\n */\n maxTimeoutMs?: number;\n /**\n * Accept `background: true` and start the command as a background job\n * (`ops.startShell`), returning a {@link RunShellBackgroundResult}. The\n * workspace sets it when the policy has `shell.background`. Default\n * `false`: a background request is refused.\n */\n background?: boolean;\n /**\n * The name of the shell lines run in (for example `bash` or\n * `PowerShell 7 (pwsh)`). When set, the description tells the model which\n * shell and syntax rules apply. Absent ⇒ argv mode, no shell.\n */\n shellLabel?: string;\n /** The grammar of that shell; selects the syntax rules in the description. */\n shellDialect?: \"posix\" | \"cmd\" | \"powershell\" | \"none\";\n /** The operating system named in the description. Default: the running one. */\n platform?: NodeJS.Platform;\n}\n\nconst DIALECT_RULES: Record<\"posix\" | \"cmd\" | \"powershell\", string> = {\n posix:\n \"Use POSIX shell syntax: `;`, `&&`, `||`, pipes, quotes, `$( )` and redirections are supported, \" +\n \"and every command in the line is checked. Compound statements (`if`, `for`), brace expansion, \" +\n \"arithmetic and `&` are not.\",\n cmd:\n \"Use cmd.exe syntax: `&&`, `||`, `&`, `|` and simple redirections are supported, and every command \" +\n \"in the line is checked. `%` and `!` expansion, `^`, parentheses and `if` / `for` are not.\",\n powershell:\n \"Use PowerShell syntax: `;`, `&&`, `||`, pipes, `$( )`, the call operator `&` and the redirections \" +\n \"`>`, `>>`, `2>` and `2>&1` are supported, and every command in the line is checked (aliases such as \" +\n \"`rm` and `Remove-Item` are treated alike). Script blocks, `Invoke-Expression`, here-strings, \" +\n \"splatting and `if` / `foreach` are not.\",\n};\n\nconst PLATFORM_NAMES: Partial<Record<NodeJS.Platform, string>> = {\n win32: \"Windows\",\n darwin: \"macOS\",\n linux: \"Linux\",\n};\n\n/** The sentence that names the shell and its syntax, or `\"\"` in argv mode. */\nfunction shellSentence(options: MakeRunShellToolOptions | undefined): string {\n const dialect = options?.shellDialect;\n\n if (options?.shellLabel === undefined || dialect === undefined || dialect === \"none\") {\n return \"\";\n }\n\n const platform = options.platform ?? process.platform;\n\n return (\n ` Commands run on ${PLATFORM_NAMES[platform] ?? platform} using ${options.shellLabel}. ` +\n DIALECT_RULES[dialect]\n );\n}\n\n/** The lower of the model's timeout and the cap (either may be absent). */\nfunction cappedTimeout(requested: number | undefined, cap: number | undefined): number | undefined {\n if (requested === undefined) {\n return cap;\n }\n\n return cap === undefined ? requested : Math.min(requested, cap);\n}\n\n/**\n * Build the `run_shell` tool — a {@link ToolContract} that runs a command\n * line through the policy-enforced {@link WorkspaceOps} layer.\n *\n * Every command in the line is checked against the shell policy by\n * `ops.exec`; a refused line throws a `WorkspacePolicyError` (with a\n * `reason` and a message that says how to fix the command) which the\n * `tool()` runtime catches and surfaces in the result's `error` field\n * (never a thrown run-killer), so the agent reads the refusal as tool data\n * and self-corrects. A command that runs but exits non-zero is *not* an\n * error — its `exitCode`/`stderr` come back in `data` for the agent to\n * inspect.\n *\n * The agent run's cancellation `signal` (from the dispatch `ctx`) is passed\n * to `ops.exec` per invocation: a cancelled run never starts the command,\n * and one cancelled mid-command kills its process tree (`cancelled: true`).\n * A background command (`background: true`, when enabled) belongs to the\n * workspace instead and keeps running after the call returns.\n *\n * @param ops - The policy-enforced operation layer to delegate `exec` to.\n * @param options - Tool-name override, timeout cap and background support.\n *\n * @example\n * const runShell = makeRunShellTool(ops);\n * const { data, error } = await runShell.invoke({ command: \"npm run build\" });\n * if (error) handleDenied(error);\n * else console.log(data.exitCode, data.stdout);\n */\nexport function makeRunShellTool(\n ops: WorkspaceOps,\n options?: MakeRunShellToolOptions & { background?: false },\n): ToolContract<RunShellInput, RunShellResult>;\nexport function makeRunShellTool(\n ops: WorkspaceOps,\n options: MakeRunShellToolOptions & { background: true },\n): ToolContract<RunShellInput, RunShellResult | RunShellBackgroundResult>;\nexport function makeRunShellTool(\n ops: WorkspaceOps,\n options?: MakeRunShellToolOptions,\n): ToolContract<RunShellInput, RunShellResult | RunShellBackgroundResult>;\nexport function makeRunShellTool(\n ops: WorkspaceOps,\n options?: MakeRunShellToolOptions,\n): ToolContract<RunShellInput, RunShellResult | RunShellBackgroundResult> {\n const background = options?.background === true;\n\n return tool<RunShellInput, RunShellResult | RunShellBackgroundResult>({\n name: options?.name ?? DEFAULT_RUN_SHELL_TOOL_NAME,\n description:\n \"Run a shell command inside the workspace. Every command in it must be \" +\n \"permitted by the shell policy; a refusal explains how to fix the \" +\n \"command. Output is byte-capped and the run is time-limited. A \" +\n \"non-zero exit code is returned as data, not an error.\" +\n shellSentence(options) +\n (background\n ? \" Pass background: true for a long-running command (dev server, \" +\n \"watcher): it returns an id at once; read its output with \" +\n \"shell_output and stop it with shell_kill.\"\n : \"\"),\n action: (input) => `Running \\`${input.command}\\``,\n input: runShellInputSchema(background),\n // The run's signal belongs to this one invocation: it is read from the\n // dispatch ctx on every call and never stored on the tool or workspace.\n execute: async (input, ctx) => {\n if (input.background === true) {\n if (!background) {\n throw new WorkspacePolicyError(\n \"Background commands are not enabled for this workspace; run the command without background.\",\n { type: \"denied-command\", reason: \"not-allowed\", command: input.command },\n );\n }\n\n const job = await ops.startShell(\n input.command,\n input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs } : undefined,\n );\n\n return { ...job, background: true };\n }\n\n const timeoutMs = cappedTimeout(input.timeoutMs, options?.maxTimeoutMs);\n\n return ops.exec(input.command, {\n ...(timeoutMs !== undefined ? { timeoutMs } : {}),\n ...(ctx?.signal ? { signal: ctx.signal } : {}),\n });\n },\n });\n}\n"],"mappings":";;;;;;;AAaA,MAAM,8BAA8B;;;;;;;;;AAUpC,SAAS,oBAAoB,YAAsD;CAgDjF,OAAO,eAAe,EA9CpB,aAAa;EACX,SAAS;EACT,QAAQ;EACR,WAAW,UAAU;GACnB,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,qBAAqB,CAAC,EAAE;GAGvD,MAAM,YAAY;GAElB,IAAI,OAAO,UAAU,YAAY,YAAY,UAAU,QAAQ,WAAW,GACxE,OAAO,EACL,QAAQ,CAAC;IAAE,SAAS;IAAsC,MAAM,CAAC,SAAS;GAAE,CAAC,EAC/E;GAGF,IACE,UAAU,cAAc,WACvB,OAAO,UAAU,cAAc,YAAY,UAAU,aAAa,IAEnE,OAAO,EACL,QAAQ,CAAC;IAAE,SAAS;IAAuC,MAAM,CAAC,WAAW;GAAE,CAAC,EAClF;GAGF,IAAI,UAAU,eAAe,UAAa,OAAO,UAAU,eAAe,WACxE,OAAO,EACL,QAAQ,CAAC;IAAE,SAAS;IAAgC,MAAM,CAAC,YAAY;GAAE,CAAC,EAC5E;GAGF,MAAM,SAAwB,EAAE,SAAS,UAAU,QAAQ;GAE3D,IAAI,OAAO,UAAU,cAAc,UACjC,OAAO,YAAY,UAAU;GAG/B,IAAI,UAAU,eAAe,MAC3B,OAAO,aAAa;GAGtB,OAAO,EAAE,OAAO,OAAO;EACzB;CACF,EAGyB,GAAG,mBAAmB,UAAU,CAAC;AAC9D;AA+BA,MAAM,gBAAgE;CACpE,OACE;CAGF,KACE;CAEF,YACE;AAIJ;AAEA,MAAM,iBAA2D;CAC/D,OAAO;CACP,QAAQ;CACR,OAAO;AACT;;AAGA,SAAS,cAAc,SAAsD;CAC3E,MAAM,UAAU,SAAS;CAEzB,IAAI,SAAS,eAAe,UAAa,YAAY,UAAa,YAAY,QAC5E,OAAO;CAGT,MAAM,WAAW,QAAQ,YAAY,QAAQ;CAE7C,OACE,oBAAoB,eAAe,aAAa,SAAS,SAAS,QAAQ,WAAW,MACrF,cAAc;AAElB;;AAGA,SAAS,cAAc,WAA+B,KAA6C;CACjG,IAAI,cAAc,QAChB,OAAO;CAGT,OAAO,QAAQ,SAAY,YAAY,KAAK,IAAI,WAAW,GAAG;AAChE;AA0CA,SAAgB,iBACd,KACA,SACwE;CACxE,MAAM,aAAa,SAAS,eAAe;CAE3C,OAAO,KAA+D;EACpE,MAAM,SAAS,QAAQ;EACvB,aACE,+PAIA,cAAc,OAAO,KACpB,aACG,sKAGA;EACN,SAAS,UAAU,aAAa,MAAM,QAAQ;EAC9C,OAAO,oBAAoB,UAAU;EAGrC,SAAS,OAAO,OAAO,QAAQ;GAC7B,IAAI,MAAM,eAAe,MAAM;IAC7B,IAAI,CAAC,YACH,MAAM,IAAI,qBACR,+FACA;KAAE,MAAM;KAAkB,QAAQ;KAAe,SAAS,MAAM;IAAQ,CAC1E;IAQF,OAAO;KAAE,GAAG,MALM,IAAI,WACpB,MAAM,SACN,MAAM,cAAc,SAAY,EAAE,WAAW,MAAM,UAAU,IAAI,MACnE;KAEiB,YAAY;IAAK;GACpC;GAEA,MAAM,YAAY,cAAc,MAAM,WAAW,SAAS,YAAY;GAEtE,OAAO,IAAI,KAAK,MAAM,SAAS;IAC7B,GAAI,cAAc,SAAY,EAAE,UAAU,IAAI,CAAC;IAC/C,GAAI,KAAK,SAAS,EAAE,QAAQ,IAAI,OAAO,IAAI,CAAC;GAC9C,CAAC;EACH;CACF,CAAC;AACH"}
|
|
1
|
+
{"version":3,"file":"run-shell.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/tools/run-shell.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { WorkspacePolicyError } from \"../errors\";\nimport type {\n RunShellBackgroundResult,\n RunShellInput,\n RunShellResult,\n WorkspaceOps,\n} from \"../contracts\";\nimport { runShellJsonSchema } from \"./json-schemas\";\nimport { withJsonSchema } from \"./schema\";\n\n/** The default tool name `run_shell` is exposed to the LLM under. */\nconst DEFAULT_RUN_SHELL_TOOL_NAME = \"run_shell\";\n\n/**\n * Hand-rolled Standard Schema for {@link RunShellInput}. We validate the\n * model's arguments without a runtime schema dependency: `command` must be\n * a non-empty string, `timeoutMs` (when present) a positive number and\n * `background` (when present) a boolean. Invalid args surface as a\n * `SchemaValidationError` in the tool result's `error` field rather than\n * reaching `ops.exec`.\n */\nfunction runShellInputSchema(background: boolean): StandardSchemaV1<RunShellInput> {\n const schema: StandardSchemaV1<RunShellInput> = {\n \"~standard\": {\n version: 1,\n vendor: \"@warlock.js/ai-workspace\",\n validate: (value) => {\n if (typeof value !== \"object\" || value === null) {\n return { issues: [{ message: \"expected an object\" }] };\n }\n\n const candidate = value as Record<string, unknown>;\n\n if (typeof candidate.command !== \"string\" || candidate.command.length === 0) {\n return {\n issues: [{ message: \"command must be a non-empty string\", path: [\"command\"] }],\n };\n }\n\n if (\n candidate.timeoutMs !== undefined &&\n (typeof candidate.timeoutMs !== \"number\" || candidate.timeoutMs <= 0)\n ) {\n return {\n issues: [{ message: \"timeoutMs must be a positive number\", path: [\"timeoutMs\"] }],\n };\n }\n\n if (candidate.background !== undefined && typeof candidate.background !== \"boolean\") {\n return {\n issues: [{ message: \"background must be a boolean\", path: [\"background\"] }],\n };\n }\n\n const result: RunShellInput = { command: candidate.command };\n\n if (typeof candidate.timeoutMs === \"number\") {\n result.timeoutMs = candidate.timeoutMs;\n }\n\n if (candidate.background === true) {\n result.background = true;\n }\n\n return { value: result };\n },\n },\n };\n\n return withJsonSchema(schema, runShellJsonSchema(background));\n}\n\n/** Options for {@link makeRunShellTool}. */\nexport interface MakeRunShellToolOptions {\n /** Override the tool name exposed to the LLM (default `\"run_shell\"`). */\n name?: string;\n /**\n * Upper bound for a model-supplied `timeoutMs` on a foreground command —\n * the model can lower the timeout, never raise it past this. The\n * workspace passes its `shell.timeoutMs`.\n */\n maxTimeoutMs?: number;\n /**\n * Accept `background: true` and start the command as a background job\n * (`ops.startShell`), returning a {@link RunShellBackgroundResult}. The\n * workspace sets it when the policy has `shell.background`. Default\n * `false`: a background request is refused.\n */\n background?: boolean;\n /**\n * The name of the shell lines run in (for example `bash` or\n * `PowerShell 7 (pwsh)`). When set, the description tells the model which\n * shell and syntax rules apply. Absent ⇒ argv mode, no shell.\n */\n shellLabel?: string;\n /** The grammar of that shell; selects the syntax rules in the description. */\n shellDialect?: \"posix\" | \"cmd\" | \"powershell\" | \"powershell5\" | \"none\";\n /** The operating system named in the description. Default: the running one. */\n platform?: NodeJS.Platform;\n}\n\nconst DIALECT_RULES: Record<\"posix\" | \"cmd\" | \"powershell\" | \"powershell5\", string> = {\n posix:\n \"Use POSIX shell syntax: `;`, `&&`, `||`, pipes, quotes, `$( )` and redirections are supported, \" +\n \"and every command in the line is checked. Compound statements (`if`, `for`), brace expansion, \" +\n \"arithmetic and `&` are not.\",\n cmd:\n \"Use cmd.exe syntax: `&&`, `||`, `&`, `|` and simple redirections are supported, and every command \" +\n \"in the line is checked. `%` and `!` expansion, `^`, parentheses and `if` / `for` are not.\",\n powershell:\n \"Use PowerShell syntax: `;`, `&&`, `||`, pipes, `$( )`, the call operator `&` and the redirections \" +\n \"`>`, `>>`, `2>` and `2>&1` are supported, and every command in the line is checked (aliases such as \" +\n \"`rm` and `Remove-Item` are treated alike). Script blocks, `Invoke-Expression`, here-strings, \" +\n \"splatting and `if` / `foreach` are not.\",\n powershell5:\n \"Use Windows PowerShell 5.1 syntax: `;`, pipes, `$( )`, the call operator `&` and the redirections \" +\n \"`>`, `>>`, `2>` and `2>&1` are supported, and every command in the line is checked (aliases such as \" +\n \"`rm` and `Remove-Item` are treated alike). This version cannot chain with `&&` or `||`: put one \" +\n \"command per call, or join with `;` (which runs the next command whatever the exit code). Script \" +\n \"blocks, `Invoke-Expression`, here-strings, splatting and `if` / `foreach` are not supported.\",\n};\n\nconst PLATFORM_NAMES: Partial<Record<NodeJS.Platform, string>> = {\n win32: \"Windows\",\n darwin: \"macOS\",\n linux: \"Linux\",\n};\n\n/** The sentence that names the shell and its syntax, or `\"\"` in argv mode. */\nfunction shellSentence(options: MakeRunShellToolOptions | undefined): string {\n const dialect = options?.shellDialect;\n\n if (options?.shellLabel === undefined || dialect === undefined || dialect === \"none\") {\n return \"\";\n }\n\n const platform = options.platform ?? process.platform;\n\n return (\n ` Commands run on ${PLATFORM_NAMES[platform] ?? platform} using ${options.shellLabel}. ` +\n DIALECT_RULES[dialect]\n );\n}\n\n/** The lower of the model's timeout and the cap (either may be absent). */\nfunction cappedTimeout(requested: number | undefined, cap: number | undefined): number | undefined {\n if (requested === undefined) {\n return cap;\n }\n\n return cap === undefined ? requested : Math.min(requested, cap);\n}\n\n/**\n * Build the `run_shell` tool — a {@link ToolContract} that runs a command\n * line through the policy-enforced {@link WorkspaceOps} layer.\n *\n * Every command in the line is checked against the shell policy by\n * `ops.exec`; a refused line throws a `WorkspacePolicyError` (with a\n * `reason` and a message that says how to fix the command) which the\n * `tool()` runtime catches and surfaces in the result's `error` field\n * (never a thrown run-killer), so the agent reads the refusal as tool data\n * and self-corrects. A command that runs but exits non-zero is *not* an\n * error — its `exitCode`/`stderr` come back in `data` for the agent to\n * inspect.\n *\n * The agent run's cancellation `signal` (from the dispatch `ctx`) is passed\n * to `ops.exec` per invocation: a cancelled run never starts the command,\n * and one cancelled mid-command kills its process tree (`cancelled: true`).\n * A background command (`background: true`, when enabled) belongs to the\n * workspace instead and keeps running after the call returns.\n *\n * @param ops - The policy-enforced operation layer to delegate `exec` to.\n * @param options - Tool-name override, timeout cap and background support.\n *\n * @example\n * const runShell = makeRunShellTool(ops);\n * const { data, error } = await runShell.invoke({ command: \"npm run build\" });\n * if (error) handleDenied(error);\n * else console.log(data.exitCode, data.stdout);\n */\nexport function makeRunShellTool(\n ops: WorkspaceOps,\n options?: MakeRunShellToolOptions & { background?: false },\n): ToolContract<RunShellInput, RunShellResult>;\nexport function makeRunShellTool(\n ops: WorkspaceOps,\n options: MakeRunShellToolOptions & { background: true },\n): ToolContract<RunShellInput, RunShellResult | RunShellBackgroundResult>;\nexport function makeRunShellTool(\n ops: WorkspaceOps,\n options?: MakeRunShellToolOptions,\n): ToolContract<RunShellInput, RunShellResult | RunShellBackgroundResult>;\nexport function makeRunShellTool(\n ops: WorkspaceOps,\n options?: MakeRunShellToolOptions,\n): ToolContract<RunShellInput, RunShellResult | RunShellBackgroundResult> {\n const background = options?.background === true;\n\n return tool<RunShellInput, RunShellResult | RunShellBackgroundResult>({\n name: options?.name ?? DEFAULT_RUN_SHELL_TOOL_NAME,\n description:\n \"Run a shell command inside the workspace. Every command in it must be \" +\n \"permitted by the shell policy; a refusal explains how to fix the \" +\n \"command. Output is byte-capped and the run is time-limited. A \" +\n \"non-zero exit code is returned as data, not an error.\" +\n shellSentence(options) +\n (background\n ? \" Pass background: true for a long-running command (dev server, \" +\n \"watcher): it returns an id at once; read its output with \" +\n \"shell_output and stop it with shell_kill.\"\n : \"\"),\n action: (input) => `Running \\`${input.command}\\``,\n input: runShellInputSchema(background),\n // The run's signal belongs to this one invocation: it is read from the\n // dispatch ctx on every call and never stored on the tool or workspace.\n execute: async (input, ctx) => {\n if (input.background === true) {\n if (!background) {\n throw new WorkspacePolicyError(\n \"Background commands are not enabled for this workspace; run the command without background.\",\n { type: \"denied-command\", reason: \"not-allowed\", command: input.command },\n );\n }\n\n const job = await ops.startShell(\n input.command,\n input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs } : undefined,\n );\n\n return { ...job, background: true };\n }\n\n const timeoutMs = cappedTimeout(input.timeoutMs, options?.maxTimeoutMs);\n\n return ops.exec(input.command, {\n ...(timeoutMs !== undefined ? { timeoutMs } : {}),\n ...(ctx?.signal ? { signal: ctx.signal } : {}),\n });\n },\n });\n}\n"],"mappings":";;;;;;;AAaA,MAAM,8BAA8B;;;;;;;;;AAUpC,SAAS,oBAAoB,YAAsD;CAgDjF,OAAO,eAAe,EA9CpB,aAAa;EACX,SAAS;EACT,QAAQ;EACR,WAAW,UAAU;GACnB,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,qBAAqB,CAAC,EAAE;GAGvD,MAAM,YAAY;GAElB,IAAI,OAAO,UAAU,YAAY,YAAY,UAAU,QAAQ,WAAW,GACxE,OAAO,EACL,QAAQ,CAAC;IAAE,SAAS;IAAsC,MAAM,CAAC,SAAS;GAAE,CAAC,EAC/E;GAGF,IACE,UAAU,cAAc,WACvB,OAAO,UAAU,cAAc,YAAY,UAAU,aAAa,IAEnE,OAAO,EACL,QAAQ,CAAC;IAAE,SAAS;IAAuC,MAAM,CAAC,WAAW;GAAE,CAAC,EAClF;GAGF,IAAI,UAAU,eAAe,UAAa,OAAO,UAAU,eAAe,WACxE,OAAO,EACL,QAAQ,CAAC;IAAE,SAAS;IAAgC,MAAM,CAAC,YAAY;GAAE,CAAC,EAC5E;GAGF,MAAM,SAAwB,EAAE,SAAS,UAAU,QAAQ;GAE3D,IAAI,OAAO,UAAU,cAAc,UACjC,OAAO,YAAY,UAAU;GAG/B,IAAI,UAAU,eAAe,MAC3B,OAAO,aAAa;GAGtB,OAAO,EAAE,OAAO,OAAO;EACzB;CACF,EAGyB,GAAG,mBAAmB,UAAU,CAAC;AAC9D;AA+BA,MAAM,gBAAgF;CACpF,OACE;CAGF,KACE;CAEF,YACE;CAIF,aACE;AAKJ;AAEA,MAAM,iBAA2D;CAC/D,OAAO;CACP,QAAQ;CACR,OAAO;AACT;;AAGA,SAAS,cAAc,SAAsD;CAC3E,MAAM,UAAU,SAAS;CAEzB,IAAI,SAAS,eAAe,UAAa,YAAY,UAAa,YAAY,QAC5E,OAAO;CAGT,MAAM,WAAW,QAAQ,YAAY,QAAQ;CAE7C,OACE,oBAAoB,eAAe,aAAa,SAAS,SAAS,QAAQ,WAAW,MACrF,cAAc;AAElB;;AAGA,SAAS,cAAc,WAA+B,KAA6C;CACjG,IAAI,cAAc,QAChB,OAAO;CAGT,OAAO,QAAQ,SAAY,YAAY,KAAK,IAAI,WAAW,GAAG;AAChE;AA0CA,SAAgB,iBACd,KACA,SACwE;CACxE,MAAM,aAAa,SAAS,eAAe;CAE3C,OAAO,KAA+D;EACpE,MAAM,SAAS,QAAQ;EACvB,aACE,+PAIA,cAAc,OAAO,KACpB,aACG,sKAGA;EACN,SAAS,UAAU,aAAa,MAAM,QAAQ;EAC9C,OAAO,oBAAoB,UAAU;EAGrC,SAAS,OAAO,OAAO,QAAQ;GAC7B,IAAI,MAAM,eAAe,MAAM;IAC7B,IAAI,CAAC,YACH,MAAM,IAAI,qBACR,+FACA;KAAE,MAAM;KAAkB,QAAQ;KAAe,SAAS,MAAM;IAAQ,CAC1E;IAQF,OAAO;KAAE,GAAG,MALM,IAAI,WACpB,MAAM,SACN,MAAM,cAAc,SAAY,EAAE,WAAW,MAAM,UAAU,IAAI,MACnE;KAEiB,YAAY;IAAK;GACpC;GAEA,MAAM,YAAY,cAAc,MAAM,WAAW,SAAS,YAAY;GAEtE,OAAO,IAAI,KAAK,MAAM,SAAS;IAC7B,GAAI,cAAc,SAAY,EAAE,UAAU,IAAI,CAAC;IAC/C,GAAI,KAAK,SAAS,EAAE,QAAQ,IAAI,OAAO,IAAI,CAAC;GAC9C,CAAC;EACH;CACF,CAAC;AACH"}
|
|
@@ -25,7 +25,7 @@ export interface MakeRunTestsToolOptions {
|
|
|
25
25
|
* The grammar of the shell the line runs in; with `interpreter` it picks
|
|
26
26
|
* the quoting: single quotes (doubled inside) for PowerShell.
|
|
27
27
|
*/
|
|
28
|
-
dialect?: "posix" | "cmd" | "powershell";
|
|
28
|
+
dialect?: "posix" | "cmd" | "powershell" | "powershell5";
|
|
29
29
|
/** The platform quoting is chosen for. Default: the running one. */
|
|
30
30
|
platform?: NodeJS.Platform;
|
|
31
31
|
}
|