UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

112 lines • 5.73 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 { findLayout } from "../../rs/find.js"; import { walk } from "../../commands/cli/walk.js"; import { BUILTIN_SPECS } from "../../commands/spec/builtins.js"; import * as constants from "../../commands/spec/constants.js"; import { parseCommand } from "../../commands/spec/parser.js"; import {} from "../../commands/spec/types.js"; import { unmarkGlobs } from "../../utils/glob_walk.js"; // Installed CLIs own their grammar. A mount command uses the selected // invocation's spec, whether flat or a tree, so the same registration // classifies the words and runs the command. A fallback flat command // without declared arguments leaves path inference to the cwd/shared // grammar; injected help/version options do not declare operand roles. export function specForCommand(name, registry, cwd, argv, access) { const install = registry.clis.get(name); if (install !== null) return install.cli.spec; const mount = argv === undefined ? registry.commandMount(name, [], cwd, access) : registry.invocationMount(name, [], cwd, argv, access); if (mount !== null) { const selected = mount.specFor(name); if (selected !== null && (mount.cliFor(name) !== null || selected.arguments.some((arg) => arg !== constants.HELP_OPTION && arg !== constants.VERSION_OPTION))) { return selected; } } const spec = registry.mountFor(cwd).specFor(name); if (spec !== null) return spec; return BUILTIN_SPECS[name] ?? null; } // Classify argv words into per-position operand kinds. // // Delegates to parseCommand so flag syntax (clusters, --flag=value, // multiple flags, providedBy) classifies identically to dispatch. Kinds // are positional, not value sets, so the same word can be TEXT in one slot // and PATH in another (`grep '*.txt' *.txt`). A flag token is TEXT even // when it carries a path (`sort -o/data/s1.txt`): the parser resolves the // value, and the shape heuristic would read the whole word as a path // under the cwd. // // parseCommand classifies ignoreTokens as TEXT itself, so there is // nothing to override here. A by-value override used to re-null them, // which was both redundant and position-blind: it matched an option's // value as readily as a grammar token, so `find /d -name '!'` lost the // TEXT the parser had correctly given the pattern. // find's `-exec` is the one grammar a spec cannot state (an option whose // argument is a program, up to a terminator), so its words are overridden // to TEXT here: the rest slot would otherwise read `echo`, `{}` and `;` as // start points. The command name is what says the words are find's. A // command tree's words are walked first, as its line runs: the verbs and // group options are TEXT, and the leaf's words are its grammar's, its // undeclared dashed words operands, as a CLI leaf parses them, under the // session environment the run walks with (`env`): a required group option it // fills would otherwise stop the walk short of the leaf. export function specWordKinds(spec, argv, name = '', env) { const literal = argv.map(unmarkGlobs); if (spec.subcommands.length > 0) { const result = walk(name, spec, literal, '/', env ?? null); if (result.leaf === null) return literal.map(() => 'str'); const lead = literal .slice(0, literal.length - result.argv.length) .map(() => 'str'); return [...lead, ...parseCommand(result.leaf, [...result.argv], '/', name, env, true).wordKinds]; } const kinds = [...parseCommand(spec, literal, '/', name, env).wordKinds]; if (name === 'find') { for (const [start, end] of findLayout(literal).spans) { for (let i = start; i <= end; i++) kinds[i] = 'str'; } } return kinds; } // Per-position base directories for a spec that declares one. tar's -C // is a chdir for the operands typed after it, so those words are not // relative to the session cwd at all. The parser already walks the line // positionally, so it is what says where each word stood; this asks it, // and only for the one command family that can answer (null everywhere // else, so 92 of 93 specs pay nothing). A command tree's leaf declares its // own, so a tree is walked to the leaf first, as specWordKinds walks it. export function specWordBases(spec, argv, cwd, name = '', env) { const literal = argv.map(unmarkGlobs); if (spec.subcommands.length > 0) { const result = walk(name, spec, literal, cwd, env ?? null); if (result.leaf?.operandBase == null) return null; const lead = literal .slice(0, literal.length - result.argv.length) .map(() => null); return [...lead, ...parseCommand(result.leaf, [...result.argv], cwd, name, env, true).wordBases]; } if (spec.operandBase === null) return null; return [...parseCommand(spec, literal, cwd, '', env).wordBases]; } //# sourceMappingURL=spec_hints.js.map