@demicodes/shell 0.1.0 → 0.2.1

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/dist/index.mjs CHANGED
@@ -1,9 +1,8 @@
1
1
  import { HostBackedFileSystem, virtualDirectory, virtualFile } from "./host-fs.mjs";
2
2
  import { AgentSessionCommandStorage } from "./storage.mjs";
3
- import { asError, concatBytes, decodeUtf8, encodeUtf8, tail, utf8Bytes, utf8Slice } from "@demicodes/utils";
4
- import { z } from "zod";
3
+ import { asError, base64ToBytes, bytesToBase64, concatBytes, decodeLatin1, decodeUtf8, decodeUtf8Strict, encodeLatin1, encodeUtf8, tail, utf8Bytes, utf8Slice } from "@demicodes/utils";
4
+ import { createLazyCommands, getCommandNames } from "@demicodes/just-bash/commands";
5
5
  import { ArithmeticError, BadSubstitutionError, ExecutionLimitError, ExitError, Interpreter } from "@demicodes/just-bash/interpreter";
6
- import { createLazyCommands } from "@demicodes/just-bash/commands";
7
6
  import { decodeBytesToUtf8, unsafeBytesFromLatin1 } from "@demicodes/just-bash/encoding";
8
7
  import { parse } from "@demicodes/just-bash/parser";
9
8
  import { ParseException } from "@demicodes/just-bash/parser/types";
@@ -11,63 +10,42 @@ import { LexerError } from "@demicodes/just-bash/parser/lexer";
11
10
  import { resolveLimits } from "@demicodes/just-bash/limits";
12
11
  //#region src/portable-commands.ts
13
12
  /**
13
+ * Deliberately routed to real OS processes (Host.process.spawn) even though
14
+ * just-bash ships portable versions. This is a semantics decision, not a
15
+ * missing feature:
16
+ *
17
+ * - `bash`, `sh`: scripts in real repositories expect a real interpreter
18
+ * (full bash semantics, real coreutils, real subprocess behavior). Routing
19
+ * them into just-bash's simulated interpreter would silently downgrade
20
+ * every `bash script.sh` a model runs. (The fork's nested-interpreter
21
+ * stdout also does not forward through this package's wrapping — but even
22
+ * with that fixed, real spawn stays the right routing.)
23
+ * - `sleep`: must be a real, abortable OS process so foreground abort and
24
+ * timeout semantics (`env.abort()` while `running`) genuinely interrupt
25
+ * it; just-bash's version is a plain in-process timer.
26
+ */
27
+ const REAL_SPAWN_DEPENDENT_COMMANDS = /* @__PURE__ */ new Set([
28
+ "bash",
29
+ "sh",
30
+ "sleep"
31
+ ]);
32
+ /**
14
33
  * Fork portable commands BashEnvironment registers in every shell, so
15
34
  * cat/ls/grep-class tools work without local coreutils on any Host backend.
35
+ *
36
+ * Sourced directly from just-bash's own registry instead of a hand-maintained
37
+ * copy: `CommandName` is just-bash's own "safe to run anywhere" set — it
38
+ * already excludes the commands that need real process/network access
39
+ * (curl → NetworkCommandName, python3/python → PythonCommandName, js-exec/node
40
+ * → JavaScriptCommandName all live in separate, non-portable type unions). A
41
+ * hand-picked subset of this list drifts silently as just-bash adds commands
42
+ * (this one was missing echo, pwd, printf, date, env, hostname, whoami, gzip,
43
+ * tar, split, sqlite3, xan, and more — all already categorized as safe
44
+ * upstream, just never backported here). Deriving it removes that drift
45
+ * entirely: everything just-bash calls portable (minus the exceptions above),
46
+ * we call portable.
16
47
  */
17
- const DEMI_PORTABLE_COMMANDS = [
18
- "cat",
19
- "ls",
20
- "mkdir",
21
- "rmdir",
22
- "touch",
23
- "rm",
24
- "cp",
25
- "mv",
26
- "ln",
27
- "chmod",
28
- "readlink",
29
- "head",
30
- "tail",
31
- "wc",
32
- "stat",
33
- "grep",
34
- "fgrep",
35
- "egrep",
36
- "rg",
37
- "sed",
38
- "awk",
39
- "sort",
40
- "uniq",
41
- "comm",
42
- "cut",
43
- "paste",
44
- "tr",
45
- "rev",
46
- "nl",
47
- "fold",
48
- "expand",
49
- "unexpand",
50
- "strings",
51
- "column",
52
- "join",
53
- "tee",
54
- "find",
55
- "basename",
56
- "dirname",
57
- "tree",
58
- "du",
59
- "jq",
60
- "base64",
61
- "diff",
62
- "seq",
63
- "expr",
64
- "md5sum",
65
- "sha1sum",
66
- "sha256sum",
67
- "file",
68
- "tac",
69
- "od"
70
- ];
48
+ const DEMI_PORTABLE_COMMANDS = getCommandNames().filter((name) => !REAL_SPAWN_DEPENDENT_COMMANDS.has(name));
71
49
  /** Shell language words and builtins the interpreter itself owns. */
72
50
  const SHELL_BUILTIN_NAMES = [
73
51
  ".",
@@ -94,7 +72,15 @@ const SHELL_BUILTIN_NAMES = [
94
72
  "unset",
95
73
  "wait"
96
74
  ];
97
- /** Ecosystem tools the model expects to reach through Host.process.spawn. */
75
+ /**
76
+ * Ecosystem tools the model expects to reach through Host.process.spawn.
77
+ *
78
+ * xargs and yq are deliberately absent even though real versions exist: both
79
+ * now have a real (non-simulated-elsewhere) implementation in just-bash's own
80
+ * portable set above, which registers and dispatches before Host.process.spawn
81
+ * is ever consulted — listing them here too would claim they fall through to
82
+ * a real spawn, which no longer happens.
83
+ */
98
84
  const SYSTEM_TOOL_NAMES = [
99
85
  "bun",
100
86
  "cargo",
@@ -108,9 +94,7 @@ const SYSTEM_TOOL_NAMES = [
108
94
  "python3",
109
95
  "ruby",
110
96
  "rustc",
111
- "xargs",
112
- "yarn",
113
- "yq"
97
+ "yarn"
114
98
  ];
115
99
  /**
116
100
  * Names registered commands must not shadow, derived from the actual portable
@@ -124,16 +108,30 @@ const RESERVED_COMMAND_NAMES = /* @__PURE__ */ new Set([
124
108
  ]);
125
109
  //#endregion
126
110
  //#region src/command.ts
127
- function isCommandGroup(node) {
128
- return "subcommands" in node;
111
+ function emptyStdin() {
112
+ return {
113
+ text: "",
114
+ bytes: /* @__PURE__ */ new Uint8Array(0)
115
+ };
129
116
  }
117
+ const EXECUTION_ONLY_FIELDS = [
118
+ "effects",
119
+ "successOutput",
120
+ "failureOutput",
121
+ "input",
122
+ "positionals",
123
+ "stdinField",
124
+ "output",
125
+ "examples"
126
+ ];
127
+ const COMMAND_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
130
128
  var CommandRegistry = class {
131
129
  commands = /* @__PURE__ */ new Map();
132
- register(spec) {
133
- if (RESERVED_COMMAND_NAMES.has(spec.name)) throw new Error(`CommandRegistry: command "${spec.name}" is reserved for shell/system commands`);
134
- if (this.commands.has(spec.name)) throw new Error(`CommandRegistry: command "${spec.name}" is already registered`);
135
- validateCommandTree(spec, spec.name);
136
- this.commands.set(spec.name, spec);
130
+ register(command) {
131
+ if (RESERVED_COMMAND_NAMES.has(command.name)) throw new Error(`CommandRegistry: command "${command.name}" is reserved for shell/system commands`);
132
+ if (this.commands.has(command.name)) throw new Error(`CommandRegistry: command "${command.name}" is already registered`);
133
+ validateCommandTree(command, command.name);
134
+ this.commands.set(command.name, command);
137
135
  }
138
136
  get(name) {
139
137
  return this.commands.get(name) ?? null;
@@ -141,49 +139,55 @@ var CommandRegistry = class {
141
139
  list() {
142
140
  return [...this.commands.values()];
143
141
  }
144
- renderPrompt() {
145
- const rendered = this.list().map((spec) => renderCommandPrompt(spec)).join("\n\n");
142
+ renderHelp() {
143
+ const rendered = this.list().map((command) => renderCommandHelp(command)).join("\n\n");
146
144
  if (!rendered) return rendered;
147
- return `${COMMAND_PROMPT_DEFAULTS}\n\n${rendered}`;
145
+ return `${COMMAND_HELP_DEFAULTS}\n\n${rendered}`;
148
146
  }
149
147
  };
150
- const COMMAND_PROMPT_DEFAULTS = "Unless a subcommand states otherwise: success prints raw text on stdout, failure writes an error message to stderr and exits non-zero.";
151
- function parseCommandInput(spec, argv, stdin = { text: "" }) {
152
- if (argv[0] !== spec.name) throw new Error(`Expected command "${spec.name}", received "${argv[0] ?? ""}"`);
153
- let group = spec;
154
- const path = [];
148
+ const COMMAND_HELP_DEFAULTS = "Unless a command states otherwise: success prints raw text on stdout, failure writes an error message to stderr and exits non-zero. Pass --help at any level to print a command's documentation.";
149
+ function parseCommandInput(root, argv, stdin = emptyStdin()) {
150
+ if (argv[0] !== root.name) throw new Error(`Expected command "${root.name}", received "${argv[0] ?? ""}"`);
151
+ let node = root;
152
+ const path = [root.name];
155
153
  let index = 1;
156
154
  while (true) {
157
- const segment = argv[index];
158
- if (!segment) throw new Error(`Command "${[spec.name, ...path].join(" ")}" requires a subcommand`);
159
- if (segment === "prompt") return {
160
- subcommand: "prompt",
161
- path,
155
+ if (index >= argv.length) {
156
+ if (node.run) return parseArgs(node, path, argv, index, stdin);
157
+ throw new Error(`Command "${path.join(" ")}" requires a subcommand`);
158
+ }
159
+ const token = argv[index];
160
+ if (token === "--help") return {
161
+ path: [...path],
162
+ help: true,
162
163
  values: {},
163
164
  json: false
164
165
  };
165
- const child = group.subcommands.find((candidate) => candidate.name === segment);
166
- if (!child) throw new Error(`Unknown subcommand "${[
167
- spec.name,
168
- ...path,
169
- segment
170
- ].join(" ")}"`);
171
- path.push(child.name);
172
- index += 1;
173
- if (isCommandGroup(child)) {
174
- group = child;
166
+ const child = node.subcommands?.find((candidate) => candidate.name === token);
167
+ if (child) {
168
+ node = child;
169
+ path.push(child.name);
170
+ index += 1;
175
171
  continue;
176
172
  }
177
- return parseLeafInput(child, [spec.name, ...path].join(" "), path, argv, index, stdin);
173
+ if (!node.run) throw new Error(`Unknown subcommand "${[...path, token].join(" ")}"`);
174
+ return parseArgs(node, path, argv, index, stdin);
178
175
  }
179
176
  }
180
- function parseLeafInput(subcommand, displayPath, path, argv, startIndex, stdin) {
181
- const input = subcommand.input ?? {};
177
+ function parseArgs(command, path, argv, startIndex, stdin) {
178
+ const displayPath = path.join(" ");
179
+ const input = command.input ?? {};
182
180
  const values = {};
183
181
  let json = false;
184
182
  let positionalIndex = 0;
185
183
  for (let i = startIndex; i < argv.length; i += 1) {
186
184
  const token = argv[i];
185
+ if (token === "--help") return {
186
+ path: [...path],
187
+ help: true,
188
+ values: {},
189
+ json: false
190
+ };
187
191
  if (token === "--json") {
188
192
  json = true;
189
193
  continue;
@@ -200,43 +204,45 @@ function parseLeafInput(subcommand, displayPath, path, argv, startIndex, stdin)
200
204
  setParsedValue(values, field, rawValue);
201
205
  continue;
202
206
  }
203
- const field = subcommand.positionals?.[positionalIndex];
207
+ const field = command.positionals?.[positionalIndex];
204
208
  if (!field) throw new Error(`Unexpected positional argument "${token}"`);
205
209
  setParsedValue(values, field, token);
206
210
  positionalIndex += 1;
207
211
  }
208
- if (subcommand.stdinField) values[subcommand.stdinField] = stdin.text;
212
+ if (command.stdinField) values[command.stdinField] = stdin.text;
209
213
  return {
210
- subcommand: subcommand.name,
211
- path,
214
+ path: [...path],
215
+ help: false,
212
216
  values: validateInput(input, values),
213
217
  json
214
218
  };
215
219
  }
216
- function resolveCommandNode(spec, path) {
217
- let node = spec;
218
- for (const segment of path) {
219
- if (!isCommandGroup(node)) throw new Error(`"${node.name}" has no subcommand "${segment}"`);
220
- const child = node.subcommands.find((candidate) => candidate.name === segment);
221
- if (!child) throw new Error(`Unknown subcommand "${segment}" under "${node.name}"`);
220
+ function resolveCommand(root, path) {
221
+ if (path[0] !== root.name) throw new Error(`Path root "${path[0] ?? ""}" does not match command "${root.name}"`);
222
+ let node = root;
223
+ for (let i = 1; i < path.length; i += 1) {
224
+ const segment = path[i];
225
+ const child = node.subcommands?.find((candidate) => candidate.name === segment);
226
+ if (!child) throw new Error(`Unknown subcommand "${path.slice(0, i + 1).join(" ")}"`);
222
227
  node = child;
223
228
  }
224
229
  return node;
225
230
  }
226
- async function runRegisteredCommand(spec, ctx) {
227
- const stdin = ctx.stdin ?? { text: "" };
228
- const parsed = parseCommandInput(spec, ctx.argv, stdin);
229
- if (parsed.subcommand === "prompt") {
230
- const node = resolveCommandNode(spec, parsed.path);
231
- const parentPath = [spec.name, ...parsed.path.slice(0, -1)].join(" ");
232
- await ctx.io.stdout(`${renderCommandPrompt(node, parsed.path.length > 0 ? parentPath : "")}\n`);
231
+ async function runRegisteredCommand(root, ctx) {
232
+ const stdin = ctx.stdin ?? emptyStdin();
233
+ const parsed = parseCommandInput(root, ctx.argv, stdin);
234
+ const displayPath = parsed.path.join(" ");
235
+ if (parsed.help) {
236
+ const node = resolveCommand(root, parsed.path);
237
+ const parentPath = parsed.path.length > 1 ? parsed.path.slice(0, -1).join(" ") : "";
238
+ await ctx.io.stdout(`${renderCommandHelp(node, parentPath)}\n`);
233
239
  return { exitCode: 0 };
234
240
  }
235
- const subcommand = resolveCommandNode(spec, parsed.path);
236
- if (isCommandGroup(subcommand)) throw new Error(`"${[spec.name, ...parsed.path].join(" ")}" is not runnable`);
237
- if (parsed.json && !subcommand.output?.json) throw new Error(`Subcommand "${[spec.name, ...parsed.path].join(" ")}" does not define JSON output`);
241
+ const command = resolveCommand(root, parsed.path);
242
+ if (!command.run) throw new Error(`"${displayPath}" is not runnable`);
243
+ if (parsed.json && !command.output?.json) throw new Error(`Command "${displayPath}" does not define JSON output`);
238
244
  const capture = new CapturingIO(ctx.io);
239
- const result = await subcommand.run({
245
+ const result = await command.run({
240
246
  argv: ctx.argv,
241
247
  parsed,
242
248
  stdin,
@@ -251,60 +257,73 @@ async function runRegisteredCommand(spec, ctx) {
251
257
  try {
252
258
  json = JSON.parse(raw);
253
259
  } catch (error) {
254
- throw new Error(`Invalid JSON output for "${[spec.name, ...parsed.path].join(" ")}": ${asError(error).message}`);
260
+ throw new Error(`Invalid JSON output for "${displayPath}": ${asError(error).message}`);
255
261
  }
256
- const validation = subcommand.output?.json?.safeParse(json);
262
+ const validation = command.output?.json?.safeParse(json);
257
263
  if (!validation?.success) {
258
264
  const issue = validation?.error.issues[0];
259
- throw new Error(`JSON output failed validation for "${[spec.name, ...parsed.path].join(" ")}": ${issue?.message}`);
265
+ throw new Error(`JSON output failed validation for "${displayPath}": ${issue?.message}`);
260
266
  }
261
267
  await ctx.io.stdout(raw);
262
268
  }
263
269
  return result;
264
270
  }
265
- function renderCommandPrompt(spec, parentPath = "") {
266
- const path = parentPath ? `${parentPath} ${spec.name}` : spec.name;
267
- const leaves = spec.subcommands.filter((node) => !isCommandGroup(node));
268
- const groups = spec.subcommands.filter(isCommandGroup);
269
- const lines = [`${path}: ${spec.summary}`];
270
- if (leaves.length > 0) {
271
- lines.push("", "Subcommands:");
272
- for (const subcommand of leaves) {
273
- lines.push("", ` ${path} ${subcommand.name}`);
274
- lines.push(` ${subcommand.summary}`);
275
- if (subcommand.effects) lines.push(` Effects: ${subcommand.effects}`);
276
- if (subcommand.successOutput) lines.push(` Success output: ${subcommand.successOutput}`);
277
- else if (subcommand.output?.json) lines.push(" Success output: raw text by default; machine-readable JSON when --json is passed");
278
- if (subcommand.failureOutput) lines.push(` Failure output: ${subcommand.failureOutput}`);
279
- const fields = Object.entries(subcommand.input ?? {});
280
- if (fields.length > 0) {
281
- lines.push(" Parameters:");
282
- for (const [field, schema] of fields) {
283
- const positional = subcommand.positionals?.includes(field) ?? false;
284
- const stdin = subcommand.stdinField === field;
285
- lines.push(` ${formatField(field, schema, positional, stdin)}`);
286
- }
287
- }
288
- if (subcommand.stdinField) lines.push(` stdin/heredoc: ${subcommand.stdinField}`);
289
- if (subcommand.output?.json) lines.push(" --json: emits machine-readable JSON for this subcommand");
290
- else lines.push(" --json: accepted only when this subcommand defines JSON output");
291
- if (subcommand.examples.length > 0) {
292
- lines.push(" Examples:");
293
- for (const example of subcommand.examples) lines.push(indent(example, 6));
271
+ function renderCommandHelp(command, parentPath = "") {
272
+ const path = parentPath ? `${parentPath} ${command.name}` : command.name;
273
+ const blocks = [];
274
+ const lines = [`${path}: ${command.summary}`];
275
+ if (command.run) {
276
+ lines.push("", "Usage:");
277
+ lines.push("", ` ${path}`);
278
+ if (command.effects) lines.push(` Effects: ${command.effects}`);
279
+ if (command.successOutput) lines.push(` Success output: ${command.successOutput}`);
280
+ else if (command.output?.json) lines.push(" Success output: raw text by default; machine-readable JSON when --json is passed");
281
+ if (command.failureOutput) lines.push(` Failure output: ${command.failureOutput}`);
282
+ const fields = Object.entries(command.input ?? {});
283
+ if (fields.length > 0) {
284
+ lines.push(" Parameters:");
285
+ for (const [field, schema] of fields) {
286
+ const positional = command.positionals?.includes(field) ?? false;
287
+ const stdin = command.stdinField === field;
288
+ lines.push(` ${formatField(field, schema, positional, stdin)}`);
294
289
  }
295
290
  }
291
+ if (command.stdinField) lines.push(` stdin/heredoc: ${command.stdinField}`);
292
+ if (command.output?.json) lines.push(" --json: emits machine-readable JSON for this command");
293
+ else lines.push(" --json: accepted only when this command defines JSON output");
294
+ const examples = command.examples ?? [];
295
+ if (examples.length > 0) {
296
+ lines.push(" Examples:");
297
+ for (const example of examples) lines.push(indent(example, 6));
298
+ }
296
299
  }
297
- const blocks = [lines.join("\n")];
298
- for (const group of groups) blocks.push(renderCommandPrompt(group, path));
300
+ const children = command.subcommands ?? [];
301
+ if (children.length > 0) {
302
+ lines.push("", "Subcommands:");
303
+ for (const child of children) lines.push(` ${path} ${child.name} — ${child.summary}`);
304
+ }
305
+ blocks.push(lines.join("\n"));
306
+ for (const child of children) blocks.push(renderCommandHelp(child, path));
299
307
  return blocks.join("\n\n");
300
308
  }
301
- function validateCommandTree(node, path) {
309
+ function validateCommandTree(command, path) {
310
+ if (!COMMAND_NAME_PATTERN.test(command.name)) throw new Error(`CommandRegistry: "${path}" has invalid name "${command.name}"; use letters, numbers, underscores, and hyphens`);
311
+ const hasRun = typeof command.run === "function";
312
+ const children = command.subcommands ?? [];
313
+ if (!hasRun && children.length === 0) throw new Error(`CommandRegistry: "${path}" must have run() and/or subcommands`);
314
+ if (hasRun) {
315
+ if (!Array.isArray(command.examples)) throw new Error(`CommandRegistry: "${path}" defines run() but is missing examples[]`);
316
+ } else for (const field of EXECUTION_ONLY_FIELDS) if (command[field] !== void 0) throw new Error(`CommandRegistry: "${path}" sets ${field} without run()`);
317
+ if (hasRun) {
318
+ const input = command.input ?? {};
319
+ if (command.stdinField && !(command.stdinField in input)) throw new Error(`CommandRegistry: "${path}" stdinField "${command.stdinField}" is not in input`);
320
+ for (const positional of command.positionals ?? []) if (!(positional in input)) throw new Error(`CommandRegistry: "${path}" positional "${positional}" is not in input`);
321
+ }
302
322
  const seen = /* @__PURE__ */ new Set();
303
- for (const child of node.subcommands) {
304
- if (child.name === "prompt") throw new Error(`CommandRegistry: "${path} prompt" is reserved for the help pseudo-subcommand`);
323
+ for (const child of children) {
305
324
  if (seen.has(child.name)) throw new Error(`CommandRegistry: duplicate subcommand "${path} ${child.name}"`);
306
325
  seen.add(child.name);
307
- if (isCommandGroup(child)) validateCommandTree(child, `${path} ${child.name}`);
326
+ validateCommandTree(child, `${path} ${child.name}`);
308
327
  }
309
328
  }
310
329
  function setParsedValue(values, field, value) {
@@ -385,75 +404,11 @@ var CapturingIO = class {
385
404
  async stderr(data) {
386
405
  await this.target.stderr(data);
387
406
  }
388
- async asset(asset) {
389
- await this.target.asset(asset);
390
- }
391
407
  stdoutText() {
392
408
  return decodeUtf8(concatBytes(this.chunks));
393
409
  }
394
410
  };
395
411
  //#endregion
396
- //#region src/command-projection.ts
397
- function listRegisteredCommandOperations(commands) {
398
- const specs = Array.isArray(commands) ? commands : commands.list();
399
- const operations = [];
400
- for (const spec of specs) collectOperations(spec, [spec.name], operations);
401
- return operations;
402
- }
403
- function collectOperations(node, path, operations) {
404
- for (const child of node.subcommands) {
405
- if (isCommandGroup(child)) {
406
- collectOperations(child, [...path, child.name], operations);
407
- continue;
408
- }
409
- operations.push(operationFromLeaf(child, [...path, child.name]));
410
- }
411
- }
412
- function operationFromLeaf(leaf, path) {
413
- const parts = [leaf.summary.trim()];
414
- if (leaf.effects) parts.push(`Effects: ${leaf.effects}`);
415
- return {
416
- path,
417
- description: parts.join(" "),
418
- inputSchema: inputSchemaFromSpec(leaf.input ?? {}),
419
- renderScript: (values) => renderInvocationScript(leaf, path, values)
420
- };
421
- }
422
- function inputSchemaFromSpec(input) {
423
- const schema = z.toJSONSchema(z.object(input));
424
- delete schema.$schema;
425
- return schema;
426
- }
427
- function renderInvocationScript(leaf, path, values) {
428
- const tokens = [...path];
429
- for (const field of Object.keys(leaf.input ?? {})) {
430
- if (field === leaf.stdinField) continue;
431
- const value = values[field];
432
- if (value === void 0 || value === null) continue;
433
- for (const item of Array.isArray(value) ? value : [value]) tokens.push(`--${field}`, shellQuote(scalarText(item)));
434
- }
435
- const script = tokens.join(" ");
436
- if (!leaf.stdinField) return script;
437
- const stdinValue = values[leaf.stdinField];
438
- const body = stdinValue === void 0 || stdinValue === null ? "" : scalarText(stdinValue);
439
- const delimiter = heredocDelimiter(body);
440
- return `${script} <<'${delimiter}'\n${body}\n${delimiter}`;
441
- }
442
- function scalarText(value) {
443
- if (typeof value === "string") return value;
444
- if (typeof value === "number" || typeof value === "boolean") return String(value);
445
- return JSON.stringify(value);
446
- }
447
- function shellQuote(value) {
448
- return `'${value.replaceAll("'", `'\\''`)}'`;
449
- }
450
- function heredocDelimiter(body) {
451
- let delimiter = "DEMI_STDIN";
452
- const lines = new Set(body.split("\n"));
453
- while (lines.has(delimiter)) delimiter += "_X";
454
- return delimiter;
455
- }
456
- //#endregion
457
412
  //#region src/background-command.ts
458
413
  function extractSimpleBackgroundCommand(statementInput) {
459
414
  const statement = statementInput;
@@ -560,8 +515,10 @@ function createOutputSinks(fs, cwd, redirections) {
560
515
  function recordForegroundChunk(foreground, sourceFd, chunk) {
561
516
  const text = decodeUtf8(chunk);
562
517
  foreground.lastOutputAt = Date.now();
563
- if (sourceFd === 1) foreground.rawStdoutBuffer += text;
564
- else foreground.rawStderrBuffer += text;
518
+ if (sourceFd === 1) {
519
+ foreground.rawStdoutBuffer += text;
520
+ foreground.rawStdoutBytes.push(chunk);
521
+ } else foreground.rawStderrBuffer += text;
565
522
  const sink = foreground.outputSinks[sourceFd];
566
523
  if (sink.kind === "file" || sink.kind === "null") {
567
524
  sink.bytes.push(chunk);
@@ -703,18 +660,18 @@ var CommandArtifactStore = class {
703
660
  };
704
661
  //#endregion
705
662
  //#region src/registered-command-adapter.ts
706
- function commandSpecToForkCommand(session, spec, storage) {
663
+ function commandToForkCommand(session, command, storage) {
707
664
  return {
708
- name: spec.name,
709
- consumesStdin: treeConsumesStdin(spec),
665
+ name: command.name,
666
+ consumesStdin: treeConsumesStdin(command),
710
667
  execute: async (args, ctx) => {
711
- const stdinText = decodeForkStdin(ctx.stdin);
668
+ const stdin = decodeForkStdin(ctx.stdin);
712
669
  const io = createForwardingIO();
713
- const argv = [spec.name, ...args];
670
+ const argv = [command.name, ...args];
714
671
  try {
715
- const result = await runRegisteredCommand(spec, {
672
+ const result = await runRegisteredCommand(command, {
716
673
  argv,
717
- stdin: { text: stdinText },
674
+ stdin,
718
675
  env: mapToRecord(ctx.env),
719
676
  cwd: ctx.cwd,
720
677
  io,
@@ -722,18 +679,19 @@ function commandSpecToForkCommand(session, spec, storage) {
722
679
  });
723
680
  session.accumulator.audit.push({
724
681
  kind: "registered-command",
725
- name: spec.name,
682
+ name: command.name,
726
683
  args,
727
684
  exitCode: result.exitCode
728
685
  });
729
686
  if (result.metadata !== void 0) session.accumulator.commandMetadata.push({
730
687
  kind: "registered-command",
731
- name: spec.name,
688
+ name: command.name,
732
689
  args,
733
690
  metadata: result.metadata
734
691
  });
735
692
  return {
736
- stdout: io.stdoutText(),
693
+ stdout: io.stdoutLatin1(),
694
+ stdoutKind: "bytes",
737
695
  stderr: io.stderrText(),
738
696
  exitCode: result.exitCode
739
697
  };
@@ -741,75 +699,90 @@ function commandSpecToForkCommand(session, spec, storage) {
741
699
  const message = error instanceof Error ? error.message : String(error);
742
700
  session.accumulator.audit.push({
743
701
  kind: "registered-command",
744
- name: spec.name,
702
+ name: command.name,
745
703
  args,
746
704
  exitCode: 1
747
705
  });
748
706
  return {
749
- stdout: io.stdoutText(),
750
- stderr: `${io.stderrText()}${spec.name}: ${message}\n`,
707
+ stdout: io.stdoutLatin1(),
708
+ stdoutKind: "bytes",
709
+ stderr: `${io.stderrText()}${command.name}: ${message}\n`,
751
710
  exitCode: 1
752
711
  };
753
- } finally {
754
- if (io.assets().length > 0) session.accumulator.assets.push(...io.assets());
755
712
  }
756
713
  }
757
714
  };
758
715
  }
716
+ /** Collects command output as raw bytes; stdout stays byte-clean for the pipe. */
759
717
  var ForwardingIO = class {
760
718
  stdoutChunks = [];
761
719
  stderrChunks = [];
762
- assetItems = [];
763
720
  async stdout(data) {
764
721
  this.stdoutChunks.push(typeof data === "string" ? encodeUtf8(data) : data);
765
722
  }
766
723
  async stderr(data) {
767
724
  this.stderrChunks.push(typeof data === "string" ? encodeUtf8(data) : data);
768
725
  }
769
- asset(asset) {
770
- this.assetItems.push(asset);
771
- }
772
- stdoutText() {
773
- return decodeUtf8(concatBytes(this.stdoutChunks));
726
+ stdoutLatin1() {
727
+ return decodeLatin1(concatBytes(this.stdoutChunks));
774
728
  }
775
729
  stderrText() {
776
730
  return decodeUtf8(concatBytes(this.stderrChunks));
777
731
  }
778
- assets() {
779
- return this.assetItems;
780
- }
781
732
  };
782
733
  function createForwardingIO() {
783
734
  return new ForwardingIO();
784
735
  }
785
- function treeConsumesStdin(spec) {
786
- return spec.subcommands.some((node) => isCommandGroup(node) ? treeConsumesStdin(node) : Boolean(node.stdinField));
736
+ function treeConsumesStdin(command) {
737
+ if (command.stdinField) return true;
738
+ return command.subcommands?.some(treeConsumesStdin) ?? false;
787
739
  }
788
740
  function mapToRecord(map) {
789
741
  const record = Object.create(null);
790
742
  for (const [key, value] of map) record[key] = value;
791
743
  return record;
792
744
  }
745
+ /** Pipes hand stdin over as a latin1-packed byte string; expose bytes and a UTF-8 text view. */
793
746
  function decodeForkStdin(stdin) {
794
- if (!stdin) return "";
795
- if (stdin instanceof Uint8Array) return decodeUtf8(stdin);
747
+ if (!stdin) return {
748
+ text: "",
749
+ bytes: /* @__PURE__ */ new Uint8Array(0)
750
+ };
751
+ if (stdin instanceof Uint8Array) return {
752
+ text: decodeUtf8(stdin),
753
+ bytes: stdin
754
+ };
796
755
  const latin1 = stdin;
797
- if (!latin1) return "";
756
+ if (!latin1) return {
757
+ text: "",
758
+ bytes: /* @__PURE__ */ new Uint8Array(0)
759
+ };
760
+ const bytes = encodeLatin1(latin1);
798
761
  let hasHighByte = false;
762
+ let hasWideChar = false;
799
763
  for (let i = 0; i < latin1.length; i += 1) {
800
764
  const code = latin1.charCodeAt(i);
801
- if (code > 255) return latin1;
802
- if (code > 127) hasHighByte = true;
765
+ if (code > 255) hasWideChar = true;
766
+ else if (code > 127) hasHighByte = true;
803
767
  }
804
- if (!hasHighByte) return latin1;
805
- const bytes = new Uint8Array(latin1.length);
806
- for (let i = 0; i < latin1.length; i += 1) bytes[i] = latin1.charCodeAt(i);
807
- return decodeUtf8(bytes);
768
+ if (hasWideChar) return {
769
+ text: latin1,
770
+ bytes: encodeUtf8(latin1)
771
+ };
772
+ if (!hasHighByte) return {
773
+ text: latin1,
774
+ bytes
775
+ };
776
+ return {
777
+ text: decodeUtf8(bytes),
778
+ bytes
779
+ };
808
780
  }
809
781
  //#endregion
810
782
  //#region src/environment.ts
811
783
  const DEFAULT_TIMEOUT_MS = 1e4;
812
784
  const DEFAULT_OUTPUT_LIMIT_BYTES = 1024 * 1024;
785
+ /** Upper bound for a single exec observation window (also the command-bridge wait ceiling). */
813
786
  const MAX_TIMEOUT_MS = 6e5;
814
787
  var BashEnvironment = class {
815
788
  host;
@@ -836,19 +809,23 @@ var BashEnvironment = class {
836
809
  getShell(shellId) {
837
810
  return this.shells.get(shellId) ?? null;
838
811
  }
839
- registerCommand(spec) {
840
- if (this.commands.get(spec.name)) return;
841
- this.commands.register(spec);
812
+ registerCommand(command) {
813
+ if (this.commands.get(command.name)) return;
814
+ this.commands.register(command);
842
815
  }
843
816
  registeredCommands() {
844
817
  return this.commands.list();
845
818
  }
846
819
  async exec(input) {
847
820
  const timeoutMs = normalizeTimeoutMs(input.timeoutMs ?? DEFAULT_TIMEOUT_MS);
848
- const session = input.shellId ? this.requireShell(input.shellId) : this.availableDefaultShell(input.agentSessionId);
821
+ if (input.shellId && input.ephemeral) throw new Error("ShellExecInput: \"shellId\" and \"ephemeral\" are mutually exclusive");
822
+ if (input.cwd !== void 0 && !input.ephemeral) throw new Error("ShellExecInput: \"cwd\" requires \"ephemeral\"; a persistent shell owns its cwd");
823
+ if (input.cwd !== void 0) {
824
+ if (!(await this.host.fs.stat(input.cwd).catch(() => null))?.isDirectory) throw new Error(`Shell exec cwd is not a directory: ${input.cwd}`);
825
+ }
826
+ const session = input.shellId ? this.requireShell(input.shellId) : input.ephemeral ? this.createShell(input.agentSessionId, input.cwd) : this.availableDefaultShell(input.agentSessionId);
849
827
  if (session.exited) throw new Error(`Shell session "${session.id}" has exited`);
850
828
  if (input.agentSessionId) {
851
- session.state.env.set("DEMI_AGENT_SESSION_ID", input.agentSessionId);
852
829
  const extraEnv = this.execEnv?.(input.agentSessionId);
853
830
  if (extraEnv) {
854
831
  const exported = session.state.exportedVars ?? /* @__PURE__ */ new Set();
@@ -962,10 +939,10 @@ var BashEnvironment = class {
962
939
  if (!agentSessionId || shell.exited || !shell.pendingExec && !shell.foreground) return shell;
963
940
  return this.createShell(agentSessionId);
964
941
  }
965
- createShell(agentSessionId) {
942
+ createShell(agentSessionId, initialCwd) {
966
943
  const id = this.shellIdFactory();
967
944
  const commandScopeId = agentSessionId ?? id;
968
- const cwd = this.host.defaultCwd;
945
+ const cwd = initialCwd ?? this.host.defaultCwd;
969
946
  const fs = new HostBackedFileSystem(this.host, { lookup: (path) => this.lookupVirtualArtifact(commandScopeId, path) });
970
947
  const env = /* @__PURE__ */ new Map();
971
948
  for (const [key, value] of Object.entries(this.initialEnv)) env.set(key, value);
@@ -1050,8 +1027,7 @@ var BashEnvironment = class {
1050
1027
  stdout: "",
1051
1028
  stderr: "",
1052
1029
  audit: [],
1053
- commandMetadata: [],
1054
- assets: []
1030
+ commandMetadata: []
1055
1031
  },
1056
1032
  foregroundWaiters: /* @__PURE__ */ new Set(),
1057
1033
  backgroundJobs: /* @__PURE__ */ new Map(),
@@ -1060,7 +1036,7 @@ var BashEnvironment = class {
1060
1036
  };
1061
1037
  for (const command of createPortableCommands(session)) forkCommands.set(command.name, command);
1062
1038
  const storage = new AgentSessionCommandStorage(this.host.store, commandScopeId);
1063
- for (const spec of this.commands.list()) forkCommands.set(spec.name, commandSpecToForkCommand(session, spec, storage));
1039
+ for (const command of this.commands.list()) forkCommands.set(command.name, commandToForkCommand(session, command, storage));
1064
1040
  session.abortController = new AbortController();
1065
1041
  session.interpreter = new Interpreter({
1066
1042
  fs,
@@ -1090,6 +1066,7 @@ var BashEnvironment = class {
1090
1066
  }
1091
1067
  async runScript(session, script, input) {
1092
1068
  const record = this.createCommandRecord(session, script);
1069
+ record.outputLimitBytes = input.maxOutputBytes ?? this.defaultOutputLimitBytes;
1093
1070
  let ast;
1094
1071
  try {
1095
1072
  ast = parse(script);
@@ -1109,8 +1086,7 @@ var BashEnvironment = class {
1109
1086
  stdout: "",
1110
1087
  stderr: "",
1111
1088
  audit: [],
1112
- commandMetadata: [],
1113
- assets: []
1089
+ commandMetadata: []
1114
1090
  };
1115
1091
  session.abortController = new AbortController();
1116
1092
  session.activeCommandId = record.id;
@@ -1146,7 +1122,7 @@ var BashEnvironment = class {
1146
1122
  outputOffset: 0,
1147
1123
  audit: [],
1148
1124
  commandMetadata: [],
1149
- assets: []
1125
+ outputLimitBytes: this.defaultOutputLimitBytes
1150
1126
  };
1151
1127
  this.commandsById.set(id, record);
1152
1128
  return record;
@@ -1352,6 +1328,7 @@ var BashEnvironment = class {
1352
1328
  startedAt,
1353
1329
  lastOutputAt: startedAt,
1354
1330
  rawStdoutBuffer: "",
1331
+ rawStdoutBytes: [],
1355
1332
  rawStderrBuffer: "",
1356
1333
  stdoutBuffer: "",
1357
1334
  stderrBuffer: "",
@@ -1372,7 +1349,7 @@ var BashEnvironment = class {
1372
1349
  };
1373
1350
  session.foreground = foreground;
1374
1351
  notifyForegroundWaiters(session.foregroundWaiters, foreground);
1375
- if (opts.stdin && opts.stdin.length > 0) await handle.writeStdin(encodeUtf8(opts.stdin));
1352
+ if (opts.stdin && opts.stdin.length > 0) await handle.writeStdin(encodeLatin1(opts.stdin));
1376
1353
  if (opts.stdinProvided) await handle.closeStdin();
1377
1354
  if (handle.output) {
1378
1355
  foreground.stdoutPump = pumpOutputStream(handle.output, (chunk) => {
@@ -1385,7 +1362,7 @@ var BashEnvironment = class {
1385
1362
  }
1386
1363
  const exit = await foreground.exitPromise;
1387
1364
  await Promise.allSettled([foreground.stdoutPump, foreground.stderrPump]);
1388
- const stdout = foreground.rawStdoutBuffer;
1365
+ const stdout = decodeLatin1(concatBytes(foreground.rawStdoutBytes));
1389
1366
  const exitCode = exit.exitCode ?? 127;
1390
1367
  const stderr = exit.exitCode === null && foreground.rawStderrBuffer.length === 0 ? `${command}: ${exit.signal ?? "command not found"}\n` : foreground.rawStderrBuffer;
1391
1368
  foreground.audit[0] = {
@@ -1406,6 +1383,7 @@ var BashEnvironment = class {
1406
1383
  session.foreground = void 0;
1407
1384
  return {
1408
1385
  stdout,
1386
+ stdoutKind: "bytes",
1409
1387
  stderr,
1410
1388
  exitCode
1411
1389
  };
@@ -1449,12 +1427,35 @@ var BashEnvironment = class {
1449
1427
  appendRecordOutput(record, "stderr", text);
1450
1428
  return this.finishExited(session, record, 1, input);
1451
1429
  }
1452
- const stdoutText = foreground ? resultOrError.stdout : decodeBytesToUtf8(unsafeBytesFromLatin1(resultOrError.stdout));
1430
+ const raw = resultOrError.stdout;
1431
+ let stdoutText;
1432
+ let binary;
1433
+ if (hasWideChar(raw)) stdoutText = raw;
1434
+ else {
1435
+ const bytes = encodeLatin1(raw);
1436
+ const strict = decodeUtf8Strict(bytes);
1437
+ if (strict !== null) stdoutText = strict;
1438
+ else {
1439
+ const cap = record.outputLimitBytes;
1440
+ const truncated = bytes.length > cap;
1441
+ binary = {
1442
+ data: truncated ? bytes.slice(0, cap) : bytes,
1443
+ truncated,
1444
+ totalBytes: bytes.length
1445
+ };
1446
+ stdoutText = `<binary stdout: ${bytes.length} bytes${truncated ? `, exceeds the ${cap}-byte output limit` : ""}; raw bytes at /@/commands/${record.id}/stdout.bin>\n`;
1447
+ }
1448
+ }
1453
1449
  const stderrText = foreground ? resultOrError.stderr : decodeBytesToUtf8(unsafeBytesFromLatin1(resultOrError.stderr));
1454
1450
  session.accumulator.stdout += stdoutText;
1455
1451
  session.accumulator.stderr += stderrText;
1456
- if (foreground) record.outputChunks = [...foreground.outputChunks];
1457
- else if (record.outputChunks.length === 0) {
1452
+ if (binary) record.binaryStdout = binary;
1453
+ if (foreground && !binary) record.outputChunks = [...foreground.outputChunks];
1454
+ else if (binary) {
1455
+ record.outputChunks = [];
1456
+ appendRecordOutput(record, "stdout", stdoutText);
1457
+ appendRecordOutput(record, "stderr", stderrText);
1458
+ } else if (record.outputChunks.length === 0) {
1458
1459
  appendRecordOutput(record, "stdout", stdoutText);
1459
1460
  appendRecordOutput(record, "stderr", stderrText);
1460
1461
  }
@@ -1473,7 +1474,6 @@ var BashEnvironment = class {
1473
1474
  record.exitCode = exitCode;
1474
1475
  record.audit = [...session.accumulator.audit];
1475
1476
  record.commandMetadata = [...session.accumulator.commandMetadata];
1476
- record.assets = [...session.accumulator.assets];
1477
1477
  session.pendingExec = void 0;
1478
1478
  if (session.activeCommandId === record.id) session.activeCommandId = void 0;
1479
1479
  return this.snapshotCommand(record, input);
@@ -1538,7 +1538,7 @@ var BashEnvironment = class {
1538
1538
  audit: record.audit
1539
1539
  };
1540
1540
  if (record.commandMetadata.length > 0) result.commandMetadata = record.commandMetadata;
1541
- if (record.assets.length > 0) result.assets = record.assets;
1541
+ if (record.binaryStdout) result.binaryStdout = record.binaryStdout;
1542
1542
  return result;
1543
1543
  }
1544
1544
  if (record.status === "aborted") return {
@@ -1555,18 +1555,22 @@ var BashEnvironment = class {
1555
1555
  if (parts.length === 1 && parts[0] === "@") return virtualDirectory(["commands"]);
1556
1556
  if (parts.length === 2 && parts[0] === "@" && parts[1] === "commands") return virtualDirectory(await this.commandArtifactIds(scopeId));
1557
1557
  if (parts.length === 3 && parts[0] === "@" && parts[1] === "commands") {
1558
- if (!await this.commandArtifact(scopeId, parts[2])) return null;
1559
- return virtualDirectory([
1558
+ const artifact = await this.commandArtifact(scopeId, parts[2]);
1559
+ if (!artifact) return null;
1560
+ const entries = [
1560
1561
  "meta.json",
1561
1562
  "stderr.txt",
1562
1563
  "stdout.txt"
1563
- ]);
1564
+ ];
1565
+ if (artifact.stdoutBinary) entries.push("stdout.bin");
1566
+ return virtualDirectory(entries);
1564
1567
  }
1565
1568
  if (parts.length !== 4 || parts[0] !== "@" || parts[1] !== "commands") return null;
1566
1569
  const artifact = await this.commandArtifact(scopeId, parts[2]);
1567
1570
  if (!artifact) return null;
1568
1571
  const fileName = parts[3];
1569
1572
  if (fileName === "stdout.txt") return virtualFile(encodeUtf8(artifact.stdout));
1573
+ if (fileName === "stdout.bin" && artifact.stdoutBinary) return virtualFile(base64ToBytes(artifact.stdoutBinary.base64));
1570
1574
  if (fileName === "stderr.txt") return virtualFile(encodeUtf8(artifact.stderr));
1571
1575
  if (fileName === "meta.json") return virtualFile(encodeUtf8(`${JSON.stringify(commandArtifactMeta(artifact), null, 2)}\n`));
1572
1576
  return null;
@@ -1592,7 +1596,7 @@ var BashEnvironment = class {
1592
1596
  return isPersistedShellCommandArtifact(value) ? value : null;
1593
1597
  }
1594
1598
  persistCommandArtifact(record) {
1595
- const fingerprint = `${record.status}:${record.exitCode ?? ""}:${record.stdout.length}:${record.stderr.length}`;
1599
+ const fingerprint = `${record.status}:${record.exitCode ?? ""}:${record.stdout.length}:${record.stderr.length}:${record.binaryStdout?.totalBytes ?? ""}`;
1596
1600
  if (record.persistedFingerprint === fingerprint) return;
1597
1601
  record.persistedFingerprint = fingerprint;
1598
1602
  this.artifacts.persist(record.commandScopeId, record.id, persistedArtifactFromRecord(record));
@@ -1623,8 +1627,13 @@ function createPortableCommands(session) {
1623
1627
  }
1624
1628
  }));
1625
1629
  }
1630
+ /** True when the string contains a char > 0xFF, i.e. already-decoded Unicode text. */
1631
+ function hasWideChar(value) {
1632
+ for (let i = 0; i < value.length; i += 1) if (value.charCodeAt(i) > 255) return true;
1633
+ return false;
1634
+ }
1626
1635
  function normalizeTimeoutMs(value) {
1627
- if (!Number.isFinite(value) || value < 1 || value > MAX_TIMEOUT_MS) throw new Error(`timeoutMs must be between 1 and ${MAX_TIMEOUT_MS}`);
1636
+ if (!Number.isFinite(value) || value < 1 || value > 6e5) throw new Error(`timeoutMs must be between 1 and ${MAX_TIMEOUT_MS}`);
1628
1637
  return Math.floor(value);
1629
1638
  }
1630
1639
  function streamArtifact(record, stream, explicitOffset, maxOutputBytes) {
@@ -1710,7 +1719,12 @@ function persistedArtifactFromRecord(record) {
1710
1719
  lastOutputAt: record.lastOutputAt,
1711
1720
  exitCode: record.exitCode ?? null,
1712
1721
  stdout: record.stdout,
1713
- stderr: record.stderr
1722
+ stderr: record.stderr,
1723
+ ...record.binaryStdout ? { stdoutBinary: {
1724
+ base64: bytesToBase64(record.binaryStdout.data),
1725
+ truncated: record.binaryStdout.truncated,
1726
+ totalBytes: record.binaryStdout.totalBytes
1727
+ } } : {}
1714
1728
  };
1715
1729
  }
1716
1730
  function commandArtifactMeta(artifact) {
@@ -1732,7 +1746,12 @@ function commandArtifactMeta(artifact) {
1732
1746
  stderr: {
1733
1747
  path: stderrPath,
1734
1748
  bytes: utf8Bytes(artifact.stderr)
1735
- }
1749
+ },
1750
+ ...artifact.stdoutBinary ? { stdoutBinary: {
1751
+ path: `/@/commands/${artifact.commandId}/stdout.bin`,
1752
+ bytes: artifact.stdoutBinary.totalBytes,
1753
+ truncated: artifact.stdoutBinary.truncated
1754
+ } } : {}
1736
1755
  };
1737
1756
  }
1738
1757
  function isPersistedShellCommandArtifact(value) {
@@ -1754,4 +1773,17 @@ function tailString(value) {
1754
1773
  return tail(value, 4096);
1755
1774
  }
1756
1775
  //#endregion
1757
- export { AgentSessionCommandStorage, BashEnvironment, COMMAND_PROMPT_DEFAULTS, CommandRegistry, DEMI_PORTABLE_COMMANDS, HostBackedFileSystem, RESERVED_COMMAND_NAMES, isCommandGroup, listRegisteredCommandOperations, parseCommandInput, renderCommandPrompt, runRegisteredCommand, virtualDirectory, virtualFile };
1776
+ //#region src/shell-quote.ts
1777
+ /** Single-quotes a value for safe use as one shell word. */
1778
+ function shellQuote(value) {
1779
+ return `'${value.replaceAll("'", `'\\''`)}'`;
1780
+ }
1781
+ /** Picks a heredoc delimiter that does not collide with any line already in `body`. */
1782
+ function heredocDelimiter(body) {
1783
+ let delimiter = "DEMI_STDIN";
1784
+ const lines = new Set(body.split("\n"));
1785
+ while (lines.has(delimiter)) delimiter += "_X";
1786
+ return delimiter;
1787
+ }
1788
+ //#endregion
1789
+ export { AgentSessionCommandStorage, BashEnvironment, COMMAND_HELP_DEFAULTS, CommandRegistry, DEMI_PORTABLE_COMMANDS, HostBackedFileSystem, MAX_TIMEOUT_MS, RESERVED_COMMAND_NAMES, emptyStdin, heredocDelimiter, parseCommandInput, renderCommandHelp, runRegisteredCommand, shellQuote, virtualDirectory, virtualFile };