JuliusBrussee/caveman · error · Error
usage: ${invokedCommand("toon")} encode|decode
Error message
usage: ${invokedCommand("toon")} encode|decode What it means
Subcommand validation for `caveman toon`: the command requires exactly `encode` or `decode` as its first argument. Anything else — a typo, a missing subcommand, or an option in that position — throws this usage error before stdin is read. The same function then shells out to the stateless `caveman-engine toon` converter.
Source
Thrown at packages/cli/src/index.ts:12989
bytes_out: input.length,
ratio: 0,
basis: "inferred",
token_count_basis: "unavailable",
content_type: contentType || "unknown",
engine: "missing",
note: "caveman-engine not installed — 0% compression, input passed through unchanged. Run `caveman setup` to see what's missing and how to install.",
}));
}
// toonConvert shells out to `caveman-engine toon encode|decode` — the stateless,
// CCR-free JSON⇄TOON converter (single source of truth in the engine, no JS
// parser). It is the manual surface for the same transform the proxy applies at
// the wire boundary; the converted output must not be fed back into the agent
// that wrote the other form, or it would double the tokens it sees.
async function toonConvert(rest: string[]) {
const sub = rest[0];
if (sub !== "encode" && sub !== "decode") {
throw new Error(`usage: ${invokedCommand("toon")} encode|decode`);
}
const input = await readStdin();
const bin = cavemanBin("caveman-engine", "CAVEMAN_ENGINE_BIN");
let handled = false;
const child = spawn(bin, ["toon", sub], { stdio: ["pipe", "inherit", "inherit"] });
emitCommandRunOnce("ok"); // exit handler below hard-exits; never returns to main()
child.on("error", () => {
if (handled) return;
handled = true;
// encode degrades byte-safe: the input is still valid JSON, just not compacted.
// decode cannot be faked without the engine — emitting raw TOON as JSON would
// hand downstream a broken payload, so it fails loudly instead.
if (sub === "encode") {
// Not silent: the pass-through must announce itself so 0% can never be
// mistaken for "TOON didn't help".
console.error(`${mark("warn")} caveman-engine not found — emitting input JSON unchanged (no TOON encoding); run \`caveman setup\` to see what's missing`);
process.stdout.write(input);
process.exit(0);View on GitHub (pinned to 5184b3d11a)
Solutions
- Use `caveman toon encode < payload.json` to compact JSON into TOON
- Use `caveman toon decode < payload.toon` for the reverse direction
- Remember the converted output must not be fed back to the agent that produced the other form (it would double the tokens it sees)
Example fix
# before cat wire.toon | caveman toon # after cat wire.toon | caveman toon decode
Defensive patterns
Strategy: validation
Validate before calling
const sub = rest[0];
if (sub !== 'encode' && sub !== 'decode') {
console.error('usage: caveman toon encode|decode');
process.exit(2);
} Type guard
const isToonSubcommand = (v: unknown): v is 'encode' | 'decode' => v === 'encode' || v === 'decode';
Prevention
- Whitelist subcommands before spawning the CLI from scripts
- Treat exit code 2 (usage) differently from runtime failures in CI
When it happens
Trigger: `caveman toon` with no subcommand; `caveman toon enc` or `caveman toon json` (aliases that do not exist); piping a file but forgetting the direction, e.g. `cat x.toon | caveman toon`.
Common situations: Scripts written from memory instead of docs; assuming symmetric flags like `--encode` exist; CI steps migrated from an older command layout.
Related errors
- caveman compress --toon-stats is unavailable; use --toon to
- usage: ${invokedCommand("compress")} [--type <content-type>]
- usage: ${invokedCommand("audit")} import --format <fmt> <fil
- usage: ${invokedCommand("audit")} eval-import <evidence.json
- cave_stale_lock:${checked.stale.join(",")}: run npm run buil
AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-08-18).
Data as JSON: /api/errors/73e9c17bb3944688.
Report an issue: GitHub.