/** * 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();