cartoon
Token-optimized output for any CLI. Prefix cartoon onto a command and
its output becomes TOON — a compact
structured format built for LLM agents. Same exit codes, same behavior,
~70%+ fewer tokens on test runs.
A cartoon is a compressed rendering of reality. So is this.
Why
Agents (Claude Code, Cursor, Codex, ...) read CLI output formatted for
humans: banners, progress noise, hundreds of PASSED lines. You pay for
every token. cartoon keeps what the agent needs — counts, failures,
tracebacks — and drops the rest.
Install
uv tool install cartoon # or: pipx install cartoon
npm install -g cartoon-wrap # installs the `cartoon` binary
cargo install cartoon
For agents (Claude Code, Codex, Copilot, Cursor, …)
Teach your agent to use cartoon automatically — and to install it when
missing — with the skills shipped in this repo:
# Claude Code (plugin)
/plugin marketplace add abhijitbansal/cartoon
/plugin install cartoon@cartoon
# Everything else (skills.sh CLI auto-detects 40+ agents)
npx skills add abhijitbansal/cartoon
Copy-paste blocks for AGENTS.md / copilot-instructions.md and the full
integration matrix: docs/agents.md.
Auto-wrap hook (Claude Code)
The plugin ships a PreToolUse hook that rewrites noisy Bash commands to
run under cartoon automatically — no skill recall needed. Standalone
install (without the plugin):
cartoon hook install # adds the hook to ~/.claude/settings.json
cartoon hook status # check where it's active
cartoon hook uninstall # remove it
What it wraps: dev-loop commands only — test runners, linters,
typecheckers, builds (pytest, jest, vitest, tsc, eslint, ruff,
mypy, make, cargo build|test|check|clippy, go test|build|vet,
npm test|ci, …). Because a rewrite auto-approves the call, the allowlist
is deliberately conservative: infra CLIs (docker, kubectl, terraform, gh,
aws) and mutating subcommands (cargo publish, npm install) are never
wrapped, commands that change shell state (cd, export, source) pass
through untouched, and anything unrecognized is left alone (fail-open).
The net-savings guard still applies — worst case the output is
byte-identical.
Use
cartoon pytest # asymmetric test report in TOON
cartoon jest src/ # same for jest
cartoon vitest run # same for vitest (watch mode passes through)
cartoon python -m unittest # same for unittest
cartoon ruff check . # lint diagnostics as a compact TOON table
cartoon npx eslint src/ # same for eslint
cartoon npx tsc --noEmit # same for tsc type errors
cartoon aws ec2 describe-instances --output json # any JSON CLI → TOON
cartoon make # safe tier auto-on: ANSI/progress/dupe collapse
cartoon --compress=aggressive make # opt-in lossy: level filter, diag tables, windowing
cartoon -c 'cd app && make -j4' # wrap a shell command string
cartoon ingest ci-run.log # compress a log you already have
some-cmd | cartoon - # same, from a pipe
cartoon --raw pytest # escape hatch: no transformation
cartoon stats --since 7d # how many tokens you've saved
cartoon learn # mine your own runs for config suggestions
cartoon adapters # list built-in adapters
cartoon --tag api pytest # tag the archived run
cartoon logs # list archived raw logs
cartoon logs --last --stdout # full raw output of the newest run
cartoon logs grep ERROR --last # search a raw log instead of re-reading it
cartoon --fast pytest # opt-in: parallel via pytest-xdist (-n auto)
Failing test run, before (pytest, ~4800 tokens) vs after (~300 tokens):
runner: pytest
summary:
total: 48
passed: 45
failed: 2
skipped: 1
duration_s: 3.2
failures[2]{id,loc,msg}:
"tests/test_auth.py::test_expiry","tests/test_auth.py:42",assert exp < now
"tests/test_user.py::test_create","tests/test_user.py:88","KeyError: 'email'"
traces:
"tests/test_auth.py::test_expiry"[2]: "tests/test_auth.py:42 in test_expiry",assert token.exp < now()
How it works
Every command (or ingested log) moves through one pipeline; the first
stage that understands the content wins: