Skip to content

Latest commit

 

History

141 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

TokenPress mascot: a screw-press robot, long lines of code going in on the left and short ones coming out on the right

TokenPress

A formatter for agentic coding β€” optimized for the tokenizer, not the human reader.

CI License

English Β· ν•œκ΅­μ–΄ Β· ζ—₯本θͺž Β· δΈ­ζ–‡ Β· EspaΓ±ol Β· FranΓ§ais Β· PortuguΓͺs

Try it in your browser β†’


If you are doing agentic coding, why are you still running a formatter built for a human reader? Black, gofmt, rustfmt and Prettier all optimize for a person's eyes: line width, alignment, blank lines between things. When the reader is a model, none of that is value. It is billed tokens.

TokenPress emits the equivalent program that costs the fewest input tokens:

minimize  tokenizer.encode(transformed_code)
s.t.      the transformed code parses, compiles, and behaves identically

It is not a minifier. Character count and token count disagree, so the transforms are chosen against a real tokenizer. Output that fails verification is never written, and identifiers and string contents are never touched.

How much it saves

Each row is a real open-source codebase, formatted whole at a pinned commit, every file passing verification. The solid bar is what every tokenizer saves; the shaded tail is how much further the most favourable one goes.

Aggressive settings: the opt-in flags that also drop comments and docstrings:

target codebase       0%   10%  20%  30%  40%  50%  60%
β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€
tokio (Rust)          β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘      -50.5 … -55.2%
ripgrep (Rust)        β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘             -37.3 … -42.7%
langchain (Python)    β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘             -37.1 … -41.1%
fastapi (Python)      β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘              -36.1 … -40.1%
requests (Python)     β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘                -31.9 … -36.5%
transformers (Python) β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘                -30.3 … -36.1%
uv (Rust + Python)    β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘                      -21.4 … -24.7%
django (Python)       β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘                      -20.7 … -24.8%

Default settings: same codebases, no flags at all. Comments, docstrings and type annotations are all kept; only whitespace, blank lines and indentation go:

target codebase       0%   10%  20%  30%  40%  50%  60%
β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€
fastapi (Python)      β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘                     -21.6 … -26.7%
ripgrep (Rust)        β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘                       -16.6 … -22.8%
uv (Rust + Python)    β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘                          -13.1 … -16.8%
langchain (Python)    β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘                          -12.2 … -15.6%
tokio (Rust)          β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘                        -11.5 … -19.1%
django (Python)       β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘                            -9.8 … -12.6%
requests (Python)     β–ˆβ–ˆβ–ˆβ–ˆβ–‘                             -7.3 … -9.7%
transformers (Python) β–ˆβ–ˆβ–ˆβ–ˆβ–‘                             -7.0 … -10.3%

Note how the order changes. tokio leads the aggressive chart because it is doc-comment-dense (strip those and half the repo is gone), but at default settings it is mid-pack, because what is left to remove is only whitespace. The default numbers are the ones that cost you nothing; the aggressive ones are a trade you are choosing.

The spread within each row is the other point: savings are per tokenizer, which is why the benchmark measures six: GLM-5.2, Kimi K3, Gemma 4, Qwen3.6, o200k_base and cl100k_base. The other five supported languages, one codebase each, aggressive, same run:

target codebase       0%   10%  20%  30%  40%  50%  60%
──────────────────────���────┼────┼────┼────┼────┼─────
commons-lang (Java)   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘           -42.9 … -46.6%
express (JS)          β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘                 -25.4 … -33.3%
rack (Ruby)           β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘                        -18.2 … -20.8%
gin (Go)              β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘                        -18.7 … -20.0%

No private or closed tokenizer has been measured, and no number here is extrapolated to one. Savings track how much of a tree is prose, not which language it is written in; one corpus per language is a data point, never a language-level expectation. Thirteen corpora, raw token counts, per-tokenizer tables and the line-ending caveats are in benchmarks/RESULTS.md, summarized in benchmarks/SHOWCASE.md.

Stripping comments and docstrings deletes context a model could have used, and whether that degrades its answers has not been measured. The savings are measured; the quality trade-off is not. Treat the aggressive flags as a choice, not as free.

Does a human read this code?

One question decides how to use this.

Yes β€” format the copy you hand the model and leave your source alone. Paste it into a prompt, hand it to an agent's context window, feed it to a RAG index. This holds even at default settings: the default run still removes blank lines and squeezes indentation. "Context-lossless" here is a claim about what a model can recover, never about what a person enjoys reading.

No β€” nobody reads it, the repository is written and maintained by agents β€” then normalizing the source itself is coherent, and the pre-commit hook and GitHub Action exist for that. Two things to know first, neither about human readers:

  • Rust joins every line. At default settings the Rust backend re-emits a whole file as one line, so line-addressed edit tools, git diff, merge conflicts and stack traces all degrade. The other backends keep newlines.
  • Comments are lost at default, and how much depends on the language. Rust drops every // and /* */, because it re-emits from the syn token stream, so only /// and //! doc comments survive. JS/TS keeps a comment that has its own line and drops one that shares a line with code. The other five keep both. There is no un-format, and under a hook this is not a one-time conversion: every comment written afterwards goes on the next run.

There is no reverse mapping: no source map, no patch-back. A model can read formatted code and answer about it, but a diff against the formatted copy will not apply to an unformatted original. A file a model is going to edit should be given to it unformatted.

TokenPress runs TokenPress on itself, through the tokenpress-format hook in its own .pre-commit-config.yaml, at default settings: -22.6%, 253,666 β†’ 196,415 tokens. The costs are the ones this section describes and they were paid deliberately: 1,941 plain comment lines deleted, git blame and stack traces degraded, the reasoning moved to commit messages and docs/. The tests and the 100%-coverage gate came through unchanged. Full before/after in SHOWCASE.md.

This is also why there is no editor plugin and no format-on-save. That is how most people meet Black, Prettier or rustfmt, and it is the one integration TokenPress should not have: the file open in your editor is, by definition, one a human is reading. An extension that ran this on save would be wrong in exactly the case the question above is asking about.

Use it in your project

Like any other formatter, the version belongs to the project rather than to your machine β€” otherwise two people on different versions reformat each other's files forever. Pin it in a hook or an Action and nobody has to install anything.

pre-commit: the framework fetches the pinned revision itself:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/starone99/TokenPress
    rev: v0.1.1                  # the pin is the point β€” bump it deliberately
    hooks:
      - id: tokenpress-check     # writes nothing; fails if anything would change
    # - id: tokenpress-format    # rewrites in place. Read the gate above first.

GitHub Action: one step in an existing workflow:

- uses: starone99/TokenPress@v0.1.1
  with:
    paths: src tests
    mode: check                  # `format` rewrites the workspace

tokenpress.toml: flags per language, picked up from the nearest parent directory, so the hook, the Action and your own runs all agree:

[python]
strip_comments = true
[rust]
strip_doc_comments = true

check is the default in both integrations, and it writes nothing. Reach for format only on the "nobody reads this code" side of the question above. Options, the full flag/config mapping and the cargo features are in INTEGRATIONS.md.

Pin to a release tag, not to a branch. On a tag both integrations download that release's binary and check it against the release's SHA256SUMS: a few seconds, and no Rust toolchain, C compiler or libclang anywhere. A branch or a bare commit has no release binary to correspond to it, so the CLI is compiled from the checkout instead: correct, and minutes rather than seconds. Asking for a smaller binary than a release ships (the hook's TOKENPRESS_NO_RUBY and friends, the Action's ruby/go/java/csharp inputs) compiles for the same reason, and so does anything the releases have no archive for (Windows, Intel macOS, and every non-x86_64 Linux). TOKENPRESS_NO_PREBUILT=1 forces the source build outright.

Or run it yourself

For a one-off, install the CLI: measuring a tree, or generating the copy you are about to hand a model.

# install script: downloads the release for your host and verifies it against
# the release's SHA256SUMS before extracting anything
curl -fsSL https://raw.githubusercontent.com/starone99/TokenPress/master/install.sh | sh
irm https://raw.githubusercontent.com/starone99/TokenPress/master/install.ps1 | iex
# or with a Rust toolchain
cargo install --git https://github.com/starone99/TokenPress tokenpress-cli

Prebuilt archives and SHA256SUMS are on the releases page for Linux x86_64, macOS (Apple Silicon) and Windows x86_64; any other platform builds from source β€” Intel macOS included, because the Intel build runner is being retired upstream and a release should not wait on a deprecated one. TOKENPRESS_VERSION pins a tag and TOKENPRESS_BIN_DIR changes where the script installs. Building the Ruby, Go, Java and C# backends needs a C compiler, and libclang for Ruby; --no-default-features needs neither, and --features go,java adds back only what you name.

Then:

tokenpress stats  <PATH>...        # what it would save β€” writes nothing
tokenpress diff   <PATH>...        # unified diff β€” writes nothing
tokenpress format <PATH>...        # rewrite in place (dirs walk recursively)
tokenpress check  <PATH>...        # exit 1 if anything would change

Start with stats. It touches nothing and tells you whether this is worth it for your tree:

tokenpress stats . --tokenizer o200k_base            # GPT-4o / o-series (default)
tokenpress stats . --tokenizer cl100k_base           # GPT-4 / GPT-3.5
tokenpress stats . --tokenizer hf:tokenizer.json     # any HF tokenizer (Qwen, GLM, Gemma…)
tokenpress stats . --tokenizer kimi:tiktoken.model   # Kimi ranks format

Everything lossy is an opt-in flag, and each says what it breaks:

--py-strip-comments        # drop # comments
--py-strip-docstrings      # empties __doc__ β€” breaks help() and doctests
--py-strip-annotations     # breaks dataclass/pydantic introspection
--py-no-merge-imports      # keep adjacent imports separate
--rs-strip-doc-comments    # drops /// and //! β€” rustdoc and doctests with them
--js-strip-comments        # drops the JS/TS comments that survive at all
--ruby-strip-comments      # shebang and magic comments kept
--go-strip-comments        # //go: directives, build constraints, cgo preamble kept
--java-strip-comments      # Javadoc included
--csharp-strip-comments    # /// XML documentation included

Exit codes: 0 ok Β· 1 check found changes Β· 2 error. Parse and verification failures are reported per file, and nothing corrupt is ever written.

How it works

  source ──▢ parse ──▢ re-emit at minimum token cost ──▢ verify ──▢ write
                                                            β”‚
                                              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                              β”‚ re-parse                  β”‚
                                              β”‚ AST / token equivalence   β”‚
                                              β”‚ the language's own tool   β”‚  ← --verify external
                                              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                            β”‚
                                                     fails ─┴─▢ file left untouched

The last step is the whole design. A transform that cannot be proven equivalent is not written, so the worst case is that a file is left alone β€” never that it is corrupted.

Language support

Python and Rust are the primary targets: what the project was built for, what the benchmarks cover most deeply, and where the work goes first. The other five are supported on the same invariant and the same verification, but each rests on a single corpus.

Language Extensions Default keeps comments External check
Python .py βœ… ❌ built-in check only
Rust .rs ❌ // and /* */ always dropped ❌ built-in check only
JavaScript / TypeScript .js .mjs .cjs .jsx .ts .mts .cts .tsx ⚠️ partial βœ… tsc --noEmit
Ruby .rb .rake .gemspec .ru, Gemfile, Rakefile βœ… βœ… ruby -c
Go .go βœ… βœ… gofmt -e
Java .java βœ… βœ… javac, stopped after parse
C# .cs βœ… βœ… Roslyn csc

That last column cuts against the paragraph above it, and is stated here rather than buried: the two primary languages are the two without external verification. Closing that is the first item on the roadmap.

Per-language detail (what each backend keeps, what it cannot, and how each external checker is invoked) is in LANGUAGES.md.

Documentation

LANGUAGES.md Per-language support, caveats and external checkers
INTEGRATIONS.md pre-commit, GitHub Action, config file, cargo features
CHANGELOG.md What changed, with the output-affecting entries marked
benchmarks/RESULTS.md Full methodology, thirteen corpora, six tokenizers
benchmarks/SHOWCASE.md The summary, and the β‰₯40% candidates per tokenizer
ROADMAP.md What is next, and the questions that are open
CONTRIBUTING.md Building, testing, and the toolchains each backend needs
SECURITY.md Reporting a vulnerability, the threat model, release integrity

Development

TDD with a hard gate: scripts/coverage.ps1 (Windows) / scripts/coverage.sh fails the build under 100% line coverage. CI runs clippy -D warnings, tests on Linux and Windows, and that gate, so the CI badge above going green is the coverage claim, rather than a badge asserting a number nothing checks.

Do not run cargo fmt here. This repository formats its own sources with TokenPress, so rustfmt is not in CI and would only produce a diff the hook undoes. Rules in CONTRIBUTING.md, which also says where to put reasoning given that // comments do not survive.

License

Licensed under the Apache License, Version 2.0 (LICENSE or https://www.apache.org/licenses/LICENSE-2.0).

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.

About

A formatter for agentic coding β€” optimizes source for the tokenizer, not the human reader. Seven languages; output that fails verification is never written.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages