@tinycloud/courier 0.2.0-beta.37 → 0.2.0-beta.38

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 (87) hide show
  1. package/README.md +331 -12
  2. package/dist/a2a.d.ts +6 -0
  3. package/dist/a2a.js +10 -0
  4. package/dist/a2a.js.map +1 -1
  5. package/dist/approval.d.ts +6 -5
  6. package/dist/approval.js +7 -16
  7. package/dist/approval.js.map +1 -1
  8. package/dist/args.js +1 -1
  9. package/dist/args.js.map +1 -1
  10. package/dist/broker-config.d.ts +24 -0
  11. package/dist/broker-config.js +99 -0
  12. package/dist/broker-config.js.map +1 -0
  13. package/dist/broker.d.ts +77 -0
  14. package/dist/broker.js +253 -0
  15. package/dist/broker.js.map +1 -0
  16. package/dist/commands.js +5 -2
  17. package/dist/commands.js.map +1 -1
  18. package/dist/consumed.d.ts +10 -0
  19. package/dist/consumed.js +54 -0
  20. package/dist/consumed.js.map +1 -0
  21. package/dist/guard/binaries.d.ts +17 -0
  22. package/dist/guard/binaries.js +89 -0
  23. package/dist/guard/binaries.js.map +1 -0
  24. package/dist/guard/cleanhome.d.ts +27 -0
  25. package/dist/guard/cleanhome.js +111 -0
  26. package/dist/guard/cleanhome.js.map +1 -0
  27. package/dist/guard/command.d.ts +13 -0
  28. package/dist/guard/command.js +158 -0
  29. package/dist/guard/command.js.map +1 -0
  30. package/dist/guard/expansion.d.ts +10 -0
  31. package/dist/guard/expansion.js +74 -0
  32. package/dist/guard/expansion.js.map +1 -0
  33. package/dist/guard/hook.d.ts +38 -0
  34. package/dist/guard/hook.js +124 -0
  35. package/dist/guard/hook.js.map +1 -0
  36. package/dist/guard/install.d.ts +67 -0
  37. package/dist/guard/install.js +193 -0
  38. package/dist/guard/install.js.map +1 -0
  39. package/dist/guard/network.d.ts +7 -0
  40. package/dist/guard/network.js +78 -0
  41. package/dist/guard/network.js.map +1 -0
  42. package/dist/guard/normalize.d.ts +28 -0
  43. package/dist/guard/normalize.js +257 -0
  44. package/dist/guard/normalize.js.map +1 -0
  45. package/dist/guard/paseo.d.ts +30 -0
  46. package/dist/guard/paseo.js +148 -0
  47. package/dist/guard/paseo.js.map +1 -0
  48. package/dist/guard/paths.d.ts +31 -0
  49. package/dist/guard/paths.js +180 -0
  50. package/dist/guard/paths.js.map +1 -0
  51. package/dist/guard/policy.d.ts +65 -0
  52. package/dist/guard/policy.js +1179 -0
  53. package/dist/guard/policy.js.map +1 -0
  54. package/dist/guard/shell.d.ts +55 -0
  55. package/dist/guard/shell.js +727 -0
  56. package/dist/guard/shell.js.map +1 -0
  57. package/dist/index.d.ts +2 -0
  58. package/dist/index.js +2 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/keys.d.ts +4 -1
  61. package/dist/keys.js +7 -2
  62. package/dist/keys.js.map +1 -1
  63. package/dist/main.js +44 -4
  64. package/dist/main.js.map +1 -1
  65. package/dist/node.d.ts +1 -1
  66. package/dist/node.js +3 -3
  67. package/dist/node.js.map +1 -1
  68. package/dist/notify.d.ts +39 -0
  69. package/dist/notify.js +88 -0
  70. package/dist/notify.js.map +1 -0
  71. package/dist/pause.d.ts +26 -0
  72. package/dist/pause.js +99 -0
  73. package/dist/pause.js.map +1 -0
  74. package/dist/profile.d.ts +3 -3
  75. package/dist/profile.js +12 -12
  76. package/dist/profile.js.map +1 -1
  77. package/dist/wake.d.ts +17 -0
  78. package/dist/wake.js +74 -13
  79. package/dist/wake.js.map +1 -1
  80. package/dist/watch-cmd.d.ts +3 -0
  81. package/dist/watch-cmd.js +56 -2
  82. package/dist/watch-cmd.js.map +1 -1
  83. package/dist/watch.d.ts +70 -0
  84. package/dist/watch.js +292 -11
  85. package/dist/watch.js.map +1 -1
  86. package/package.json +1 -1
  87. package/skills/courier/SKILL.md +94 -8
@@ -0,0 +1,727 @@
1
+ /**
2
+ * A small, conservative POSIX-shell reader for the guard. It does not run anything. It turns a command string into the
3
+ * flat list of simple commands that string could execute (including command substitutions, subshells, groups and loop
4
+ * bodies), with every word split into literal and dynamic parts so the policy can tell "I know this word" from "this word
5
+ * is decided at run time". Anything it cannot read exactly becomes dynamic; unterminated constructs are reported as
6
+ * errors (the guard denies on them).
7
+ */
8
+ const MAX_DEPTH = 16;
9
+ const NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
10
+ const SPECIAL = new Set(['@', '*', '#', '?', '$', '!', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']);
11
+ const RESERVED_SKIP = new Set(['if', 'then', 'elif', 'else', 'do', 'time', '!', 'coproc']);
12
+ export function parseShell(src) {
13
+ const st = { cmds: [], errors: [] };
14
+ parseScript(src, st, { loop: 0, func: false, depth: 0 });
15
+ return { cmds: st.cmds, errors: st.errors };
16
+ }
17
+ function newCmd(loop, func, piped) {
18
+ return { assigns: [], words: [], redirs: [], heredocs: [], hereStrings: [], inLoop: loop > 0, inFunc: func, piped, forVars: [] };
19
+ }
20
+ const isBlank = (c) => c === ' ' || c === '\t' || c === '\r';
21
+ const isWordEnd = (c) => c === undefined || isBlank(c) || c === '\n' || c === ';' || c === '&' || c === '|' || c === '(' || c === ')' || c === '<' || c === '>';
22
+ /** Index of the `)` matching the `(` that opened at `start - 1`, or -1. Honours quotes, escapes and nesting. */
23
+ function matchParen(src, start) {
24
+ let depth = 1;
25
+ let i = start;
26
+ while (i < src.length) {
27
+ const c = src[i];
28
+ if (c === '\\') {
29
+ i += 2;
30
+ continue;
31
+ }
32
+ if (c === "'") {
33
+ const e = src.indexOf("'", i + 1);
34
+ if (e < 0)
35
+ return -1;
36
+ i = e + 1;
37
+ continue;
38
+ }
39
+ if (c === '"') {
40
+ i++;
41
+ while (i < src.length && src[i] !== '"') {
42
+ if (src[i] === '\\') {
43
+ i += 2;
44
+ continue;
45
+ }
46
+ if (src[i] === '$' && src[i + 1] === '(') {
47
+ const e = matchParen(src, i + 2);
48
+ if (e < 0)
49
+ return -1;
50
+ i = e + 1;
51
+ continue;
52
+ }
53
+ i++;
54
+ }
55
+ if (i >= src.length)
56
+ return -1;
57
+ i++;
58
+ continue;
59
+ }
60
+ if (c === '`') {
61
+ const e = closeBacktick(src, i + 1);
62
+ if (e < 0)
63
+ return -1;
64
+ i = e + 1;
65
+ continue;
66
+ }
67
+ if (c === '(')
68
+ depth++;
69
+ else if (c === ')') {
70
+ depth--;
71
+ if (depth === 0)
72
+ return i;
73
+ }
74
+ i++;
75
+ }
76
+ return -1;
77
+ }
78
+ function closeBacktick(src, start) {
79
+ for (let i = start; i < src.length; i++) {
80
+ if (src[i] === '\\') {
81
+ i++;
82
+ continue;
83
+ }
84
+ if (src[i] === '`')
85
+ return i;
86
+ }
87
+ return -1;
88
+ }
89
+ /** Index of the `}` closing a `${`, or -1. */
90
+ function matchBrace(src, start) {
91
+ let depth = 1;
92
+ for (let i = start; i < src.length; i++) {
93
+ const c = src[i];
94
+ if (c === '\\') {
95
+ i++;
96
+ continue;
97
+ }
98
+ if (c === "'") {
99
+ const e = src.indexOf("'", i + 1);
100
+ if (e < 0)
101
+ return -1;
102
+ i = e;
103
+ continue;
104
+ }
105
+ if (c === '$' && src[i + 1] === '(') {
106
+ const e = matchParen(src, i + 2);
107
+ if (e < 0)
108
+ return -1;
109
+ i = e;
110
+ continue;
111
+ }
112
+ if (c === '`') {
113
+ const e = closeBacktick(src, i + 1);
114
+ if (e < 0)
115
+ return -1;
116
+ i = e;
117
+ continue;
118
+ }
119
+ if (c === '{')
120
+ depth++;
121
+ else if (c === '}') {
122
+ depth--;
123
+ if (depth === 0)
124
+ return i;
125
+ }
126
+ }
127
+ return -1;
128
+ }
129
+ /** Parse every `$(…)`, backtick and process substitution found in `text` (heredoc bodies, `${…}` words, arithmetic). */
130
+ function harvest(text, st, base) {
131
+ for (let i = 0; i < text.length; i++) {
132
+ const c = text[i];
133
+ if (c === '\\') {
134
+ i++;
135
+ continue;
136
+ }
137
+ if (c === '$' && text[i + 1] === '(' && text[i + 2] !== '(') {
138
+ const e = matchParen(text, i + 2);
139
+ if (e < 0) {
140
+ st.errors.push('unterminated $(');
141
+ return;
142
+ }
143
+ sub(text.slice(i + 2, e), st, base);
144
+ i = e;
145
+ }
146
+ else if (c === '$' && text[i + 1] === '(') {
147
+ const e = matchParen(text, i + 2);
148
+ if (e < 0) {
149
+ st.errors.push('unterminated $((');
150
+ return;
151
+ }
152
+ harvest(text.slice(i + 3, Math.max(i + 3, e - 1)), st, base);
153
+ i = e;
154
+ }
155
+ else if (c === '`') {
156
+ const e = closeBacktick(text, i + 1);
157
+ if (e < 0) {
158
+ st.errors.push('unterminated backtick');
159
+ return;
160
+ }
161
+ sub(unescapeBacktick(text.slice(i + 1, e)), st, base);
162
+ i = e;
163
+ }
164
+ else if ((c === '<' || c === '>') && text[i + 1] === '(') {
165
+ const e = matchParen(text, i + 2);
166
+ if (e < 0) {
167
+ st.errors.push('unterminated process substitution');
168
+ return;
169
+ }
170
+ sub(text.slice(i + 2, e), st, base);
171
+ i = e;
172
+ }
173
+ }
174
+ }
175
+ function unescapeBacktick(s) {
176
+ return s.replace(/\\([`\\$])/g, '$1');
177
+ }
178
+ function sub(inner, st, base) {
179
+ if (base.depth + 1 > MAX_DEPTH) {
180
+ st.errors.push('substitutions nested too deeply');
181
+ return;
182
+ }
183
+ parseScript(inner, st, { ...base, depth: base.depth + 1 });
184
+ }
185
+ const ANSI = { n: '\n', t: '\t', r: '\r', a: '\x07', b: '\b', f: '\f', v: '\v', e: '\x1b', E: '\x1b', '\\': '\\', "'": "'", '"': '"', '?': '?' };
186
+ /** Decode the body of `$'…'`. */
187
+ function ansiC(body) {
188
+ let out = '';
189
+ for (let i = 0; i < body.length; i++) {
190
+ const c = body[i];
191
+ if (c !== '\\') {
192
+ out += c;
193
+ continue;
194
+ }
195
+ const n = body[++i];
196
+ if (n === undefined) {
197
+ out += '\\';
198
+ break;
199
+ }
200
+ if (n in ANSI) {
201
+ out += ANSI[n];
202
+ continue;
203
+ }
204
+ if (n === 'x') {
205
+ const m = /^[0-9a-fA-F]{1,2}/.exec(body.slice(i + 1));
206
+ if (m) {
207
+ out += String.fromCharCode(parseInt(m[0], 16));
208
+ i += m[0].length;
209
+ continue;
210
+ }
211
+ }
212
+ if (n === 'u' || n === 'U') {
213
+ const m = new RegExp(`^[0-9a-fA-F]{1,${n === 'u' ? 4 : 8}}`).exec(body.slice(i + 1));
214
+ if (m) {
215
+ out += String.fromCodePoint(parseInt(m[0], 16));
216
+ i += m[0].length;
217
+ continue;
218
+ }
219
+ }
220
+ if (/[0-7]/.test(n)) {
221
+ const m = /^[0-7]{1,3}/.exec(body.slice(i));
222
+ out += String.fromCharCode(parseInt(m[0], 8) & 0xff);
223
+ i += m[0].length - 1;
224
+ continue;
225
+ }
226
+ out += `\\${n}`;
227
+ }
228
+ return out;
229
+ }
230
+ /** Read one word starting at `i`. Never throws; unterminated constructs go to `st.errors`. */
231
+ function lexWord(src, i, st, base, allowAssignParen = true) {
232
+ const parts = [];
233
+ const lit = (s, q) => {
234
+ const last = parts[parts.length - 1];
235
+ if (last && last.t === 'lit' && last.q === q)
236
+ last.s += s;
237
+ else
238
+ parts.push({ t: 'lit', s, q });
239
+ };
240
+ let wordStart = true;
241
+ while (i < src.length) {
242
+ const c = src[i];
243
+ // Process substitution `<(` `>(` at the very start of a word.
244
+ if ((c === '<' || c === '>') && src[i + 1] === '(' && wordStart) {
245
+ const e = matchParen(src, i + 2);
246
+ if (e < 0) {
247
+ st.errors.push('unterminated process substitution');
248
+ return { word: { parts: [...parts, { t: 'dyn' }] }, end: src.length };
249
+ }
250
+ sub(src.slice(i + 2, e), st, base);
251
+ parts.push({ t: 'dyn' });
252
+ i = e + 1;
253
+ wordStart = false;
254
+ continue;
255
+ }
256
+ if (isWordEnd(c))
257
+ break;
258
+ if (c === '\\') {
259
+ if (src[i + 1] === '\n') {
260
+ i += 2;
261
+ continue;
262
+ }
263
+ if (i + 1 >= src.length) {
264
+ lit('\\', true);
265
+ i++;
266
+ continue;
267
+ }
268
+ lit(src[i + 1], true);
269
+ i += 2;
270
+ }
271
+ else if (c === "'") {
272
+ const e = src.indexOf("'", i + 1);
273
+ if (e < 0) {
274
+ st.errors.push('unterminated single quote');
275
+ lit(src.slice(i + 1), true);
276
+ i = src.length;
277
+ break;
278
+ }
279
+ lit(src.slice(i + 1, e), true);
280
+ i = e + 1;
281
+ }
282
+ else if (c === '"') {
283
+ i = lexDouble(src, i + 1, parts, st, base);
284
+ }
285
+ else if (c === '$' && src[i + 1] === "'") {
286
+ let e = i + 2;
287
+ while (e < src.length && src[e] !== "'")
288
+ e += src[e] === '\\' ? 2 : 1;
289
+ if (e >= src.length) {
290
+ st.errors.push("unterminated $'");
291
+ i = src.length;
292
+ break;
293
+ }
294
+ lit(ansiC(src.slice(i + 2, e)), true);
295
+ i = e + 1;
296
+ }
297
+ else if (c === '$' && src[i + 1] === '"') {
298
+ i = lexDouble(src, i + 2, parts, st, base);
299
+ }
300
+ else if (c === '$') {
301
+ i = lexDollar(src, i, parts, false, st, base);
302
+ }
303
+ else if (c === '`') {
304
+ const e = closeBacktick(src, i + 1);
305
+ if (e < 0) {
306
+ st.errors.push('unterminated backtick');
307
+ i = src.length;
308
+ break;
309
+ }
310
+ sub(unescapeBacktick(src.slice(i + 1, e)), st, base);
311
+ parts.push({ t: 'dyn' });
312
+ i = e + 1;
313
+ }
314
+ else if (c === '[' && !/^\[[^\s\]]*\]/.test(src.slice(i, i + 256))) {
315
+ // `[` and `[[` are the test command; a glob bracket needs its closing `]` inside the same word.
316
+ lit(c, false);
317
+ i++;
318
+ }
319
+ else if (c === '*' || c === '?' || c === '[') {
320
+ parts.push({ t: 'dyn' });
321
+ i++;
322
+ }
323
+ else if (c === '~' && wordStart) {
324
+ parts.push({ t: 'dyn' });
325
+ i++;
326
+ }
327
+ else if (c === '{' && !(wordStart && isWordEnd(src[i + 1] ?? ' '))) {
328
+ // Brace expansion `{a,b}` / `{1..3}` inside a word: dynamic. A lone `{` stays literal (grouping).
329
+ const stop = (() => { let j = i; while (j < src.length && !/[\s;&|()<>]/.test(src[j]))
330
+ j++; return j; })();
331
+ const chunk = src.slice(i, stop);
332
+ const close = chunk.indexOf('}');
333
+ if (close > 0 && (/,/.test(chunk.slice(0, close)) || /\.\./.test(chunk.slice(0, close)))) {
334
+ parts.push({ t: 'dyn' });
335
+ i += close + 1;
336
+ }
337
+ else {
338
+ lit('{', false);
339
+ i++;
340
+ }
341
+ }
342
+ else if (c === '(') {
343
+ break;
344
+ }
345
+ else if (c === '=' && allowAssignParen && src[i + 1] === '(' && /^[A-Za-z_][A-Za-z0-9_]*\+?$/.test(partsText(parts))) {
346
+ // Array assignment `NAME=( … )`.
347
+ const e = matchParen(src, i + 2);
348
+ if (e < 0) {
349
+ st.errors.push('unterminated array assignment');
350
+ i = src.length;
351
+ break;
352
+ }
353
+ harvest(src.slice(i + 2, e), st, base);
354
+ lit('=', false);
355
+ parts.push({ t: 'dyn' });
356
+ i = e + 1;
357
+ }
358
+ else {
359
+ lit(c, false);
360
+ i++;
361
+ }
362
+ wordStart = false;
363
+ }
364
+ return { word: { parts }, end: i };
365
+ }
366
+ const partsText = (parts) => parts.map((p) => (p.t === 'lit' ? p.s : '\u0001')).join('');
367
+ /** Read a double-quoted string body starting just after the opening quote; returns the index after the closing quote. */
368
+ function lexDouble(src, start, parts, st, base) {
369
+ const lit = (s) => {
370
+ const last = parts[parts.length - 1];
371
+ if (last && last.t === 'lit' && last.q)
372
+ last.s += s;
373
+ else
374
+ parts.push({ t: 'lit', s, q: true });
375
+ };
376
+ let i = start;
377
+ lit('');
378
+ while (i < src.length && src[i] !== '"') {
379
+ const c = src[i];
380
+ if (c === '\\') {
381
+ const n = src[i + 1];
382
+ if (n === '\n') {
383
+ i += 2;
384
+ continue;
385
+ }
386
+ if (n === '$' || n === '`' || n === '"' || n === '\\') {
387
+ lit(n);
388
+ i += 2;
389
+ continue;
390
+ }
391
+ lit('\\');
392
+ i++;
393
+ }
394
+ else if (c === '$') {
395
+ i = lexDollar(src, i, parts, true, st, base);
396
+ }
397
+ else if (c === '`') {
398
+ const e = closeBacktick(src, i + 1);
399
+ if (e < 0) {
400
+ st.errors.push('unterminated backtick');
401
+ return src.length;
402
+ }
403
+ sub(unescapeBacktick(src.slice(i + 1, e)), st, base);
404
+ parts.push({ t: 'dyn' });
405
+ i = e + 1;
406
+ }
407
+ else {
408
+ lit(c);
409
+ i++;
410
+ }
411
+ }
412
+ if (i >= src.length) {
413
+ st.errors.push('unterminated double quote');
414
+ return src.length;
415
+ }
416
+ return i + 1;
417
+ }
418
+ /** `$…` at src[i]. Pushes a part and returns the next index. */
419
+ function lexDollar(src, i, parts, q, st, base) {
420
+ const n = src[i + 1];
421
+ const lit = (s) => {
422
+ const last = parts[parts.length - 1];
423
+ if (last && last.t === 'lit' && last.q === q)
424
+ last.s += s;
425
+ else
426
+ parts.push({ t: 'lit', s, q });
427
+ };
428
+ if (n === '(') {
429
+ const arith = src[i + 2] === '(';
430
+ const e = matchParen(src, i + 2);
431
+ if (e < 0) {
432
+ st.errors.push('unterminated $(');
433
+ parts.push({ t: 'dyn' });
434
+ return src.length;
435
+ }
436
+ if (arith)
437
+ harvest(src.slice(i + 3, Math.max(i + 3, e - 1)), st, base);
438
+ else
439
+ sub(src.slice(i + 2, e), st, base);
440
+ parts.push({ t: 'dyn' });
441
+ return e + 1;
442
+ }
443
+ if (n === '{') {
444
+ const e = matchBrace(src, i + 2);
445
+ if (e < 0) {
446
+ st.errors.push('unterminated ${');
447
+ parts.push({ t: 'dyn' });
448
+ return src.length;
449
+ }
450
+ const body = src.slice(i + 2, e);
451
+ if (NAME.test(body))
452
+ parts.push({ t: 'var', name: body, q });
453
+ else {
454
+ harvest(body, st, base);
455
+ parts.push({ t: 'dyn' });
456
+ }
457
+ return e + 1;
458
+ }
459
+ const m = /^[A-Za-z_][A-Za-z0-9_]*/.exec(src.slice(i + 1, i + 1 + 256));
460
+ if (m) {
461
+ parts.push({ t: 'var', name: m[0], q });
462
+ return i + 1 + m[0].length;
463
+ }
464
+ if (n !== undefined && SPECIAL.has(n)) {
465
+ parts.push({ t: 'dyn' });
466
+ return i + 2;
467
+ }
468
+ lit('$');
469
+ return i + 1;
470
+ }
471
+ /** Plain text of a word if every part is literal, else undefined. */
472
+ export function wordLiteral(w) {
473
+ let s = '';
474
+ for (const p of w.parts) {
475
+ if (p.t !== 'lit')
476
+ return undefined;
477
+ s += p.s;
478
+ }
479
+ return s;
480
+ }
481
+ function hasQuoted(w) {
482
+ return w.parts.some((p) => p.t === 'lit' && p.q);
483
+ }
484
+ function parseScript(src, st, base) {
485
+ if (src.length > 1_000_000) {
486
+ st.errors.push('command too large to inspect');
487
+ return;
488
+ }
489
+ let i = 0;
490
+ let loop = base.loop;
491
+ const stack = base.func ? ['func'] : [];
492
+ let cur = null;
493
+ let atCmd = true;
494
+ let nextPiped = false;
495
+ let pendingFunc = false;
496
+ let skipWords = null;
497
+ const heredocs = [];
498
+ const inFunc = () => stack.includes('func');
499
+ const start = () => {
500
+ if (!cur) {
501
+ cur = newCmd(loop, inFunc(), nextPiped);
502
+ nextPiped = false;
503
+ }
504
+ return cur;
505
+ };
506
+ const flush = () => {
507
+ if (cur && (cur.words.length || cur.assigns.length || cur.redirs.length || cur.hereStrings.length || cur.heredocs.length || cur.forVars.length))
508
+ st.cmds.push(cur);
509
+ cur = null;
510
+ atCmd = true;
511
+ };
512
+ while (i < src.length) {
513
+ const c = src[i];
514
+ if (isBlank(c)) {
515
+ i++;
516
+ continue;
517
+ }
518
+ if (c === '\\' && src[i + 1] === '\n') {
519
+ i += 2;
520
+ continue;
521
+ }
522
+ if (c === '#') {
523
+ while (i < src.length && src[i] !== '\n')
524
+ i++;
525
+ continue;
526
+ }
527
+ if (c === '\n') {
528
+ flush();
529
+ skipWords = skipWords === 'do' ? null : skipWords;
530
+ i++;
531
+ for (const h of heredocs.splice(0)) {
532
+ const lines = [];
533
+ while (i < src.length) {
534
+ let e = src.indexOf('\n', i);
535
+ if (e < 0)
536
+ e = src.length;
537
+ const line = src.slice(i, e);
538
+ i = Math.min(src.length, e + 1);
539
+ if ((h.strip ? line.replace(/^\t+/, '') : line) === h.delim)
540
+ break;
541
+ lines.push(h.strip ? line.replace(/^\t+/, '') : line);
542
+ }
543
+ const body = lines.join('\n');
544
+ h.cmd.heredocs.push(body);
545
+ if (!h.quoted)
546
+ harvest(body, st, base.depth >= MAX_DEPTH ? base : { loop, func: inFunc(), depth: base.depth });
547
+ }
548
+ continue;
549
+ }
550
+ if (c === ';') {
551
+ flush();
552
+ skipWords = skipWords === 'do' ? null : skipWords;
553
+ i += src[i + 1] === ';' ? (src[i + 2] === '&' ? 3 : 2) : src[i + 1] === '&' ? 2 : 1;
554
+ continue;
555
+ }
556
+ if (c === '&' && src[i + 1] === '>') {
557
+ // `&>file` / `&>>file`
558
+ const op = src[i + 2] === '>' ? '&>>' : '&>';
559
+ i += op.length;
560
+ while (isBlank(src[i]))
561
+ i++;
562
+ const { word, end } = lexWord(src, i, st, base);
563
+ i = end;
564
+ start().redirs.push({ op, target: word });
565
+ continue;
566
+ }
567
+ if (c === '&' || c === '|') {
568
+ const two = src[i + 1] === c;
569
+ flush();
570
+ if (c === '|')
571
+ nextPiped = true;
572
+ i += two ? 2 : c === '|' && src[i + 1] === '&' ? 2 : 1;
573
+ continue;
574
+ }
575
+ if (c === '(') {
576
+ // `name () {` function definition?
577
+ const rest = src.slice(i + 1);
578
+ const m = /^\s*\)/.exec(rest);
579
+ if (m && cur && cur.words.length === 1 && !cur.assigns.length) {
580
+ cur = null;
581
+ pendingFunc = true;
582
+ atCmd = true;
583
+ i += 1 + m[0].length;
584
+ continue;
585
+ }
586
+ flush();
587
+ stack.push(pendingFunc ? 'func' : 'sub');
588
+ pendingFunc = false;
589
+ i++;
590
+ continue;
591
+ }
592
+ if (c === ')') {
593
+ flush();
594
+ stack.pop();
595
+ i++;
596
+ continue;
597
+ }
598
+ if ((c === '<' || c === '>') && src[i + 1] !== '(') {
599
+ i = lexRedirect(src, i, undefined);
600
+ continue;
601
+ }
602
+ const { word, end } = lexWord(src, i, st, base);
603
+ i = end;
604
+ if (!word.parts.length) {
605
+ i = Math.max(i, i + 1);
606
+ continue;
607
+ }
608
+ // `2>file` / `3<&0`: a bare digit word glued to a redirect operator.
609
+ const text = wordLiteral(word);
610
+ if (text !== undefined && /^[0-9]+$/.test(text) && (src[i] === '<' || src[i] === '>') && src[i + 1] !== '(') {
611
+ i = lexRedirect(src, i, text);
612
+ continue;
613
+ }
614
+ if (skipWords) {
615
+ if (skipWords === 'do' && text === 'do') {
616
+ skipWords = null;
617
+ atCmd = true;
618
+ }
619
+ else if (skipWords === 'in' && text === 'in') {
620
+ skipWords = null;
621
+ atCmd = true;
622
+ }
623
+ continue;
624
+ }
625
+ if (atCmd && text !== undefined && !hasQuoted(word)) {
626
+ if (RESERVED_SKIP.has(text))
627
+ continue;
628
+ if (text === 'fi' || text === 'esac')
629
+ continue;
630
+ if (text === 'done') {
631
+ loop = Math.max(0, loop - 1);
632
+ continue;
633
+ }
634
+ if (text === 'while' || text === 'until') {
635
+ loop++;
636
+ continue;
637
+ }
638
+ if (text === 'for' || text === 'select') {
639
+ loop++;
640
+ // The next word is the loop variable; the words up to `do` are data, not commands.
641
+ while (isBlank(src[i]))
642
+ i++;
643
+ const v = lexWord(src, i, st, base);
644
+ i = v.end;
645
+ const name = wordLiteral(v.word);
646
+ const cmd = start();
647
+ if (name && NAME.test(name))
648
+ cmd.forVars.push(name);
649
+ flush();
650
+ skipWords = 'do';
651
+ continue;
652
+ }
653
+ if (text === 'case') {
654
+ skipWords = 'in';
655
+ continue;
656
+ }
657
+ if (text === 'function') {
658
+ while (isBlank(src[i]))
659
+ i++;
660
+ const v = lexWord(src, i, st, base);
661
+ i = v.end;
662
+ pendingFunc = true;
663
+ continue;
664
+ }
665
+ if (text === '{') {
666
+ stack.push(pendingFunc ? 'func' : 'group');
667
+ pendingFunc = false;
668
+ continue;
669
+ }
670
+ if (text === '}') {
671
+ stack.pop();
672
+ continue;
673
+ }
674
+ }
675
+ const cmd = start();
676
+ if (atCmd) {
677
+ const eq = word.parts[0].t === 'lit' && !word.parts[0].q ? /^([A-Za-z_][A-Za-z0-9_]*)(\+?)=/.exec(word.parts[0].s) : null;
678
+ if (eq && cmd.words.length === 0) {
679
+ const first = word.parts[0];
680
+ const rest = [];
681
+ const tail = first.s.slice(eq[0].length);
682
+ if (tail || word.parts.length === 1)
683
+ rest.push({ t: 'lit', s: tail, q: false });
684
+ rest.push(...word.parts.slice(1));
685
+ cmd.assigns.push({ name: eq[1], append: eq[2] === '+', value: { parts: rest } });
686
+ continue;
687
+ }
688
+ }
689
+ atCmd = false;
690
+ cmd.words.push(word);
691
+ }
692
+ flush();
693
+ function lexRedirect(s, at, fd) {
694
+ let j = at;
695
+ let op = s[j];
696
+ if (s[j] === '<' && s[j + 1] === '<' && s[j + 2] === '<')
697
+ op = '<<<';
698
+ else if (s[j] === '<' && s[j + 1] === '<')
699
+ op = s[j + 2] === '-' ? '<<-' : '<<';
700
+ else if (s[j] === '>' && s[j + 1] === '>')
701
+ op = '>>';
702
+ else if ((s[j] === '<' || s[j] === '>') && s[j + 1] === '&')
703
+ op = `${s[j]}&`;
704
+ else if (s[j] === '<' && s[j + 1] === '>')
705
+ op = '<>';
706
+ else if (s[j] === '>' && s[j + 1] === '|')
707
+ op = '>|';
708
+ j += op.length;
709
+ while (isBlank(s[j]))
710
+ j++;
711
+ const lexed = lexWord(s, j, st, base);
712
+ j = lexed.end;
713
+ const command = start();
714
+ if (op === '<<' || op === '<<-') {
715
+ const delim = lexed.word.parts.map((p) => (p.t === 'lit' ? p.s : '')).join('');
716
+ heredocs.push({ delim, strip: op === '<<-', quoted: hasQuoted(lexed.word), cmd: command });
717
+ }
718
+ else if (op === '<<<') {
719
+ command.hereStrings.push(lexed.word);
720
+ }
721
+ else {
722
+ command.redirs.push({ op, ...(fd !== undefined ? { fd } : {}), target: lexed.word });
723
+ }
724
+ return j;
725
+ }
726
+ }
727
+ //# sourceMappingURL=shell.js.map