Skip to main content

Token-optimized TOON output wrapper for any CLI

Project description

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:

  1. Adapter match — known runners (pytest, jest, vitest, …) get their machine-readable format injected and re-rendered as a compact report.
  2. JSON detection — any JSON document in stdout is TOON-encoded.
  3. Ladder, safe tier (default) — ANSI stripping, progress-bar collapse, duplicate-line collapse, blank-run collapse. Deterministic and non-lossy in practice.
  4. Ladder, aggressive tier (opt-in) — log-level filtering (INFO/DEBUG to counts, WARN+ kept with context), near-duplicate templating, compiler-diagnostic extraction into a TOON table (gcc/clang one-liners and rustc multi-line blocks), error-anchored windowing.
  5. Net-savings guard — the transform plus the raw_log footer must beat the original token count, or the original is emitted byte-identically. Trying cartoon is zero-risk by construction.

Every rule is a pure function that no-ops when its pattern is absent, so plain prose is never mangled. Measured on the golden corpus that runs in CI (token reduction at the aggressive tier, signal lines asserted intact):

Fixture Reduction Signal kept
chatty service log (156 lines, 1 ERROR) −92.5% ERROR + WARN verbatim, ±2 lines context
real cargo build failure (3 errors) −61.5% all errors + locations in a TOON table
npm ERESOLVE conflict ±0% guard emits the original — never negative

Guarantees

  • Exit codes always mirrored — cartoon pytest && deploy behaves like pytest && deploy.
  • If parsing fails, the original output passes through untouched (one warning on stderr). The safe tier preserves all non-redundant text; lossy tiers are opt-in and always leave a raw_log pointer to the unmodified output.
  • A transform must pay for itself: if the TOON rendering (footer included) wouldn't beat the original token count, the original is emitted byte-identically. Savings are never negative.

Raw log archive

Every wrapped run keeps its full raw output under ~/.local/state/cartoon/runs/<run-id>/ (stdout.log, stderr.log, meta.json). Transformed output ends with a raw_log: line pointing at the archive — if the TOON summary dropped something you need, search it with cartoon logs grep <pattern> --last (capped, disclosed) or fetch it with cartoon logs <id> instead of rerunning. Passthrough and --raw output stay byte-identical (no footer) but are still archived. Retention is capped (keep_runs, default 50; max_archive_mb, default 50); keep_runs = 0 disables archiving.

Fast mode

cartoon --fast pytest appends -n auto so pytest-xdist runs the suite in parallel. Strictly opt-in — parallel execution is NOT "same behavior" (test order changes; shared-state tests can flake), so cartoon never enables it on its own and always discloses it with a fast: "-n auto" line in the report. Failures under --fast? Rerun without it before debugging. If pytest-xdist isn't installed, cartoon retries serially once and notes it on stderr. Other runners: no-op (jest is already parallel; unittest has no parallel runner).

Config

~/.config/cartoon/config.toml:

tokenizer = "o200k"  # or "approx" (bytes/4) for zero-cost estimates
trace_lines = 20     # per-failure traceback cap
keep_runs = 50       # archived raw logs to keep (0 disables)
max_archive_mb = 50  # max total archive size

[compress]
level = "safe"       # default for non-adapter output: safe | aggressive

[command.docker]
level = "aggressive" # per-command pin; CLI --compress wins over config

Compression precedence: --compress flag > --heuristic (deprecated alias for aggressive) > [command.<name>] > [compress] > legacy heuristic key > safe.

Stats live in ~/.local/state/cartoon/stats.jsonl.

Adapters

Adapter Trigger Source
pytest pytest, python -m pytest injected --junit-xml
unittest python -m unittest stderr text parse
jest jest, npx jest injected --json
vitest vitest run (watch mode passes through) injected --reporter=json
swift-test swift test injected --xunit-output + --parallel (merges XCTest + Swift Testing files)
xcodebuild-test xcodebuild test injected -resultBundlePath, parsed via xcresulttool … test-results summary (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-build swift build stdout/stderr text parse
xcodebuild-build xcodebuild build (no test action) stdout/stderr diagnostics parse

No adapter match → JSON auto-detection → compression ladder (safe tier by default, aggressive opt-in) → passthrough when nothing pays for itself.

Want another runner (cargo test, go test, rspec)? See CONTRIBUTING.md — adapters are one trait impl + fixtures. The roadmap lives in docs/superpowers/specs/2026-06-11-cartoon-v02-roadmap.md.

Support

If cartoon saves you tokens, a ⭐ on this repo helps other agent users find it — and cartoon stats will tell you exactly how much it earned one.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

cartoon-0.4.0-py3-none-win_amd64.whl (3.4 MB view details)

Uploaded Python 3Windows x86-64

cartoon-0.4.0-py3-none-manylinux_2_39_x86_64.whl (3.7 MB view details)

Uploaded Python 3manylinux: glibc 2.39+ x86-64

cartoon-0.4.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (3.7 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

cartoon-0.4.0-py3-none-macosx_11_0_arm64.whl (3.6 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

cartoon-0.4.0-py3-none-macosx_10_12_x86_64.whl (3.6 MB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

Details for the file cartoon-0.4.0-py3-none-win_amd64.whl.

File metadata

  • Download URL: cartoon-0.4.0-py3-none-win_amd64.whl
  • Upload date:
  • Size: 3.4 MB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for cartoon-0.4.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 0add65fb8dd62a12adbd9773176f460b51f7074dc9d4e0136f3c6e7ac59692cc
MD5 777eb8c56d819a11e81bb672f8711a7b
BLAKE2b-256 baa1a4cdd62d801456d5258cb515e2d43a09f6d1fc723de0710c96054a39dda4

See more details on using hashes here.

Provenance

The following attestation bundles were made for cartoon-0.4.0-py3-none-win_amd64.whl:

Publisher: release.yml on abhijitbansal/cartoon

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cartoon-0.4.0-py3-none-manylinux_2_39_x86_64.whl.

File metadata

File hashes

Hashes for cartoon-0.4.0-py3-none-manylinux_2_39_x86_64.whl
Algorithm Hash digest
SHA256 4b87093e77bcbc0a2c0ab7363c966e9b6e0d72b3ebb0f9571e5410387b54236e
MD5 fb860984fe422654dfc8ab0a2b52a9a0
BLAKE2b-256 6136c91bd3b315d44cb7803b126f315ab0c07bd1a35e7c199b2f6185a7b8ee29

See more details on using hashes here.

Provenance

The following attestation bundles were made for cartoon-0.4.0-py3-none-manylinux_2_39_x86_64.whl:

Publisher: release.yml on abhijitbansal/cartoon

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cartoon-0.4.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for cartoon-0.4.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 71b7bb5c8012482dead9de47d51885667604f97296a4ece314af4a844fb20b5b
MD5 62ed179d205fd1041ca2c3cc9cb1cb01
BLAKE2b-256 135e72cc9bb311fadaa206199d2c83d494fccee9e0e9df7fe0b805fe7692b326

See more details on using hashes here.

Provenance

The following attestation bundles were made for cartoon-0.4.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on abhijitbansal/cartoon

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cartoon-0.4.0-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for cartoon-0.4.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 d0f2f632e38c30908095eee888314a50f2b21a2691b26995aa79ec71b1a046a5
MD5 0946f977c7ddad82196a34fc02ebd894
BLAKE2b-256 cf8a7d3710973336a3f3a3834aff302111835a7de09633f02c6b658128a5632a

See more details on using hashes here.

Provenance

The following attestation bundles were made for cartoon-0.4.0-py3-none-macosx_11_0_arm64.whl:

Publisher: release.yml on abhijitbansal/cartoon

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cartoon-0.4.0-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for cartoon-0.4.0-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 5549f32ffe92071a7e4c31def7adeb782089baa51899f32a54fbb74b7702cf19
MD5 85e8d4d882c93754debbd9bfedc50b5c
BLAKE2b-256 b97c435f0f6fcbdd37a44b84ada0c2c20e4d9550a23c562ef579a0690beee704

See more details on using hashes here.

Provenance

The following attestation bundles were made for cartoon-0.4.0-py3-none-macosx_10_12_x86_64.whl:

Publisher: release.yml on abhijitbansal/cartoon

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page