@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
136 lines • 5.88 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 { program } from "../../../lookup/lookup.js";
import { ExecutionNode } from "../../../types.js";
import { lastOf, scanOptions } from "../getopt.js";
import { builtinUsage, result } from "../shared.js";
import { describe, locations, programFile } from "./classify.js";
import { TYPE_OPTIONS, WHICH_OPTIONS, WHICH_USAGE } from "./constants.js";
import { NameKind } from "./types.js";
/**
* Run the `type` builtin (`type [-afptP] name [name ...]`).
*
* Resolution matches `command -V`, but the exit rule is `type`'s: 0 only
* when every name resolves. `-t` prints the classification word; `-p`
* prints the file of a name that resolves to one (none for a builtin,
* which still resolves) and `-P` searches PATH for one even past a
* builtin, a miss there being a miss; the three are one group, the last
* winning. `-a` prints one line per layer holding the name (a builtin
* that is also a program ends with its file's line), `-f` ignores the
* function table, and a missing name warns on stderr unless a word-only
* mode (`-t`/`-p`/`-P`) is active. Pinned against bash 5.2 on
* debian:stable-slim.
*/
export function handleType(args, session, registry, parser) {
const scan = scanOptions(args, TYPE_OPTIONS);
if (scan.bad !== null) {
return result('type', {
exitCode: 2,
stderr: `bash: type: ${scan.bad}: invalid option\n${builtinUsage('type')}`,
});
}
const mode = lastOf(scan.letters, 'tpP');
const allMode = scan.letters.includes('a');
const hidden = scan.letters.includes('f') ? NameKind.FUNCTION : null;
const outLines = [];
const errLines = [];
let allFound = true;
for (const name of scan.operands) {
if (mode === 'P') {
if (program(name, session, registry) === null)
allFound = false;
else
outLines.push(`${programFile(name)}\n`);
continue;
}
const kinds = locations(name, session, registry, allMode, hidden);
if (kinds.length === 0) {
allFound = false;
if (mode === null)
errLines.push(`bash: type: ${name}: not found\n`);
continue;
}
if (mode === 't')
for (const item of kinds.map((kind) => `${kind}\n`))
outLines.push(item);
else if (mode === 'p') {
for (const kind of kinds)
if (kind === NameKind.FILE)
outLines.push(`${programFile(name)}\n`);
}
else
outLines.push(...kinds.map((kind) => `${describe(name, kind, session, parser)}\n`));
}
const out = outLines.length > 0 ? encodeText(outLines.join('')) : null;
const err = encodeText(errLines.join(''));
const code = scan.operands.length === 0 || allFound ? 0 : 1;
return [
out,
new IOResult({ exitCode: code, stderr: err }),
new ExecutionNode({ command: 'type', exitCode: code, stderr: err }),
];
}
/**
* Run the `which` builtin (`which [-as] name [name ...]`).
*
* Pinned against debianutils `which` (debian:stable-slim): it prints the
* file PATH finds for each name, which is the program's under `/usr/bin`
* (the one PATH directory), a miss prints nothing at all, the exit status
* is 0 only when every name resolves (1 with no operands), and `-s`
* reports through the status alone. A builtin with no program (`cd`), a
* function, an alias and a reserved word are no file, so each is a miss;
* `-a` has one directory to search and so one line per name. `$PATH`
* itself is not read: mirage runs a program by its name whatever PATH
* holds, so `which` answers as dispatch does. The refusal for an unknown
* option is bash's shape, not the C tool's `Illegal option`, because this
* is a builtin.
*/
export function handleWhich(args, session, registry) {
const scan = scanOptions(args, WHICH_OPTIONS);
if (scan.bad !== null) {
return result('which', {
exitCode: 2,
stderr: `which: ${scan.bad}: invalid option\n${WHICH_USAGE}`,
});
}
const silent = scan.letters.includes('s');
const outLines = [];
let allFound = true;
for (const name of scan.operands) {
if (program(name, session, registry) === null) {
allFound = false;
continue;
}
if (!silent)
outLines.push(`${programFile(name)}\n`);
}
const out = outLines.length > 0 ? encodeText(outLines.join('')) : null;
const code = scan.operands.length > 0 && allFound ? 0 : 1;
return [
out,
new IOResult({ exitCode: code }),
new ExecutionNode({ command: 'which', exitCode: code }),
];
}
/** The `type` arm. */
export function typeBuiltin(call) {
return Promise.resolve(handleType([...call.argv.args], call.context.session, call.registry, call.parser));
}
/** The `which` arm. */
export function whichBuiltin(call) {
return Promise.resolve(handleWhich([...call.argv.args], call.context.session, call.registry));
}
//# sourceMappingURL=lookup.js.map