shell-dsl 0.0.40 → 0.0.41

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 (57) hide show
  1. package/README.md +66 -0
  2. package/dist/cjs/package.json +1 -1
  3. package/dist/cjs/src/index.cjs +8 -1
  4. package/dist/cjs/src/index.cjs.map +3 -3
  5. package/dist/cjs/src/input-analysis.cjs +154 -0
  6. package/dist/cjs/src/input-analysis.cjs.map +10 -0
  7. package/dist/cjs/src/interpreter/context.cjs +3 -1
  8. package/dist/cjs/src/interpreter/context.cjs.map +3 -3
  9. package/dist/cjs/src/interpreter/interpreter.cjs +146 -19
  10. package/dist/cjs/src/interpreter/interpreter.cjs.map +3 -3
  11. package/dist/cjs/src/io/async-queue.cjs +105 -0
  12. package/dist/cjs/src/io/async-queue.cjs.map +10 -0
  13. package/dist/cjs/src/io/index.cjs +4 -1
  14. package/dist/cjs/src/io/index.cjs.map +3 -3
  15. package/dist/cjs/src/io/input-controller.cjs +113 -0
  16. package/dist/cjs/src/io/input-controller.cjs.map +10 -0
  17. package/dist/cjs/src/io/stdout.cjs +9 -6
  18. package/dist/cjs/src/io/stdout.cjs.map +3 -3
  19. package/dist/cjs/src/shell-dsl.cjs +13 -5
  20. package/dist/cjs/src/shell-dsl.cjs.map +3 -3
  21. package/dist/cjs/src/shell-session.cjs +128 -0
  22. package/dist/cjs/src/shell-session.cjs.map +10 -0
  23. package/dist/cjs/src/types.cjs.map +2 -2
  24. package/dist/mjs/package.json +1 -1
  25. package/dist/mjs/src/index.mjs +17 -2
  26. package/dist/mjs/src/index.mjs.map +3 -3
  27. package/dist/mjs/src/input-analysis.mjs +114 -0
  28. package/dist/mjs/src/input-analysis.mjs.map +10 -0
  29. package/dist/mjs/src/interpreter/context.mjs +3 -1
  30. package/dist/mjs/src/interpreter/context.mjs.map +3 -3
  31. package/dist/mjs/src/interpreter/interpreter.mjs +146 -19
  32. package/dist/mjs/src/interpreter/interpreter.mjs.map +3 -3
  33. package/dist/mjs/src/io/async-queue.mjs +64 -0
  34. package/dist/mjs/src/io/async-queue.mjs.map +10 -0
  35. package/dist/mjs/src/io/index.mjs +4 -1
  36. package/dist/mjs/src/io/index.mjs.map +3 -3
  37. package/dist/mjs/src/io/input-controller.mjs +72 -0
  38. package/dist/mjs/src/io/input-controller.mjs.map +10 -0
  39. package/dist/mjs/src/io/stdout.mjs +9 -6
  40. package/dist/mjs/src/io/stdout.mjs.map +3 -3
  41. package/dist/mjs/src/shell-dsl.mjs +13 -5
  42. package/dist/mjs/src/shell-dsl.mjs.map +3 -3
  43. package/dist/mjs/src/shell-session.mjs +88 -0
  44. package/dist/mjs/src/shell-session.mjs.map +10 -0
  45. package/dist/mjs/src/types.mjs.map +2 -2
  46. package/dist/types/src/index.d.ts +5 -2
  47. package/dist/types/src/input-analysis.d.ts +14 -0
  48. package/dist/types/src/interpreter/context.d.ts +3 -1
  49. package/dist/types/src/interpreter/interpreter.d.ts +12 -1
  50. package/dist/types/src/io/async-queue.d.ts +12 -0
  51. package/dist/types/src/io/index.d.ts +1 -0
  52. package/dist/types/src/io/input-controller.d.ts +15 -0
  53. package/dist/types/src/io/stdout.d.ts +4 -3
  54. package/dist/types/src/shell-dsl.d.ts +2 -0
  55. package/dist/types/src/shell-session.d.ts +23 -0
  56. package/dist/types/src/types.d.ts +39 -0
  57. package/package.json +1 -1
package/README.md CHANGED
@@ -339,6 +339,72 @@ const myls: Command = async (ctx) => {
339
339
  };
340
340
  ```
341
341
 
342
+ ## Streaming Sessions
343
+
344
+ Use `createShellSession()` when you want a stateful, streaming shell runtime for a terminal, agent console, or other UI. A session preserves shell state across runs, including `cwd`, environment changes, and `$?`:
345
+
346
+ ```ts
347
+ import { createShellInput, createShellSession, createVirtualFS } from "shell-dsl";
348
+ import { builtinCommands } from "shell-dsl/commands";
349
+
350
+ const session = createShellSession({
351
+ fs,
352
+ cwd: "/",
353
+ env: {},
354
+ commands: builtinCommands,
355
+ terminal: { isTTY: true, columns: 100, rows: 30 },
356
+ });
357
+
358
+ await session.run("cd /work").exit;
359
+
360
+ const execution = session.run("pwd; echo hello");
361
+ for await (const event of execution.output) {
362
+ const stream = event.fd === 1 ? process.stdout : process.stderr;
363
+ stream.write(event.chunk);
364
+ }
365
+
366
+ const result = await execution.exit;
367
+ console.log(result.exitCode);
368
+ ```
369
+
370
+ For interactive input, create a writable async stdin source:
371
+
372
+ ```ts
373
+ const stdin = createShellInput();
374
+ const execution = session.run("cat", { stdin });
375
+
376
+ await stdin.write("hello\n");
377
+ stdin.close();
378
+ await execution.exit;
379
+ ```
380
+
381
+ Unknown commands can be delegated to a runtime-specific adapter without making shell-dsl spawn processes itself:
382
+
383
+ ```ts
384
+ const session = createShellSession({
385
+ fs,
386
+ cwd: "/",
387
+ env: {},
388
+ commands: builtinCommands,
389
+ externalCommand: async (ctx) => {
390
+ await ctx.stderr.writeText(`${ctx.name}: not implemented by this host\n`);
391
+ return 127;
392
+ },
393
+ });
394
+ ```
395
+
396
+ Use `analyzeInput(source)` before running user-entered text to distinguish complete commands from multiline input such as unclosed quotes, heredocs, trailing pipes, and compound statements.
397
+
398
+ ## Terminal Demo
399
+
400
+ This repository includes a Bun-powered terminal demo:
401
+
402
+ ```bash
403
+ bun examples/terminal/shell-cli.ts
404
+ ```
405
+
406
+ The CLI process uses `node:readline/promises`, so normal prompt editing such as Ctrl+A, Ctrl+E, arrow-key movement, and history are handled by your terminal/readline layer. Tab completion is served by the executor process and includes registered shell-dsl command names plus paths in the virtual filesystem. The demo spawns `shell-executor.ts` with Bun and exchanges JSON-lines messages over stdio. Full raw PTY behavior for interactive child programs is intentionally left to demo or host runtime code rather than shell-dsl core.
407
+
342
408
  ## Field Splitting
343
409
 
344
410
  Unquoted parameter, command, and arithmetic expansions follow shell-style word expansion:
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "shell-dsl",
3
- "version": "0.0.40",
3
+ "version": "0.0.41",
4
4
  "type": "commonjs"
5
5
  }
@@ -62,12 +62,17 @@ __export(exports_src, {
62
62
  createStdout: () => import_io2.createStdout,
63
63
  createStdin: () => import_io.createStdin,
64
64
  createStderr: () => import_io2.createStderr,
65
+ createShellSession: () => import_shell_session.createShellSession,
66
+ createShellInput: () => import_io2.createShellInput,
65
67
  createShellDSL: () => import_shell_dsl.createShellDSL,
66
68
  createPipe: () => import_io2.createPipe,
69
+ analyzeInput: () => import_input_analysis.analyzeInput,
67
70
  WebFileSystem: () => import_fs2.WebFileSystem,
68
71
  VersionControlSystem: () => import_vcs.VersionControlSystem,
69
72
  StdinImpl: () => import_io.StdinImpl,
73
+ ShellSession: () => import_shell_session.ShellSession,
70
74
  ShellPromise: () => import_shell_promise.ShellPromise,
75
+ ShellInputControllerImpl: () => import_io2.ShellInputControllerImpl,
71
76
  ShellError: () => import_errors.ShellError,
72
77
  ShellDSL: () => import_shell_dsl.ShellDSL,
73
78
  ReadOnlyFileSystem: () => import_fs2.ReadOnlyFileSystem,
@@ -86,6 +91,7 @@ __export(exports_src, {
86
91
  module.exports = __toCommonJS(exports_src);
87
92
  var import_shell_dsl = require("./shell-dsl.cjs");
88
93
  var import_shell_promise = require("./shell-promise.cjs");
94
+ var import_shell_session = require("./shell-session.cjs");
89
95
  var import_types = require("./types.cjs");
90
96
  var import_errors = require("./errors.cjs");
91
97
  var import_lexer = require("./lexer/index.cjs");
@@ -96,7 +102,8 @@ var import_fs = require("./fs/index.cjs");
96
102
  var import_fs2 = require("./fs/index.cjs");
97
103
  var import_io = require("./io/index.cjs");
98
104
  var import_io2 = require("./io/index.cjs");
105
+ var import_input_analysis = require("./input-analysis.cjs");
99
106
  var import_utils = require("./utils/index.cjs");
100
107
  var import_vcs = require("./vcs/index.cjs");
101
108
 
102
- //# debugId=A1C14CC7E153FA4864756E2164756E21
109
+ //# debugId=CCA17BE2B94A373E64756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../../src/index.ts"],
4
4
  "sourcesContent": [
5
- "// Main class exports\nexport { ShellDSL, createShellDSL, type Program } from \"./shell-dsl.cjs\";\nexport { ShellPromise, type ShellPromiseOptions } from \"./shell-promise.cjs\";\n\n// Types\nexport type {\n VirtualFS,\n VirtualFSWritable,\n FileStat,\n Command,\n CommandContext,\n Stdin,\n Stdout,\n Stderr,\n OutputCollector,\n ExecResult,\n ShellConfig,\n ShellCommandApi,\n ShellRunOptions,\n RawValue,\n} from \"./types.cjs\";\nexport { isRawValue } from \"./types.cjs\";\n\n// Errors\nexport { ShellError, LexError, ParseError } from \"./errors.cjs\";\n\n// Lexer\nexport { Lexer, lex, tokenToString } from \"./lexer/index.cjs\";\nexport type { Token, RedirectMode } from \"./lexer/index.cjs\";\n\n// Parser\nexport { Parser, parse } from \"./parser/index.cjs\";\nexport type {\n ASTNode,\n Redirect,\n CommandNode,\n PipelineNode,\n AndNode,\n OrNode,\n SequenceNode,\n WordNode,\n WordPart,\n TextPart,\n VariablePart,\n SubstitutionPart,\n ArithmeticPart,\n IfNode,\n ForNode,\n WhileNode,\n UntilNode,\n CaseNode,\n CaseClause,\n} from \"./parser/index.cjs\";\nexport {\n isWordNode,\n isCommandNode,\n isPipelineNode,\n isAndNode,\n isOrNode,\n isSequenceNode,\n isIfNode,\n isForNode,\n isWhileNode,\n isUntilNode,\n isCaseNode,\n} from \"./parser/index.cjs\";\n\n// Interpreter\nexport { Interpreter, type InterpreterOptions, BreakException, ContinueException, ExitException } from \"./interpreter/index.cjs\";\n\n// Filesystem\nexport { createVirtualFS } from \"./fs/index.cjs\";\nexport {\n FileSystem,\n ReadOnlyFileSystem,\n WebFileSystem,\n createWebUnderlyingFS,\n type PathOps,\n type Permission,\n type PermissionRules,\n type UnderlyingFS,\n} from \"./fs/index.cjs\";\n\n// I/O\nexport { createStdin, StdinImpl } from \"./io/index.cjs\";\nexport { createStdout, createStderr, createPipe, OutputCollectorImpl, PipeBuffer } from \"./io/index.cjs\";\n\n// Utilities\nexport { escape, escapeForInterpolation, globVirtualFS } from \"./utils/index.cjs\";\nexport type { GlobVirtualFS, GlobOptions } from \"./utils/index.cjs\";\n\n// Version Control\nexport { VersionControlSystem } from \"./vcs/index.cjs\";\nexport type {\n VCSConfig,\n VCSAttributeRule,\n VCSResolvedAttributes,\n VCSDiffMode,\n VCSPatchSuppressionReason,\n Revision,\n DiffEntry,\n TreeManifest,\n TreeEntry,\n FileEntry,\n DirectoryEntry,\n VCSIndexEntry,\n VCSIndexFile,\n CommitOptions,\n CheckoutOptions,\n LogOptions,\n LogEntry,\n BranchInfo,\n} from \"./vcs/index.cjs\";\n"
5
+ "// Main class exports\nexport { ShellDSL, createShellDSL, type Program } from \"./shell-dsl.cjs\";\nexport { ShellPromise, type ShellPromiseOptions } from \"./shell-promise.cjs\";\nexport { ShellSession, createShellSession, type ShellSessionOptions } from \"./shell-session.cjs\";\n\n// Types\nexport type {\n VirtualFS,\n VirtualFSWritable,\n FileStat,\n Command,\n CommandContext,\n Stdin,\n Stdout,\n Stderr,\n OutputCollector,\n ExecResult,\n ShellConfig,\n ShellCommandApi,\n ShellCommandFallback,\n ExternalCommandContext,\n ShellRunOptions,\n TerminalInfo,\n ShellInputController,\n ShellInputSource,\n ShellExecutionOptions,\n ShellExecution,\n ShellOutputEvent,\n RawValue,\n} from \"./types.cjs\";\nexport { isRawValue } from \"./types.cjs\";\n\n// Errors\nexport { ShellError, LexError, ParseError } from \"./errors.cjs\";\n\n// Lexer\nexport { Lexer, lex, tokenToString } from \"./lexer/index.cjs\";\nexport type { Token, RedirectMode } from \"./lexer/index.cjs\";\n\n// Parser\nexport { Parser, parse } from \"./parser/index.cjs\";\nexport type {\n ASTNode,\n Redirect,\n CommandNode,\n PipelineNode,\n AndNode,\n OrNode,\n SequenceNode,\n WordNode,\n WordPart,\n TextPart,\n VariablePart,\n SubstitutionPart,\n ArithmeticPart,\n IfNode,\n ForNode,\n WhileNode,\n UntilNode,\n CaseNode,\n CaseClause,\n} from \"./parser/index.cjs\";\nexport {\n isWordNode,\n isCommandNode,\n isPipelineNode,\n isAndNode,\n isOrNode,\n isSequenceNode,\n isIfNode,\n isForNode,\n isWhileNode,\n isUntilNode,\n isCaseNode,\n} from \"./parser/index.cjs\";\n\n// Interpreter\nexport { Interpreter, type InterpreterOptions, BreakException, ContinueException, ExitException } from \"./interpreter/index.cjs\";\n\n// Filesystem\nexport { createVirtualFS } from \"./fs/index.cjs\";\nexport {\n FileSystem,\n ReadOnlyFileSystem,\n WebFileSystem,\n createWebUnderlyingFS,\n type PathOps,\n type Permission,\n type PermissionRules,\n type UnderlyingFS,\n} from \"./fs/index.cjs\";\n\n// I/O\nexport { createStdin, StdinImpl } from \"./io/index.cjs\";\nexport {\n createStdout,\n createStderr,\n createPipe,\n createShellInput,\n OutputCollectorImpl,\n PipeBuffer,\n ShellInputControllerImpl,\n} from \"./io/index.cjs\";\n\n// Interactive input analysis\nexport { analyzeInput } from \"./input-analysis.cjs\";\nexport type { InputAnalysis, InputIncompleteReason } from \"./input-analysis.cjs\";\n\n// Utilities\nexport { escape, escapeForInterpolation, globVirtualFS } from \"./utils/index.cjs\";\nexport type { GlobVirtualFS, GlobOptions } from \"./utils/index.cjs\";\n\n// Version Control\nexport { VersionControlSystem } from \"./vcs/index.cjs\";\nexport type {\n VCSConfig,\n VCSAttributeRule,\n VCSResolvedAttributes,\n VCSDiffMode,\n VCSPatchSuppressionReason,\n Revision,\n DiffEntry,\n TreeManifest,\n TreeEntry,\n FileEntry,\n DirectoryEntry,\n VCSIndexEntry,\n VCSIndexFile,\n CommitOptions,\n CheckoutOptions,\n LogOptions,\n LogEntry,\n BranchInfo,\n} from \"./vcs/index.cjs\";\n"
6
6
  ],
7
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACuD,IAAvD;AACuD,IAAvD;AAmB2B,IAA3B;AAGiD,IAAjD;AAG0C,IAA1C;AAI8B,IAA9B;AAkCO,IAZP;AAeuG,IAAvG;AAGgC,IAAhC;AAUO,IATP;AAYuC,IAAvC;AACwF,IAAxF;AAG8D,IAA9D;AAIqC,IAArC;",
8
- "debugId": "A1C14CC7E153FA4864756E2164756E21",
7
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACuD,IAAvD;AACuD,IAAvD;AAC2E,IAA3E;AA2B2B,IAA3B;AAGiD,IAAjD;AAG0C,IAA1C;AAI8B,IAA9B;AAkCO,IAZP;AAeuG,IAAvG;AAGgC,IAAhC;AAUO,IATP;AAYuC,IAAvC;AASO,IARP;AAW6B,IAA7B;AAI8D,IAA9D;AAIqC,IAArC;",
8
+ "debugId": "CCA17BE2B94A373E64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -0,0 +1,154 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ function __accessProp(key) {
6
+ return this[key];
7
+ }
8
+ var __toCommonJS = (from) => {
9
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
10
+ if (entry)
11
+ return entry;
12
+ entry = __defProp({}, "__esModule", { value: true });
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (var key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(entry, key))
16
+ __defProp(entry, key, {
17
+ get: __accessProp.bind(from, key),
18
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
+ });
20
+ }
21
+ __moduleCache.set(from, entry);
22
+ return entry;
23
+ };
24
+ var __moduleCache;
25
+ var __returnValue = (v) => v;
26
+ function __exportSetter(name, newValue) {
27
+ this[name] = __returnValue.bind(null, newValue);
28
+ }
29
+ var __export = (target, all) => {
30
+ for (var name in all)
31
+ __defProp(target, name, {
32
+ get: all[name],
33
+ enumerable: true,
34
+ configurable: true,
35
+ set: __exportSetter.bind(all, name)
36
+ });
37
+ };
38
+
39
+ // src/input-analysis.ts
40
+ var exports_input_analysis = {};
41
+ __export(exports_input_analysis, {
42
+ analyzeInput: () => analyzeInput
43
+ });
44
+ module.exports = __toCommonJS(exports_input_analysis);
45
+ var import_lexer = require("./lexer/lexer.cjs");
46
+ var import_parser = require("./parser/parser.cjs");
47
+ var import_errors = require("./errors.cjs");
48
+ function analyzeInput(source) {
49
+ const heredoc = findIncompleteHeredoc(source);
50
+ if (heredoc) {
51
+ return { kind: "incomplete", reason: "heredoc" };
52
+ }
53
+ let tokens;
54
+ try {
55
+ tokens = new import_lexer.Lexer(source, { preserveNewlines: true }).tokenize();
56
+ } catch (err) {
57
+ if (err instanceof import_errors.LexError && err.message.toLowerCase().includes("unterminated")) {
58
+ return { kind: "incomplete", reason: "quote" };
59
+ }
60
+ return { kind: "invalid", error: err instanceof Error ? err : new Error(String(err)) };
61
+ }
62
+ if (tokens.every((token) => token.type === "newline" || token.type === "eof")) {
63
+ return { kind: "complete", ast: emptyCommandAst() };
64
+ }
65
+ if (hasTrailingPipeline(tokens)) {
66
+ return { kind: "incomplete", reason: "pipeline" };
67
+ }
68
+ try {
69
+ const ast = new import_parser.Parser(tokens).parse();
70
+ return { kind: "complete", ast };
71
+ } catch (err) {
72
+ if (hasOpenCompound(tokens)) {
73
+ return { kind: "incomplete", reason: "compound" };
74
+ }
75
+ if (err instanceof import_errors.ParseError && isIncompleteParseMessage(err.message)) {
76
+ return { kind: "incomplete", reason: "compound" };
77
+ }
78
+ return { kind: "invalid", error: err instanceof Error ? err : new Error(String(err)) };
79
+ }
80
+ }
81
+ function emptyCommandAst() {
82
+ return {
83
+ type: "command",
84
+ name: { type: "word", parts: [] },
85
+ args: [],
86
+ redirects: [],
87
+ assignments: []
88
+ };
89
+ }
90
+ function hasTrailingPipeline(tokens) {
91
+ let i = tokens.length - 1;
92
+ while (i >= 0 && (tokens[i].type === "eof" || tokens[i].type === "newline")) {
93
+ i--;
94
+ }
95
+ return i >= 0 && tokens[i].type === "pipe";
96
+ }
97
+ function hasOpenCompound(tokens) {
98
+ const stack = [];
99
+ for (const token of tokens) {
100
+ if (token.type !== "keyword") {
101
+ continue;
102
+ }
103
+ switch (token.value) {
104
+ case "if":
105
+ stack.push("fi");
106
+ break;
107
+ case "for":
108
+ case "while":
109
+ case "until":
110
+ stack.push("done");
111
+ break;
112
+ case "case":
113
+ stack.push("esac");
114
+ break;
115
+ case "fi":
116
+ case "done":
117
+ case "esac":
118
+ if (stack[stack.length - 1] === token.value) {
119
+ stack.pop();
120
+ }
121
+ break;
122
+ }
123
+ }
124
+ return stack.length > 0;
125
+ }
126
+ function isIncompleteParseMessage(message) {
127
+ return message.includes("Expected 'fi'") || message.includes("Expected 'done'") || message.includes("Expected 'esac'") || message.includes("Expected 'then'") || message.includes("Expected 'do'");
128
+ }
129
+ function findIncompleteHeredoc(source) {
130
+ const lines = source.split(/\n/);
131
+ for (let i = 0;i < lines.length; i++) {
132
+ const match = lines[i].match(/<<-?\s*(?:"([^"]+)"|'([^']+)'|([A-Za-z_][A-Za-z0-9_]*|\S+))/);
133
+ if (!match) {
134
+ continue;
135
+ }
136
+ const delimiter = match[1] ?? match[2] ?? match[3];
137
+ if (!delimiter) {
138
+ continue;
139
+ }
140
+ let found = false;
141
+ for (let j = i + 1;j < lines.length; j++) {
142
+ if (lines[j] === delimiter || lines[j].replace(/^\t+/, "") === delimiter) {
143
+ found = true;
144
+ break;
145
+ }
146
+ }
147
+ if (!found) {
148
+ return true;
149
+ }
150
+ }
151
+ return false;
152
+ }
153
+
154
+ //# debugId=32A2D9A6396B91EF64756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/input-analysis.ts"],
4
+ "sourcesContent": [
5
+ "import type { ASTNode } from \"./parser/ast.cjs\";\nimport type { Token, KeywordValue } from \"./lexer/tokens.cjs\";\nimport { Lexer } from \"./lexer/lexer.cjs\";\nimport { Parser } from \"./parser/parser.cjs\";\nimport { LexError, ParseError } from \"./errors.cjs\";\n\nexport type InputIncompleteReason = \"quote\" | \"heredoc\" | \"compound\" | \"pipeline\";\n\nexport type InputAnalysis =\n | { kind: \"complete\"; ast: ASTNode }\n | { kind: \"incomplete\"; reason: InputIncompleteReason }\n | { kind: \"invalid\"; error: LexError | ParseError | Error };\n\nexport function analyzeInput(source: string): InputAnalysis {\n const heredoc = findIncompleteHeredoc(source);\n if (heredoc) {\n return { kind: \"incomplete\", reason: \"heredoc\" };\n }\n\n let tokens: Token[];\n try {\n tokens = new Lexer(source, { preserveNewlines: true }).tokenize();\n } catch (err) {\n if (err instanceof LexError && err.message.toLowerCase().includes(\"unterminated\")) {\n return { kind: \"incomplete\", reason: \"quote\" };\n }\n return { kind: \"invalid\", error: err instanceof Error ? err : new Error(String(err)) };\n }\n\n if (tokens.every((token) => token.type === \"newline\" || token.type === \"eof\")) {\n return { kind: \"complete\", ast: emptyCommandAst() };\n }\n\n if (hasTrailingPipeline(tokens)) {\n return { kind: \"incomplete\", reason: \"pipeline\" };\n }\n\n try {\n const ast = new Parser(tokens).parse();\n return { kind: \"complete\", ast };\n } catch (err) {\n if (hasOpenCompound(tokens)) {\n return { kind: \"incomplete\", reason: \"compound\" };\n }\n if (err instanceof ParseError && isIncompleteParseMessage(err.message)) {\n return { kind: \"incomplete\", reason: \"compound\" };\n }\n return { kind: \"invalid\", error: err instanceof Error ? err : new Error(String(err)) };\n }\n}\n\nfunction emptyCommandAst(): ASTNode {\n return {\n type: \"command\",\n name: { type: \"word\", parts: [] },\n args: [],\n redirects: [],\n assignments: [],\n };\n}\n\nfunction hasTrailingPipeline(tokens: Token[]): boolean {\n let i = tokens.length - 1;\n while (i >= 0 && (tokens[i]!.type === \"eof\" || tokens[i]!.type === \"newline\")) {\n i--;\n }\n return i >= 0 && tokens[i]!.type === \"pipe\";\n}\n\nfunction hasOpenCompound(tokens: Token[]): boolean {\n const stack: KeywordValue[] = [];\n\n for (const token of tokens) {\n if (token.type !== \"keyword\") {\n continue;\n }\n\n switch (token.value) {\n case \"if\":\n stack.push(\"fi\");\n break;\n case \"for\":\n case \"while\":\n case \"until\":\n stack.push(\"done\");\n break;\n case \"case\":\n stack.push(\"esac\");\n break;\n case \"fi\":\n case \"done\":\n case \"esac\":\n if (stack[stack.length - 1] === token.value) {\n stack.pop();\n }\n break;\n }\n }\n\n return stack.length > 0;\n}\n\nfunction isIncompleteParseMessage(message: string): boolean {\n return (\n message.includes(\"Expected 'fi'\") ||\n message.includes(\"Expected 'done'\") ||\n message.includes(\"Expected 'esac'\") ||\n message.includes(\"Expected 'then'\") ||\n message.includes(\"Expected 'do'\")\n );\n}\n\nfunction findIncompleteHeredoc(source: string): boolean {\n const lines = source.split(/\\n/);\n\n for (let i = 0; i < lines.length; i++) {\n const match = lines[i]!.match(/<<-?\\s*(?:\"([^\"]+)\"|'([^']+)'|([A-Za-z_][A-Za-z0-9_]*|\\S+))/);\n if (!match) {\n continue;\n }\n\n const delimiter = match[1] ?? match[2] ?? match[3];\n if (!delimiter) {\n continue;\n }\n\n let found = false;\n for (let j = i + 1; j < lines.length; j++) {\n if (lines[j] === delimiter || lines[j]!.replace(/^\\t+/, \"\") === delimiter) {\n found = true;\n break;\n }\n }\n if (!found) {\n return true;\n }\n }\n\n return false;\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "32A2D9A6396B91EF64756E2164756E21",
9
+ "names": []
10
+ }
@@ -51,6 +51,8 @@ function createCommandContext(options) {
51
51
  fs: options.fs,
52
52
  cwd: options.cwd,
53
53
  env: options.env,
54
+ terminal: options.terminal,
55
+ signal: options.signal,
54
56
  setCwd: options.setCwd
55
57
  };
56
58
  if (options.exec) {
@@ -62,4 +64,4 @@ function createCommandContext(options) {
62
64
  return ctx;
63
65
  }
64
66
 
65
- //# debugId=A1213B6F49E1441764756E2164756E21
67
+ //# debugId=EF2EDEF001ADFBB864756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/interpreter/context.ts"],
4
4
  "sourcesContent": [
5
- "import type { CommandContext, VirtualFS, Stdin, Stdout, Stderr, ExecResult, ShellCommandApi } from \"../types.cjs\";\n\nexport interface ContextOptions {\n args: string[];\n stdin: Stdin;\n stdout: Stdout;\n stderr: Stderr;\n fs: VirtualFS;\n cwd: string;\n env: Record<string, string>;\n setCwd: (path: string) => void;\n exec?: (name: string, args: string[]) => Promise<ExecResult>;\n shell?: ShellCommandApi;\n}\n\nexport function createCommandContext(options: ContextOptions): CommandContext {\n const ctx: CommandContext = {\n args: options.args,\n stdin: options.stdin,\n stdout: options.stdout,\n stderr: options.stderr,\n fs: options.fs,\n cwd: options.cwd,\n env: options.env,\n setCwd: options.setCwd,\n };\n if (options.exec) {\n ctx.exec = options.exec;\n }\n if (options.shell) {\n ctx.shell = options.shell;\n }\n return ctx;\n}\n"
5
+ "import type {\n CommandContext,\n VirtualFS,\n Stdin,\n Stdout,\n Stderr,\n ExecResult,\n ShellCommandApi,\n TerminalInfo,\n} from \"../types.cjs\";\n\nexport interface ContextOptions {\n args: string[];\n stdin: Stdin;\n stdout: Stdout;\n stderr: Stderr;\n fs: VirtualFS;\n cwd: string;\n env: Record<string, string>;\n terminal: TerminalInfo;\n signal: AbortSignal;\n setCwd: (path: string) => void;\n exec?: (name: string, args: string[]) => Promise<ExecResult>;\n shell?: ShellCommandApi;\n}\n\nexport function createCommandContext(options: ContextOptions): CommandContext {\n const ctx: CommandContext = {\n args: options.args,\n stdin: options.stdin,\n stdout: options.stdout,\n stderr: options.stderr,\n fs: options.fs,\n cwd: options.cwd,\n env: options.env,\n terminal: options.terminal,\n signal: options.signal,\n setCwd: options.setCwd,\n };\n if (options.exec) {\n ctx.exec = options.exec;\n }\n if (options.shell) {\n ctx.shell = options.shell;\n }\n return ctx;\n}\n"
6
6
  ],
7
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeO,SAAS,oBAAoB,CAAC,SAAyC;AAAA,EAC5E,MAAM,MAAsB;AAAA,IAC1B,MAAM,QAAQ;AAAA,IACd,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ;AAAA,IAChB,QAAQ,QAAQ;AAAA,IAChB,IAAI,QAAQ;AAAA,IACZ,KAAK,QAAQ;AAAA,IACb,KAAK,QAAQ;AAAA,IACb,QAAQ,QAAQ;AAAA,EAClB;AAAA,EACA,IAAI,QAAQ,MAAM;AAAA,IAChB,IAAI,OAAO,QAAQ;AAAA,EACrB;AAAA,EACA,IAAI,QAAQ,OAAO;AAAA,IACjB,IAAI,QAAQ,QAAQ;AAAA,EACtB;AAAA,EACA,OAAO;AAAA;",
8
- "debugId": "A1213B6F49E1441764756E2164756E21",
7
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BO,SAAS,oBAAoB,CAAC,SAAyC;AAAA,EAC5E,MAAM,MAAsB;AAAA,IAC1B,MAAM,QAAQ;AAAA,IACd,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ;AAAA,IAChB,QAAQ,QAAQ;AAAA,IAChB,IAAI,QAAQ;AAAA,IACZ,KAAK,QAAQ;AAAA,IACb,KAAK,QAAQ;AAAA,IACb,UAAU,QAAQ;AAAA,IAClB,QAAQ,QAAQ;AAAA,IAChB,QAAQ,QAAQ;AAAA,EAClB;AAAA,EACA,IAAI,QAAQ,MAAM;AAAA,IAChB,IAAI,OAAO,QAAQ;AAAA,EACrB;AAAA,EACA,IAAI,QAAQ,OAAO;AAAA,IACjB,IAAI,QAAQ,QAAQ;AAAA,EACtB;AAAA,EACA,OAAO;AAAA;",
8
+ "debugId": "EF2EDEF001ADFBB864756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -50,6 +50,7 @@ var import_lexer = require("../lexer/lexer.cjs");
50
50
  var import_parser = require("../parser/parser.cjs");
51
51
  var import_stdin = require("../io/stdin.cjs");
52
52
  var import_stdout = require("../io/stdout.cjs");
53
+ var import_async_queue = require("../io/async-queue.cjs");
53
54
  var import_special_files = require("../fs/special-files.cjs");
54
55
  var DEFAULT_IFS = `
55
56
  `;
@@ -87,6 +88,9 @@ class Interpreter {
87
88
  redirectObjects;
88
89
  loopDepth = 0;
89
90
  isTTY;
91
+ terminal;
92
+ activeSignal;
93
+ externalCommand;
90
94
  argv0;
91
95
  positionalParameters;
92
96
  lastExitCode;
@@ -96,7 +100,10 @@ class Interpreter {
96
100
  this.env = { ...options.env };
97
101
  this.commands = options.commands;
98
102
  this.redirectObjects = options.redirectObjects ?? {};
99
- this.isTTY = options.isTTY ?? false;
103
+ this.terminal = options.terminal ?? { isTTY: options.isTTY ?? false };
104
+ this.isTTY = this.terminal.isTTY;
105
+ this.activeSignal = options.signal ?? new AbortController().signal;
106
+ this.externalCommand = options.externalCommand;
100
107
  this.argv0 = options.argv0 ?? "sh";
101
108
  this.positionalParameters = [...options.positionalParameters ?? []];
102
109
  this.lastExitCode = options.lastExitCode ?? 0;
@@ -105,27 +112,88 @@ class Interpreter {
105
112
  return this.loopDepth;
106
113
  }
107
114
  async execute(ast) {
108
- const stdout = import_stdout.createStdout(this.isTTY);
109
- const stderr = import_stdout.createStderr(this.isTTY);
110
- let exitCode;
111
- try {
112
- exitCode = await this.executeNode(ast, null, stdout, stderr);
113
- } catch (err) {
114
- if (!(err instanceof ExitException)) {
115
- throw err;
115
+ return this.executeStreaming(ast).exit;
116
+ }
117
+ executeStreaming(ast, options = {}) {
118
+ const terminal = options.terminal ?? this.terminal;
119
+ const controller = new AbortController;
120
+ const eventQueue = new import_async_queue.AsyncQueue;
121
+ if (options.signal) {
122
+ if (options.signal.aborted) {
123
+ controller.abort(options.signal.reason);
124
+ } else {
125
+ options.signal.addEventListener("abort", () => controller.abort(options.signal?.reason), { once: true });
116
126
  }
117
- exitCode = err.exitCode;
118
- this.lastExitCode = exitCode;
119
127
  }
120
- stdout.close();
121
- stderr.close();
128
+ const stdout = import_stdout.createStdout(terminal.isTTY, async (chunk) => {
129
+ eventQueue.push({ fd: 1, chunk });
130
+ await options.stdout?.write(chunk);
131
+ });
132
+ const stderr = import_stdout.createStderr(terminal.isTTY, async (chunk) => {
133
+ eventQueue.push({ fd: 2, chunk });
134
+ await options.stderr?.write(chunk);
135
+ });
136
+ const previousSignal = this.activeSignal;
137
+ const previousTerminal = this.terminal;
138
+ const previousIsTTY = this.isTTY;
139
+ const stdinSource = this.normalizeInputSource(options.stdin);
140
+ const exit = (async () => {
141
+ this.activeSignal = controller.signal;
142
+ this.terminal = terminal;
143
+ this.isTTY = terminal.isTTY;
144
+ let exitCode;
145
+ try {
146
+ this.throwIfAborted();
147
+ exitCode = await this.executeNode(ast, stdinSource, stdout, stderr);
148
+ } catch (err) {
149
+ if (err instanceof ExitException) {
150
+ exitCode = err.exitCode;
151
+ } else if (controller.signal.aborted) {
152
+ exitCode = 130;
153
+ } else {
154
+ throw err;
155
+ }
156
+ } finally {
157
+ stdout.close();
158
+ stderr.close();
159
+ eventQueue.close();
160
+ this.activeSignal = previousSignal;
161
+ this.terminal = previousTerminal;
162
+ this.isTTY = previousIsTTY;
163
+ }
164
+ this.lastExitCode = exitCode;
165
+ return {
166
+ stdout: await stdout.collect(),
167
+ stderr: await stderr.collect(),
168
+ exitCode
169
+ };
170
+ })();
122
171
  return {
123
- stdout: await stdout.collect(),
124
- stderr: await stderr.collect(),
125
- exitCode
172
+ stdout: stdout.getReadableStream(),
173
+ stderr: stderr.getReadableStream(),
174
+ output: eventQueue,
175
+ exit,
176
+ kill: (reason) => controller.abort(reason ?? new Error("Shell execution killed"))
126
177
  };
127
178
  }
179
+ normalizeInputSource(source) {
180
+ if (source === undefined || source === null) {
181
+ return null;
182
+ }
183
+ if (typeof source === "string") {
184
+ return async function* () {
185
+ yield new TextEncoder().encode(source);
186
+ }();
187
+ }
188
+ if (Buffer.isBuffer(source)) {
189
+ return async function* () {
190
+ yield new Uint8Array(source);
191
+ }();
192
+ }
193
+ return source;
194
+ }
128
195
  async executeNode(node, stdinSource, stdout, stderr) {
196
+ this.throwIfAborted();
129
197
  let exitCode;
130
198
  switch (node.type) {
131
199
  case "command":
@@ -164,6 +232,11 @@ class Interpreter {
164
232
  this.lastExitCode = exitCode;
165
233
  return exitCode;
166
234
  }
235
+ throwIfAborted() {
236
+ if (this.activeSignal.aborted) {
237
+ throw this.activeSignal.reason ?? new Error("Shell execution aborted");
238
+ }
239
+ }
167
240
  async executeCommand(node, stdinSource, stdout, stderr) {
168
241
  const assignmentEnv = { ...this.env };
169
242
  for (const assignment of node.assignments) {
@@ -235,12 +308,55 @@ class Interpreter {
235
308
  return this.invokeRegisteredCommand(name, command, args, stdinSource, stdout, stderr, env);
236
309
  }
237
310
  if (name.includes("/")) {
311
+ const resolved = this.fs.resolve(this.cwd, name);
312
+ if (await this.fs.exists(resolved)) {
313
+ return this.executeExecutableFile(name, args, stdinSource, stdout, stderr, env);
314
+ }
315
+ if (this.externalCommand) {
316
+ return this.invokeExternalCommand(name, args, stdinSource, stdout, stderr, env);
317
+ }
238
318
  return this.executeExecutableFile(name, args, stdinSource, stdout, stderr, env);
239
319
  }
320
+ if (this.externalCommand) {
321
+ return this.invokeExternalCommand(name, args, stdinSource, stdout, stderr, env);
322
+ }
240
323
  await stderr.writeText(`${name}: command not found
241
324
  `);
242
325
  return 127;
243
326
  }
327
+ async invokeExternalCommand(name, args, stdinSource, stdout, stderr, env) {
328
+ if (!this.externalCommand) {
329
+ return 127;
330
+ }
331
+ const ctx = import_context.createCommandContext({
332
+ args,
333
+ stdin: import_stdin.createStdin(stdinSource),
334
+ stdout,
335
+ stderr,
336
+ fs: this.fs,
337
+ cwd: this.cwd,
338
+ env,
339
+ terminal: this.terminal,
340
+ signal: this.activeSignal,
341
+ setCwd: (path) => this.setCwd(path),
342
+ exec: this.createExec(env),
343
+ shell: this.createShellApi(stdinSource, stdout, stderr, env)
344
+ });
345
+ try {
346
+ return await this.externalCommand({ ...ctx, name });
347
+ } catch (err) {
348
+ if (err instanceof BreakException || err instanceof ContinueException || err instanceof ExitException) {
349
+ throw err;
350
+ }
351
+ if (this.activeSignal.aborted) {
352
+ throw err;
353
+ }
354
+ const message = err instanceof Error ? err.message : String(err);
355
+ await stderr.writeText(`${name}: ${message}
356
+ `);
357
+ return 1;
358
+ }
359
+ }
244
360
  async invokeRegisteredCommand(name, command, args, stdinSource, stdout, stderr, env) {
245
361
  const exec = this.createExec(env);
246
362
  const shell = this.createShellApi(stdinSource, stdout, stderr, env);
@@ -252,6 +368,8 @@ class Interpreter {
252
368
  fs: this.fs,
253
369
  cwd: this.cwd,
254
370
  env,
371
+ terminal: this.terminal,
372
+ signal: this.activeSignal,
255
373
  setCwd: (path) => this.setCwd(path),
256
374
  exec,
257
375
  shell
@@ -315,7 +433,9 @@ class Interpreter {
315
433
  env: { ...env },
316
434
  commands: this.commands,
317
435
  redirectObjects: this.redirectObjects,
318
- isTTY: this.isTTY,
436
+ terminal: this.terminal,
437
+ externalCommand: this.externalCommand,
438
+ signal: this.activeSignal,
319
439
  argv0: shebang.command,
320
440
  positionalParameters: []
321
441
  });
@@ -335,7 +455,9 @@ class Interpreter {
335
455
  env: { ...env },
336
456
  commands: this.commands,
337
457
  redirectObjects: this.redirectObjects,
338
- isTTY: this.isTTY,
458
+ terminal: this.terminal,
459
+ externalCommand: this.externalCommand,
460
+ signal: this.activeSignal,
339
461
  argv0,
340
462
  positionalParameters: args
341
463
  });
@@ -928,6 +1050,8 @@ class Interpreter {
928
1050
  commands: this.commands,
929
1051
  redirectObjects: this.redirectObjects,
930
1052
  isTTY: false,
1053
+ externalCommand: this.externalCommand,
1054
+ signal: this.activeSignal,
931
1055
  argv0: this.argv0,
932
1056
  positionalParameters: this.positionalParameters,
933
1057
  lastExitCode: this.lastExitCode
@@ -1208,6 +1332,9 @@ class Interpreter {
1208
1332
  getEnv() {
1209
1333
  return { ...this.env };
1210
1334
  }
1335
+ getLastExitCode() {
1336
+ return this.lastExitCode;
1337
+ }
1211
1338
  }
1212
1339
 
1213
- //# debugId=48778DC29A4E811364756E2164756E21
1340
+ //# debugId=F9FD7440AC6CB03664756E2164756E21