@warlock.js/ai-workspace 5.43.0 → 5.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +14 -1
  3. package/cjs/index.cjs +1781 -141
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/backends/local.d.mts.map +1 -1
  6. package/esm/backends/local.mjs +36 -22
  7. package/esm/backends/local.mjs.map +1 -1
  8. package/esm/contracts/index.d.mts +5 -1
  9. package/esm/contracts/shell-approval.type.d.mts +78 -0
  10. package/esm/contracts/shell-approval.type.d.mts.map +1 -0
  11. package/esm/contracts/tool-io.type.d.mts +35 -1
  12. package/esm/contracts/tool-io.type.d.mts.map +1 -1
  13. package/esm/contracts/workspace-backend.contract.d.mts +11 -1
  14. package/esm/contracts/workspace-backend.contract.d.mts.map +1 -1
  15. package/esm/contracts/workspace-edit.type.d.mts +66 -0
  16. package/esm/contracts/workspace-edit.type.d.mts.map +1 -0
  17. package/esm/contracts/workspace-ops.contract.d.mts +7 -1
  18. package/esm/contracts/workspace-ops.contract.d.mts.map +1 -1
  19. package/esm/contracts/workspace-output.type.d.mts +57 -0
  20. package/esm/contracts/workspace-output.type.d.mts.map +1 -0
  21. package/esm/contracts/workspace-policy.type.d.mts +38 -1
  22. package/esm/contracts/workspace-policy.type.d.mts.map +1 -1
  23. package/esm/contracts/workspace.contract.d.mts +11 -0
  24. package/esm/contracts/workspace.contract.d.mts.map +1 -1
  25. package/esm/edit/apply-replacement.mjs +58 -0
  26. package/esm/edit/apply-replacement.mjs.map +1 -0
  27. package/esm/edit/match.d.mts +16 -0
  28. package/esm/edit/match.d.mts.map +1 -0
  29. package/esm/edit/match.mjs +214 -0
  30. package/esm/edit/match.mjs.map +1 -0
  31. package/esm/errors.d.mts +12 -1
  32. package/esm/errors.d.mts.map +1 -1
  33. package/esm/errors.mjs +1 -0
  34. package/esm/errors.mjs.map +1 -1
  35. package/esm/hooks/after-edit.mjs +36 -0
  36. package/esm/hooks/after-edit.mjs.map +1 -0
  37. package/esm/index.d.mts +7 -2
  38. package/esm/index.mjs +3 -2
  39. package/esm/ops.d.mts +3 -1
  40. package/esm/ops.d.mts.map +1 -1
  41. package/esm/ops.mjs +196 -75
  42. package/esm/ops.mjs.map +1 -1
  43. package/esm/output/spill-dir.mjs +15 -0
  44. package/esm/output/spill-dir.mjs.map +1 -0
  45. package/esm/output/spill.mjs +220 -0
  46. package/esm/output/spill.mjs.map +1 -0
  47. package/esm/policy/policy.d.mts +3 -1
  48. package/esm/policy/policy.d.mts.map +1 -1
  49. package/esm/policy/policy.mjs +12 -4
  50. package/esm/policy/policy.mjs.map +1 -1
  51. package/esm/policy/powershell-aliases.mjs +275 -0
  52. package/esm/policy/powershell-aliases.mjs.map +1 -0
  53. package/esm/policy/powershell-parser.mjs +418 -0
  54. package/esm/policy/powershell-parser.mjs.map +1 -0
  55. package/esm/policy/shell-check.d.mts +32 -2
  56. package/esm/policy/shell-check.d.mts.map +1 -1
  57. package/esm/policy/shell-check.mjs +183 -31
  58. package/esm/policy/shell-check.mjs.map +1 -1
  59. package/esm/policy/shell-interpreter.d.mts +45 -0
  60. package/esm/policy/shell-interpreter.d.mts.map +1 -0
  61. package/esm/policy/shell-interpreter.mjs +109 -0
  62. package/esm/policy/shell-interpreter.mjs.map +1 -0
  63. package/esm/policy/shell-parser.d.mts +31 -0
  64. package/esm/policy/shell-parser.d.mts.map +1 -0
  65. package/esm/policy/shell-parser.mjs +3 -0
  66. package/esm/policy/shell-parser.mjs.map +1 -1
  67. package/esm/tools/glob.mjs +1 -0
  68. package/esm/tools/glob.mjs.map +1 -1
  69. package/esm/tools/run-shell.d.mts +10 -0
  70. package/esm/tools/run-shell.d.mts.map +1 -1
  71. package/esm/tools/run-shell.mjs +18 -1
  72. package/esm/tools/run-shell.mjs.map +1 -1
  73. package/esm/tools/run-tests.d.mts +7 -0
  74. package/esm/tools/run-tests.d.mts.map +1 -1
  75. package/esm/tools/run-tests.mjs +5 -4
  76. package/esm/tools/run-tests.mjs.map +1 -1
  77. package/esm/tools/write-file.mjs +3 -2
  78. package/esm/tools/write-file.mjs.map +1 -1
  79. package/esm/workspace.d.mts.map +1 -1
  80. package/esm/workspace.mjs +16 -5
  81. package/esm/workspace.mjs.map +1 -1
  82. package/llms-full.txt +64 -17
  83. package/package.json +3 -3
  84. package/skills/use-a-workspace/SKILL.md +64 -17
@@ -0,0 +1,418 @@
1
+ //#region ../../packages/ai-workspace/src/policy/powershell-parser.ts
2
+ /**
3
+ * Parser for the PowerShell dialect (`powershell -Command` / `pwsh -Command`).
4
+ *
5
+ * Like the other dialects it is deliberately conservative: it finds every
6
+ * simple command a line would run and every redirection target, and refuses
7
+ * what it cannot read with confidence. Supported: `;` and line breaks, `&&`,
8
+ * `||`, `|`, `$( )` subexpressions (their commands are checked too), the call
9
+ * operator `&` in front of a command, single and double
10
+ * quotes (including the typographic quotes PowerShell treats as quotes),
11
+ * backtick escapes, `$name` / `${name}` / `$env:NAME` variables (dynamic
12
+ * words), and the redirections `>`, `>>`, `2>`, `2>>`, `*>`, `2>&1`.
13
+ *
14
+ * Refused: script blocks and braces, bare parentheses,
15
+ * `@( )` / `@{ }` / splatting / here-strings, the stop-parsing token `--%`,
16
+ * `<` redirection, comments, the comma operator, compound-statement
17
+ * keywords (`if`, `foreach`, `function`, `try`, …), dot-sourcing, a `&` that
18
+ * is not a call operator (background jobs), and type literals.
19
+ */
20
+ /** Statement keywords that start a compound command; refused in command position. */
21
+ const POWERSHELL_KEYWORDS = /* @__PURE__ */ new Set([
22
+ "if",
23
+ "elseif",
24
+ "else",
25
+ "switch",
26
+ "foreach",
27
+ "for",
28
+ "while",
29
+ "do",
30
+ "until",
31
+ "function",
32
+ "filter",
33
+ "workflow",
34
+ "param",
35
+ "begin",
36
+ "process",
37
+ "end",
38
+ "try",
39
+ "catch",
40
+ "finally",
41
+ "trap",
42
+ "data",
43
+ "class",
44
+ "enum",
45
+ "using",
46
+ "return",
47
+ "break",
48
+ "continue",
49
+ "throw",
50
+ "parallel",
51
+ "sequence",
52
+ "inlinescript"
53
+ ]);
54
+ /** Characters PowerShell reads as a single quote. */
55
+ const SINGLE_QUOTES = "'‘’‚‛";
56
+ /** Characters PowerShell reads as a double quote. */
57
+ const DOUBLE_QUOTES = "\"“”„";
58
+ /** Variable-name characters after `$`. */
59
+ const VARIABLE_CHAR = /[A-Za-z0-9_:?^$]/;
60
+ /** Whether `char` (never the empty end-of-input string) is one of `set`. */
61
+ function inSet(set, char) {
62
+ return char !== "" && set.includes(char);
63
+ }
64
+ var PowerShellRefusal = class extends Error {
65
+ constructor(reason, message) {
66
+ super(message);
67
+ this.reason = reason;
68
+ }
69
+ };
70
+ function unsupported(message) {
71
+ return new PowerShellRefusal("unsupported-syntax", message);
72
+ }
73
+ var PowerShellParser = class PowerShellParser {
74
+ constructor(src, commands = [], depth = 0) {
75
+ this.src = src;
76
+ this.commands = commands;
77
+ this.depth = depth;
78
+ this.pos = 0;
79
+ }
80
+ atEnd() {
81
+ return this.pos >= this.src.length;
82
+ }
83
+ peek(offset = 0) {
84
+ return this.src.charAt(this.pos + offset);
85
+ }
86
+ skipBlanks() {
87
+ while (this.peek() === " " || this.peek() === " ") this.pos++;
88
+ }
89
+ atLineBreak() {
90
+ return this.peek() === "\n" || this.peek() === "\r";
91
+ }
92
+ parseProgram(segments) {
93
+ while (true) {
94
+ this.skipBlanks();
95
+ if (this.atEnd()) {
96
+ if (segments.length === 0) throw unsupported("The command is empty.");
97
+ if (segments[segments.length - 1]?.operator === "&&" || segments[segments.length - 1]?.operator === "||") throw unsupported("The line ends with an operator but no command follows it.");
98
+ return;
99
+ }
100
+ const start = this.pos;
101
+ this.parsePipeline();
102
+ const end = this.pos;
103
+ this.skipBlanks();
104
+ const operator = this.readListOperator();
105
+ segments.push({
106
+ source: this.src.slice(start, end).trim(),
107
+ ...operator !== null ? { operator } : {}
108
+ });
109
+ if (operator === null) {
110
+ if (!this.atEnd()) throw unsupported(`Unexpected \`${this.peek()}\`.`);
111
+ return;
112
+ }
113
+ if (operator === ";" || operator === "\n") while (this.peek() === " " || this.peek() === " " || this.atLineBreak()) this.pos++;
114
+ else this.skipBlanks();
115
+ }
116
+ }
117
+ readListOperator() {
118
+ const char = this.peek();
119
+ if (char === "&" && this.peek(1) === "&") {
120
+ this.pos += 2;
121
+ return "&&";
122
+ }
123
+ if (char === "|" && this.peek(1) === "|") {
124
+ this.pos += 2;
125
+ return "||";
126
+ }
127
+ if (char === ";") {
128
+ this.pos++;
129
+ return ";";
130
+ }
131
+ if (this.atLineBreak()) {
132
+ while (this.atLineBreak()) this.pos++;
133
+ return "\n";
134
+ }
135
+ return null;
136
+ }
137
+ parsePipeline() {
138
+ this.parseCommand();
139
+ while (this.peek() === "|" && this.peek(1) !== "|") {
140
+ this.pos++;
141
+ this.skipBlanks();
142
+ if (this.atEnd() || this.atLineBreak() || this.peek() === ";") throw unsupported("The line ends with `|` but no command follows it.");
143
+ this.parseCommand();
144
+ }
145
+ }
146
+ atRedirectStart() {
147
+ const char = this.peek();
148
+ if (char === ">") return true;
149
+ return /[1-6*]/.test(char) && this.peek(1) === ">";
150
+ }
151
+ parseCommand() {
152
+ const start = this.pos;
153
+ const command = {
154
+ assignments: [],
155
+ words: [],
156
+ redirects: [],
157
+ source: ""
158
+ };
159
+ let callOperator = false;
160
+ if (this.peek() === "&" && this.peek(1) !== "&") {
161
+ callOperator = true;
162
+ this.pos++;
163
+ this.skipBlanks();
164
+ }
165
+ while (true) {
166
+ this.skipBlanks();
167
+ if (this.atEnd() || this.atLineBreak() || this.peek() === ";" || this.peek() === "|") break;
168
+ if (this.peek() === "&") {
169
+ if (this.peek(1) === "&") break;
170
+ throw unsupported("`&` is only supported as the call operator in front of a command (not as a background job).");
171
+ }
172
+ if (this.peek() === "<") throw unsupported("`<` is not supported in PowerShell command lines.");
173
+ if (this.atRedirectStart()) {
174
+ command.redirects.push(this.parseRedirect());
175
+ continue;
176
+ }
177
+ const word = this.parseWord();
178
+ command.words.push(word);
179
+ if (command.words.length === 1) this.checkCommandWord(word);
180
+ }
181
+ if (command.words.length === 0 && (callOperator || command.redirects.length === 0)) throw unsupported("The line has an empty command (two operators in a row?).");
182
+ command.source = this.src.slice(start, this.pos).trim();
183
+ this.commands.push(command);
184
+ }
185
+ checkCommandWord(word) {
186
+ if (word.raw.startsWith("[")) throw unsupported(`\`${word.raw}\` (a type literal) is not supported.`);
187
+ if (!word.quoted && word.value === ".") throw unsupported("Dot-sourcing (`. file`) runs a script in the current scope and is not supported.");
188
+ if (!word.quoted && POWERSHELL_KEYWORDS.has(word.value.toLowerCase())) throw unsupported(`\`${word.value}\` starts a PowerShell statement, which is not supported.`);
189
+ }
190
+ parseRedirect() {
191
+ let fd;
192
+ if (this.peek() === "*") this.pos++;
193
+ else if (/[1-6]/.test(this.peek())) {
194
+ fd = Number(this.peek());
195
+ this.pos++;
196
+ }
197
+ let op = ">";
198
+ if (this.peek(1) === ">") op = ">>";
199
+ this.pos += op.length;
200
+ if (op === ">" && this.peek() === "&") {
201
+ if (/[1-6]/.test(this.peek(1))) {
202
+ const target = {
203
+ raw: this.peek(1),
204
+ value: this.peek(1),
205
+ dynamic: false,
206
+ quoted: false
207
+ };
208
+ this.pos += 2;
209
+ return {
210
+ op: ">&",
211
+ ...fd !== void 0 ? { fd } : {},
212
+ target,
213
+ type: "dup"
214
+ };
215
+ }
216
+ throw unsupported("Only `>&1`-style stream merges (`2>&1`) are supported.");
217
+ }
218
+ this.skipBlanks();
219
+ if (this.atEnd() || this.atLineBreak() || /[;&|<>]/.test(this.peek())) throw unsupported(`The redirection \`${op}\` has no target.`);
220
+ const target = this.parseWord();
221
+ return {
222
+ op,
223
+ ...fd !== void 0 ? { fd } : {},
224
+ target,
225
+ type: "write"
226
+ };
227
+ }
228
+ parseWord() {
229
+ const start = this.pos;
230
+ let value = "";
231
+ let dynamic = false;
232
+ let quoted = false;
233
+ if (this.src.startsWith("--%", this.pos) && /[\s;|&]|^$/.test(this.src.charAt(this.pos + 3))) throw unsupported("The stop-parsing token `--%` is not supported.");
234
+ while (!this.atEnd()) {
235
+ const char = this.peek();
236
+ if (char === " " || char === " " || char === "\n" || char === "\r" || char === ";" || char === "|" || char === "&" || char === ">" || char === "<") break;
237
+ if (inSet(SINGLE_QUOTES, char)) {
238
+ this.pos++;
239
+ quoted = true;
240
+ while (true) {
241
+ if (this.atEnd()) throw new PowerShellRefusal("unbalanced-quotes", "A single quote is never closed.");
242
+ const inner = this.peek();
243
+ if (inSet(SINGLE_QUOTES, inner)) {
244
+ if (inSet(SINGLE_QUOTES, this.peek(1))) {
245
+ value += this.peek(1);
246
+ this.pos += 2;
247
+ continue;
248
+ }
249
+ this.pos++;
250
+ break;
251
+ }
252
+ value += inner;
253
+ this.pos++;
254
+ }
255
+ continue;
256
+ }
257
+ if (inSet(DOUBLE_QUOTES, char)) {
258
+ this.pos++;
259
+ quoted = true;
260
+ while (true) {
261
+ if (this.atEnd()) throw new PowerShellRefusal("unbalanced-quotes", "A double quote is never closed.");
262
+ const inner = this.peek();
263
+ if (inSet(DOUBLE_QUOTES, inner)) {
264
+ if (inSet(DOUBLE_QUOTES, this.peek(1))) {
265
+ value += this.peek(1);
266
+ this.pos += 2;
267
+ continue;
268
+ }
269
+ this.pos++;
270
+ break;
271
+ }
272
+ if (inner === "`") {
273
+ const next = this.peek(1);
274
+ if (next !== "" && (inSet(DOUBLE_QUOTES, next) || next === "`" || next === "$")) {
275
+ value += next;
276
+ this.pos += 2;
277
+ continue;
278
+ }
279
+ throw unsupported("Only `\"`, `` ` `` and `$` can follow a backtick inside double quotes.");
280
+ }
281
+ if (inner === "$") {
282
+ const read = this.readVariable();
283
+ value += read;
284
+ dynamic = true;
285
+ continue;
286
+ }
287
+ value += inner;
288
+ this.pos++;
289
+ }
290
+ continue;
291
+ }
292
+ if (char === "`") {
293
+ const next = this.peek(1);
294
+ if (next === "" || next === "\n" || next === "\r") throw unsupported("A backtick at the end of a line (line continuation) is not supported.");
295
+ value += next;
296
+ quoted = true;
297
+ this.pos += 2;
298
+ continue;
299
+ }
300
+ if (char === "$") {
301
+ const wasLiteral = this.src.charAt(this.pos + 1);
302
+ if (wasLiteral === "" || !(wasLiteral === "{" || wasLiteral === "(" || VARIABLE_CHAR.test(wasLiteral))) {
303
+ value += "$";
304
+ this.pos++;
305
+ continue;
306
+ }
307
+ value += this.readVariable();
308
+ dynamic = true;
309
+ continue;
310
+ }
311
+ if (char === "@" && this.pos === start) {
312
+ const next = this.peek(1);
313
+ if (next === "(" || next === "{" || next === "'" || next === "\"" || /[A-Za-z_$]/.test(next)) throw unsupported("Arrays (`@( )`), hashtables, splatting (`@name`) and here-strings are not supported.");
314
+ }
315
+ if (char === "{" || char === "}") throw unsupported("Script blocks and braces are not supported.");
316
+ if (char === "(" || char === ")") throw unsupported("Parentheses (grouping and `$( )` subexpressions) are not supported; quote the text.");
317
+ if (char === ",") throw unsupported("The comma operator builds an array and is not supported; quote the text.");
318
+ if (char === "#" && this.pos === start) throw unsupported("Comments are not supported in a command line.");
319
+ if (char === "*" || char === "?" || char === "[" || char === "~" && this.pos === start) dynamic = true;
320
+ value += char;
321
+ this.pos++;
322
+ }
323
+ return {
324
+ raw: this.src.slice(start, this.pos),
325
+ value,
326
+ dynamic,
327
+ quoted
328
+ };
329
+ }
330
+ /**
331
+ * Parse a `$( … )` subexpression: its commands run, so they are collected
332
+ * (before the command that contains it) and checked like any other.
333
+ */
334
+ parseSubexpression() {
335
+ if (this.depth >= 8) throw unsupported("`$( )` subexpressions are nested too deeply.");
336
+ const open = this.pos;
337
+ let level = 0;
338
+ let index = open;
339
+ for (; index < this.src.length; index++) {
340
+ const char = this.src.charAt(index);
341
+ if (inSet(SINGLE_QUOTES, char) || inSet(DOUBLE_QUOTES, char)) {
342
+ const set = inSet(SINGLE_QUOTES, char) ? SINGLE_QUOTES : DOUBLE_QUOTES;
343
+ index++;
344
+ while (index < this.src.length) {
345
+ const inner = this.src.charAt(index);
346
+ if (inner === "`" && set === DOUBLE_QUOTES) {
347
+ index += 2;
348
+ continue;
349
+ }
350
+ if (inSet(set, inner)) {
351
+ if (inSet(set, this.src.charAt(index + 1))) {
352
+ index += 2;
353
+ continue;
354
+ }
355
+ break;
356
+ }
357
+ index++;
358
+ }
359
+ continue;
360
+ }
361
+ if (char === "`") {
362
+ index++;
363
+ continue;
364
+ }
365
+ if (char === "(") level++;
366
+ else if (char === ")") {
367
+ level--;
368
+ if (level === 0) break;
369
+ }
370
+ }
371
+ if (level !== 0) throw new PowerShellRefusal("unbalanced-quotes", "A `$(` subexpression is never closed.");
372
+ const inner = this.src.slice(open + 1, index);
373
+ new PowerShellParser(inner, this.commands, this.depth + 1).parseProgram([]);
374
+ this.pos = index + 1;
375
+ }
376
+ /** Read `$name`, `${name}` or `$env:NAME` at the cursor; a `$(` is refused. */
377
+ readVariable() {
378
+ const begin = this.pos;
379
+ this.pos++;
380
+ if (this.peek() === "(") {
381
+ this.parseSubexpression();
382
+ return this.src.slice(begin, this.pos);
383
+ }
384
+ if (this.peek() === "{") {
385
+ const close = this.src.indexOf("}", this.pos);
386
+ if (close === -1) throw new PowerShellRefusal("unbalanced-quotes", "A `${` variable name is never closed.");
387
+ this.pos = close + 1;
388
+ return this.src.slice(begin, this.pos);
389
+ }
390
+ while (!this.atEnd() && VARIABLE_CHAR.test(this.peek())) this.pos++;
391
+ return this.src.slice(begin, this.pos);
392
+ }
393
+ };
394
+ /** Parse a PowerShell command line for the shell policy. Never throws. */
395
+ function parsePowerShellLine(line) {
396
+ const segments = [];
397
+ const parser = new PowerShellParser(line);
398
+ try {
399
+ parser.parseProgram(segments);
400
+ return {
401
+ ok: true,
402
+ commands: parser.commands,
403
+ segments,
404
+ background: false
405
+ };
406
+ } catch (error) {
407
+ if (error instanceof PowerShellRefusal) return {
408
+ ok: false,
409
+ reason: error.reason,
410
+ message: error.message
411
+ };
412
+ throw error;
413
+ }
414
+ }
415
+
416
+ //#endregion
417
+ export { parsePowerShellLine };
418
+ //# sourceMappingURL=powershell-parser.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"powershell-parser.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/policy/powershell-parser.ts"],"sourcesContent":["import type {\n ShellParseResult,\n ShellRedirect,\n ShellSegment,\n ShellSimpleCommand,\n ShellWord,\n} from \"./shell-parser\";\n\n/**\n * Parser for the PowerShell dialect (`powershell -Command` / `pwsh -Command`).\n *\n * Like the other dialects it is deliberately conservative: it finds every\n * simple command a line would run and every redirection target, and refuses\n * what it cannot read with confidence. Supported: `;` and line breaks, `&&`,\n * `||`, `|`, `$( )` subexpressions (their commands are checked too), the call\n * operator `&` in front of a command, single and double\n * quotes (including the typographic quotes PowerShell treats as quotes),\n * backtick escapes, `$name` / `${name}` / `$env:NAME` variables (dynamic\n * words), and the redirections `>`, `>>`, `2>`, `2>>`, `*>`, `2>&1`.\n *\n * Refused: script blocks and braces, bare parentheses,\n * `@( )` / `@{ }` / splatting / here-strings, the stop-parsing token `--%`,\n * `<` redirection, comments, the comma operator, compound-statement\n * keywords (`if`, `foreach`, `function`, `try`, …), dot-sourcing, a `&` that\n * is not a call operator (background jobs), and type literals.\n */\n\n/** Statement keywords that start a compound command; refused in command position. */\nconst POWERSHELL_KEYWORDS = new Set([\n \"if\",\n \"elseif\",\n \"else\",\n \"switch\",\n \"foreach\",\n \"for\",\n \"while\",\n \"do\",\n \"until\",\n \"function\",\n \"filter\",\n \"workflow\",\n \"param\",\n \"begin\",\n \"process\",\n \"end\",\n \"try\",\n \"catch\",\n \"finally\",\n \"trap\",\n \"data\",\n \"class\",\n \"enum\",\n \"using\",\n \"return\",\n \"break\",\n \"continue\",\n \"throw\",\n \"parallel\",\n \"sequence\",\n \"inlinescript\",\n]);\n\n/** Characters PowerShell reads as a single quote. */\nconst SINGLE_QUOTES = \"'‘’‚‛\";\n\n/** Characters PowerShell reads as a double quote. */\nconst DOUBLE_QUOTES = '\"“”„';\n\n/** Variable-name characters after `$`. */\nconst VARIABLE_CHAR = /[A-Za-z0-9_:?^$]/;\n\n/** Whether `char` (never the empty end-of-input string) is one of `set`. */\nfunction inSet(set: string, char: string): boolean {\n return char !== \"\" && set.includes(char);\n}\n\nclass PowerShellRefusal extends Error {\n public constructor(\n public readonly reason: \"unbalanced-quotes\" | \"unsupported-syntax\",\n message: string,\n ) {\n super(message);\n }\n}\n\nfunction unsupported(message: string): PowerShellRefusal {\n return new PowerShellRefusal(\"unsupported-syntax\", message);\n}\n\nclass PowerShellParser {\n private pos = 0;\n\n public constructor(\n private readonly src: string,\n public readonly commands: ShellSimpleCommand[] = [],\n private readonly depth = 0,\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 atLineBreak(): boolean {\n return this.peek() === \"\\n\" || this.peek() === \"\\r\";\n }\n\n public parseProgram(segments: ShellSegment[]): void {\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (this.atEnd()) {\n if (segments.length === 0) {\n throw unsupported(\"The command is empty.\");\n }\n\n if (\n segments[segments.length - 1]?.operator === \"&&\" ||\n segments[segments.length - 1]?.operator === \"||\"\n ) {\n throw unsupported(\"The line ends with an operator but no command follows it.\");\n }\n\n return;\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 if (operator === \";\" || operator === \"\\n\") {\n // Further separators and blanks are allowed after one; a trailing run is fine.\n while (this.peek() === \" \" || this.peek() === \"\\t\" || this.atLineBreak()) {\n this.pos++;\n }\n } else {\n this.skipBlanks();\n }\n }\n }\n\n private readListOperator(): string | null {\n const char = this.peek();\n\n if (char === \"&\" && this.peek(1) === \"&\") {\n this.pos += 2;\n\n return \"&&\";\n }\n\n if (char === \"|\" && this.peek(1) === \"|\") {\n this.pos += 2;\n\n return \"||\";\n }\n\n if (char === \";\") {\n this.pos++;\n\n return \";\";\n }\n\n if (this.atLineBreak()) {\n while (this.atLineBreak()) {\n this.pos++;\n }\n\n return \"\\n\";\n }\n\n return null;\n }\n\n private parsePipeline(): void {\n this.parseCommand();\n\n while (this.peek() === \"|\" && this.peek(1) !== \"|\") {\n this.pos++;\n this.skipBlanks();\n\n if (this.atEnd() || this.atLineBreak() || this.peek() === \";\") {\n throw unsupported(\"The line ends with `|` but no command follows it.\");\n }\n\n this.parseCommand();\n }\n }\n\n private atRedirectStart(): boolean {\n const char = this.peek();\n\n if (char === \">\") {\n return true;\n }\n\n return /[1-6*]/.test(char) && this.peek(1) === \">\";\n }\n\n private parseCommand(): void {\n const start = this.pos;\n const command: ShellSimpleCommand = { assignments: [], words: [], redirects: [], source: \"\" };\n let callOperator = false;\n\n if (this.peek() === \"&\" && this.peek(1) !== \"&\") {\n callOperator = true;\n this.pos++;\n this.skipBlanks();\n }\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (this.atEnd() || this.atLineBreak() || this.peek() === \";\" || this.peek() === \"|\") {\n break;\n }\n\n if (this.peek() === \"&\") {\n if (this.peek(1) === \"&\") {\n break;\n }\n\n throw unsupported(\n \"`&` is only supported as the call operator in front of a command (not as a background job).\",\n );\n }\n\n if (this.peek() === \"<\") {\n throw unsupported(\"`<` is not supported in PowerShell command lines.\");\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 && (callOperator || 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(\"[\")) {\n throw unsupported(`\\`${word.raw}\\` (a type literal) is not supported.`);\n }\n\n if (!word.quoted && word.value === \".\") {\n throw unsupported(\n \"Dot-sourcing (`. file`) runs a script in the current scope and is not supported.\",\n );\n }\n\n if (!word.quoted && POWERSHELL_KEYWORDS.has(word.value.toLowerCase())) {\n throw unsupported(`\\`${word.value}\\` starts a PowerShell statement, which is not supported.`);\n }\n }\n\n private parseRedirect(): ShellRedirect {\n let fd: number | undefined;\n\n if (this.peek() === \"*\") {\n this.pos++;\n } else if (/[1-6]/.test(this.peek())) {\n fd = Number(this.peek());\n this.pos++;\n }\n\n let op = \">\";\n\n if (this.peek(1) === \">\") {\n op = \">>\";\n }\n\n this.pos += op.length;\n\n if (op === \">\" && this.peek() === \"&\") {\n if (/[1-6]/.test(this.peek(1))) {\n const target: ShellWord = {\n raw: this.peek(1),\n value: this.peek(1),\n dynamic: false,\n quoted: false,\n };\n\n this.pos += 2;\n\n return { op: \">&\", ...(fd !== undefined ? { fd } : {}), target, type: \"dup\" };\n }\n\n throw unsupported(\"Only `>&1`-style stream merges (`2>&1`) are supported.\");\n }\n\n this.skipBlanks();\n\n if (this.atEnd() || this.atLineBreak() || /[;&|<>]/.test(this.peek())) {\n throw unsupported(`The redirection \\`${op}\\` has no target.`);\n }\n\n const target = this.parseWord();\n\n return { op, ...(fd !== undefined ? { fd } : {}), target, type: \"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 if (this.src.startsWith(\"--%\", this.pos) && /[\\s;|&]|^$/.test(this.src.charAt(this.pos + 3))) {\n throw unsupported(\"The stop-parsing token `--%` is not supported.\");\n }\n\n while (!this.atEnd()) {\n const char = this.peek();\n\n if (\n char === \" \" ||\n char === \"\\t\" ||\n char === \"\\n\" ||\n char === \"\\r\" ||\n char === \";\" ||\n char === \"|\" ||\n char === \"&\" ||\n char === \">\" ||\n char === \"<\"\n ) {\n break;\n }\n\n if (inSet(SINGLE_QUOTES, char)) {\n this.pos++;\n quoted = true;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (this.atEnd()) {\n throw new PowerShellRefusal(\"unbalanced-quotes\", \"A single quote is never closed.\");\n }\n\n const inner = this.peek();\n\n if (inSet(SINGLE_QUOTES, inner)) {\n if (inSet(SINGLE_QUOTES, this.peek(1))) {\n value += this.peek(1);\n this.pos += 2;\n\n continue;\n }\n\n this.pos++;\n\n break;\n }\n\n value += inner;\n this.pos++;\n }\n\n continue;\n }\n\n if (inSet(DOUBLE_QUOTES, char)) {\n this.pos++;\n quoted = true;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (this.atEnd()) {\n throw new PowerShellRefusal(\"unbalanced-quotes\", \"A double quote is never closed.\");\n }\n\n const inner = this.peek();\n\n if (inSet(DOUBLE_QUOTES, inner)) {\n if (inSet(DOUBLE_QUOTES, this.peek(1))) {\n value += this.peek(1);\n this.pos += 2;\n\n continue;\n }\n\n this.pos++;\n\n break;\n }\n\n if (inner === \"`\") {\n const next = this.peek(1);\n\n if (next !== \"\" && (inSet(DOUBLE_QUOTES, next) || next === \"`\" || next === \"$\")) {\n value += next;\n this.pos += 2;\n\n continue;\n }\n\n throw unsupported(\n 'Only `\"`, `` ` `` and `$` can follow a backtick inside double quotes.',\n );\n }\n\n if (inner === \"$\") {\n const read = this.readVariable();\n\n value += read;\n dynamic = true;\n\n continue;\n }\n\n value += inner;\n this.pos++;\n }\n\n continue;\n }\n\n if (char === \"`\") {\n const next = this.peek(1);\n\n if (next === \"\" || next === \"\\n\" || next === \"\\r\") {\n throw unsupported(\n \"A backtick at the end of a line (line continuation) is not supported.\",\n );\n }\n\n value += next;\n quoted = true;\n this.pos += 2;\n\n continue;\n }\n\n if (char === \"$\") {\n const wasLiteral = this.src.charAt(this.pos + 1);\n\n if (\n wasLiteral === \"\" ||\n !(wasLiteral === \"{\" || wasLiteral === \"(\" || VARIABLE_CHAR.test(wasLiteral))\n ) {\n value += \"$\";\n this.pos++;\n\n continue;\n }\n\n value += this.readVariable();\n dynamic = true;\n\n continue;\n }\n\n if (char === \"@\" && this.pos === start) {\n const next = this.peek(1);\n\n if (\n next === \"(\" ||\n next === \"{\" ||\n next === \"'\" ||\n next === '\"' ||\n /[A-Za-z_$]/.test(next)\n ) {\n throw unsupported(\n \"Arrays (`@( )`), hashtables, splatting (`@name`) and here-strings are not supported.\",\n );\n }\n }\n\n if (char === \"{\" || char === \"}\") {\n throw unsupported(\"Script blocks and braces are not supported.\");\n }\n\n if (char === \"(\" || char === \")\") {\n throw unsupported(\n \"Parentheses (grouping and `$( )` subexpressions) are not supported; quote the text.\",\n );\n }\n\n if (char === \",\") {\n throw unsupported(\n \"The comma operator builds an array and is not supported; quote the text.\",\n );\n }\n\n if (char === \"#\" && this.pos === start) {\n throw unsupported(\"Comments are not supported in a command line.\");\n }\n\n if (char === \"*\" || char === \"?\" || char === \"[\" || (char === \"~\" && this.pos === start)) {\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 * Parse a `$( … )` subexpression: its commands run, so they are collected\n * (before the command that contains it) and checked like any other.\n */\n private parseSubexpression(): void {\n if (this.depth >= 8) {\n throw unsupported(\"`$( )` subexpressions are nested too deeply.\");\n }\n\n const open = this.pos;\n let level = 0;\n let index = open;\n\n for (; index < this.src.length; index++) {\n const char = this.src.charAt(index);\n\n if (inSet(SINGLE_QUOTES, char) || inSet(DOUBLE_QUOTES, char)) {\n const set = inSet(SINGLE_QUOTES, char) ? SINGLE_QUOTES : DOUBLE_QUOTES;\n\n index++;\n\n while (index < this.src.length) {\n const inner = this.src.charAt(index);\n\n if (inner === \"`\" && set === DOUBLE_QUOTES) {\n index += 2;\n\n continue;\n }\n\n if (inSet(set, inner)) {\n if (inSet(set, this.src.charAt(index + 1))) {\n index += 2;\n\n continue;\n }\n\n break;\n }\n\n index++;\n }\n\n continue;\n }\n\n if (char === \"`\") {\n index++;\n\n continue;\n }\n\n if (char === \"(\") {\n level++;\n } else if (char === \")\") {\n level--;\n\n if (level === 0) {\n break;\n }\n }\n }\n\n if (level !== 0) {\n throw new PowerShellRefusal(\"unbalanced-quotes\", \"A `$(` subexpression is never closed.\");\n }\n\n const inner = this.src.slice(open + 1, index);\n\n new PowerShellParser(inner, this.commands, this.depth + 1).parseProgram([]);\n this.pos = index + 1;\n }\n\n /** Read `$name`, `${name}` or `$env:NAME` at the cursor; a `$(` is refused. */\n private readVariable(): string {\n const begin = this.pos;\n\n this.pos++;\n\n if (this.peek() === \"(\") {\n this.parseSubexpression();\n\n return this.src.slice(begin, this.pos);\n }\n\n if (this.peek() === \"{\") {\n const close = this.src.indexOf(\"}\", this.pos);\n\n if (close === -1) {\n throw new PowerShellRefusal(\"unbalanced-quotes\", \"A `${` variable name is never closed.\");\n }\n\n this.pos = close + 1;\n\n return this.src.slice(begin, this.pos);\n }\n\n while (!this.atEnd() && VARIABLE_CHAR.test(this.peek())) {\n this.pos++;\n }\n\n return this.src.slice(begin, this.pos);\n }\n}\n\n/** Parse a PowerShell command line for the shell policy. Never throws. */\nexport function parsePowerShellLine(line: string): ShellParseResult {\n const segments: ShellSegment[] = [];\n const parser = new PowerShellParser(line);\n\n try {\n parser.parseProgram(segments);\n\n return { ok: true, commands: parser.commands, segments, background: false };\n } catch (error) {\n if (error instanceof PowerShellRefusal) {\n return { ok: false, reason: error.reason, message: error.message };\n }\n\n throw error;\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,8 @@
1
- import { WorkspacePolicy } from "../contracts/workspace-policy.type.mjs";
1
+ import { WorkspaceShellAsk, WorkspaceShellClassification } from "../contracts/shell-approval.type.mjs";
2
+ import { WorkspaceCommandPattern, WorkspacePolicy } from "../contracts/workspace-policy.type.mjs";
2
3
  import "../contracts/index.mjs";
3
4
  import { WorkspaceShellRefusalReason } from "../errors.mjs";
5
+ import { ResolvedShell, ShellHost } from "./shell-interpreter.mjs";
4
6
  //#region ../../packages/ai-workspace/src/policy/shell-check.d.ts
5
7
  /** How a shell-mode line touches a path the jail has to vet. */
6
8
  export type WorkspaceShellPathAccess = "read" | "write" | "cwd";
@@ -28,11 +30,26 @@ export interface WorkspaceShellCheck {
28
30
  executable?: string;
29
31
  /** The redirection target or `cd` directory a `"path-escape"` refusal is about. */
30
32
  path?: string;
33
+ /**
34
+ * Commands that need a decision from `shell.onAsk` before the line may run.
35
+ * When non-empty, `allowed` is `false` with reason `"approval-required"`;
36
+ * the ops layer asks, and refuses with `"approval-denied"` if the answer is no.
37
+ */
38
+ asks: WorkspaceShellAsk[];
31
39
  /** The argv of every simple command checked, in order (wrapped commands included). */
32
40
  commands: string[][];
33
41
  /** Shell mode: every redirection target and `cd` directory, for the jail recheck. */
34
42
  paths: WorkspaceShellPath[];
35
43
  }
44
+ /** Options a check takes beyond the policy and the line. */
45
+ export interface ShellCheckOptions {
46
+ /** Patterns a host approved earlier with `remember`; they allow what would ask, never what is denied. */
47
+ remembered?: readonly WorkspaceCommandPattern[];
48
+ /** The host the `"auto"` interpreter is resolved against (tests inject one). */
49
+ host?: ShellHost;
50
+ /** The already-resolved shell, to skip resolving it again. */
51
+ resolved?: ResolvedShell;
52
+ }
36
53
  /**
37
54
  * The shell policy's verdict on a command line, without touching the
38
55
  * filesystem.
@@ -58,6 +75,19 @@ export interface WorkspaceShellCheck {
58
75
  * checkShellCommand({ cwd, shell: { allow: ["git"] } }, "git add -A && git commit -m wip");
59
76
  * // { allowed: false, reason: "metacharacter", message: "`&&` is not supported in argv mode: run `git add -A`, then `git commit -m wip`, …" }
60
77
  */
61
- export declare function checkShellCommand(policy: WorkspacePolicy, command: string): WorkspaceShellCheck;
78
+ export declare function checkShellCommand(policy: WorkspacePolicy, command: string, options?: ShellCheckOptions): WorkspaceShellCheck;
79
+ /**
80
+ * The simple commands a line would run (pipeline members, `&&` / `||` / `;`
81
+ * parts, subshells and substitutions included), without checking anything.
82
+ * A line the parser cannot read with confidence comes back with `opaque`
83
+ * set to the reason and no commands.
84
+ *
85
+ * The line is read with the policy's shell dialect when its `interpreter`
86
+ * is a shell, and with the platform's default shell otherwise.
87
+ *
88
+ * @example
89
+ * classifyShellCommand("a | b"); // { commands: [["a"], ["b"]] }
90
+ */
91
+ export declare function classifyShellCommand(command: string, policy?: Pick<WorkspacePolicy, "shell">, host?: ShellHost): WorkspaceShellClassification;
62
92
  //#endregion
63
93
  //# sourceMappingURL=shell-check.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shell-check.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/policy/shell-check.ts"],"mappings":";;;;;YAmBY;;iBAGK;;EAEf;;EAEA,QAAQ;;EAER;;;;;;iBAOe;;EAEf;;EAEA,SAAS;;EAET;;EAEA;;EAEA;;EAEA;;EAEA,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA6hCO,kBAAkB,QAAQ,iBAAiB,kBAAkB"}
1
+ {"version":3,"file":"shell-check.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/policy/shell-check.ts"],"mappings":";;;;;;;YAkCY;;iBAGK;;EAEf;;EAEA,QAAQ;;EAER;;;;;;iBAOe;;EAEf;;EAEA,SAAS;;EAET;;EAEA;;EAEA;;;;;;EAMA,MAAM;;EAEN;;EAEA,OAAO;;;iBAwQQ;;EAEf,sBAAsB;;EAEtB,OAAO;;EAEP,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAggCG,kBACd,QAAQ,iBACR,iBACA,UAAU,oBACT;;;;;;;;;;;;;wBAyHa,qBACd,iBACA,SAAS,KAAK,2BACd,OAAO,YACN"}