UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

131 lines • 6.01 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import { IOResult } from "../../../../io/types.js"; import { encodeText } from "../../../../shell/bytes.js"; import { shellJoin } from "../../../../shell/join.js"; import { singleQuote } from "../../../../utils/quote.js"; import { sessionEntry } from "../../../session/session.js"; import { ExecutionNode } from "../../../types.js"; import { lastOf, scanOptions } from "../getopt.js"; import { classify, describe, programFile } from "../lookup/index.js"; import { NameKind } from "../lookup/types.js"; import { builtinUsage } from "../shared.js"; /** * Run the `-v`/`-V` introspection modes. * * The exit status is 0 when no names are given, otherwise 0 if any name * resolved and 1 if none did (bash's `command` uses this any-found rule, * unlike `type`'s all-found rule). `-v` prints a program's file and any * other resolvable name bare (a builtin, a function); `-V` prints a * verbose line. Not-found names are silent under `-v` and warn on stderr * under `-V`. */ function probe(mode, rest, session, registry, parser) { const outLines = []; const errLines = []; let anyFound = false; for (const name of rest) { const kind = classify(name, session, registry); if (kind === null) { if (mode === 'V') errLines.push(`bash: command: ${name}: not found`); continue; } anyFound = true; // `command -v` prints an alias as its definition, the one form that // is not just the name. const line = mode === 'V' ? describe(name, kind, session, parser) : kind === NameKind.ALIAS ? `alias ${name}=${singleQuote(sessionEntry(session.aliases, name) ?? '')}` : kind === NameKind.FILE ? programFile(name) : name; outLines.push(line); } const out = outLines.length > 0 ? encodeText(`${outLines.join('\n')}\n`) : null; const err = errLines.length > 0 ? encodeText(`${errLines.join('\n')}\n`) : new Uint8Array(); const code = rest.length === 0 || anyFound ? 0 : 1; return [ out, new IOResult({ exitCode: code, stderr: err }), new ExecutionNode({ command: 'command', exitCode: code, stderr: err }), ]; } /** * Run the `command` builtin (`command [-pVv] name [arg ...]`). * * Without `-v`/`-V` it runs the target ignoring any shell function of the * same name (bash's function bypass): the name is masked in the session * function table for the inner run so a shadowing function is skipped * while builtins and mount commands still resolve. Already expanded * operands are re-joined with shellJoin so they survive re-parsing as one * token each; the pipe stdin flows to the inner command. `-p` is accepted * but inert (the default PATH is the one PATH there is) and the last of * `-v`/`-V` wins. */ export async function handleCommandBuiltin(executeFn, args, session, registry, stdin = null, // The caller's frames: the target runs in them, as a builtin does in // bash, so an `exit` in it ends the shell and a `return` the function. // Null runs the target as a line of its own. callStack = null, parser) { const scan = scanOptions(args, 'pvV'); if (scan.bad !== null) { const err = encodeText(`bash: command: ${scan.bad}: invalid option\n${builtinUsage('command')}`); return [ null, new IOResult({ exitCode: 2, stderr: err }), new ExecutionNode({ command: 'command', exitCode: 2, stderr: err }), ]; } const mode = lastOf(scan.letters, 'vV'); const rest = scan.operands; if (mode !== null) return probe(mode, rest, session, registry, parser); if (rest.length === 0) { return [null, new IOResult(), new ExecutionNode({ command: 'command', exitCode: 0 })]; } const innerName = rest[0] ?? ''; const inner = shellJoin(rest); // Function bodies are never undefined, so a defined captured value means // a shadowing function was masked and must be restored after the run. const savedFn = session.functions[innerName]; // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete session.functions[innerName]; // An alias is masked the same way: bash expands an alias only as a // command's first word, which `command` is, so `command cat` runs the // program past `alias cat=...` too. const savedAlias = session.aliases[innerName]; // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete session.aliases[innerName]; try { const io = await executeFn(inner, { sessionId: session.sessionId, stdin, ...(callStack !== null ? { callStack } : {}), }); return [io.stdout, io, new ExecutionNode({ command: 'command', exitCode: io.exitCode })]; } finally { if (savedFn !== undefined) session.functions[innerName] = savedFn; if (savedAlias !== undefined) session.aliases[innerName] = savedAlias; } } /** The `command` arm. */ export async function commandBuiltin(call) { return handleCommandBuiltin(call.executeFn, [...call.argv.args], call.context.session, call.registry, call.stdin, call.callStack, call.parser); } //# sourceMappingURL=command.js.map