{"id":50756986,"url":"https://github.com/abhijitbansal/cartoon","last_synced_at":"2026-06-16T11:00:46.145Z","repository":{"id":363954699,"uuid":"1264529244","full_name":"abhijitbansal/cartoon","owner":"abhijitbansal","description":"Token-optimized output for any CLI — your AI agent reads 12 lines instead of 800, raw log always archived. Adapters for pytest/jest/vitest/ruff/eslint/tsc + a compression ladder for everything else.","archived":false,"fork":false,"pushed_at":"2026-06-15T04:01:23.000Z","size":640,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-15T10:03:01.512Z","etag":null,"topics":["agents","ai-agents","claude-code","cli","codex","cursor","developer-tools","eslint","jest","llm","log-compression","pytest","rust","token-optimization","tokens","toon","typescript","vitest"],"latest_commit_sha":null,"homepage":"https://abhijitbansal.github.io/cartoon/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/abhijitbansal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"docs/agents.md","dco":null,"cla":null}},"created_at":"2026-06-10T00:58:09.000Z","updated_at":"2026-06-15T04:00:38.000Z","dependencies_parsed_at":null,"dependency_job_id":"6fdc0bd4-5216-4307-9d40-7fc1bbc309fb","html_url":"https://github.com/abhijitbansal/cartoon","commit_stats":null,"previous_names":["abhijitbansal/cartoon"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/abhijitbansal/cartoon","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abhijitbansal%2Fcartoon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abhijitbansal%2Fcartoon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abhijitbansal%2Fcartoon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abhijitbansal%2Fcartoon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/abhijitbansal","download_url":"https://codeload.github.com/abhijitbansal/cartoon/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abhijitbansal%2Fcartoon/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34402663,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agents","ai-agents","claude-code","cli","codex","cursor","developer-tools","eslint","jest","llm","log-compression","pytest","rust","token-optimization","tokens","toon","typescript","vitest"],"created_at":"2026-06-11T06:00:41.674Z","updated_at":"2026-06-16T11:00:46.103Z","avatar_url":"https://github.com/abhijitbansal.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# cartoon\n\n**Token-optimized output for any CLI.** Prefix `cartoon` onto a command and\nits output becomes [TOON](https://github.com/toon-format/toon) — a compact\nstructured format built for LLM agents. Same exit codes, same behavior,\n~70%+ fewer tokens on test runs.\n\nA cartoon is a compressed rendering of reality. So is this.\n\n## Why\n\nAgents (Claude Code, Cursor, Codex, ...) read CLI output formatted for\nhumans: banners, progress noise, hundreds of `PASSED` lines. You pay for\nevery token. `cartoon` keeps what the agent needs — counts, failures,\ntracebacks — and drops the rest.\n\n## Install\n\n```bash\nuv tool install cartoon        # or: pipx install cartoon\nnpm install -g cartoon-wrap    # installs the `cartoon` binary\ncargo install cartoon\n```\n\n## For agents (Claude Code, Codex, Copilot, Cursor, …)\n\nTeach your agent to use cartoon automatically — and to install it when\nmissing — with the skills shipped in this repo:\n\n```text\n# Claude Code (plugin)\n/plugin marketplace add abhijitbansal/cartoon\n/plugin install cartoon@cartoon\n```\n\n```bash\n# Everything else (skills.sh CLI auto-detects 40+ agents)\nnpx skills add abhijitbansal/cartoon\n```\n\nCopy-paste blocks for AGENTS.md / copilot-instructions.md and the full\nintegration matrix: [docs/agents.md](docs/agents.md).\n\n### Auto-wrap hook (Claude Code)\n\nThe plugin ships a `PreToolUse` hook that rewrites noisy Bash commands to\nrun under cartoon automatically — no skill recall needed. Standalone\ninstall (without the plugin):\n\n```bash\ncartoon hook install     # adds the hook to ~/.claude/settings.json\ncartoon hook status      # check where it's active\ncartoon hook uninstall   # remove it\n```\n\nWhat it wraps: dev-loop commands only — test runners, linters,\ntypecheckers, builds (`pytest`, `jest`, `vitest`, `tsc`, `eslint`, `ruff`,\n`mypy`, `make`, `cargo build|test|check|clippy`, `go test|build|vet`,\n`npm test|ci`, …). Because a rewrite auto-approves the call, the allowlist\nis deliberately conservative: infra CLIs (docker, kubectl, terraform, gh,\naws) and mutating subcommands (`cargo publish`, `npm install`) are never\nwrapped, commands that change shell state (`cd`, `export`, `source`) pass\nthrough untouched, and anything unrecognized is left alone (fail-open).\nThe net-savings guard still applies — worst case the output is\nbyte-identical.\n\n## Use\n\n```bash\ncartoon pytest                 # asymmetric test report in TOON\ncartoon jest src/              # same for jest\ncartoon vitest run             # same for vitest (watch mode passes through)\ncartoon python -m unittest     # same for unittest\ncartoon ruff check .           # lint diagnostics as a compact TOON table\ncartoon npx eslint src/        # same for eslint\ncartoon npx tsc --noEmit       # same for tsc type errors\ncartoon aws ec2 describe-instances --output json   # any JSON CLI → TOON\ncartoon make                   # safe tier auto-on: ANSI/progress/dupe collapse\ncartoon --compress=aggressive make   # opt-in lossy: level filter, diag tables, windowing\ncartoon -c 'cd app \u0026\u0026 make -j4'      # wrap a shell command string\ncartoon ingest ci-run.log      # compress a log you already have\nsome-cmd | cartoon -           # same, from a pipe\ncartoon --raw pytest           # escape hatch: no transformation\ncartoon stats --since 7d       # how many tokens you've saved\ncartoon learn                  # mine your own runs for config suggestions\ncartoon adapters               # list built-in adapters\ncartoon --tag api pytest       # tag the archived run\ncartoon logs                   # list archived raw logs\ncartoon logs --last --stdout   # full raw output of the newest run\ncartoon logs grep ERROR --last # search a raw log instead of re-reading it\ncartoon --fast pytest          # opt-in: parallel via pytest-xdist (-n auto)\n```\n\nFailing test run, before (pytest, ~4800 tokens) vs after (~300 tokens):\n\n```\nrunner: pytest\nsummary:\n  total: 48\n  passed: 45\n  failed: 2\n  skipped: 1\n  duration_s: 3.2\nfailures[2]{id,loc,msg}:\n  \"tests/test_auth.py::test_expiry\",\"tests/test_auth.py:42\",assert exp \u003c now\n  \"tests/test_user.py::test_create\",\"tests/test_user.py:88\",\"KeyError: 'email'\"\ntraces:\n  \"tests/test_auth.py::test_expiry\"[2]: \"tests/test_auth.py:42 in test_expiry\",assert token.exp \u003c now()\n```\n\n## How it works\n\nEvery command (or ingested log) moves through one pipeline; the first\nstage that understands the content wins:\n\n1. **Adapter match** — known runners (pytest, jest, vitest, …) get their\n   machine-readable format injected and re-rendered as a compact report.\n2. **JSON detection** — any JSON document in stdout is TOON-encoded.\n3. **Ladder, safe tier (default)** — ANSI stripping, progress-bar\n   collapse, duplicate-line collapse, blank-run collapse. Deterministic\n   and non-lossy in practice.\n4. **Ladder, aggressive tier (opt-in)** — log-level filtering (INFO/DEBUG\n   to counts, WARN+ kept with context), near-duplicate templating,\n   compiler-diagnostic extraction into a TOON table (gcc/clang one-liners\n   and rustc multi-line blocks), error-anchored windowing.\n5. **Net-savings guard** — the transform plus the `raw_log` footer must\n   beat the original token count, or the original is emitted\n   byte-identically. Trying cartoon is zero-risk by construction.\n\nEvery rule is a pure function that no-ops when its pattern is absent, so\nplain prose is never mangled. Measured on the golden corpus that runs in\nCI (token reduction at the aggressive tier, signal lines asserted intact):\n\n| Fixture | Reduction | Signal kept |\n|---|---|---|\n| chatty service log (156 lines, 1 ERROR) | **−92.5%** | ERROR + WARN verbatim, ±2 lines context |\n| real `cargo build` failure (3 errors) | **−61.5%** | all errors + locations in a TOON table |\n| npm ERESOLVE conflict | ±0% | guard emits the original — never negative |\n\n## Guarantees\n\n- Exit codes always mirrored — `cartoon pytest \u0026\u0026 deploy` behaves like\n  `pytest \u0026\u0026 deploy`.\n- If parsing fails, the original output passes through untouched (one\n  warning on stderr). The safe tier preserves all non-redundant text;\n  lossy tiers are opt-in and always leave a `raw_log` pointer to the\n  unmodified output.\n- A transform must pay for itself: if the TOON rendering (footer included)\n  wouldn't beat the original token count, the original is emitted\n  byte-identically. Savings are never negative.\n\n## Raw log archive\n\nEvery wrapped run keeps its full raw output under\n`~/.local/state/cartoon/runs/\u003crun-id\u003e/` (`stdout.log`, `stderr.log`,\n`meta.json`). Transformed output ends with a `raw_log:` line pointing at the\narchive — if the TOON summary dropped something you need, search it with\n`cartoon logs grep \u003cpattern\u003e --last` (capped, disclosed) or fetch it with\n`cartoon logs \u003cid\u003e` instead of rerunning. Passthrough and\n`--raw` output stay byte-identical (no footer) but are still archived.\nRetention is capped (`keep_runs`, default 50; `max_archive_mb`, default 50);\n`keep_runs = 0` disables archiving.\n\n## Fast mode\n\n`cartoon --fast pytest` appends `-n auto` so [pytest-xdist] runs the suite in\nparallel. Strictly opt-in — parallel execution is NOT \"same behavior\" (test\norder changes; shared-state tests can flake), so cartoon never enables it on\nits own and always discloses it with a `fast: \"-n auto\"` line in the report.\nFailures under `--fast`? Rerun without it before debugging. If pytest-xdist\nisn't installed, cartoon retries serially once and notes it on stderr.\nOther runners: no-op (jest is already parallel; unittest has no parallel\nrunner).\n\n[pytest-xdist]: https://pypi.org/project/pytest-xdist/\n\n## Config\n\n`~/.config/cartoon/config.toml`:\n\n```toml\ntokenizer = \"o200k\"  # or \"approx\" (bytes/4) for zero-cost estimates\ntrace_lines = 20     # per-failure traceback cap\nkeep_runs = 50       # archived raw logs to keep (0 disables)\nmax_archive_mb = 50  # max total archive size\n\n[compress]\nlevel = \"safe\"       # default for non-adapter output: safe | aggressive\n\n[command.docker]\nlevel = \"aggressive\" # per-command pin; CLI --compress wins over config\n```\n\nCompression precedence: `--compress` flag \u003e `--heuristic` (deprecated alias\nfor aggressive) \u003e `[command.\u003cname\u003e]` \u003e `[compress]` \u003e legacy `heuristic`\nkey \u003e safe.\n\nStats live in `~/.local/state/cartoon/stats.jsonl`.\n\n## Adapters\n\n| Adapter | Trigger | Source |\n|---|---|---|\n| pytest | `pytest`, `python -m pytest` | injected `--junit-xml` |\n| unittest | `python -m unittest` | stderr text parse |\n| jest | `jest`, `npx jest` | injected `--json` |\n| vitest | `vitest run` (watch mode passes through) | injected `--reporter=json` |\n| ruff | `ruff check` | injected `--output-format json` |\n| eslint | `eslint`, `npx eslint` | injected `--format json` |\n| tsc | `tsc`, `npx tsc` (not `--watch`) | injected `--pretty false` |\n\nNo adapter match → JSON auto-detection → compression ladder (safe tier by\ndefault, aggressive opt-in) → passthrough when nothing pays for itself.\n\nWant another runner (cargo test, go test, rspec)? See\n[CONTRIBUTING.md](CONTRIBUTING.md) — adapters are one trait impl + fixtures.\nThe roadmap lives in\n[docs/superpowers/specs/2026-06-11-cartoon-v02-roadmap.md](docs/superpowers/specs/2026-06-11-cartoon-v02-roadmap.md).\n\n## Support\n\nIf cartoon saves you tokens, a ⭐ on this repo helps other agent users find\nit — and `cartoon stats` will tell you exactly how much it earned one.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabhijitbansal%2Fcartoon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fabhijitbansal%2Fcartoon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabhijitbansal%2Fcartoon/lists"}