Files
reverse-engineering-anything/src/cliBuiltinArguments.ts
T

125 lines
4.3 KiB
TypeScript

/** Global output flags that take a value. */
const VALUED_OUTPUT_FLAGS = new Set([
"--format",
"--filter-output",
"--token-limit",
"--token-offset",
]);
const ROOT_UPDATE_OUTPUT_FLAGS = new Set([
"--json",
"--full-output",
"--token-count",
]);
/** Valueless flags Incur removes from every position before command parsing. */
const INCUR_BUILTIN_FLAGS = new Set([
"--full-output",
"--llms",
"--llms-full",
"--mcp",
"--help",
"-h",
"--update",
// Incur's internal update-check flag.
"--incur-update-check",
"--schema",
"--json",
"--token-count",
]);
/** Command tokens and output-flag values as Incur's builtin pass assigns them. */
export interface IncurBuiltinArgumentSplit {
/** Tokens left for command resolution and command option parsing. */
readonly command: readonly string[];
/** Positions of nonempty values consumed by valued output flags. */
readonly outputValues: readonly {
readonly flag: string;
readonly index: number;
}[];
}
/**
* Mirror Incur's builtin pass over normalized arguments without stopping at a
* rejected value, so callers can still see every later token. REA configures
* neither a config flag nor global options, which Incur would also remove.
*/
export const splitIncurBuiltinArguments = (
arguments_: readonly string[],
): IncurBuiltinArgumentSplit => {
const command: string[] = [];
const outputValues: { readonly flag: string; readonly index: number }[] = [];
for (let index = 0; index < arguments_.length; index += 1) {
const argument = arguments_[index]!;
const next = arguments_[index + 1];
if (
INCUR_BUILTIN_FLAGS.has(argument) ||
// A following value belongs to a command-local `--version` option.
(argument === "--version" && (next === undefined || next.startsWith("-")))
)
continue;
if (VALUED_OUTPUT_FLAGS.has(argument) && next) {
outputValues.push({ flag: argument, index: index + 1 });
index += 1;
continue;
}
command.push(argument);
}
return { command, outputValues };
};
/**
* Route the root update alias and split `--flag=value` output forms. Incur reads these
* flags only in spaced form, so `--format=json` otherwise reaches the command
* parser and fails as an unknown flag. Command options accept both forms.
*/
export const normalizeCliArguments = (
arguments_: readonly string[],
): string[] => {
const normalized: string[] = [];
const updates = new Set<number>();
let rootUpdate = true;
for (let index = 0; index < arguments_.length; index += 1) {
const argument = arguments_[index]!;
if (VALUED_OUTPUT_FLAGS.has(argument) && arguments_[index + 1]) {
// Incur consumes a nonempty spaced value before looking for more flags.
// Its contents belong to that option, even when they look like a flag.
normalized.push(argument, arguments_[index + 1]!);
index += 1;
continue;
}
const separator = argument.indexOf("=");
const flag = argument.slice(0, separator);
if (separator >= 0 && VALUED_OUTPUT_FLAGS.has(flag))
normalized.push(flag, argument.slice(separator + 1));
else {
if (argument === "--update") updates.add(normalized.length);
else if (!ROOT_UPDATE_OUTPUT_FLAGS.has(argument)) rootUpdate = false;
normalized.push(argument);
}
}
// Output option values were consumed above; flag-shaped data is never an alias.
// Help/check/command/unknown tokens retain Incur's parsing and precedence.
return rootUpdate && updates.size > 0
? ["update", ...normalized.filter((_, index) => !updates.has(index))]
: normalized;
};
/** Select production doctor using the same normalized builtin argument owner as CLI dispatch. */
export const productionMcpDoctorArguments = (
arguments_: readonly string[],
): readonly string[] | undefined => {
const normalized = normalizeCliArguments(arguments_);
const split = splitIncurBuiltinArguments(normalized);
if (split.command[0] !== "mcp" || split.command[1] !== "doctor")
return undefined;
const values = new Set(split.outputValues.map(({ index }) => index));
const mcp = normalized.findIndex(
(token, index) => token === "mcp" && !values.has(index),
);
const doctor = normalized.findIndex(
(token, index) => index > mcp && token === "doctor" && !values.has(index),
);
return normalized.filter((_, index) => index !== mcp && index !== doctor);
};