token-distiller 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. token_distiller-0.1.0/.claude-plugin/marketplace.json +18 -0
  2. token_distiller-0.1.0/.github/workflows/ci.yml +61 -0
  3. token_distiller-0.1.0/.gitignore +16 -0
  4. token_distiller-0.1.0/LICENSE +133 -0
  5. token_distiller-0.1.0/PKG-INFO +257 -0
  6. token_distiller-0.1.0/README.md +220 -0
  7. token_distiller-0.1.0/plugin/.claude-plugin/plugin.json +12 -0
  8. token_distiller-0.1.0/plugin/commands/distill.md +10 -0
  9. token_distiller-0.1.0/plugin/hooks/hooks.json +28 -0
  10. token_distiller-0.1.0/plugin/skills/token-distiller/SKILL.md +50 -0
  11. token_distiller-0.1.0/pyproject.toml +60 -0
  12. token_distiller-0.1.0/src/token_distiller/__init__.py +1 -0
  13. token_distiller-0.1.0/src/token_distiller/activity.py +105 -0
  14. token_distiller-0.1.0/src/token_distiller/bash_compress.py +250 -0
  15. token_distiller-0.1.0/src/token_distiller/boilerplate.py +67 -0
  16. token_distiller-0.1.0/src/token_distiller/cache.py +186 -0
  17. token_distiller-0.1.0/src/token_distiller/chunker.py +79 -0
  18. token_distiller-0.1.0/src/token_distiller/cli.py +623 -0
  19. token_distiller-0.1.0/src/token_distiller/config.py +100 -0
  20. token_distiller-0.1.0/src/token_distiller/embeddings.py +26 -0
  21. token_distiller-0.1.0/src/token_distiller/exact_tokens.py +23 -0
  22. token_distiller-0.1.0/src/token_distiller/hook_installer.py +86 -0
  23. token_distiller-0.1.0/src/token_distiller/image_ingest.py +17 -0
  24. token_distiller-0.1.0/src/token_distiller/index_store.py +102 -0
  25. token_distiller-0.1.0/src/token_distiller/models.py +112 -0
  26. token_distiller-0.1.0/src/token_distiller/ocr.py +110 -0
  27. token_distiller-0.1.0/src/token_distiller/pdf_extract.py +107 -0
  28. token_distiller-0.1.0/src/token_distiller/pipeline.py +218 -0
  29. token_distiller-0.1.0/src/token_distiller/repo_pack.py +148 -0
  30. token_distiller-0.1.0/src/token_distiller/retrieval.py +45 -0
  31. token_distiller-0.1.0/src/token_distiller/session_audit.py +89 -0
  32. token_distiller-0.1.0/src/token_distiller/storage.py +111 -0
  33. token_distiller-0.1.0/src/token_distiller/tokens.py +51 -0
  34. token_distiller-0.1.0/src/token_distiller/vision_fallback.py +61 -0
  35. token_distiller-0.1.0/tests/conftest.py +258 -0
  36. token_distiller-0.1.0/tests/test_activity.py +65 -0
  37. token_distiller-0.1.0/tests/test_bash_compress.py +186 -0
  38. token_distiller-0.1.0/tests/test_benchmark.py +141 -0
  39. token_distiller-0.1.0/tests/test_boilerplate.py +74 -0
  40. token_distiller-0.1.0/tests/test_cache.py +110 -0
  41. token_distiller-0.1.0/tests/test_exact_tokens.py +86 -0
  42. token_distiller-0.1.0/tests/test_hook_installer.py +103 -0
  43. token_distiller-0.1.0/tests/test_hook_read.py +166 -0
  44. token_distiller-0.1.0/tests/test_models.py +64 -0
  45. token_distiller-0.1.0/tests/test_pipeline.py +305 -0
  46. token_distiller-0.1.0/tests/test_repo_pack.py +103 -0
  47. token_distiller-0.1.0/tests/test_retrieval.py +89 -0
  48. token_distiller-0.1.0/tests/test_session_audit.py +93 -0
  49. token_distiller-0.1.0/tests/test_tokens.py +58 -0
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "token-distiller",
3
+ "owner": {
4
+ "name": "Sri PriYa N. Chen",
5
+ "email": "p.chen@NeoclassicalPopArt.com",
6
+ "url": "https://github.com/priyanchen/token-distiller"
7
+ },
8
+ "plugins": [
9
+ {
10
+ "name": "token-distiller",
11
+ "source": "./plugin",
12
+ "description": "Distills PDFs, photos, and repos into token-efficient context for LLM agents. Local OCR reads embedded diagrams, not just text layers; content-hash caching collapses re-reads; BM25 retrieval bounds huge documents. Nothing is discarded — anything shortened expands back in full. 33x on a 25-page PDF, 97% on pytest output.",
13
+ "version": "0.1.0",
14
+ "license": "PolyForm-Noncommercial-1.0.0",
15
+ "keywords": ["token", "context", "pdf", "ocr", "retrieval", "rag"]
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,61 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+
23
+ # poppler backs pdf2image's page rasterization; tesseract is the OCR engine; the
24
+ # DejaVu fonts are needed by the fixtures that render real text for OCR to read.
25
+ - name: Install system dependencies
26
+ run: |
27
+ sudo apt-get update
28
+ sudo apt-get install -y poppler-utils tesseract-ocr fonts-dejavu-core
29
+
30
+ - name: Install package
31
+ run: pip install ".[dev]"
32
+
33
+ - name: Verify OCR engine is reachable
34
+ run: python -c "import pytesseract; print(pytesseract.get_tesseract_version())"
35
+
36
+ - name: Default suite
37
+ run: pytest -q
38
+
39
+ # Deselected by default because they drive real Tesseract; run explicitly here so the
40
+ # compression claims in the README and the figure-reading path stay covered.
41
+ - name: OCR-marked suite
42
+ run: pytest -q -m ocr
43
+
44
+ - name: Smoke-test the CLI end to end
45
+ run: |
46
+ distill --help
47
+ python - <<'PY'
48
+ import subprocess, sys
49
+ sys.path.insert(0, "tests")
50
+ from conftest import make_pdf
51
+ make_pdf("/tmp/ci_smoke.pdf", [["A page of native text long enough to skip OCR entirely."]])
52
+ out = subprocess.run(
53
+ ["distill", "file", "/tmp/ci_smoke.pdf", "--json"],
54
+ capture_output=True, text=True, check=True,
55
+ ).stdout
56
+ import json
57
+ d = json.loads(out)
58
+ assert d["method_counts"] == {"native_text": 1}, d["method_counts"]
59
+ assert d["compression_ratio"] > 1.0, d["compression_ratio"]
60
+ print("CLI smoke test OK:", d["raw_tokens_est"], "->", d["distilled_tokens_est"])
61
+ PY
@@ -0,0 +1,16 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
8
+ data/
9
+ .env
10
+ .env.*
11
+ .DS_Store
12
+ **/.DS_Store
13
+
14
+ # local working artifacts, not part of the project
15
+ *.patch
16
+ PR-BRIEF-*.md
@@ -0,0 +1,133 @@
1
+ Copyright (C) 2026 Sri PriYa N. Chen
2
+
3
+ Required Notice: Copyright Sri PriYa N. Chen
4
+
5
+ # PolyForm Noncommercial License 1.0.0
6
+
7
+ <https://polyformproject.org/licenses/noncommercial/1.0.0>
8
+
9
+ ## Acceptance
10
+
11
+ In order to get any license under these terms, you must agree to them
12
+ as both strict obligations and conditions to all your licenses.
13
+
14
+ ## Copyright License
15
+
16
+ The licensor grants you a copyright license for the software to do
17
+ everything you might do with the software that would otherwise infringe
18
+ the licensor's copyright in it for any permitted purpose. However, you
19
+ may only distribute the software according to Distribution License and
20
+ make changes or new works based on the software according to Changes and
21
+ New Works License.
22
+
23
+ ## Distribution License
24
+
25
+ The licensor grants you an additional copyright license to distribute
26
+ copies of the software. Your license to distribute covers distributing
27
+ the software with changes and new works permitted by Changes and New
28
+ Works License.
29
+
30
+ ## Notices
31
+
32
+ You must ensure that anyone who gets a copy of any part of the software
33
+ from you also gets a copy of these terms or the URL for them above, as
34
+ well as copies of any plain-text lines beginning with `Required Notice:`
35
+ that the licensor provided with the software.
36
+
37
+ ## Changes and New Works License
38
+
39
+ The licensor grants you an additional copyright license to make changes
40
+ and new works based on the software for any permitted purpose.
41
+
42
+ ## Patent License
43
+
44
+ The licensor grants you a patent license for the software that covers
45
+ patent claims the licensor can license, or becomes able to license, that
46
+ you would infringe by using the software.
47
+
48
+ ## Noncommercial Purposes
49
+
50
+ Any noncommercial purpose is a permitted purpose.
51
+
52
+ ## Personal Uses
53
+
54
+ Personal use for research, experiment, and testing for the benefit of
55
+ public knowledge, personal study, private entertainment, hobby projects,
56
+ amateur pursuits, or religious observance, without any anticipated
57
+ commercial application, is use for a permitted purpose.
58
+
59
+ ## Noncommercial Organizations
60
+
61
+ Use by any charitable organization, educational institution, public
62
+ research organization, public safety or health organization,
63
+ environmental protection organization, or government institution is use
64
+ for a permitted purpose regardless of the source of funding or
65
+ obligations resulting from the funding.
66
+
67
+ ## Fair Use
68
+
69
+ You may have "fair use" rights for the software under the law. These
70
+ terms do not limit them.
71
+
72
+ ## No Other Rights
73
+
74
+ These terms do not allow you to sublicense or transfer any of your
75
+ licenses to anyone else, or prevent the licensor from granting licenses
76
+ to anyone else. These terms do not imply any other licenses.
77
+
78
+ ## Patent Defense
79
+
80
+ If you make any written claim that the software infringes or contributes
81
+ to infringement of any patent, your patent license for the software
82
+ granted under these terms ends immediately. If your company makes such a
83
+ claim, your patent license ends immediately for work on behalf of your
84
+ company.
85
+
86
+ ## Violations
87
+
88
+ The first time you are notified in writing that you have violated any of
89
+ these terms, or done anything with the software not covered by your
90
+ licenses, your licenses can nonetheless continue if you come into full
91
+ compliance with these terms, and take practical steps to correct past
92
+ violations, within 32 days of receiving notice. Otherwise, all your
93
+ licenses end immediately.
94
+
95
+ ## No Liability
96
+
97
+ As far as the law allows, the software comes as is, without any warranty
98
+ or condition, and the licensor will not be liable to you for any damages
99
+ arising out of these terms or the use or nature of the software, under
100
+ any kind of legal claim.
101
+
102
+ ## Definitions
103
+
104
+ The **licensor** is the individual or entity offering these terms, and
105
+ the **software** is the software the licensor makes available under
106
+ these terms.
107
+
108
+ **You** refers to the individual or entity agreeing to these terms.
109
+
110
+ **Your company** is any legal entity, sole proprietorship, or other kind
111
+ of organization that you work for, plus all organizations that have
112
+ control over, are under the control of, or are under common control with
113
+ that organization. **Control** means ownership of substantially all the
114
+ assets of an entity, or the power to direct its management and policies
115
+ by vote, contract, or otherwise. Control can be direct or indirect.
116
+
117
+ **Your licenses** are all the licenses granted to you for the software
118
+ under these terms.
119
+
120
+ **Use** means anything you do with the software requiring one of your
121
+ licenses.
122
+
123
+ ---
124
+
125
+ ## Commercial Licensing
126
+
127
+ Personal, private, and noncommercial use (including by individuals,
128
+ students, hobbyists, and noncommercial/nonprofit/educational
129
+ organizations) is free under the terms above — no license purchase
130
+ needed.
131
+
132
+ For commercial use, please contact Sri PriYa N. Chen:
133
+ p.chen@NeoclassicalPopArt.com
@@ -0,0 +1,257 @@
1
+ Metadata-Version: 2.4
2
+ Name: token-distiller
3
+ Version: 0.1.0
4
+ Summary: Distills PDFs, photos, and repos into token-efficient context for LLM agents. Local OCR reads embedded diagrams, not just text layers; content-hash caching collapses re-reads; BM25 retrieval bounds huge documents. Nothing is discarded — anything shortened expands back in full. 33x on a 25-page PDF, 97% on pytest output.
5
+ Project-URL: Homepage, https://github.com/priyanchen/token-distiller
6
+ Project-URL: Repository, https://github.com/priyanchen/token-distiller
7
+ Project-URL: Issues, https://github.com/priyanchen/token-distiller/issues
8
+ Author: Sri PriYa N. Chen
9
+ License-Expression: PolyForm-Noncommercial-1.0.0
10
+ License-File: LICENSE
11
+ Keywords: anthropic,claude,context,llm,ocr,pdf,rag,retrieval,tokens
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: Text Processing :: General
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: anthropic>=0.40
23
+ Requires-Dist: pathspec>=0.12
24
+ Requires-Dist: pdf2image>=1.17
25
+ Requires-Dist: pdfplumber>=0.11
26
+ Requires-Dist: pillow-heif>=0.15
27
+ Requires-Dist: pillow>=10.0
28
+ Requires-Dist: pypdf>=4.0
29
+ Requires-Dist: pytesseract>=0.3.10
30
+ Requires-Dist: rank-bm25>=0.2
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest-mock; extra == 'dev'
33
+ Requires-Dist: pytest>=8; extra == 'dev'
34
+ Provides-Extra: rag-semantic
35
+ Requires-Dist: voyageai>=0.3; extra == 'rag-semantic'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # Token Distiller
39
+
40
+ Distills PDFs and photos into token-efficient text before they enter an LLM's context, packs repos Repomix-style, indexes ingested content for retrieval instead of raw dumping, and tracks session activity mode to bias what a context audit flags.
41
+
42
+ Pure Python. No shell scripts, no Node/TypeScript.
43
+
44
+ ## Setup
45
+
46
+ ### From PyPI
47
+
48
+ ```bash
49
+ brew install poppler # required by pdf2image for PDF page rasterization
50
+ pip install token-distiller
51
+ ```
52
+
53
+ ### As a Claude Code plugin
54
+
55
+ ```
56
+ /plugin marketplace add priyanchen/token-distiller
57
+ /plugin install token-distiller@token-distiller
58
+ ```
59
+
60
+ This wires the hook configuration only. Claude Code has no mechanism to run setup
61
+ commands after installing a plugin, so it can't provision a Python environment for you —
62
+ separately run `pip install token-distiller` so the `distill` binary the hook invokes is
63
+ on `PATH`. Skipping that step doesn't break anything: the hook fails open, so `Read` on a
64
+ PDF passes through unchanged rather than erroring. It just means nothing gets distilled
65
+ until the binary is actually installed.
66
+
67
+ ### From source
68
+
69
+ ```bash
70
+ brew install poppler # required by pdf2image for PDF page rasterization
71
+ python3 -m venv .venv
72
+ source .venv/bin/activate
73
+ pip install .
74
+ ```
75
+
76
+ Use a regular install, not `pip install -e .`. On this machine the editable install's
77
+ `_editable_impl_token_distiller.pth` was silently ignored by `site.py` (the file was
78
+ readable and its contents correct, and a byte-identical copy under a different filename
79
+ *was* honored — root cause unresolved), leaving `token_distiller` unimportable. A regular
80
+ install copies the package into `site-packages` and avoids the `.pth` indirection
81
+ entirely. Re-run `pip install .` after editing source.
82
+
83
+ Optional: set **`TOKEN_DISTILLER_ANTHROPIC_API_KEY`** to enable vision-model fallback for
84
+ figures and pages OCR can't read. Prefer that name over plain `ANTHROPIC_API_KEY` — a host
85
+ agent (Claude Code included) may also read `ANTHROPIC_API_KEY` and switch from subscription
86
+ authentication to per-token API billing if it finds one. The scoped name is checked first,
87
+ so this tool gets a key without changing anyone else's auth.
88
+
89
+ Set `VOYAGE_API_KEY` and `pip install ".[rag-semantic]"` to enable semantic (embedding)
90
+ retrieval on top of the default BM25 keyword index.
91
+
92
+ ## CLI
93
+
94
+ Placeholders are uppercase; substitute your own path, directory, or question.
95
+
96
+ ```bash
97
+ distill file PATH # distill one PDF/photo
98
+ distill scan DIR # batch distill a directory
99
+ distill repo DIR # Repomix-style repo pack
100
+ distill index DIR # build a retrieval index
101
+ distill query "QUESTION" # query the index
102
+ distill mode # current session activity mode
103
+ distill audit PATH # CLAUDE.md/MEMORY.md structural audit (defaults to .)
104
+ distill report # cumulative token/savings report
105
+ distill expand HANDLE # full distilled text for a handle (--list to browse)
106
+ distill compress # compress verbose command output read from stdin
107
+ distill install-hook # wire the PreToolUse Read-interception hook into a project
108
+ ```
109
+
110
+ Every command that distills a PDF accepts `--no-figures` to skip reading embedded figures,
111
+ and `--no-vision` to stay on local OCR only. `distill file` additionally accepts
112
+ `--accurate-tokens`, which calls Anthropic's `count_tokens` endpoint to report the real
113
+ tokenizer count for the distilled output next to the chars/4 estimate. It's opt-in because
114
+ it needs an API key (`TOKEN_DISTILLER_ANTHROPIC_API_KEY` or `ANTHROPIC_API_KEY`) and a
115
+ network round trip — the call itself is free and doesn't count against message-creation
116
+ rate limits, but the estimate is what every other command uses by default.
117
+
118
+ ## Compressing command output
119
+
120
+ Verbose CLI output is the one context cost the document pipeline doesn't touch. Pipe it in:
121
+
122
+ ```bash
123
+ pytest -q | distill compress --stats
124
+ git status | distill compress
125
+ npm install | distill compress
126
+ ```
127
+
128
+ Measured on this repo's own output: a 169-test `pytest` run goes **289 → 8 tokens (97%)**,
129
+ keeping the failure list, the first assertion detail, and the summary line while dropping
130
+ the wall of dots. Plain `git status` goes **153 → 58 tokens (62%)**, grouped by state.
131
+
132
+ Two properties worth knowing:
133
+
134
+ - **It never inflates.** `git status --porcelain` is already denser than any per-state
135
+ summary of it, so when compression would produce more text than it consumed, the original
136
+ is returned unchanged.
137
+ - **It never executes anything.** `distill compress` reads stdin and writes stdout. The
138
+ alternative — a hook that rewrites your Bash command to route it through a wrapper —
139
+ means building shell strings out of model-supplied input, which is exactly where command
140
+ injection lives. Piping output that you already ran has no such surface. Automatic
141
+ interception is deliberately not implemented for that reason.
142
+
143
+ ## Verified in a live session
144
+
145
+ The `Read` interception was checked against a real Claude Code session, not only against
146
+ synthetic hook payloads. A one-page PDF holding a canary string was read through the hook:
147
+ the session reported `2,572 → 38 tokens` and the model quoted the canary correctly,
148
+ confirming the substituted text is what actually reaches it. A second run asking for one
149
+ specific page of a four-page PDF passed straight through to the native ranged read with no
150
+ hook note, confirming a page range is never answered with whole-document content.
151
+
152
+ ## How it avoids losing anything
153
+
154
+ Every distillation is stored whole, keyed by a SHA-256 of the file's bytes, before any
155
+ shortening happens. Anything the hook shortens carries a handle, and `distill expand
156
+ <handle>` returns the complete text. Concretely:
157
+
158
+ - **Re-reads collapse.** Reading the same unchanged file twice in a session returns a
159
+ one-line pointer the second time instead of the whole document again (7,634 → 287
160
+ chars measured). Edit the file and the hash changes, so it is re-distilled in full —
161
+ a stale cache can never be served.
162
+ - **Repeated page boilerplate is restated once.** A line must appear on ≥80% of pages to
163
+ qualify, so a running copyright footer (25/25 pages) collapses while a structural
164
+ marker like `Example:` (15/25) is left alone. Collapsed lines are listed at the top of
165
+ the output.
166
+ - **Large documents defer rather than truncate.** Past `TOKEN_DISTILLER_LARGE_DOC_TOKENS`
167
+ (default 8000) the hook returns a head plus retrieval instructions. Nothing is
168
+ discarded — `distill expand` or `distill index` + `distill query` reach the rest.
169
+ - **Embedded figures are read, not skipped.** Native-text extraction sees a page's text
170
+ layer only, so a diagram sitting beside that text would otherwise go unread. Each
171
+ embedded figure is cropped out by its bounding box and put through the same OCR →
172
+ vision chain used for scanned pages, then written into the output labelled
173
+ `[figure N on page M]`. Cropping to the figure matters: the surrounding prose is
174
+ already captured losslessly, so including it would pay vision tokens to re-read text
175
+ we already have. Hairline rules and background strips are skipped via
176
+ `TOKEN_DISTILLER_FIGURE_MIN_SIDE_PT` (default 48pt). `--no-figures` turns it off.
177
+ - **A weak OCR pass is retried on a preprocessed copy.** Figures cropped from a PDF are
178
+ often below the ~300 DPI Tesseract expects and sit on a tinted panel, which is exactly
179
+ when raw OCR returns nothing. A second attempt greyscales, stretches contrast, upscales
180
+ small crops, and binarizes with an Otsu threshold. It is a retry rather than the default
181
+ because binarizing can destroy anti-aliased text that read fine raw, so the preprocessed
182
+ pass has to win on word count and confidence to be used. Measured on a real 765-page
183
+ book: of 13 figures that raw OCR could not read at all, **10 were recovered** — one at
184
+ confidence 96, transcribing `Market Research / Competitive Analysis / SWOT Analysis /
185
+ Goal Setting / Resource Allocation`. That book ends at 60 of 63 figures read, with no
186
+ API key.
187
+ - **Figures that still can't be read are flagged, never dropped silently.** A purely
188
+ graphical diagram with no legible labels yields nothing from OCR, and without
189
+ `ANTHROPIC_API_KEY` there's no vision fallback to describe it. Those pages stay in
190
+ `pages_with_uncaptured_images()` and surface as one compact note — never one line per
191
+ page — in `distill file`, `--json`, and the hook-read response. Set `ANTHROPIC_API_KEY`
192
+ to close the remainder.
193
+
194
+ ## What leaves your machine
195
+
196
+ With no API keys set, nothing does. OCR runs locally through Tesseract and retrieval runs
197
+ locally through BM25, so the default configuration makes **zero network calls**.
198
+
199
+ Two features are opt-in, and they send different things to different companies:
200
+
201
+ | Enabled by | Goes to | What is sent |
202
+ |---|---|---|
203
+ | `TOKEN_DISTILLER_ANTHROPIC_API_KEY` (or `ANTHROPIC_API_KEY`) | Anthropic | A PNG of a **single cropped figure**, plus a fixed prompt. No source code, no file paths, no surrounding page text. |
204
+ | `VOYAGE_API_KEY` + `pip install ".[rag-semantic]"` | Voyage AI | **Chunk text** from whatever you indexed. If you indexed a repo pack, that includes your source code. |
205
+
206
+ The Voyage path is the one to think hardest about — it is a separate company under separate
207
+ terms, and it is the only path that can transmit code. It is off unless you both install the
208
+ extra and set the key; BM25 retrieval works without it.
209
+
210
+ On the Anthropic path, [their commercial terms](https://www.anthropic.com/legal/commercial-terms)
211
+ state that the customer "retains all rights to its Inputs", "owns its Outputs", that
212
+ Anthropic "disclaims any rights it receives to the Customer Content", and that Anthropic
213
+ "may not train models on Customer Content from Services". Read them yourself rather than
214
+ relying on this summary; this is not legal advice.
215
+
216
+ Turn figure reading off entirely with `--no-figures` or
217
+ `TOKEN_DISTILLER_DESCRIBE_FIGURES=0`, and it will never reach for the vision model.
218
+
219
+ Toggle any of it off: `TOKEN_DISTILLER_CACHE=0`, `TOKEN_DISTILLER_REREAD_COLLAPSE=0`,
220
+ `TOKEN_DISTILLER_BOILERPLATE=0`.
221
+
222
+ ## A note on the numbers
223
+
224
+ `raw_tokens_est` models what the **host** pays to ingest the file, not what its text
225
+ alone would cost. Reading a PDF natively renders each page to an image and bills those
226
+ pixels on top of the text, so a 25-page text PDF costs ~60,000 tokens to read raw but
227
+ ~1,800 distilled (33x). Scoring it as text-only would have reported a meaningless 1.0x.
228
+
229
+ Both figures are estimates (chars/4 for text, Anthropic's published pixel formula for
230
+ images) — good enough to compare methods, not exact. `distill file --accurate-tokens`
231
+ swaps the distilled-side estimate for a real count from Anthropic's tokenizer, to check
232
+ the estimate rather than trust it.
233
+
234
+ That 33x describes a *sparse* page, where a fixed per-page rendering cost dominates a
235
+ small amount of actual text — it is not a document-size-independent multiplier. A
236
+ densely-written page compresses by far less through this mechanism alone, because the
237
+ distilled side scales with real content: measured on a 765-page, prose-dense book, the
238
+ whole-document ratio was 4.77x (1,567,889 raw → 328,589 distilled), not 33x. That is
239
+ expected, not a regression — a page's text cannot be compressed below its own token
240
+ count by an extraction step that isn't lossy.
241
+
242
+ For a document that size, the number that actually matters is not the whole-document
243
+ ratio anyway. 328,589 distilled tokens is well past `TOKEN_DISTILLER_LARGE_DOC_TOKENS`
244
+ (default 8000), so the hook's large-document deferral fires: a live session reading that
245
+ 765-page file through the hook receives a head plus a `distill index` / `distill query`
246
+ pointer, measured at ~1,675 tokens — roughly **940x** against the 1,567,889 raw cost.
247
+ (The payload embeds the file's absolute path, so the exact token count shifts a little
248
+ with where the file lives.)
249
+ For large documents, the deferral-and-retrieval path is where the real savings come
250
+ from; the raw-vs-native-text mechanism the 33x figure describes matters most for
251
+ documents small enough to be read in full.
252
+
253
+ ## License
254
+
255
+ **PolyForm Noncommercial 1.0.0.** Free for personal, private, and noncommercial use —
256
+ no license purchase needed. Commercial use requires contacting Sri PriYa N. Chen
257
+ (p.chen@NeoclassicalPopArt.com) for a commercial license.