npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@guy-lifshitz/bytepress

v0.1.0

Published

Reversible deterministic tool-output compression for Claude Code hooks

Readme

BytePress

Reversible, deterministic compression for oversized tool output in Claude Code hooks. Big outputs get squeezed before they reach the model; the byte-identical original stays on disk, one command away.

Why

Every tool call in an agent session dumps its full output into the context window: a package-lock.json read, a 200KB test log, a gh pr list --json with 120 rows. That text does not get paid for once. It gets paid for on every turn after that, because the whole conversation rides along with each API call. A single 200KB log read early in a session can cost more than the rest of the session combined.

You have three ways out, and two of them are bad:

  1. Truncate. Cheap, but lossy in the worst way: the one line you needed tends to be in the part that got cut, and there is no way back.
  2. Summarize with a model. You spend tokens to save tokens, you add latency, and you can't trust the summary at the exact moment it matters.
  3. Compress with plain code, keep the original. This is BytePress.

What that costs in practice

One 200KB log read, session runs 40 more turns, Opus at $5/M input tokens (~4 bytes/token):

| | tokens in context | ×40 turns | uncached | cached (0.1×) | |---|---|---|---|---| | without BytePress | ~50,000 | ~2.0M | ~$10 | ~$1 | | with BytePress (32KB cap) | ~8,000 | ~0.3M | ~$1.60 | ~$0.16 |

That's one read; a chatty session has several. On a subscription the bill is context window instead of dollars: fewer oversized outputs means later compaction, and compaction is a lossy summary of your own working history.

Who should install this

  • Claude Code users whose sessions touch big files — lock files, node_modules listings, long test or build logs, gh/kubectl/aws JSON dumps. That's most people doing real work in a repo.
  • Anyone paying per token for agent harnesses built on Claude Code hooks: the savings compound with session length.
  • Long-session users on subscriptions who keep hitting compaction and losing context detail.

Who shouldn't bother: sessions built of small outputs (quick Q&A, short scripts) never trip the 2KB threshold, and BytePress will sit idle. MCP tool outputs are exempt by default. It compresses tool output only — it never touches prompts, model responses, or content you paste in yourself.

What the model sees

Before: 40KB of gh pr list --json output entering the context window:

[
  {
    "number": 901,
    "title": "fix(runner): wave batch-gate invariant",
    "state": "OPEN",
    "author": "guy-lifshitz",
    ...
  },
  ...119 more objects like this
]

After: same information, half the bytes, plus a receipt:

⟦bp:3f9c2d1a8e4b7c60⟧ compressed 40362B→20393B; full output: bp-retrieve 3f9c2d1a8e4b7c60
⟦bp-table cols=number	title	state	author	...⟧
901	fix(runner): wave batch-gate invariant	OPEN	guy-lifshitz	...
902	feat(intake): typed wave batch	MERGED	guy-lifshitz	...

The model reads the table like it read the JSON: nothing semantic was lost, the repeated keys and braces are gone. And because the first line names the hash, the model itself knows how to ask for the full original when it needs it.

Getting the original back is one command:

bp-retrieve 3f9c2d1a8e4b7c60                    # full original, byte-identical
bp-retrieve 3f9c2d1a8e4b7c60 --range 4000:8000  # just a slice

How it works

BytePress runs inside a Claude Code PostToolUse hook. After every tool call, Claude Code hands the hook the tool's output on stdin; if the hook answers with updatedToolOutput, that replacement is what the model sees. The pipeline on each call:

  1. Gate. Output below 2KB, exempt tools, MCP tools, and already-compressed text pass through untouched. Kill switch: BYTEPRESS=0.
  2. Stash. The original goes byte-for-byte into a content-addressed store keyed by SHA-256, before any replacement happens. This is what makes every later step safe.
  3. Tableify. A uniform JSON array of flat objects (the shape of gh --json, file listings, API pages) becomes a header plus TSV rows. Pure re-encoding, zero information loss, kept only if it's smaller.
  4. Budget. A 32KB byte cap trims whatever is still oversized, cut at a UTF-8 boundary, with a marker recording the original size. Unlike a plain truncation, the cut part is retrievable by hash.

Two invariants hold everywhere. Never inflate: if compression wouldn't shrink the output, BytePress writes nothing and replaces nothing. Fail open: on any error in the hook (malformed payload, unwritable store) Claude Code keeps the original output byte-identical. The worst case is always "nothing happened."

No model calls, no prompt overhead, no dependencies. The whole thing is one TypeScript file on node:crypto, node:fs, node:path.

How to use it

1. Install:

bun add @guy-lifshitz/bytepress        # or: npm install @guy-lifshitz/bytepress

2. Drop in the hook. Copy examples/posttooluse-hook.ts somewhere in your project. It's the complete hook, ~40 lines: read the payload from stdin, call compressToolOutput, print the replacement.

3. Wire it into .claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "*",
        "hooks": [{ "type": "command", "command": "bun /path/to/posttooluse-hook.ts" }]
      }
    ]
  }
}

4. Check it's working. Trigger any tool call with a big output (read a lock file, run a chatty command). The output in the transcript now starts with ⟦bp:<hash>⟧, and the original sits in .bytepress/objects/.

5. Recover when needed:

bp-retrieve <hash> [--range a:b]

One setting worth pairing with it. Claude Code truncates Bash output on its own — BASH_MAX_OUTPUT_LENGTH, ~30K characters by default — and that cut is permanent: the tail is gone, nothing to retrieve. It also happens before hooks run, so with default settings the harness trims most oversized Bash output first, before BytePress's byte budget gets a turn. Raise the harness limit and let BytePress do the cutting instead, since its cut keeps the original:

{ "env": { "BASH_MAX_OUTPUT_LENGTH": "200000" } }

With that in settings.json, a 200KB command output reaches the hook intact, the model sees 32KB plus a hash, and bp-retrieve has the whole thing.

That's the whole integration. Tuning is env vars with safe defaults:

| var | default | meaning | |---|---|---| | BP_MIN_BYTES | 2048 | compress nothing smaller | | BP_BUDGET_BYTES | 32768 | hard cap on what the model sees | | BP_STORE_DIR | .bytepress | content-addressed store for originals | | BP_EXEMPT_TOOLS | — | comma-separated tools to never touch | | BYTEPRESS | — | set to 0 to switch the whole thing off |

Numbers

Measured on representative fixtures (bytes in → bytes the model sees):

| input | before | after | saved | |---|---|---|---| | gh pr list --json, 120 rows, pretty-printed | 40,362 | 20,393 | 49.5% | | same, compact JSON | 33,161 | 20,393 | 38.5% | | file listing as JSON, 300 entries | 38,592 | 18,327 | 52.5% | | 200KB test-run log | 201,600 | 32,923 | 83.7% | | package-lock.json, 800 packages | 229,089 | 32,923 | 85.6% | | 1KB output | 900 | — | untouched |

Read the table for what it is: structured JSON shrinks 40–50% with zero information loss (tableify is a pure re-encoding). The 84–86% rows are the byte budget doing its job on outputs no model needs in full, and the cut half stays retrievable by hash. One honest caveat: those big-output rows assume the output reaches the hook intact — true for file reads, and true for Bash once you raise BASH_MAX_OUTPUT_LENGTH as described above. On stock settings the harness trims Bash output to ~30K by itself, with no way back, and BytePress's contribution there is the JSON re-encoding, not the byte cap.

vs. caveman and friends

caveman attacks the same bill from the prompt side: instruct the model to talk like a caveman and output tokens drop ~65%. It works, the numbers are real, and 85K stars say people care about this problem. The trade-offs are different in kind, not degree:

| | caveman | BytePress | |---|---|---| | what it compresses | model output (its own prose) | tool output entering context | | mechanism | prompt skill; the model does the compressing | deterministic code, no model involved | | lossy? | yes, by design | tableify is lossless; budget cuts are recoverable by hash | | overhead | ~1–1.5K input tokens per turn for the skill itself | zero prompt tokens; microseconds of CPU | | failure mode | model drifts out of caveman-speak | fail-open: original passes through untouched |

They compose. Caveman shrinks what the model says; BytePress shrinks what the model reads. Semantic approaches like caveman-compression (strip predictable grammar, keep the surprising words) sit in between: better ratios than re-encoding, but a model or NLP pass in the loop and no way back to the original. BytePress stays on the boring end of that spectrum on purpose: the compression a hook can do in a subprocess, as plain code, with a receipt.

Closest by description is TokenJuice in tinyhumansai/openhuman — it appears to do something similar on the tool-output side. Their code is GPL-3.0 and none of it was read or used here, so how the two mechanisms compare is an open question.

See also

ByteDigger is a software factory that drives the whole SDLC through deterministic checks. BytePress is the same philosophy applied to one narrow problem: the cheapest layer that can do the job is code, not a model.

License

MIT