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.
cartoon exists to save your agent input tokens — so install it first. Pick your harness; each option makes noisy commands wrap automatically.
Skill + auto-wrap hook in one install. Claude wraps noisy commands without being asked.
/plugin marketplace add abhijitbansal/cartoon /plugin install cartoon@cartoon
skills.sh auto-detects the agents you have and installs the cartoon skill into each.
npx skills add abhijitbansal/cartoon
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
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.
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
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)
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
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..github/hooks/cartoon.json so the whole team (and the Copilot coding agent) gets auto-wrap for free.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..github/hooks format), so install it with --vscode.pytest | grep x. Keep them to tools the agent runs bare; disable per-shell with CARTOON_NO_SHIM=1../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.~/.copilot/hooks or .github/hooks, not a plugin dir — plugin-defined Copilot hooks currently don't fire.cartoon --raw for live streaming.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.
==================== 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…
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
pytest, unittest, jest, vitest, ruff, eslint, tsc, mypy, pre-commit, cargo test/nextest, cargo build/check/clippy, go test, golangci-lint, phpunit, rspec, swiftlint, gradle, maven, dotnet test, and npm/pnpm/yarn/bun test scripts that run jest or vitest — plus first-class Apple support: swift test, swift build, xcodebuild test, xcodebuild build/archive. cartoon injects each tool's machine-readable flag (junit-xml, --json, --message-format=json, --xunit-output, -resultBundlePath) and renders a compact report. Clean lint/typecheck/build runs collapse to a summary line. Runs through uv and xcrun are first-class too — uv run pytest, uvx ruff check, xcrun xcodebuild test are detected and auto-wrapped just like the bare tools. gradle and maven need no flag at all: the JUnit XML they already write is read back after the run. Anything else that writes JUnit XML gets the same report via --junit.
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.
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.
cartoon ingestAlready 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.
cartoon learnMines 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.
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.
cartoon statsA 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.
--fast modeOpt-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.
cartoon doctor reports hook status per agent surface, config parse status, and every allowlisted tool that still has no adapter. --max-tokens N is a hard ceiling — head and tail kept, the middle replaced by one disclosed logs grep marker. --junit <path> renders any runner's JUnit XML as a test report. And cartoon -c 'pytest | tail -5' runs the adapter and drops the filter, disclosed as pipe_filter_dropped.
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.
Filter tools must be trusted blindly — dropped lines are gone. cartoon is built so you never have to trust it.
Every run archives full raw stdout/stderr with a one-command escape hatch. Compression you can always undo.
The net-savings guard: output gets smaller or passes through byte-identical. Trying cartoon is zero-risk.
Exit codes mirrored exactly — cartoon pytest && deploy behaves like pytest && deploy. Parse failures pass the original through.
TOON re-encoding keeps every test and diagnostic as queryable data, and the stats ledger makes savings self-measuring.
Twenty-four runners get native, machine-readable handling. Everything else still rides the compression ladder, and output that arrives without a matching command — a ./build.sh that runs xcodebuild inside, JUnit XML on stdout — is recognized by shape. cartoon doctor lists any hook-allowlisted tool that still has no adapter.
| Adapter | Trigger | Source |
|---|---|---|
pytest | pytest, python -m pytest, uv run [-m] pytest, uvx pytest | injected junit-xml |
unittest | python -m unittest, uv run [python] -m unittest | stderr text parse |
jest | jest, npx jest | injected --json |
vitest | vitest run (watch passes through) | injected --reporter=json |
swift-testNew · Apple | swift test | injected --xunit-output + --parallel; merges XCTest + Swift Testing xml |
xcodebuild-testNew · Apple | xcodebuild test | injected -resultBundlePath, parsed via xcrun xcresulttool get test-results summary --format json (Xcode 16+) |
ruff | ruff check | injected --output-format json |
eslint | eslint, npx eslint | injected --format json |
tsc | tsc, npx tsc (not --watch) | injected --pretty false |
swift-buildNew · Apple | swift build | stdout/stderr diagnostics parse |
xcodebuild-buildNew · Apple | xcodebuild build / archive / -exportArchive (no test action) | stdout/stderr diagnostics parse |
pre-commitNew · 0.6 | pre-commit, pre-commit run … | stdout text parse (--color=never injected) |
cargo-testNew · 0.6 | cargo test, cargo nextest run | stable text parse (never nightly JSON) |
cargo-buildNew · 0.6 | cargo build / check / clippy | injected --message-format=json (before --) |
go-testNew · 0.6 | go test | injected -json |
mypyNew · 0.6 | mypy, python -m mypy, uv run mypy | injected --output json |
phpunitNew · 0.6 | phpunit, vendor/bin/phpunit | injected --log-junit |
rspecNew · 0.6 | rspec, bundle exec rspec | injected --format json --out <file> |
swiftlintNew · Apple | swiftlint, swiftlint lint (never --fix / autocorrect) | injected --reporter json |
gradleNew | gradle, ./gradlew test / check / build / *Test | nothing injected; reads the build/test-results JUnit XML this run wrote, plus javac/kotlinc and failed-task errors |
mavenNew | mvn, ./mvnw test / verify / package / install | nothing injected; reads the surefire/failsafe reports this run wrote, plus compiler and goal errors |
dotnet-testNew | dotnet test (VSTest) | injected --logger trx --results-directory <temp>; MSBuild errors lifted in |
golangci-lintNew | golangci-lint run | injected --output.json.path=stdout (v2) / --out-format json (v1) |
pkg-scriptNew | npm test, npm run test, pnpm test, yarn test, bun run test — test script is plain jest / vitest | the jest / vitest flags, forwarded to the script |
"Your agent reads 12 lines instead of 800 — and the receipt is always on disk."