/**
* Minimal CLI for querying an exported RA3 Mod XML agent snapshot.
*
* This is intentionally not installed into PATH. It is meant for scripts,
* debugging, and as a reference for MCP tool implementations.
*
* Usage examples:
* node out/agent/cli.js --project D:/Mods/Example status
* node out/agent/cli.js --snapshot /path/to/snapshot.json.gz find ExampleUnit
* node out/agent/cli.js --project D:/Mods/Example refs ExampleUnit GameObject
*/
import { readSnapshotFile, snapshotPathForProject } from "./snapshot";
import {
findAssets,
findDefine,
findReferenceGroups,
isFileActive,
listAssetsByType,
resolveIncludeSource,
statusFromSnapshot,
} from "./query";
interface CliOptions {
snapshotPath: string | null;
projectDir: string | null;
command: string;
args: string[];
}
function parseArgs(argv: string[]): CliOptions {
const options: CliOptions = {
snapshotPath: null,
projectDir: null,
command: "status",
args: [],
};
const positional: string[] = [];
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (arg === "--snapshot" || arg === "-s") {
options.snapshotPath = argv[++i] ?? null;
} else if (arg === "--project" || arg === "-p") {
options.projectDir = argv[++i] ?? null;
} else if (arg === "--help" || arg === "-h") {
options.command = "help";
} else if (arg.startsWith("-")) {
// ignore unknown flags
} else {
positional.push(arg);
}
}
if (positional.length > 0) {
options.command = positional[0];
options.args = positional.slice(1);
}
return options;
}
function printHelp(): void {
console.log(`RA3 Mod XML agent snapshot CLI
Usage:
node out/agent/cli.js --project
[args...]
node out/agent/cli.js --snapshot [args...]
Commands:
status
find [type]
refs [type]
list [prefix]
active
define
resolve
help
`);
}
async function main(): Promise {
const options = parseArgs(process.argv.slice(2));
if (options.command === "help") {
printHelp();
return;
}
const snapshotPath =
options.snapshotPath ??
(options.projectDir ? snapshotPathForProject(options.projectDir) : null);
if (!snapshotPath) {
console.error("No --project or --snapshot provided.");
process.exitCode = 2;
return;
}
const snapshot = await readSnapshotFile(snapshotPath);
if (!snapshot) {
console.error(`Snapshot not found or unreadable: ${snapshotPath}`);
process.exitCode = 3;
return;
}
const [a, b] = options.args;
switch (options.command) {
case "status":
console.log(JSON.stringify(statusFromSnapshot(snapshot), null, 2));
break;
case "find":
if (!a) {
console.error("find requires an id.");
process.exitCode = 2;
return;
}
console.log(JSON.stringify(findAssets(snapshot, a, b), null, 2));
break;
case "refs":
if (!a) {
console.error("refs requires an id.");
process.exitCode = 2;
return;
}
console.log(JSON.stringify(findReferenceGroups(snapshot, a, b), null, 2));
break;
case "list":
if (!a) {
console.error("list requires a type.");
process.exitCode = 2;
return;
}
console.log(JSON.stringify(listAssetsByType(snapshot, a, b ?? ""), null, 2));
break;
case "active":
if (!a) {
console.error("active requires a file path.");
process.exitCode = 2;
return;
}
console.log(JSON.stringify({ active: isFileActive(snapshot, a) }, null, 2));
break;
case "define":
if (!a) {
console.error("define requires a name.");
process.exitCode = 2;
return;
}
console.log(JSON.stringify(findDefine(snapshot, a), null, 2));
break;
case "resolve":
if (!a) {
console.error("resolve requires a source.");
process.exitCode = 2;
return;
}
console.log(JSON.stringify(resolveIncludeSource(snapshot, a), null, 2));
break;
default:
console.error(`Unknown command: ${options.command}`);
process.exitCode = 2;
}
}
void main();