shell-dsl 0.0.41 → 0.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/index.cjs.map +2 -2
- package/dist/cjs/src/shell-session.cjs +96 -2
- package/dist/cjs/src/shell-session.cjs.map +3 -3
- package/dist/cjs/src/types.cjs.map +2 -2
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/src/index.mjs.map +2 -2
- package/dist/mjs/src/shell-session.mjs +96 -2
- package/dist/mjs/src/shell-session.mjs.map +3 -3
- package/dist/mjs/src/types.mjs.map +2 -2
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/shell-session.d.ts +8 -1
- package/dist/types/src/types.d.ts +19 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -393,6 +393,29 @@ const session = createShellSession({
|
|
|
393
393
|
});
|
|
394
394
|
```
|
|
395
395
|
|
|
396
|
+
Custom commands can provide argument completions by registering a `CommandCompleter` under the same command name:
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
import type { CommandCompleter } from "shell-dsl";
|
|
400
|
+
|
|
401
|
+
const completions: Record<string, CommandCompleter> = {
|
|
402
|
+
deploy: async (ctx) => ({
|
|
403
|
+
replacement: ctx.word,
|
|
404
|
+
matches: ["--dry-run ", "--prod ", "--staging "].filter((match) =>
|
|
405
|
+
match.startsWith(ctx.word)
|
|
406
|
+
),
|
|
407
|
+
}),
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const session = createShellSession({
|
|
411
|
+
fs,
|
|
412
|
+
cwd: "/",
|
|
413
|
+
env: {},
|
|
414
|
+
commands: { ...builtinCommands, deploy },
|
|
415
|
+
completions,
|
|
416
|
+
});
|
|
417
|
+
```
|
|
418
|
+
|
|
396
419
|
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
420
|
|
|
398
421
|
## Terminal Demo
|
package/dist/cjs/package.json
CHANGED
|
@@ -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\";\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"
|
|
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 CommandCompleter,\n CommandContext,\n CompletionContext,\n CompletionResult,\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;AAC2E,IAA3E;
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACuD,IAAvD;AACuD,IAAvD;AAC2E,IAA3E;AA8B2B,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
8
|
"debugId": "CCA17BE2B94A373E64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -50,14 +50,22 @@ var import_stdout = require("./io/stdout.cjs");
|
|
|
50
50
|
var import_async_queue = require("./io/async-queue.cjs");
|
|
51
51
|
|
|
52
52
|
class ShellSession {
|
|
53
|
+
fs;
|
|
54
|
+
commands;
|
|
55
|
+
completions;
|
|
56
|
+
terminal;
|
|
53
57
|
interpreter;
|
|
54
58
|
constructor(options) {
|
|
59
|
+
this.fs = options.fs;
|
|
60
|
+
this.commands = options.commands;
|
|
61
|
+
this.completions = options.completions ?? {};
|
|
62
|
+
this.terminal = options.terminal ?? { isTTY: options.isTTY ?? false };
|
|
55
63
|
this.interpreter = new import_interpreter.Interpreter({
|
|
56
64
|
fs: options.fs,
|
|
57
65
|
cwd: options.cwd,
|
|
58
66
|
env: options.env,
|
|
59
67
|
commands: options.commands,
|
|
60
|
-
terminal:
|
|
68
|
+
terminal: this.terminal,
|
|
61
69
|
externalCommand: options.externalCommand
|
|
62
70
|
});
|
|
63
71
|
}
|
|
@@ -87,7 +95,63 @@ class ShellSession {
|
|
|
87
95
|
getLastExitCode() {
|
|
88
96
|
return this.interpreter.getLastExitCode();
|
|
89
97
|
}
|
|
98
|
+
async complete(source, cursor = source.length) {
|
|
99
|
+
const boundedCursor = Math.max(0, Math.min(cursor, source.length));
|
|
100
|
+
const prefix = source.slice(0, boundedCursor);
|
|
101
|
+
const { word, start } = getCurrentWord(prefix);
|
|
102
|
+
if (isCommandPosition(prefix, start) && !looksLikePath(word)) {
|
|
103
|
+
return {
|
|
104
|
+
replacement: word,
|
|
105
|
+
matches: Object.keys(this.commands).filter((name) => name.startsWith(word)).sort().map((name) => `${name} `)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const commandLine = getCurrentCommandLine(prefix, start);
|
|
109
|
+
const [command = "", ...args] = splitCompletionWords(commandLine);
|
|
110
|
+
const completer = command === "" ? undefined : this.completions[command];
|
|
111
|
+
if (completer) {
|
|
112
|
+
return completer({
|
|
113
|
+
source,
|
|
114
|
+
cursor: boundedCursor,
|
|
115
|
+
command,
|
|
116
|
+
args,
|
|
117
|
+
word,
|
|
118
|
+
wordStart: start,
|
|
119
|
+
wordEnd: boundedCursor,
|
|
120
|
+
cwd: this.getCwd(),
|
|
121
|
+
env: this.getEnv(),
|
|
122
|
+
fs: this.fs,
|
|
123
|
+
terminal: this.terminal
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return this.completePath(word);
|
|
127
|
+
}
|
|
90
128
|
async dispose() {}
|
|
129
|
+
async completePath(word) {
|
|
130
|
+
const slash = word.lastIndexOf("/");
|
|
131
|
+
const dirPart = slash === -1 ? "" : word.slice(0, slash + 1);
|
|
132
|
+
const namePrefix = slash === -1 ? word : word.slice(slash + 1);
|
|
133
|
+
const basePath = dirPart === "" ? this.getCwd() : dirPart.startsWith("/") ? dirPart : this.fs.resolve(this.getCwd(), dirPart);
|
|
134
|
+
let entries;
|
|
135
|
+
try {
|
|
136
|
+
entries = await this.fs.readdir(basePath);
|
|
137
|
+
} catch {
|
|
138
|
+
return { replacement: word, matches: [] };
|
|
139
|
+
}
|
|
140
|
+
const matches = await Promise.all(entries.filter((entry) => entry.startsWith(namePrefix)).sort().map(async (entry) => {
|
|
141
|
+
const candidate = `${dirPart}${escapeCompletionSegment(entry)}`;
|
|
142
|
+
const path = this.fs.resolve(basePath, entry);
|
|
143
|
+
try {
|
|
144
|
+
const stat = await this.fs.stat(path);
|
|
145
|
+
return stat.isDirectory() ? `${candidate}/` : candidate;
|
|
146
|
+
} catch {
|
|
147
|
+
return candidate;
|
|
148
|
+
}
|
|
149
|
+
}));
|
|
150
|
+
if (matches.length === 1 && !matches[0].endsWith("/")) {
|
|
151
|
+
matches[0] = `${matches[0]} `;
|
|
152
|
+
}
|
|
153
|
+
return { replacement: word, matches };
|
|
154
|
+
}
|
|
91
155
|
createImmediateExecution(exitCode, stdoutText, stderrText) {
|
|
92
156
|
const stdout = import_stdout.createStdout();
|
|
93
157
|
const stderr = import_stdout.createStderr();
|
|
@@ -124,5 +188,35 @@ class ShellSession {
|
|
|
124
188
|
function createShellSession(config) {
|
|
125
189
|
return new ShellSession(config);
|
|
126
190
|
}
|
|
191
|
+
function getCurrentWord(prefix) {
|
|
192
|
+
let start = prefix.length;
|
|
193
|
+
while (start > 0 && !/\s/.test(prefix[start - 1])) {
|
|
194
|
+
start--;
|
|
195
|
+
}
|
|
196
|
+
return { word: prefix.slice(start), start };
|
|
197
|
+
}
|
|
198
|
+
function isCommandPosition(prefix, wordStart) {
|
|
199
|
+
const beforeWord = prefix.slice(0, wordStart);
|
|
200
|
+
const segmentStart = Math.max(beforeWord.lastIndexOf(";"), beforeWord.lastIndexOf("|"), beforeWord.lastIndexOf("&"));
|
|
201
|
+
const segment = beforeWord.slice(segmentStart + 1).trim();
|
|
202
|
+
if (segment === "") {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
return segment.split(/\s+/).every((part) => /^[A-Za-z_][A-Za-z0-9_]*=/.test(part));
|
|
206
|
+
}
|
|
207
|
+
function getCurrentCommandLine(prefix, wordStart) {
|
|
208
|
+
const beforeWord = prefix.slice(0, wordStart);
|
|
209
|
+
const segmentStart = Math.max(beforeWord.lastIndexOf(";"), beforeWord.lastIndexOf("|"), beforeWord.lastIndexOf("&"));
|
|
210
|
+
return prefix.slice(segmentStart + 1).trimStart();
|
|
211
|
+
}
|
|
212
|
+
function splitCompletionWords(source) {
|
|
213
|
+
return source.trim().split(/\s+/).filter(Boolean);
|
|
214
|
+
}
|
|
215
|
+
function looksLikePath(word) {
|
|
216
|
+
return word.startsWith("/") || word.startsWith(".") || word.includes("/");
|
|
217
|
+
}
|
|
218
|
+
function escapeCompletionSegment(segment) {
|
|
219
|
+
return segment.replace(/([\s\\'"$`!#&;|<>()[\]{}*?])/g, "\\$1");
|
|
220
|
+
}
|
|
127
221
|
|
|
128
|
-
//# debugId=
|
|
222
|
+
//# debugId=C807EA9CC9D2A2E164756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/shell-session.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type {\n Command,\n ExecResult,\n ShellCommandFallback,\n ShellConfig,\n ShellExecution,\n ShellExecutionOptions,\n TerminalInfo,\n VirtualFS,\n} from \"./types.cjs\";\nimport type { Program } from \"./shell-dsl.cjs\";\nimport { Lexer } from \"./lexer/lexer.cjs\";\nimport { Parser } from \"./parser/parser.cjs\";\nimport { Interpreter } from \"./interpreter/interpreter.cjs\";\nimport { createStderr, createStdout } from \"./io/stdout.cjs\";\nimport { AsyncQueue } from \"./io/async-queue.cjs\";\nimport type { ShellOutputEvent } from \"./types.cjs\";\n\nexport interface ShellSessionOptions {\n fs: VirtualFS;\n cwd: string;\n env: Record<string, string>;\n commands: Record<string, Command>;\n isTTY?: boolean;\n terminal?: TerminalInfo;\n externalCommand?: ShellCommandFallback;\n}\n\nexport class ShellSession {\n private interpreter: Interpreter;\n\n constructor(options: ShellSessionOptions) {\n this.interpreter = new Interpreter({\n fs: options.fs,\n cwd: options.cwd,\n env: options.env,\n commands: options.commands,\n terminal:
|
|
5
|
+
"import type {\n Command,\n CommandCompleter,\n CompletionResult,\n ExecResult,\n ShellCommandFallback,\n ShellConfig,\n ShellExecution,\n ShellExecutionOptions,\n TerminalInfo,\n VirtualFS,\n} from \"./types.cjs\";\nimport type { Program } from \"./shell-dsl.cjs\";\nimport { Lexer } from \"./lexer/lexer.cjs\";\nimport { Parser } from \"./parser/parser.cjs\";\nimport { Interpreter } from \"./interpreter/interpreter.cjs\";\nimport { createStderr, createStdout } from \"./io/stdout.cjs\";\nimport { AsyncQueue } from \"./io/async-queue.cjs\";\nimport type { ShellOutputEvent } from \"./types.cjs\";\n\nexport interface ShellSessionOptions {\n fs: VirtualFS;\n cwd: string;\n env: Record<string, string>;\n commands: Record<string, Command>;\n completions?: Record<string, CommandCompleter>;\n isTTY?: boolean;\n terminal?: TerminalInfo;\n externalCommand?: ShellCommandFallback;\n}\n\nexport class ShellSession {\n private fs: VirtualFS;\n private commands: Record<string, Command>;\n private completions: Record<string, CommandCompleter>;\n private terminal: TerminalInfo;\n private interpreter: Interpreter;\n\n constructor(options: ShellSessionOptions) {\n this.fs = options.fs;\n this.commands = options.commands;\n this.completions = options.completions ?? {};\n this.terminal = options.terminal ?? { isTTY: options.isTTY ?? false };\n this.interpreter = new Interpreter({\n fs: options.fs,\n cwd: options.cwd,\n env: options.env,\n commands: options.commands,\n terminal: this.terminal,\n externalCommand: options.externalCommand,\n });\n }\n\n run(source: string, options: ShellExecutionOptions = {}): ShellExecution {\n try {\n const tokens = new Lexer(source, { preserveNewlines: true }).tokenize();\n if (tokens.every((token) => token.type === \"newline\" || token.type === \"eof\")) {\n return this.createImmediateExecution(0, \"\", \"\");\n }\n const ast = new Parser(tokens).parse();\n return this.interpreter.executeStreaming(ast, options);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return this.createImmediateExecution(2, \"\", `sh: ${message}\\n`);\n }\n }\n\n runProgram(program: Program, options: ShellExecutionOptions = {}): ShellExecution {\n return this.interpreter.executeStreaming(program.ast, options);\n }\n\n getCwd(): string {\n return this.interpreter.getCwd();\n }\n\n getEnv(): Record<string, string> {\n return this.interpreter.getEnv();\n }\n\n getLastExitCode(): number {\n return this.interpreter.getLastExitCode();\n }\n\n async complete(source: string, cursor: number = source.length): Promise<CompletionResult> {\n const boundedCursor = Math.max(0, Math.min(cursor, source.length));\n const prefix = source.slice(0, boundedCursor);\n const { word, start } = getCurrentWord(prefix);\n\n if (isCommandPosition(prefix, start) && !looksLikePath(word)) {\n return {\n replacement: word,\n matches: Object.keys(this.commands)\n .filter((name) => name.startsWith(word))\n .sort()\n .map((name) => `${name} `),\n };\n }\n\n const commandLine = getCurrentCommandLine(prefix, start);\n const [command = \"\", ...args] = splitCompletionWords(commandLine);\n const completer = command === \"\" ? undefined : this.completions[command];\n if (completer) {\n return completer({\n source,\n cursor: boundedCursor,\n command,\n args,\n word,\n wordStart: start,\n wordEnd: boundedCursor,\n cwd: this.getCwd(),\n env: this.getEnv(),\n fs: this.fs,\n terminal: this.terminal,\n });\n }\n\n return this.completePath(word);\n }\n\n async dispose(): Promise<void> {\n // Reserved for future resources owned by a session.\n }\n\n private async completePath(word: string): Promise<CompletionResult> {\n const slash = word.lastIndexOf(\"/\");\n const dirPart = slash === -1 ? \"\" : word.slice(0, slash + 1);\n const namePrefix = slash === -1 ? word : word.slice(slash + 1);\n const basePath = dirPart === \"\"\n ? this.getCwd()\n : dirPart.startsWith(\"/\")\n ? dirPart\n : this.fs.resolve(this.getCwd(), dirPart);\n\n let entries: string[];\n try {\n entries = await this.fs.readdir(basePath);\n } catch {\n return { replacement: word, matches: [] };\n }\n\n const matches = await Promise.all(\n entries\n .filter((entry) => entry.startsWith(namePrefix))\n .sort()\n .map(async (entry) => {\n const candidate = `${dirPart}${escapeCompletionSegment(entry)}`;\n const path = this.fs.resolve(basePath, entry);\n try {\n const stat = await this.fs.stat(path);\n return stat.isDirectory() ? `${candidate}/` : candidate;\n } catch {\n return candidate;\n }\n })\n );\n\n if (matches.length === 1 && !matches[0]!.endsWith(\"/\")) {\n matches[0] = `${matches[0]} `;\n }\n\n return { replacement: word, matches };\n }\n\n private createImmediateExecution(exitCode: number, stdoutText: string, stderrText: string): ShellExecution {\n const stdout = createStdout();\n const stderr = createStderr();\n const output = new AsyncQueue<ShellOutputEvent>();\n\n const exit = (async (): Promise<ExecResult> => {\n if (stdoutText.length > 0) {\n const chunk = new TextEncoder().encode(stdoutText);\n output.push({ fd: 1, chunk });\n await stdout.write(chunk);\n }\n if (stderrText.length > 0) {\n const chunk = new TextEncoder().encode(stderrText);\n output.push({ fd: 2, chunk });\n await stderr.write(chunk);\n }\n stdout.close();\n stderr.close();\n output.close();\n return {\n stdout: await stdout.collect(),\n stderr: await stderr.collect(),\n exitCode,\n };\n })();\n\n return {\n stdout: stdout.getReadableStream(),\n stderr: stderr.getReadableStream(),\n output,\n exit,\n kill: () => {},\n };\n }\n}\n\nexport function createShellSession(config: ShellConfig): ShellSession {\n return new ShellSession(config);\n}\n\nfunction getCurrentWord(prefix: string): { word: string; start: number } {\n let start = prefix.length;\n while (start > 0 && !/\\s/.test(prefix[start - 1]!)) {\n start--;\n }\n return { word: prefix.slice(start), start };\n}\n\nfunction isCommandPosition(prefix: string, wordStart: number): boolean {\n const beforeWord = prefix.slice(0, wordStart);\n const segmentStart = Math.max(\n beforeWord.lastIndexOf(\";\"),\n beforeWord.lastIndexOf(\"|\"),\n beforeWord.lastIndexOf(\"&\")\n );\n const segment = beforeWord.slice(segmentStart + 1).trim();\n if (segment === \"\") {\n return true;\n }\n return segment.split(/\\s+/).every((part) => /^[A-Za-z_][A-Za-z0-9_]*=/.test(part));\n}\n\nfunction getCurrentCommandLine(prefix: string, wordStart: number): string {\n const beforeWord = prefix.slice(0, wordStart);\n const segmentStart = Math.max(\n beforeWord.lastIndexOf(\";\"),\n beforeWord.lastIndexOf(\"|\"),\n beforeWord.lastIndexOf(\"&\")\n );\n return prefix.slice(segmentStart + 1).trimStart();\n}\n\nfunction splitCompletionWords(source: string): string[] {\n return source.trim().split(/\\s+/).filter(Boolean);\n}\n\nfunction looksLikePath(word: string): boolean {\n return word.startsWith(\"/\") || word.startsWith(\".\") || word.includes(\"/\");\n}\n\nfunction escapeCompletionSegment(segment: string): string {\n return segment.replace(/([\\s\\\\'\"$`!#&;|<>()[\\]{}*?])/g, \"\\\\$1\");\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "C807EA9CC9D2A2E164756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/types.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"// Virtual Filesystem Interface\nexport interface VirtualFSWritable {\n write(chunk: Uint8Array): Promise<void>;\n close(): Promise<void>;\n abort?(reason?: unknown): Promise<void>;\n}\n\nexport interface VirtualFS {\n readFile(path: string): Promise<Buffer>;\n readFile(path: string, encoding: BufferEncoding): Promise<string>;\n readStream(path: string): AsyncIterable<Uint8Array>;\n readdir(path: string): Promise<string[]>;\n stat(path: string): Promise<FileStat>;\n exists(path: string): Promise<boolean>;\n\n writeFile(path: string, data: Buffer | string): Promise<void>;\n appendFile(path: string, data: Buffer | string): Promise<void>;\n writeStream(path: string, opts?: { append?: boolean }): Promise<VirtualFSWritable>;\n mkdir(path: string, opts?: { recursive?: boolean }): Promise<void>;\n\n rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void>;\n\n resolve(...paths: string[]): string;\n dirname(path: string): string;\n basename(path: string): string;\n glob(pattern: string, opts?: { cwd?: string }): Promise<string[]>;\n}\n\nexport interface FileStat {\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n mtimeMs: number;\n}\n\n// Command Interfaces\nexport type Command = (ctx: CommandContext) => Promise<number>;\n\nexport interface ShellRunOptions {\n argv0?: string;\n args?: string[];\n}\n\nexport interface ShellCommandApi {\n eval(source: string): Promise<number>;\n source(path: string, args?: string[]): Promise<number>;\n runScript(path: string, args?: string[]): Promise<number>;\n runShell(source: string, options?: ShellRunOptions): Promise<number>;\n getLastExitCode(): number;\n exit(exitCode?: number): never;\n}\n\nexport interface TerminalInfo {\n isTTY: boolean;\n columns?: number;\n rows?: number;\n colorDepth?: number;\n}\n\nexport interface CommandContext {\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 interface ExternalCommandContext extends CommandContext {\n name: string;\n}\n\nexport type ShellCommandFallback = (ctx: ExternalCommandContext) => Promise<number>;\n\nexport interface Stdin {\n stream(): AsyncIterable<Uint8Array>;\n buffer(): Promise<Buffer>;\n text(): Promise<string>;\n lines(): AsyncIterable<string>;\n}\n\nexport interface ShellInputController extends AsyncIterable<Uint8Array> {\n write(chunk: Uint8Array | string): Promise<void>;\n close(): void;\n abort(reason?: unknown): void;\n}\n\nexport interface Stdout {\n write(chunk: Uint8Array): Promise<void>;\n writeText(str: string): Promise<void>;\n isTTY: boolean;\n}\n\nexport interface Stderr {\n write(chunk: Uint8Array): Promise<void>;\n writeText(str: string): Promise<void>;\n isTTY: boolean;\n}\n\nexport interface OutputCollector extends Stdout {\n close(): void;\n collect(): Promise<Buffer>;\n getReadableStream(): AsyncIterable<Uint8Array>;\n}\n\n// Execution Result\nexport interface ExecResult {\n stdout: Buffer;\n stderr: Buffer;\n exitCode: number;\n}\n\nexport interface ShellOutputEvent {\n fd: 1 | 2;\n chunk: Uint8Array;\n}\n\nexport type ShellInputSource = AsyncIterable<Uint8Array> | Buffer | string | null;\n\nexport interface ShellExecutionOptions {\n stdin?: ShellInputSource;\n stdout?: Stdout;\n stderr?: Stderr;\n terminal?: TerminalInfo;\n signal?: AbortSignal;\n outputMode?: \"separate\" | \"merged\";\n}\n\nexport interface ShellExecution {\n stdout: AsyncIterable<Uint8Array>;\n stderr: AsyncIterable<Uint8Array>;\n output: AsyncIterable<ShellOutputEvent>;\n exit: Promise<ExecResult>;\n kill(reason?: unknown): void;\n}\n\n// Shell Configuration\nexport interface ShellConfig {\n fs: VirtualFS;\n cwd: string;\n env: Record<string, string>;\n commands: Record<string, Command>;\n isTTY?: boolean;\n terminal?: TerminalInfo;\n externalCommand?: ShellCommandFallback;\n}\n\n// Raw escape hatch type\nexport interface RawValue {\n raw: string;\n}\n\nexport function isRawValue(value: unknown): value is RawValue {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"raw\" in value &&\n typeof (value as RawValue).raw === \"string\"\n );\n}\n\n// JS Object Redirection types\nexport type RedirectObject = Buffer | Blob | Response | string;\n\nexport interface RedirectObjectMap {\n [marker: string]: RedirectObject;\n}\n\nexport function isRedirectObject(value: unknown): value is RedirectObject {\n return (\n Buffer.isBuffer(value) ||\n value instanceof Blob ||\n value instanceof Response ||\n typeof value === \"string\"\n );\n}\n"
|
|
5
|
+
"// Virtual Filesystem Interface\nexport interface VirtualFSWritable {\n write(chunk: Uint8Array): Promise<void>;\n close(): Promise<void>;\n abort?(reason?: unknown): Promise<void>;\n}\n\nexport interface VirtualFS {\n readFile(path: string): Promise<Buffer>;\n readFile(path: string, encoding: BufferEncoding): Promise<string>;\n readStream(path: string): AsyncIterable<Uint8Array>;\n readdir(path: string): Promise<string[]>;\n stat(path: string): Promise<FileStat>;\n exists(path: string): Promise<boolean>;\n\n writeFile(path: string, data: Buffer | string): Promise<void>;\n appendFile(path: string, data: Buffer | string): Promise<void>;\n writeStream(path: string, opts?: { append?: boolean }): Promise<VirtualFSWritable>;\n mkdir(path: string, opts?: { recursive?: boolean }): Promise<void>;\n\n rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void>;\n\n resolve(...paths: string[]): string;\n dirname(path: string): string;\n basename(path: string): string;\n glob(pattern: string, opts?: { cwd?: string }): Promise<string[]>;\n}\n\nexport interface FileStat {\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n mtimeMs: number;\n}\n\n// Command Interfaces\nexport type Command = (ctx: CommandContext) => Promise<number>;\n\nexport interface ShellRunOptions {\n argv0?: string;\n args?: string[];\n}\n\nexport interface ShellCommandApi {\n eval(source: string): Promise<number>;\n source(path: string, args?: string[]): Promise<number>;\n runScript(path: string, args?: string[]): Promise<number>;\n runShell(source: string, options?: ShellRunOptions): Promise<number>;\n getLastExitCode(): number;\n exit(exitCode?: number): never;\n}\n\nexport interface TerminalInfo {\n isTTY: boolean;\n columns?: number;\n rows?: number;\n colorDepth?: number;\n}\n\nexport interface CompletionContext {\n source: string;\n cursor: number;\n command: string;\n args: string[];\n word: string;\n wordStart: number;\n wordEnd: number;\n cwd: string;\n env: Record<string, string>;\n fs: VirtualFS;\n terminal: TerminalInfo;\n}\n\nexport interface CompletionResult {\n replacement: string;\n matches: string[];\n}\n\nexport type CommandCompleter = (ctx: CompletionContext) => CompletionResult | Promise<CompletionResult>;\n\nexport interface CommandContext {\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 interface ExternalCommandContext extends CommandContext {\n name: string;\n}\n\nexport type ShellCommandFallback = (ctx: ExternalCommandContext) => Promise<number>;\n\nexport interface Stdin {\n stream(): AsyncIterable<Uint8Array>;\n buffer(): Promise<Buffer>;\n text(): Promise<string>;\n lines(): AsyncIterable<string>;\n}\n\nexport interface ShellInputController extends AsyncIterable<Uint8Array> {\n write(chunk: Uint8Array | string): Promise<void>;\n close(): void;\n abort(reason?: unknown): void;\n}\n\nexport interface Stdout {\n write(chunk: Uint8Array): Promise<void>;\n writeText(str: string): Promise<void>;\n isTTY: boolean;\n}\n\nexport interface Stderr {\n write(chunk: Uint8Array): Promise<void>;\n writeText(str: string): Promise<void>;\n isTTY: boolean;\n}\n\nexport interface OutputCollector extends Stdout {\n close(): void;\n collect(): Promise<Buffer>;\n getReadableStream(): AsyncIterable<Uint8Array>;\n}\n\n// Execution Result\nexport interface ExecResult {\n stdout: Buffer;\n stderr: Buffer;\n exitCode: number;\n}\n\nexport interface ShellOutputEvent {\n fd: 1 | 2;\n chunk: Uint8Array;\n}\n\nexport type ShellInputSource = AsyncIterable<Uint8Array> | Buffer | string | null;\n\nexport interface ShellExecutionOptions {\n stdin?: ShellInputSource;\n stdout?: Stdout;\n stderr?: Stderr;\n terminal?: TerminalInfo;\n signal?: AbortSignal;\n outputMode?: \"separate\" | \"merged\";\n}\n\nexport interface ShellExecution {\n stdout: AsyncIterable<Uint8Array>;\n stderr: AsyncIterable<Uint8Array>;\n output: AsyncIterable<ShellOutputEvent>;\n exit: Promise<ExecResult>;\n kill(reason?: unknown): void;\n}\n\n// Shell Configuration\nexport interface ShellConfig {\n fs: VirtualFS;\n cwd: string;\n env: Record<string, string>;\n commands: Record<string, Command>;\n completions?: Record<string, CommandCompleter>;\n isTTY?: boolean;\n terminal?: TerminalInfo;\n externalCommand?: ShellCommandFallback;\n}\n\n// Raw escape hatch type\nexport interface RawValue {\n raw: string;\n}\n\nexport function isRawValue(value: unknown): value is RawValue {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"raw\" in value &&\n typeof (value as RawValue).raw === \"string\"\n );\n}\n\n// JS Object Redirection types\nexport type RedirectObject = Buffer | Blob | Response | string;\n\nexport interface RedirectObjectMap {\n [marker: string]: RedirectObject;\n}\n\nexport function isRedirectObject(value: unknown): value is RedirectObject {\n return (\n Buffer.isBuffer(value) ||\n value instanceof Blob ||\n value instanceof Response ||\n typeof value === \"string\"\n );\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqLO,SAAS,UAAU,CAAC,OAAmC;AAAA,EAC5D,OACE,OAAO,UAAU,YACjB,UAAU,QACV,SAAS,SACT,OAAQ,MAAmB,QAAQ;AAAA;AAWhC,SAAS,gBAAgB,CAAC,OAAyC;AAAA,EACxE,OACE,OAAO,SAAS,KAAK,KACrB,iBAAiB,QACjB,iBAAiB,YACjB,OAAO,UAAU;AAAA;",
|
|
8
8
|
"debugId": "324A9AF65A2908F664756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/mjs/package.json
CHANGED
|
@@ -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.mjs\";\nexport { ShellPromise, type ShellPromiseOptions } from \"./shell-promise.mjs\";\nexport { ShellSession, createShellSession, type ShellSessionOptions } from \"./shell-session.mjs\";\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.mjs\";\nexport { isRawValue } from \"./types.mjs\";\n\n// Errors\nexport { ShellError, LexError, ParseError } from \"./errors.mjs\";\n\n// Lexer\nexport { Lexer, lex, tokenToString } from \"./lexer/index.mjs\";\nexport type { Token, RedirectMode } from \"./lexer/index.mjs\";\n\n// Parser\nexport { Parser, parse } from \"./parser/index.mjs\";\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.mjs\";\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.mjs\";\n\n// Interpreter\nexport { Interpreter, type InterpreterOptions, BreakException, ContinueException, ExitException } from \"./interpreter/index.mjs\";\n\n// Filesystem\nexport { createVirtualFS } from \"./fs/index.mjs\";\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.mjs\";\n\n// I/O\nexport { createStdin, StdinImpl } from \"./io/index.mjs\";\nexport {\n createStdout,\n createStderr,\n createPipe,\n createShellInput,\n OutputCollectorImpl,\n PipeBuffer,\n ShellInputControllerImpl,\n} from \"./io/index.mjs\";\n\n// Interactive input analysis\nexport { analyzeInput } from \"./input-analysis.mjs\";\nexport type { InputAnalysis, InputIncompleteReason } from \"./input-analysis.mjs\";\n\n// Utilities\nexport { escape, escapeForInterpolation, globVirtualFS } from \"./utils/index.mjs\";\nexport type { GlobVirtualFS, GlobOptions } from \"./utils/index.mjs\";\n\n// Version Control\nexport { VersionControlSystem } from \"./vcs/index.mjs\";\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.mjs\";\n"
|
|
5
|
+
"// Main class exports\nexport { ShellDSL, createShellDSL, type Program } from \"./shell-dsl.mjs\";\nexport { ShellPromise, type ShellPromiseOptions } from \"./shell-promise.mjs\";\nexport { ShellSession, createShellSession, type ShellSessionOptions } from \"./shell-session.mjs\";\n\n// Types\nexport type {\n VirtualFS,\n VirtualFSWritable,\n FileStat,\n Command,\n CommandCompleter,\n CommandContext,\n CompletionContext,\n CompletionResult,\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.mjs\";\nexport { isRawValue } from \"./types.mjs\";\n\n// Errors\nexport { ShellError, LexError, ParseError } from \"./errors.mjs\";\n\n// Lexer\nexport { Lexer, lex, tokenToString } from \"./lexer/index.mjs\";\nexport type { Token, RedirectMode } from \"./lexer/index.mjs\";\n\n// Parser\nexport { Parser, parse } from \"./parser/index.mjs\";\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.mjs\";\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.mjs\";\n\n// Interpreter\nexport { Interpreter, type InterpreterOptions, BreakException, ContinueException, ExitException } from \"./interpreter/index.mjs\";\n\n// Filesystem\nexport { createVirtualFS } from \"./fs/index.mjs\";\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.mjs\";\n\n// I/O\nexport { createStdin, StdinImpl } from \"./io/index.mjs\";\nexport {\n createStdout,\n createStderr,\n createPipe,\n createShellInput,\n OutputCollectorImpl,\n PipeBuffer,\n ShellInputControllerImpl,\n} from \"./io/index.mjs\";\n\n// Interactive input analysis\nexport { analyzeInput } from \"./input-analysis.mjs\";\nexport type { InputAnalysis, InputIncompleteReason } from \"./input-analysis.mjs\";\n\n// Utilities\nexport { escape, escapeForInterpolation, globVirtualFS } from \"./utils/index.mjs\";\nexport type { GlobVirtualFS, GlobOptions } from \"./utils/index.mjs\";\n\n// Version Control\nexport { VersionControlSystem } from \"./vcs/index.mjs\";\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.mjs\";\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";AACA;AACA;AACA;
|
|
7
|
+
"mappings": ";AACA;AACA;AACA;AA8BA;AAGA;AAGA;AAIA;AAsBA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeA;AAGA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA;AAIA;AAIA;",
|
|
8
8
|
"debugId": "A98B7667567C2E6F64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -6,14 +6,22 @@ import { createStderr, createStdout } from "./io/stdout.mjs";
|
|
|
6
6
|
import { AsyncQueue } from "./io/async-queue.mjs";
|
|
7
7
|
|
|
8
8
|
class ShellSession {
|
|
9
|
+
fs;
|
|
10
|
+
commands;
|
|
11
|
+
completions;
|
|
12
|
+
terminal;
|
|
9
13
|
interpreter;
|
|
10
14
|
constructor(options) {
|
|
15
|
+
this.fs = options.fs;
|
|
16
|
+
this.commands = options.commands;
|
|
17
|
+
this.completions = options.completions ?? {};
|
|
18
|
+
this.terminal = options.terminal ?? { isTTY: options.isTTY ?? false };
|
|
11
19
|
this.interpreter = new Interpreter({
|
|
12
20
|
fs: options.fs,
|
|
13
21
|
cwd: options.cwd,
|
|
14
22
|
env: options.env,
|
|
15
23
|
commands: options.commands,
|
|
16
|
-
terminal:
|
|
24
|
+
terminal: this.terminal,
|
|
17
25
|
externalCommand: options.externalCommand
|
|
18
26
|
});
|
|
19
27
|
}
|
|
@@ -43,7 +51,63 @@ class ShellSession {
|
|
|
43
51
|
getLastExitCode() {
|
|
44
52
|
return this.interpreter.getLastExitCode();
|
|
45
53
|
}
|
|
54
|
+
async complete(source, cursor = source.length) {
|
|
55
|
+
const boundedCursor = Math.max(0, Math.min(cursor, source.length));
|
|
56
|
+
const prefix = source.slice(0, boundedCursor);
|
|
57
|
+
const { word, start } = getCurrentWord(prefix);
|
|
58
|
+
if (isCommandPosition(prefix, start) && !looksLikePath(word)) {
|
|
59
|
+
return {
|
|
60
|
+
replacement: word,
|
|
61
|
+
matches: Object.keys(this.commands).filter((name) => name.startsWith(word)).sort().map((name) => `${name} `)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const commandLine = getCurrentCommandLine(prefix, start);
|
|
65
|
+
const [command = "", ...args] = splitCompletionWords(commandLine);
|
|
66
|
+
const completer = command === "" ? undefined : this.completions[command];
|
|
67
|
+
if (completer) {
|
|
68
|
+
return completer({
|
|
69
|
+
source,
|
|
70
|
+
cursor: boundedCursor,
|
|
71
|
+
command,
|
|
72
|
+
args,
|
|
73
|
+
word,
|
|
74
|
+
wordStart: start,
|
|
75
|
+
wordEnd: boundedCursor,
|
|
76
|
+
cwd: this.getCwd(),
|
|
77
|
+
env: this.getEnv(),
|
|
78
|
+
fs: this.fs,
|
|
79
|
+
terminal: this.terminal
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return this.completePath(word);
|
|
83
|
+
}
|
|
46
84
|
async dispose() {}
|
|
85
|
+
async completePath(word) {
|
|
86
|
+
const slash = word.lastIndexOf("/");
|
|
87
|
+
const dirPart = slash === -1 ? "" : word.slice(0, slash + 1);
|
|
88
|
+
const namePrefix = slash === -1 ? word : word.slice(slash + 1);
|
|
89
|
+
const basePath = dirPart === "" ? this.getCwd() : dirPart.startsWith("/") ? dirPart : this.fs.resolve(this.getCwd(), dirPart);
|
|
90
|
+
let entries;
|
|
91
|
+
try {
|
|
92
|
+
entries = await this.fs.readdir(basePath);
|
|
93
|
+
} catch {
|
|
94
|
+
return { replacement: word, matches: [] };
|
|
95
|
+
}
|
|
96
|
+
const matches = await Promise.all(entries.filter((entry) => entry.startsWith(namePrefix)).sort().map(async (entry) => {
|
|
97
|
+
const candidate = `${dirPart}${escapeCompletionSegment(entry)}`;
|
|
98
|
+
const path = this.fs.resolve(basePath, entry);
|
|
99
|
+
try {
|
|
100
|
+
const stat = await this.fs.stat(path);
|
|
101
|
+
return stat.isDirectory() ? `${candidate}/` : candidate;
|
|
102
|
+
} catch {
|
|
103
|
+
return candidate;
|
|
104
|
+
}
|
|
105
|
+
}));
|
|
106
|
+
if (matches.length === 1 && !matches[0].endsWith("/")) {
|
|
107
|
+
matches[0] = `${matches[0]} `;
|
|
108
|
+
}
|
|
109
|
+
return { replacement: word, matches };
|
|
110
|
+
}
|
|
47
111
|
createImmediateExecution(exitCode, stdoutText, stderrText) {
|
|
48
112
|
const stdout = createStdout();
|
|
49
113
|
const stderr = createStderr();
|
|
@@ -80,9 +144,39 @@ class ShellSession {
|
|
|
80
144
|
function createShellSession(config) {
|
|
81
145
|
return new ShellSession(config);
|
|
82
146
|
}
|
|
147
|
+
function getCurrentWord(prefix) {
|
|
148
|
+
let start = prefix.length;
|
|
149
|
+
while (start > 0 && !/\s/.test(prefix[start - 1])) {
|
|
150
|
+
start--;
|
|
151
|
+
}
|
|
152
|
+
return { word: prefix.slice(start), start };
|
|
153
|
+
}
|
|
154
|
+
function isCommandPosition(prefix, wordStart) {
|
|
155
|
+
const beforeWord = prefix.slice(0, wordStart);
|
|
156
|
+
const segmentStart = Math.max(beforeWord.lastIndexOf(";"), beforeWord.lastIndexOf("|"), beforeWord.lastIndexOf("&"));
|
|
157
|
+
const segment = beforeWord.slice(segmentStart + 1).trim();
|
|
158
|
+
if (segment === "") {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
return segment.split(/\s+/).every((part) => /^[A-Za-z_][A-Za-z0-9_]*=/.test(part));
|
|
162
|
+
}
|
|
163
|
+
function getCurrentCommandLine(prefix, wordStart) {
|
|
164
|
+
const beforeWord = prefix.slice(0, wordStart);
|
|
165
|
+
const segmentStart = Math.max(beforeWord.lastIndexOf(";"), beforeWord.lastIndexOf("|"), beforeWord.lastIndexOf("&"));
|
|
166
|
+
return prefix.slice(segmentStart + 1).trimStart();
|
|
167
|
+
}
|
|
168
|
+
function splitCompletionWords(source) {
|
|
169
|
+
return source.trim().split(/\s+/).filter(Boolean);
|
|
170
|
+
}
|
|
171
|
+
function looksLikePath(word) {
|
|
172
|
+
return word.startsWith("/") || word.startsWith(".") || word.includes("/");
|
|
173
|
+
}
|
|
174
|
+
function escapeCompletionSegment(segment) {
|
|
175
|
+
return segment.replace(/([\s\\'"$`!#&;|<>()[\]{}*?])/g, "\\$1");
|
|
176
|
+
}
|
|
83
177
|
export {
|
|
84
178
|
createShellSession,
|
|
85
179
|
ShellSession
|
|
86
180
|
};
|
|
87
181
|
|
|
88
|
-
//# debugId=
|
|
182
|
+
//# debugId=EDCA97BB05872ACD64756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/shell-session.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type {\n Command,\n ExecResult,\n ShellCommandFallback,\n ShellConfig,\n ShellExecution,\n ShellExecutionOptions,\n TerminalInfo,\n VirtualFS,\n} from \"./types.mjs\";\nimport type { Program } from \"./shell-dsl.mjs\";\nimport { Lexer } from \"./lexer/lexer.mjs\";\nimport { Parser } from \"./parser/parser.mjs\";\nimport { Interpreter } from \"./interpreter/interpreter.mjs\";\nimport { createStderr, createStdout } from \"./io/stdout.mjs\";\nimport { AsyncQueue } from \"./io/async-queue.mjs\";\nimport type { ShellOutputEvent } from \"./types.mjs\";\n\nexport interface ShellSessionOptions {\n fs: VirtualFS;\n cwd: string;\n env: Record<string, string>;\n commands: Record<string, Command>;\n isTTY?: boolean;\n terminal?: TerminalInfo;\n externalCommand?: ShellCommandFallback;\n}\n\nexport class ShellSession {\n private interpreter: Interpreter;\n\n constructor(options: ShellSessionOptions) {\n this.interpreter = new Interpreter({\n fs: options.fs,\n cwd: options.cwd,\n env: options.env,\n commands: options.commands,\n terminal:
|
|
5
|
+
"import type {\n Command,\n CommandCompleter,\n CompletionResult,\n ExecResult,\n ShellCommandFallback,\n ShellConfig,\n ShellExecution,\n ShellExecutionOptions,\n TerminalInfo,\n VirtualFS,\n} from \"./types.mjs\";\nimport type { Program } from \"./shell-dsl.mjs\";\nimport { Lexer } from \"./lexer/lexer.mjs\";\nimport { Parser } from \"./parser/parser.mjs\";\nimport { Interpreter } from \"./interpreter/interpreter.mjs\";\nimport { createStderr, createStdout } from \"./io/stdout.mjs\";\nimport { AsyncQueue } from \"./io/async-queue.mjs\";\nimport type { ShellOutputEvent } from \"./types.mjs\";\n\nexport interface ShellSessionOptions {\n fs: VirtualFS;\n cwd: string;\n env: Record<string, string>;\n commands: Record<string, Command>;\n completions?: Record<string, CommandCompleter>;\n isTTY?: boolean;\n terminal?: TerminalInfo;\n externalCommand?: ShellCommandFallback;\n}\n\nexport class ShellSession {\n private fs: VirtualFS;\n private commands: Record<string, Command>;\n private completions: Record<string, CommandCompleter>;\n private terminal: TerminalInfo;\n private interpreter: Interpreter;\n\n constructor(options: ShellSessionOptions) {\n this.fs = options.fs;\n this.commands = options.commands;\n this.completions = options.completions ?? {};\n this.terminal = options.terminal ?? { isTTY: options.isTTY ?? false };\n this.interpreter = new Interpreter({\n fs: options.fs,\n cwd: options.cwd,\n env: options.env,\n commands: options.commands,\n terminal: this.terminal,\n externalCommand: options.externalCommand,\n });\n }\n\n run(source: string, options: ShellExecutionOptions = {}): ShellExecution {\n try {\n const tokens = new Lexer(source, { preserveNewlines: true }).tokenize();\n if (tokens.every((token) => token.type === \"newline\" || token.type === \"eof\")) {\n return this.createImmediateExecution(0, \"\", \"\");\n }\n const ast = new Parser(tokens).parse();\n return this.interpreter.executeStreaming(ast, options);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return this.createImmediateExecution(2, \"\", `sh: ${message}\\n`);\n }\n }\n\n runProgram(program: Program, options: ShellExecutionOptions = {}): ShellExecution {\n return this.interpreter.executeStreaming(program.ast, options);\n }\n\n getCwd(): string {\n return this.interpreter.getCwd();\n }\n\n getEnv(): Record<string, string> {\n return this.interpreter.getEnv();\n }\n\n getLastExitCode(): number {\n return this.interpreter.getLastExitCode();\n }\n\n async complete(source: string, cursor: number = source.length): Promise<CompletionResult> {\n const boundedCursor = Math.max(0, Math.min(cursor, source.length));\n const prefix = source.slice(0, boundedCursor);\n const { word, start } = getCurrentWord(prefix);\n\n if (isCommandPosition(prefix, start) && !looksLikePath(word)) {\n return {\n replacement: word,\n matches: Object.keys(this.commands)\n .filter((name) => name.startsWith(word))\n .sort()\n .map((name) => `${name} `),\n };\n }\n\n const commandLine = getCurrentCommandLine(prefix, start);\n const [command = \"\", ...args] = splitCompletionWords(commandLine);\n const completer = command === \"\" ? undefined : this.completions[command];\n if (completer) {\n return completer({\n source,\n cursor: boundedCursor,\n command,\n args,\n word,\n wordStart: start,\n wordEnd: boundedCursor,\n cwd: this.getCwd(),\n env: this.getEnv(),\n fs: this.fs,\n terminal: this.terminal,\n });\n }\n\n return this.completePath(word);\n }\n\n async dispose(): Promise<void> {\n // Reserved for future resources owned by a session.\n }\n\n private async completePath(word: string): Promise<CompletionResult> {\n const slash = word.lastIndexOf(\"/\");\n const dirPart = slash === -1 ? \"\" : word.slice(0, slash + 1);\n const namePrefix = slash === -1 ? word : word.slice(slash + 1);\n const basePath = dirPart === \"\"\n ? this.getCwd()\n : dirPart.startsWith(\"/\")\n ? dirPart\n : this.fs.resolve(this.getCwd(), dirPart);\n\n let entries: string[];\n try {\n entries = await this.fs.readdir(basePath);\n } catch {\n return { replacement: word, matches: [] };\n }\n\n const matches = await Promise.all(\n entries\n .filter((entry) => entry.startsWith(namePrefix))\n .sort()\n .map(async (entry) => {\n const candidate = `${dirPart}${escapeCompletionSegment(entry)}`;\n const path = this.fs.resolve(basePath, entry);\n try {\n const stat = await this.fs.stat(path);\n return stat.isDirectory() ? `${candidate}/` : candidate;\n } catch {\n return candidate;\n }\n })\n );\n\n if (matches.length === 1 && !matches[0]!.endsWith(\"/\")) {\n matches[0] = `${matches[0]} `;\n }\n\n return { replacement: word, matches };\n }\n\n private createImmediateExecution(exitCode: number, stdoutText: string, stderrText: string): ShellExecution {\n const stdout = createStdout();\n const stderr = createStderr();\n const output = new AsyncQueue<ShellOutputEvent>();\n\n const exit = (async (): Promise<ExecResult> => {\n if (stdoutText.length > 0) {\n const chunk = new TextEncoder().encode(stdoutText);\n output.push({ fd: 1, chunk });\n await stdout.write(chunk);\n }\n if (stderrText.length > 0) {\n const chunk = new TextEncoder().encode(stderrText);\n output.push({ fd: 2, chunk });\n await stderr.write(chunk);\n }\n stdout.close();\n stderr.close();\n output.close();\n return {\n stdout: await stdout.collect(),\n stderr: await stderr.collect(),\n exitCode,\n };\n })();\n\n return {\n stdout: stdout.getReadableStream(),\n stderr: stderr.getReadableStream(),\n output,\n exit,\n kill: () => {},\n };\n }\n}\n\nexport function createShellSession(config: ShellConfig): ShellSession {\n return new ShellSession(config);\n}\n\nfunction getCurrentWord(prefix: string): { word: string; start: number } {\n let start = prefix.length;\n while (start > 0 && !/\\s/.test(prefix[start - 1]!)) {\n start--;\n }\n return { word: prefix.slice(start), start };\n}\n\nfunction isCommandPosition(prefix: string, wordStart: number): boolean {\n const beforeWord = prefix.slice(0, wordStart);\n const segmentStart = Math.max(\n beforeWord.lastIndexOf(\";\"),\n beforeWord.lastIndexOf(\"|\"),\n beforeWord.lastIndexOf(\"&\")\n );\n const segment = beforeWord.slice(segmentStart + 1).trim();\n if (segment === \"\") {\n return true;\n }\n return segment.split(/\\s+/).every((part) => /^[A-Za-z_][A-Za-z0-9_]*=/.test(part));\n}\n\nfunction getCurrentCommandLine(prefix: string, wordStart: number): string {\n const beforeWord = prefix.slice(0, wordStart);\n const segmentStart = Math.max(\n beforeWord.lastIndexOf(\";\"),\n beforeWord.lastIndexOf(\"|\"),\n beforeWord.lastIndexOf(\"&\")\n );\n return prefix.slice(segmentStart + 1).trimStart();\n}\n\nfunction splitCompletionWords(source: string): string[] {\n return source.trim().split(/\\s+/).filter(Boolean);\n}\n\nfunction looksLikePath(word: string): boolean {\n return word.startsWith(\"/\") || word.startsWith(\".\") || word.includes(\"/\");\n}\n\nfunction escapeCompletionSegment(segment: string): string {\n return segment.replace(/([\\s\\\\'\"$`!#&;|<>()[\\]{}*?])/g, \"\\\\$1\");\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "EDCA97BB05872ACD64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/types.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"// Virtual Filesystem Interface\nexport interface VirtualFSWritable {\n write(chunk: Uint8Array): Promise<void>;\n close(): Promise<void>;\n abort?(reason?: unknown): Promise<void>;\n}\n\nexport interface VirtualFS {\n readFile(path: string): Promise<Buffer>;\n readFile(path: string, encoding: BufferEncoding): Promise<string>;\n readStream(path: string): AsyncIterable<Uint8Array>;\n readdir(path: string): Promise<string[]>;\n stat(path: string): Promise<FileStat>;\n exists(path: string): Promise<boolean>;\n\n writeFile(path: string, data: Buffer | string): Promise<void>;\n appendFile(path: string, data: Buffer | string): Promise<void>;\n writeStream(path: string, opts?: { append?: boolean }): Promise<VirtualFSWritable>;\n mkdir(path: string, opts?: { recursive?: boolean }): Promise<void>;\n\n rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void>;\n\n resolve(...paths: string[]): string;\n dirname(path: string): string;\n basename(path: string): string;\n glob(pattern: string, opts?: { cwd?: string }): Promise<string[]>;\n}\n\nexport interface FileStat {\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n mtimeMs: number;\n}\n\n// Command Interfaces\nexport type Command = (ctx: CommandContext) => Promise<number>;\n\nexport interface ShellRunOptions {\n argv0?: string;\n args?: string[];\n}\n\nexport interface ShellCommandApi {\n eval(source: string): Promise<number>;\n source(path: string, args?: string[]): Promise<number>;\n runScript(path: string, args?: string[]): Promise<number>;\n runShell(source: string, options?: ShellRunOptions): Promise<number>;\n getLastExitCode(): number;\n exit(exitCode?: number): never;\n}\n\nexport interface TerminalInfo {\n isTTY: boolean;\n columns?: number;\n rows?: number;\n colorDepth?: number;\n}\n\nexport interface CommandContext {\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 interface ExternalCommandContext extends CommandContext {\n name: string;\n}\n\nexport type ShellCommandFallback = (ctx: ExternalCommandContext) => Promise<number>;\n\nexport interface Stdin {\n stream(): AsyncIterable<Uint8Array>;\n buffer(): Promise<Buffer>;\n text(): Promise<string>;\n lines(): AsyncIterable<string>;\n}\n\nexport interface ShellInputController extends AsyncIterable<Uint8Array> {\n write(chunk: Uint8Array | string): Promise<void>;\n close(): void;\n abort(reason?: unknown): void;\n}\n\nexport interface Stdout {\n write(chunk: Uint8Array): Promise<void>;\n writeText(str: string): Promise<void>;\n isTTY: boolean;\n}\n\nexport interface Stderr {\n write(chunk: Uint8Array): Promise<void>;\n writeText(str: string): Promise<void>;\n isTTY: boolean;\n}\n\nexport interface OutputCollector extends Stdout {\n close(): void;\n collect(): Promise<Buffer>;\n getReadableStream(): AsyncIterable<Uint8Array>;\n}\n\n// Execution Result\nexport interface ExecResult {\n stdout: Buffer;\n stderr: Buffer;\n exitCode: number;\n}\n\nexport interface ShellOutputEvent {\n fd: 1 | 2;\n chunk: Uint8Array;\n}\n\nexport type ShellInputSource = AsyncIterable<Uint8Array> | Buffer | string | null;\n\nexport interface ShellExecutionOptions {\n stdin?: ShellInputSource;\n stdout?: Stdout;\n stderr?: Stderr;\n terminal?: TerminalInfo;\n signal?: AbortSignal;\n outputMode?: \"separate\" | \"merged\";\n}\n\nexport interface ShellExecution {\n stdout: AsyncIterable<Uint8Array>;\n stderr: AsyncIterable<Uint8Array>;\n output: AsyncIterable<ShellOutputEvent>;\n exit: Promise<ExecResult>;\n kill(reason?: unknown): void;\n}\n\n// Shell Configuration\nexport interface ShellConfig {\n fs: VirtualFS;\n cwd: string;\n env: Record<string, string>;\n commands: Record<string, Command>;\n isTTY?: boolean;\n terminal?: TerminalInfo;\n externalCommand?: ShellCommandFallback;\n}\n\n// Raw escape hatch type\nexport interface RawValue {\n raw: string;\n}\n\nexport function isRawValue(value: unknown): value is RawValue {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"raw\" in value &&\n typeof (value as RawValue).raw === \"string\"\n );\n}\n\n// JS Object Redirection types\nexport type RedirectObject = Buffer | Blob | Response | string;\n\nexport interface RedirectObjectMap {\n [marker: string]: RedirectObject;\n}\n\nexport function isRedirectObject(value: unknown): value is RedirectObject {\n return (\n Buffer.isBuffer(value) ||\n value instanceof Blob ||\n value instanceof Response ||\n typeof value === \"string\"\n );\n}\n"
|
|
5
|
+
"// Virtual Filesystem Interface\nexport interface VirtualFSWritable {\n write(chunk: Uint8Array): Promise<void>;\n close(): Promise<void>;\n abort?(reason?: unknown): Promise<void>;\n}\n\nexport interface VirtualFS {\n readFile(path: string): Promise<Buffer>;\n readFile(path: string, encoding: BufferEncoding): Promise<string>;\n readStream(path: string): AsyncIterable<Uint8Array>;\n readdir(path: string): Promise<string[]>;\n stat(path: string): Promise<FileStat>;\n exists(path: string): Promise<boolean>;\n\n writeFile(path: string, data: Buffer | string): Promise<void>;\n appendFile(path: string, data: Buffer | string): Promise<void>;\n writeStream(path: string, opts?: { append?: boolean }): Promise<VirtualFSWritable>;\n mkdir(path: string, opts?: { recursive?: boolean }): Promise<void>;\n\n rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void>;\n\n resolve(...paths: string[]): string;\n dirname(path: string): string;\n basename(path: string): string;\n glob(pattern: string, opts?: { cwd?: string }): Promise<string[]>;\n}\n\nexport interface FileStat {\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n mtimeMs: number;\n}\n\n// Command Interfaces\nexport type Command = (ctx: CommandContext) => Promise<number>;\n\nexport interface ShellRunOptions {\n argv0?: string;\n args?: string[];\n}\n\nexport interface ShellCommandApi {\n eval(source: string): Promise<number>;\n source(path: string, args?: string[]): Promise<number>;\n runScript(path: string, args?: string[]): Promise<number>;\n runShell(source: string, options?: ShellRunOptions): Promise<number>;\n getLastExitCode(): number;\n exit(exitCode?: number): never;\n}\n\nexport interface TerminalInfo {\n isTTY: boolean;\n columns?: number;\n rows?: number;\n colorDepth?: number;\n}\n\nexport interface CompletionContext {\n source: string;\n cursor: number;\n command: string;\n args: string[];\n word: string;\n wordStart: number;\n wordEnd: number;\n cwd: string;\n env: Record<string, string>;\n fs: VirtualFS;\n terminal: TerminalInfo;\n}\n\nexport interface CompletionResult {\n replacement: string;\n matches: string[];\n}\n\nexport type CommandCompleter = (ctx: CompletionContext) => CompletionResult | Promise<CompletionResult>;\n\nexport interface CommandContext {\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 interface ExternalCommandContext extends CommandContext {\n name: string;\n}\n\nexport type ShellCommandFallback = (ctx: ExternalCommandContext) => Promise<number>;\n\nexport interface Stdin {\n stream(): AsyncIterable<Uint8Array>;\n buffer(): Promise<Buffer>;\n text(): Promise<string>;\n lines(): AsyncIterable<string>;\n}\n\nexport interface ShellInputController extends AsyncIterable<Uint8Array> {\n write(chunk: Uint8Array | string): Promise<void>;\n close(): void;\n abort(reason?: unknown): void;\n}\n\nexport interface Stdout {\n write(chunk: Uint8Array): Promise<void>;\n writeText(str: string): Promise<void>;\n isTTY: boolean;\n}\n\nexport interface Stderr {\n write(chunk: Uint8Array): Promise<void>;\n writeText(str: string): Promise<void>;\n isTTY: boolean;\n}\n\nexport interface OutputCollector extends Stdout {\n close(): void;\n collect(): Promise<Buffer>;\n getReadableStream(): AsyncIterable<Uint8Array>;\n}\n\n// Execution Result\nexport interface ExecResult {\n stdout: Buffer;\n stderr: Buffer;\n exitCode: number;\n}\n\nexport interface ShellOutputEvent {\n fd: 1 | 2;\n chunk: Uint8Array;\n}\n\nexport type ShellInputSource = AsyncIterable<Uint8Array> | Buffer | string | null;\n\nexport interface ShellExecutionOptions {\n stdin?: ShellInputSource;\n stdout?: Stdout;\n stderr?: Stderr;\n terminal?: TerminalInfo;\n signal?: AbortSignal;\n outputMode?: \"separate\" | \"merged\";\n}\n\nexport interface ShellExecution {\n stdout: AsyncIterable<Uint8Array>;\n stderr: AsyncIterable<Uint8Array>;\n output: AsyncIterable<ShellOutputEvent>;\n exit: Promise<ExecResult>;\n kill(reason?: unknown): void;\n}\n\n// Shell Configuration\nexport interface ShellConfig {\n fs: VirtualFS;\n cwd: string;\n env: Record<string, string>;\n commands: Record<string, Command>;\n completions?: Record<string, CommandCompleter>;\n isTTY?: boolean;\n terminal?: TerminalInfo;\n externalCommand?: ShellCommandFallback;\n}\n\n// Raw escape hatch type\nexport interface RawValue {\n raw: string;\n}\n\nexport function isRawValue(value: unknown): value is RawValue {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"raw\" in value &&\n typeof (value as RawValue).raw === \"string\"\n );\n}\n\n// JS Object Redirection types\nexport type RedirectObject = Buffer | Blob | Response | string;\n\nexport interface RedirectObjectMap {\n [marker: string]: RedirectObject;\n}\n\nexport function isRedirectObject(value: unknown): value is RedirectObject {\n return (\n Buffer.isBuffer(value) ||\n value instanceof Blob ||\n value instanceof Response ||\n typeof value === \"string\"\n );\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";
|
|
7
|
+
"mappings": ";AAqLO,SAAS,UAAU,CAAC,OAAmC;AAAA,EAC5D,OACE,OAAO,UAAU,YACjB,UAAU,QACV,SAAS,SACT,OAAQ,MAAmB,QAAQ;AAAA;AAWhC,SAAS,gBAAgB,CAAC,OAAyC;AAAA,EACxE,OACE,OAAO,SAAS,KAAK,KACrB,iBAAiB,QACjB,iBAAiB,YACjB,OAAO,UAAU;AAAA;",
|
|
8
8
|
"debugId": "9C8E04A91CC7802764756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { ShellDSL, createShellDSL, type Program } from "./shell-dsl.ts";
|
|
2
2
|
export { ShellPromise, type ShellPromiseOptions } from "./shell-promise.ts";
|
|
3
3
|
export { ShellSession, createShellSession, type ShellSessionOptions } from "./shell-session.ts";
|
|
4
|
-
export type { VirtualFS, VirtualFSWritable, FileStat, Command, CommandContext, Stdin, Stdout, Stderr, OutputCollector, ExecResult, ShellConfig, ShellCommandApi, ShellCommandFallback, ExternalCommandContext, ShellRunOptions, TerminalInfo, ShellInputController, ShellInputSource, ShellExecutionOptions, ShellExecution, ShellOutputEvent, RawValue, } from "./types.ts";
|
|
4
|
+
export type { VirtualFS, VirtualFSWritable, FileStat, Command, CommandCompleter, CommandContext, CompletionContext, CompletionResult, Stdin, Stdout, Stderr, OutputCollector, ExecResult, ShellConfig, ShellCommandApi, ShellCommandFallback, ExternalCommandContext, ShellRunOptions, TerminalInfo, ShellInputController, ShellInputSource, ShellExecutionOptions, ShellExecution, ShellOutputEvent, RawValue, } from "./types.ts";
|
|
5
5
|
export { isRawValue } from "./types.ts";
|
|
6
6
|
export { ShellError, LexError, ParseError } from "./errors.ts";
|
|
7
7
|
export { Lexer, lex, tokenToString } from "./lexer/index.ts";
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import type { Command, ShellCommandFallback, ShellConfig, ShellExecution, ShellExecutionOptions, TerminalInfo, VirtualFS } from "./types.ts";
|
|
1
|
+
import type { Command, CommandCompleter, CompletionResult, ShellCommandFallback, ShellConfig, ShellExecution, ShellExecutionOptions, TerminalInfo, VirtualFS } from "./types.ts";
|
|
2
2
|
import type { Program } from "./shell-dsl.ts";
|
|
3
3
|
export interface ShellSessionOptions {
|
|
4
4
|
fs: VirtualFS;
|
|
5
5
|
cwd: string;
|
|
6
6
|
env: Record<string, string>;
|
|
7
7
|
commands: Record<string, Command>;
|
|
8
|
+
completions?: Record<string, CommandCompleter>;
|
|
8
9
|
isTTY?: boolean;
|
|
9
10
|
terminal?: TerminalInfo;
|
|
10
11
|
externalCommand?: ShellCommandFallback;
|
|
11
12
|
}
|
|
12
13
|
export declare class ShellSession {
|
|
14
|
+
private fs;
|
|
15
|
+
private commands;
|
|
16
|
+
private completions;
|
|
17
|
+
private terminal;
|
|
13
18
|
private interpreter;
|
|
14
19
|
constructor(options: ShellSessionOptions);
|
|
15
20
|
run(source: string, options?: ShellExecutionOptions): ShellExecution;
|
|
@@ -17,7 +22,9 @@ export declare class ShellSession {
|
|
|
17
22
|
getCwd(): string;
|
|
18
23
|
getEnv(): Record<string, string>;
|
|
19
24
|
getLastExitCode(): number;
|
|
25
|
+
complete(source: string, cursor?: number): Promise<CompletionResult>;
|
|
20
26
|
dispose(): Promise<void>;
|
|
27
|
+
private completePath;
|
|
21
28
|
private createImmediateExecution;
|
|
22
29
|
}
|
|
23
30
|
export declare function createShellSession(config: ShellConfig): ShellSession;
|
|
@@ -55,6 +55,24 @@ export interface TerminalInfo {
|
|
|
55
55
|
rows?: number;
|
|
56
56
|
colorDepth?: number;
|
|
57
57
|
}
|
|
58
|
+
export interface CompletionContext {
|
|
59
|
+
source: string;
|
|
60
|
+
cursor: number;
|
|
61
|
+
command: string;
|
|
62
|
+
args: string[];
|
|
63
|
+
word: string;
|
|
64
|
+
wordStart: number;
|
|
65
|
+
wordEnd: number;
|
|
66
|
+
cwd: string;
|
|
67
|
+
env: Record<string, string>;
|
|
68
|
+
fs: VirtualFS;
|
|
69
|
+
terminal: TerminalInfo;
|
|
70
|
+
}
|
|
71
|
+
export interface CompletionResult {
|
|
72
|
+
replacement: string;
|
|
73
|
+
matches: string[];
|
|
74
|
+
}
|
|
75
|
+
export type CommandCompleter = (ctx: CompletionContext) => CompletionResult | Promise<CompletionResult>;
|
|
58
76
|
export interface CommandContext {
|
|
59
77
|
args: string[];
|
|
60
78
|
stdin: Stdin;
|
|
@@ -129,6 +147,7 @@ export interface ShellConfig {
|
|
|
129
147
|
cwd: string;
|
|
130
148
|
env: Record<string, string>;
|
|
131
149
|
commands: Record<string, Command>;
|
|
150
|
+
completions?: Record<string, CommandCompleter>;
|
|
132
151
|
isTTY?: boolean;
|
|
133
152
|
terminal?: TerminalInfo;
|
|
134
153
|
externalCommand?: ShellCommandFallback;
|
package/package.json
CHANGED