cartoon

Stop paying your AI agent to read PASSED PASSED PASSED.

Prefix cartoon onto any command and its output becomes compact, structured, agent-ready — same exit codes, same behavior, a fraction of the input tokens. The full raw log is always archived, so compression is reversible by construction.

uv tool install cartoon
pipx install cartoon
npm i -g cartoon-wrap
cargo install cartoon
cartoon compression: ~800 noisy log lines collapse into 12 structured TOON lines A faux terminal. On the left, a scrolling stack of ~800 noisy pytest log lines (~4,800 tokens) with one buried failure in rust. They funnel through a guarded aperture in the center into a clean panel of 12 structured TOON lines (~300 tokens) on the right. cartoon@cli — pytest run FIG.1 STDOUT / STDERR · RAW ~800 lines PASSED tests/test_auth.py::login PASSED tests/test_auth.py::token PASSED tests/test_user.py::create [ 14%] collecting ... PASSED tests/test_user.py::email PASSED tests/test_db.py::commit FAILED tests/test_auth.py::expiry PASSED tests/test_db.py::rollback PASSED tests/test_api.py::status WARNING DeprecationWarning x12 PASSED tests/test_api.py::headers [ 61%] running ... PASSED tests/test_fmt.py::round PASSED tests/test_auth.py::login PASSED tests/test_auth.py::token PASSED tests/test_user.py::create [ 14%] collecting ... PASSED tests/test_user.py::email PASSED tests/test_db.py::commit FAILED tests/test_user.py::create PASSED tests/test_db.py::rollback PASSED tests/test_api.py::status WARNING DeprecationWarning x12 PASSED tests/test_api.py::headers [ 61%] running ... PASSED tests/test_fmt.py::round ~4,800 tokens · 1 ERROR buried GUARD MATCH · DETECT · LADDER TOON · STRUCTURED · 12 lines 1runner:pytest 2summary: 3total:48passed:45 4failed:2skipped:1 5failures[2]{id,loc}: 6test_auth::expiryauth.py:42 7test_user::createuser.py:88 8traces[1]: 9assert token.exp < now() 10exit:1duration_s:3.2 11raw_log:runs/20260611-051415 12 ~300 tokens · this run −93.8%
FIG.1 — ~800 noisy lines → 12 structured TOON rows · one buried failure kept verbatim
Install · for agents

Wire it into your agent once, forget it

cartoon exists to save your agent input tokens — so install it first. Pick your harness; each option makes noisy commands wrap automatically.

Step 1 · recommended

Claude Code — plugin

Skill + auto-wrap hook in one install. Claude wraps noisy commands without being asked.

/plugin marketplace add abhijitbansal/cartoon
/plugin install cartoon@cartoon
Step 2 · 40+ agents

Codex, Cursor, Copilot

skills.sh auto-detects the agents you have and installs the cartoon skill into each.

npx skills add abhijitbansal/cartoon
Step 3 · no plugin

Standalone hook / instructions

No plugin? Install the Claude Code hook directly, or paste the snippet from docs/agents.md into your AGENTS.md.

cartoon hook install   # auto-wrap in ~/.claude/settings.json
cartoon hook status    # check · uninstall to remove
~70%
smaller test runs, typical
92.5%
token cut · chatty service log
CI-measured
61.5%
token cut · cargo build failure
CI-measured
0
risk — output never gets bigger
Copilot · VS Code · any agent

Guaranteed wrapping, not just instructions

Pasting rules into copilot-instructions.md or AGENTS.md only asks the model to prefix cartoon — and it sometimes forgets. A PreToolUse hook intercepts the command and wraps it every time. One cartoon hook rewrite serves every agent; install it where each one looks.

GitHub Copilot CLI · v1.0.24+

Transparent rewrite

Writes a Copilot hook that rewrites the command in place — same as Claude Code.

cartoon hook install --copilot
# repo-shared (also the coding agent):
cartoon hook install --copilot --project
VS Code Copilot Chat · Preview

Deny & suggest

Chat exposes no rewrite field, so the hook blocks the raw command and tells the agent to re-run it wrapped. It reads the same Claude-format file, so one entry covers both.

cartoon hook install --vscode
# → ~/.claude/settings.json
#   (also covers Claude Code)
Any agent · no hook needed

Shell shims

Functions shadow the bare tool names and re-invoke through cartoon — they beat PATH and venv binaries.

cartoon shim install
export BASH_ENV=~/.config/cartoon/shims.sh
Tips

Get the smoothest Copilot setup

  • Seeing a confirmation dialog on every wrapped command (a known Copilot CLI v1.0.24 bug)? Reinstall with cartoon hook install --copilot --deny — it blocks-and-suggests instead of rewriting, no modal.
  • cartoon hook rewrite --deny-mode forces deny anywhere; handy on Copilot builds before updatedInput landed.
  • cartoon hook status shows every surface at once; cartoon shim status shows the shim file.
  • Commit .github/hooks/cartoon.json so the whole team (and the Copilot coding agent) gets auto-wrap for free.
  • Disable without uninstalling: set CARTOON_NO_WRAP=1 (hook) or CARTOON_NO_SHIM=1 (shims) in the agent's environment — any non-empty value turns it off for the session. cartoon --raw <cmd> runs one command untouched.
Limitations

Where each layer stops

  • VS Code Copilot Chat can only deny, not rewrite — wrapping costs one extra round-trip while the agent re-issues the command. Its hooks are Preview, and it reads the Claude-format settings file (not the Copilot-CLI .github/hooks format), so install it with --vscode.
  • Shims see only their own argv, not surrounding pipes — so unlike the hook they can't refuse pytest | grep x. Keep them to tools the agent runs bare; disable per-shell with CARTOON_NO_SHIM=1.
  • Path-invoked binaries (./gradlew, ./node_modules/.bin/jest) bypass shell functions entirely. Tools with non-identifier names (pre-commit) and python -m/xcodebuild aren't shimmed — the hook covers them.
  • Install to ~/.copilot/hooks or .github/hooks, not a plugin dir — plugin-defined Copilot hooks currently don't fire.
  • Wrapped output is buffered — the report prints when the command finishes, not live (cartoon adds parse/encode time proportional to output size, milliseconds in practice). Use cartoon --raw for live streaming.
See it

A failing pytest run, before and after

Agents read CLI output formatted for humans: banners, progress noise, hundreds of pass lines — and you pay for every token, on every subsequent turn of the conversation. cartoon keeps what the agent needs and drops the rest.

pytest · raw
==================== test session starts ====================
platform darwin -- Python 3.12.4, pytest-9.0.3
collected 48 items

tests/test_auth.py::test_login PASSED              [  2%]
tests/test_auth.py::test_logout PASSED             [  4%]
…44 more lines of PASSED…
tests/test_auth.py::test_expiry FAILED             [ 87%]
…full session banner, warnings, summary…
~4,800 tokens → the agent
cartoon pytest
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()
raw_log: ~/.local/state/cartoon/runs/20260611-051415-342d
~300 tokens — failures keep full detail
~4,800 → ~300 tokens · this run −93.8%
Features

Everything in the box

01 Adapters

Eleven runners, native formats

pytest, unittest, jest, vitest, ruff, eslint, tsc — plus first-class Apple support: swift test, swift build, xcodebuild test, xcodebuild build. cartoon injects each tool's machine-readable flag (junit-xml, --json, --xunit-output, -resultBundlePath) and renders a compact report. Clean lint/typecheck/build runs collapse to a summary line. Runs through uv are first-class too — uv run pytest (uv auto-picks the project .venv), uv run -m pytest, and uvx ruff check are detected and auto-wrapped just like the bare tools.

02 Default on

Compression ladder

No adapter? The safe tier still fires: ANSI stripping, progress-bar collapse, duplicate and blank collapse — deterministic, non-lossy in practice. Opt into --compress=aggressive for log-level filtering, near-duplicate templating, compiler-diagnostic tables, and error-anchored windowing.

03 Reversible

Raw log archive + grep

Every run stores complete stdout/stderr on disk and prints a raw_log: pointer. Need something the summary dropped? cartoon logs grep ERROR --last fetches just the matching lines — never re-run, never re-read 100k tokens.

04 Bring your logs

cartoon ingest

Already have the log? cartoon ingest build.log — or pipe it: some-cmd | cartoon -. Existing files go through the exact same flow as a wrapped command: detection, ladder, guard, archive, stats.

05 Self-tuning

cartoon learn

Mines your own usage ledger — all local, no telemetry. Commands wasting tokens uncompressed get ready-to-paste config pins; repeated identical failures get a "read the archive instead of re-running" nudge.

06 Hands-free

Claude Code auto-wrap hook

The plugin ships a PreToolUse hook that rewrites noisy dev commands (test, lint, typecheck, build) to run under cartoon automatically. Conservative allowlist, fail-open on anything unrecognized — savings stop depending on the model remembering a skill.

07 Receipts

cartoon stats

A local ledger records tokens in, tokens out, and savings per call, per adapter. cartoon stats --since 7d tells you exactly what cartoon earned — self-measuring, not self-reported.

08 Speed, disclosed

--fast mode

Opt-in acceleration: pytest gains -n auto via pytest-xdist, disclosed right in the report. Strictly off by default — parallel execution is not "same behavior", so cartoon never enables it on its own.

How it's achieved

One pipeline, first understanding wins

Every wrapped command and ingested log flows through the same stages. Each compression rule is a pure function that no-ops when its pattern is absent — plain prose is never mangled.

cartoon pipeline: IN through five stages to OUT, with a guard feedback path A seven-node blueprint flow: IN command or log, 1 MATCH adapter, 2 DETECT json to TOON, 3 SAFE default rules, 4 OPT-IN aggressive rules, 5 GUARD net-savings, OUT compact plus raw_log receipt with exit code mirrored. The guard has a dashed feedback path: if output would be larger, it passes the original through byte-identical. FIG.3 — PIPELINE · FIRST UNDERSTANDING WINS IN command or log · pipe 1 · MATCH adapter? 11 runners 2 · DETECT json? → TOON 3 · SAFE default rules ansi · dupes blanks · progress 4 · OPT-IN aggressive level filter error windows 5 · GUARD net-savings ≥ 0 or bypass OUT compact + receipt exit code mirrored if worse → pass original through CI ENFORCEMENT: golden corpus · must-survive signal assertions · token-reduction floors PURE FUNCTIONS: each rule no-ops when its pattern is absent — plain prose is never mangled
FIG.3 — quality enforced by a golden corpus in CI: real captured logs, must-survive signal assertions, measured token-reduction floors
Why you can trust it

Four guarantees, by construction

Filter tools must be trusted blindly — dropped lines are gone. cartoon is built so you never have to trust it.

Reversible

Every run archives full raw stdout/stderr with a one-command escape hatch. Compression you can always undo.

Never worse

The net-savings guard: output gets smaller or passes through byte-identical. Trying cartoon is zero-risk.

Never lies

Exit codes mirrored exactly — cartoon pytest && deploy behaves like pytest && deploy. Parse failures pass the original through.

Structure, not deletion

TOON re-encoding keeps every test and diagnostic as queryable data, and the stats ledger makes savings self-measuring.

Coverage

Adapters & measured results

Eleven runners get native, machine-readable handling. Everything else still rides the compression ladder. The four Apple-platform adapters are new.

AdapterTriggerSource
pytestpytest, python -m pytest, uv run [-m] pytest, uvx pytestinjected junit-xml
unittestpython -m unittest, uv run [python] -m unitteststderr text parse
jestjest, npx jestinjected --json
vitestvitest run (watch passes through)injected --reporter=json
swift-testNew · Appleswift testinjected --xunit-output + --parallel; merges XCTest + Swift Testing xml
xcodebuild-testNew · Applexcodebuild testinjected -resultBundlePath, parsed via xcrun xcresulttool get test-results summary --format json (Xcode 16+)
ruffruff checkinjected --output-format json
eslinteslint, npx eslintinjected --format json
tsctsc, npx tsc (not --watch)injected --pretty false
swift-buildNew · Appleswift buildstdout/stderr diagnostics parse
xcodebuild-buildNew · Applexcodebuild build (no test action)stdout/stderr diagnostics parse
Measured token reduction across the golden corpus Horizontal blueprint bar chart, measured in CI. Chatty service log reduced 92.5 percent. Cargo build failure reduced 61.5 percent. npm ERESOLVE conflict net zero percent because the net-savings guard emits the original byte-for-byte. Typical test runs are about 70 percent smaller. FIG.2 — TOKEN REDUCTION · GOLDEN CORPUS · CI-MEASURED 100% raw 0% 50% cut chatty service log 156 lines · 1 ERROR buried −92.5% ERROR+WARN verbatim cargo build failure 3 compile errors −61.5% all errors + locs npm ERESOLVE conflict guard engages passes through byte-identical ±0% never negative test runs (typical) across the corpus ~−70% (estimate) kept (sent to agent) removed noise guard pass-through (±0%)
FIG.2 — token reduction, golden corpus · three CI-measured bars + the ~70% typical estimate · the adapter table above is the copy-paste twin

"Your agent reads 12 lines instead of 800 — and the receipt is always on disk."

REV 0.5.1 · MIT
a cartoon is a compressed rendering of reality — so is this