@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
131 lines • 6.01 kB
JavaScript
// ========= 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