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

  1. Use `caveman toon encode < payload.json` to compact JSON into TOON
  2. Use `caveman toon decode < payload.toon` for the reverse direction
  3. 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

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


AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-08-18). Data as JSON: /api/errors/73e9c17bb3944688. Report an issue: GitHub.