tanuki-context 0.1.1 → 0.2.1

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.
package/README.md CHANGED
@@ -1,36 +1,44 @@
1
1
  # tanuki-context
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/tanuki-context)](https://www.npmjs.com/package/tanuki-context)
4
- Zero dependencies. 0.98 MB tarball. Runs anywhere Node >= 18 runs. MIT.
4
+ Zero dependencies. 0.98 MB install. Runs anywhere Node >= 18 runs. MIT.
5
5
 
6
- A model pays about 1 token per 4 characters to read text. A PNG page is
7
- billed by pixel dimensions (Anthropic's 28x28-pixel patch grid), and a dense
8
- page fits 3+ characters per token. [pxpipe](https://github.com/teamchong/pxpipe)
9
- measured ~3.1 chars per image-token on real Claude Code traffic and built a
10
- transparent proxy on that gap. tanuki-context is the same imaging engine with
11
- a different contract: the model calls a tool when it wants the cut, sees
12
- exactly what got imaged, and nothing is rewritten behind its back.
6
+ **AI models charge you for every word they read. tanuki-context turns the
7
+ bulky parts of a conversation logs, command output, long documents — into
8
+ compact images the same model reads for a fraction of the price.**
13
9
 
14
- It takes bulky context (logs, docs, command output), optionally distills and
15
- compresses it, then renders it as dense PNG pages. You get the numbers before
16
- committing: `estimate` prices both routes and says which one wins.
10
+ That sounds like a trick, but it is just how the pricing works. Text costs
11
+ about 1 token per 4 characters. An image costs a fixed amount set by its
12
+ size in pixels, no matter how much text is drawn inside it. So if you pack
13
+ 28,000 characters of text into one dense 1568x728 image, the model reads it
14
+ for 1,456 tokens instead of about 7,000. [pxpipe](https://github.com/teamchong/pxpipe)
15
+ discovered how far that gap stretches on real traffic (~3.1 characters per
16
+ image token, against ~1 for text) and built a proxy on it. tanuki-context is
17
+ the same imaging engine, packaged so the model itself decides when to use it
18
+ — plus a relay mode for when you can't change anything.
17
19
 
18
- ## Measured
20
+ This is a real page, straight out of the pipeline — 200 KB of system log,
21
+ cleaned up and drawn. A vision model reads it directly:
19
22
 
20
- A 200 KB slice of this machine's system journal: 1,370 lines from tailscaled,
21
- NetworkManager, the kernel, wpa_supplicant, and friends. Not a synthetic
22
- fixture, and not cherry-picked to repeat itself (a plain `journalctl` tail is
23
- so repetitive that distill flattens it to 5 lines, which looks fake even
24
- though it isn't).
23
+ ![a rendered page: dense 5x8-pixel text, 312 columns of system log](https://raw.githubusercontent.com/Osyna/tanuki-context/main/docs/example-page.png)
25
24
 
26
- | how it enters context | tokens | saved |
27
- | -------------------------------- | -----: | --------: |
28
- | pasted as raw text | 51,200 | 0 |
29
- | rendered as pages (level 0) | 10,752 | **-79%** |
30
- | distilled first, then rendered | 5,320 | **-90%** |
31
- | distilled + codebook + tiny font | 2,352 | **-95%** |
25
+ *Four pages like this replace 51,200 tokens of raw text with 5,320 —
26
+ a 90% cut. Zoom in: it's all still there, errors verbatim.*
32
27
 
33
- Reproduce on your own log; every row is one command:
28
+ ## What you save
29
+
30
+ Measured on that same 200 KB slice of a real system journal (1,370 lines
31
+ from tailscaled, NetworkManager, the kernel, and friends — mixed on purpose;
32
+ a plain `journalctl` tail is so repetitive the result looks fake):
33
+
34
+ | how the log enters the conversation | tokens | saved |
35
+ | ----------------------------------- | -----: | --------: |
36
+ | pasted as raw text | 51,200 | 0 |
37
+ | drawn as image pages | 10,752 | **-79%** |
38
+ | noise removed first, then drawn | 5,320 | **-90%** |
39
+ | plus codebook and tiny font | 2,352 | **-95%** |
40
+
41
+ Every row is one command on your own file:
34
42
 
35
43
  ```
36
44
  npx tanuki-context estimate your.log 0
@@ -38,74 +46,84 @@ npx tanuki-context estimate your.log 0 --distill
38
46
  npx tanuki-context estimate your.log 0 --distill --codebook --font tiny
39
47
  ```
40
48
 
41
- Savings depend on the content. Repetitive logs crush well. Dense unique text
42
- still gets the flat ~3-4x from imaging alone. Small inputs technically win
43
- too, but by so little it is rarely worth the modality switch; see
44
- [Limits](#limits-plainly).
49
+ `estimate` is instant, never renders anything, and tells you honestly when
50
+ plain text would be cheaper.
45
51
 
46
- ## Quick start
52
+ ## Try it in 30 seconds
53
+
54
+ Claude Code:
47
55
 
48
56
  ```
49
- npx tanuki-context
57
+ claude mcp add tanuki-context -- npx -y tanuki-context
50
58
  ```
51
59
 
52
- That is the MCP stdio server. Register it in any client:
60
+ Any other MCP client:
53
61
 
54
62
  ```json
55
63
  { "mcpServers": { "tanuki-context": { "command": "npx", "args": ["-y", "tanuki-context"] } } }
56
64
  ```
57
65
 
58
- Or poke at the CLI first. Real output, same 200 KB journal slice:
66
+ No AI client at all, just curious what it would save you:
59
67
 
60
68
  ```
61
- npx tanuki-context estimate journal.log 2 --distill --codebook
62
- # { "imageTokens": 3920, "rawTextTokens": 51200, "pages": 3, "verdict": "PIPELINE cheaper", ... }
63
- npx tanuki-context render journal.log 2 ./pages --codebook
64
- # ./pages/page0.png ...
69
+ npx tanuki-context estimate some-big-file.log 0
65
70
  ```
66
71
 
67
- ## Clients
72
+ ## The two modes
68
73
 
69
- **OMP (oh-my-pi)** - `~/.omp/agent/mcp.json`, or `.omp/mcp.json` in the project:
74
+ **Explicit (MCP tools) the default and the recommendation.** Your AI gets
75
+ five tools. It calls `tanuki_estimate` on bulky text, reads the verdict, and
76
+ renders only when the pipeline wins. The model stays in charge and can see
77
+ exactly what happened to every byte. Since 0.2.0 the estimate answer includes
78
+ a `recommend` field — the cheapest safe settings, already priced — so one
79
+ call replaces a whole round of trial and error:
70
80
 
71
- ```json
72
- { "mcpServers": { "tanuki-context": { "command": "npx", "args": ["-y", "tanuki-context"] } } }
73
81
  ```
74
-
75
- **jcode** - `~/.jcode/mcp.json`, or `.jcode/mcp.json` in the project. jcode
76
- speaks stdio only; `"shared": true` reuses one server across sessions:
77
-
78
- ```json
79
- { "mcpServers": { "tanuki-context": { "command": "npx", "args": ["-y", "tanuki-context"], "shared": true } } }
82
+ npx tanuki-context estimate journal.log 0
83
+ # { "imageTokens": 10752, "verdict": "PIPELINE cheaper",
84
+ # "recommend": { "distill": true, "codebook": true, "imageTokens": 3920,
85
+ # "pages": 3, "tinyImageTokens": 2352 }, ... }
80
86
  ```
81
87
 
82
- **pi** - pi has no MCP layer, so this package doubles as a pi extension (the
83
- `"pi"` manifest field in package.json):
88
+ **Implicit (proxy) for clients you can't modify.** A small local relay.
89
+ Point your client's Anthropic URL at it and oversized blocks get swapped for
90
+ image pages on the way out, under strict rules (listed below — the short
91
+ version is: it never touches your prompt, your tools, or your latest
92
+ message, and it never rewrites anything unless the numbers clearly win):
84
93
 
85
94
  ```
86
- pi install npm:tanuki-context
95
+ npx tanuki-context proxy # listens on 127.0.0.1:8484
96
+ export ANTHROPIC_BASE_URL=http://127.0.0.1:8484
87
97
  ```
88
98
 
89
- The five `tanuki_*` tools register natively; the extension spawns this
90
- package's own stdio server per session.
99
+ Both modes run the same engine and log their savings to the same file, which
100
+ `tanuki_stats` summarizes.
91
101
 
92
- **Claude Code**:
102
+ ## What people actually use it for
93
103
 
94
- ```
95
- claude mcp add tanuki-context -- npx -y tanuki-context
96
- ```
104
+ - **Pasting a build log into a chat.** 200 KB of CI output becomes a few
105
+ pages; the error lines survive verbatim, the 4,000 repeats don't.
106
+ - **Agents that read big files.** An agent reviewing code calls `estimate`
107
+ first, renders when it wins, and keeps thousands of tokens of headroom.
108
+ - **Long sessions near the context limit.** Old bulky turns can be imaged by
109
+ the proxy while recent ones stay text.
110
+ - **Fleets of agents.** One in-process server shared by a whole team via the
111
+ Agent SDK glue (below), with a canned instruction block that teaches every
112
+ agent the estimate-first habit.
97
113
 
98
- **Rust instead of Node?** Same pipeline, same numbers, one 5.7 MB static
99
- binary:
114
+ ## The five tools
100
115
 
101
- ```
102
- cargo install --git https://github.com/Osyna/tanuki-context --branch rust
103
- ```
116
+ | tool | what it does | when to reach for it |
117
+ | ----------------- | ----------------------------------------------------------- | ---------------------------------------- |
118
+ | `tanuki_estimate` | prices both routes, returns `recommend` | always first; it's free and instant |
119
+ | `tanuki_render` | the full pipeline, returns PNG pages + a breakdown | when estimate says the pipeline wins |
120
+ | `tanuki_distill` | de-noises a log, output stays text | when you want greppable text, not images |
121
+ | `tanuki_compress` | text-only compression, five levels | prose you'll paraphrase anyway |
122
+ | `tanuki_stats` | totals from the session's savings log | end-of-session accounting |
104
123
 
105
- Then `"command": "tanuki-context", "args": []` in any snippet above, or
106
- `TANUKI_BIN=~/.cargo/bin/tanuki-context` for the pi extension. The two
107
- engines are parity-tested against each other on every change; see
108
- [Two engines](#two-engines) below.
124
+ ---
125
+
126
+ Everything below is for readers who want the machinery.
109
127
 
110
128
  ## How it works
111
129
 
@@ -119,121 +137,182 @@ flowchart LR
119
137
  E --> F["PNG pages<br/>tokens = 28-px patches"]
120
138
  ```
121
139
 
122
- Three stages. The first two are optional.
123
-
124
- **Stage 0, distill.** Built for logs. Repeated lines and repeated multi-line
125
- blocks collapse to one exemplar plus an exact `xN` count. Near-duplicates
126
- that differ only in timestamps, ids, or numbers fold into a template, also
127
- counted. Error, warning, and fail lines are never touched. A `query` regex
128
- returns only the slice you care about, with context. On the journal slice
129
- above: 1,371 lines to 621, half the characters gone, all 318 error/warn
130
- lines kept verbatim.
131
-
132
- **Stage 1, the ladder.** Five text-compression levels, 0 (off) to 4 (gist
133
- only). From level 2 up an exact-recall guard keeps code, identifiers, hashes,
134
- paths, and indented lines verbatim, so loss is confined to prose. Honest
135
- consequence: on technical files there is little prose to cut. This repo's
136
- README loses 10% of its characters at level 2 and 14% at level 4; DESIGN.md
137
- loses 6% at level 4. The ladder earns its keep on wordy prose (meeting notes,
138
- chat transcripts, tickets); on code-heavy content it mostly just guards.
139
- Level 1 (whitespace cleanup) is lossless and always safe.
140
-
141
- **Stage 2, imaging.** The renderer packs text into 312-column pages of 5x8
142
- antialiased cells (1568x728 px max; short pages get width-trimmed) and
143
- encodes grayscale PNGs. This is where the big cut comes from, and it is flat:
144
- raw text pays ~1 token per 4 chars, a full page carries ~28,000 chars for
145
- 1,456 image tokens. Full Unicode, 92,812 codepoints: CJK, Cyrillic, emoji,
146
- astral planes. Unassigned codepoints render as readable `[U+HEX]` escapes.
147
- Nothing disappears silently.
148
-
149
- ## Density knobs
150
-
151
- Measured by `reference/methods-report.mjs` on three corpora; regenerate the
152
- table with `bun reference/methods-report.mjs`. Percentages are against the
153
- baseline renderer on the same content.
154
-
155
- | knob | what it does | code | prose | log |
156
- | ------------------- | ----------------------------------------------------------------------------- | ---: | ----: | ---: |
157
- | `pack` (default on) | single-cell tabs, `⇥N` indent runs, width-trimmed pages; byte-exact | -5% | 0% | 0% |
158
- | `codebook` | repeated tokens and path prefixes become 1-cell sigils plus a `·legend·` line | -9% | 0% | -38% |
159
- | `font: "tiny"` | glyphs box-filtered into 4x6 cells; opt-in, see limits below | -36% | -38% | -40% |
160
- | all three stacked | | **-45%** | **-38%** | **-62%** |
161
-
162
- The three corpora: `src/main.ts` (code), `DESIGN.md` (prose), a path-heavy
163
- synthetic log. Knobs compose but do not add up linearly; `codebook` needs
164
- repetition to bite, `pack` needs indentation.
165
-
166
- ## Tools (MCP, stdio)
167
-
168
- | tool | arguments | returns |
169
- | ----------------- | --------------------------------------------------------- | ------------------------------------------------ |
170
- | `tanuki_render` | `text, level?, distill?, query?, pack?, font?, codebook?` | PNG page blocks + savings breakdown |
171
- | `tanuki_estimate` | same as render | exact page geometry and token math, numbers only |
172
- | `tanuki_distill` | `text, query?` | stage 0 alone; output stays greppable text |
173
- | `tanuki_compress` | `text, level` | stage 1 alone |
174
- | `tanuki_stats` | none | savings summary from the session event log |
175
-
176
- `estimate` never touches pixel data. Call it on everything; render only when
177
- the verdict says the pipeline wins.
140
+ **Stage 0 distill.** Built for logs. Repeated lines and repeated
141
+ multi-line blocks collapse to one example plus an exact count.
142
+ Near-duplicates that differ only in timestamps, ids, or numbers fold into a
143
+ template, also counted. Errors and warnings are never touched. Here is the
144
+ whole idea in eleven lines real output:
145
+
146
+ ```
147
+ input (11 lines): distilled (deterministic):
148
+
149
+ 12:00:01 INFO sync started 12:00:01 INFO sync started
150
+ 12:00:02 INFO uploaded photo_001.jpg ok 12:00:02 INFO uploaded photo_001.jpg ok
151
+ 12:00:02 INFO uploaded photo_002.jpg ok 12:00:02 INFO uploaded photo_002.jpg ok
152
+ 12:00:03 INFO uploaded photo_003.jpg ok 12:00:03 INFO uploaded photo_003.jpg ok
153
+ 12:00:03 INFO uploaded photo_004.jpg ok 12:00:04 ERROR upload failed photo_006.jpg: connection reset
154
+ 12:00:04 INFO uploaded photo_005.jpg ok 12:00:06 INFO sync finished: 8 ok, 1 failed
155
+ 12:00:04 ERROR upload failed photo_006.jpg: connection reset
156
+ 12:00:05 INFO uploaded photo_007.jpg ok ── 5 repeated lines suppressed (0 exact ×N,
157
+ 12:00:05 INFO uploaded photo_008.jpg ok 5 same-template; first occurrences kept above) ──
158
+ 12:00:06 INFO uploaded photo_009.jpg ok ×8 (template) 12:00:02 INFO uploaded photo_001.jpg ok
159
+ 12:00:06 INFO sync finished: 8 ok, 1 failed
160
+ ```
161
+
162
+ The ERROR line survived untouched, the count is exact, and the same input
163
+ always produces the same output. On the 200 KB journal: 1,371 lines to 621,
164
+ half the characters gone, all 318 error/warn lines kept verbatim.
165
+
166
+ **Stage 1 — the ladder.** Five text-compression levels, 0 (off) to 4 (gist
167
+ only). From level 2 up, an exact-recall guard keeps code, identifiers,
168
+ hashes, paths, and indented lines verbatim, so loss is confined to prose.
169
+ Honest consequence: technical files barely shrink, because the guard
170
+ protects most of them. This repo's README loses 10% of its characters at
171
+ level 2 and 14% at level 4. The ladder earns its keep on wordy prose;
172
+ on code it mostly just guards. Level 1 (whitespace cleanup) is lossless.
173
+
174
+ **Stage 2 imaging.** Text is packed into 312-column pages of 5x8
175
+ antialiased glyphs (1568x728 px max; short pages are width-trimmed) and
176
+ encoded as grayscale PNGs. Anthropic bills images by 28x28-pixel patches:
177
+ a full page is 56x26 = 1,456 tokens carrying up to 28,080 characters
178
+ about 19 characters per token, where text gets 4. Full Unicode: 92,812
179
+ codepoints, CJK to emoji. Unassigned codepoints become readable `[U+HEX]`
180
+ escapes. Nothing disappears silently.
181
+
182
+ ## The techniques, named
183
+
184
+ | technique | what it does | why it saves |
185
+ | --- | --- | --- |
186
+ | pixel pricing | pages billed by 28-px patch grid, not content | the core ~4x gap text never gets |
187
+ | distill | dedupe with exact counts, errors verbatim | noisy logs are mostly repetition |
188
+ | exact-recall guard | levels 2-4 can't touch code/ids/paths | loss stays confined to prose |
189
+ | pack | single-cell tabs, `⇥N` indent runs, width-trimmed pages | -5% on code, byte-exact |
190
+ | codebook | repeated tokens/paths become 1-cell sigils + a `·legend·` line | -38% on path-heavy logs, reversible |
191
+ | tiny font | glyphs box-filtered to 4x6 cells | -36 to -40% more, opt-in (lossy read-back) |
192
+ | `recommend` | the estimate call walks all safe knob combos server-side | saves ~590 tokens of tool-call probing |
193
+ | proxy dedupe | byte-identical repeats become a one-line pointer | ~1,400 tokens per repeated 30 KB block |
194
+ | append-stable pages | appending text never changes earlier pages | prompt caching keeps pricing them at cache rates |
195
+
196
+ ## Benchmarks
197
+
198
+ Density knobs, measured by `reference/methods-report.mjs` (regenerate it
199
+ yourself with `bun reference/methods-report.mjs`; percentages vs the
200
+ baseline renderer on the same content):
201
+
202
+ | knob | code | prose | log |
203
+ | ------------------- | ---: | ----: | ---: |
204
+ | `pack` (default on) | -5% | 0% | 0% |
205
+ | `codebook` | -9% | 0% | -38% |
206
+ | `font: "tiny"` | -36% | -38% | -40% |
207
+ | all three stacked | **-45%** | **-38%** | **-62%** |
208
+
209
+ Corpora: `src/main.ts` (code), `DESIGN.md` (prose), a path-heavy synthetic
210
+ log. Knobs need something to bite on: codebook needs repetition, pack needs
211
+ indentation.
212
+
213
+ Footprint, measured 2026-07-26 on a Ryzen 7 9700X (median of 5 cold starts;
214
+ the reference server is the original node MCP wrapping pxpipe's library):
215
+
216
+ | metric | node | bun | rust | node ref |
217
+ | ----------------------- | ------- | ------- | ------- | -------- |
218
+ | spawn to first response | 35 ms | 27 ms | 0.4 ms | 158 ms |
219
+ | idle server RSS | 87 MB | 50 MB | 3.8 MB | 177 MB |
220
+ | distill a 12 MB log | 0.42 s | 0.31 s | 0.28 s | - |
221
+ | install | 0.98 MB tarball, zero deps | same | 5.7 MB static binary | node_modules tree |
222
+
223
+ Correctness: 39 TypeScript tests, 23 Rust tests, and a 92-check parity
224
+ harness that holds the two engines to byte-identical JSON and
225
+ pixel-identical PNGs on every knob combination, including distilled
226
+ renders and a full MCP session with error paths.
178
227
 
179
228
  ## Limits, plainly
180
229
 
181
230
  - **Rendering is exact; reading is a model skill.** Pages are pixel-faithful
182
- to pxpipe's production renderer, and pxpipe has receipts on how well models
183
- read them: near-perfect on arithmetic, gist, and state, but 13/15 on exact
184
- 12-char hex strings for claude-fable-5, and misses are silent. Their
185
- numbers: [pxpipe benchmarks](https://github.com/teamchong/pxpipe#benchmark-results-and-receipts).
231
+ to pxpipe's production renderer, and pxpipe has receipts on how well
232
+ models read them: near-perfect on arithmetic, gist, and state, but 13/15
233
+ on exact 12-char hex strings for claude-fable-5 and misses are silent.
234
+ Their numbers: [pxpipe benchmarks](https://github.com/teamchong/pxpipe#benchmark-results-and-receipts).
186
235
  Keep byte-exact-critical values (secrets, hashes you must retype) in text.
187
236
  - **`font: "tiny"` trades legibility for tokens.** We measured 99.7%
188
- character read-back, and `M`/`H` is the confusable pair. Fine for logs and
189
- prose; skip it when the model must transcribe identifiers exactly.
190
- - **Levels 2-4 reword prose.** The guard keeps code and identifiers verbatim,
191
- but prose comes back paraphrase-shaped. Level 4 is gist only. Do not use it
192
- for anything you may need to quote.
237
+ character read-back; `M`/`H` is the confusable pair. Fine for logs, skip
238
+ it when the model must transcribe identifiers exactly.
239
+ - **Levels 2-4 reword prose.** Level 4 is gist only. Never use it for
240
+ anything you may need to quote.
193
241
  - **Tiny wins are not worth taking.** Width-trimming makes even small pages
194
242
  cheap on paper (12 short lines: 56 image tokens vs 115 as text), but a
195
- 50-token win buys you an image the model has to read back instead of text
196
- it can quote. The proxy gate defaults to at least 25% and 300 tokens saved
197
- for exactly this reason; in tool mode, just leave small things alone.
198
-
199
- ## Implicit mode (proxy)
243
+ 50-token win buys you an image instead of quotable text. The proxy gate
244
+ requires at least 25% and 300 tokens saved for exactly this reason.
200
245
 
201
- If you cannot touch the client, tanuki also runs as a local middlebox, the
202
- way pxpipe deploys. We left the proxy model early on because rewriting the
203
- system prompt into a user turn reads exactly like a prompt injection (an
204
- agent once flagged it as an attack, and it was right to). The middlebox
205
- came back with rules that remove that exact failure:
246
+ ## Proxy mode, the fine print
206
247
 
207
- ```
208
- npx tanuki-context proxy # listens on 127.0.0.1:8484
209
- export ANTHROPIC_BASE_URL=http://127.0.0.1:8484
210
- ```
248
+ We left the proxy model early in this project because rewriting the system
249
+ prompt into a user turn reads exactly like a prompt injection — an agent
250
+ once flagged it as an attack, and it was right to. The proxy came back only
251
+ with rules that remove that failure:
211
252
 
212
- Oversized text blocks in user messages and tool results are replaced in
213
- place by a short visible marker plus PNG blocks, only when `estimate` says
214
- imaging wins by a clear margin (default: at least 25% and 300 tokens). What
215
- it never does:
253
+ - the system prompt and tool definitions are never touched
254
+ - nothing moves between roles or positions; a block is replaced in place by
255
+ a visible `[tanuki-context: ...]` marker plus its pages
256
+ - the latest message is never imaged (you may need to quote it)
257
+ - blocks carrying `cache_control` are never touched
258
+ - anything that doesn't clearly win forwards byte-for-byte
216
259
 
217
- - touch the system prompt or tool definitions
218
- - move content between roles or positions
219
- - image the latest message (you may need to quote it)
220
- - rewrite blocks carrying `cache_control` (that would break their cache)
221
- - rewrite anything when text is cheaper; those requests forward byte-for-byte
260
+ One reuse rule on top: when the same oversized block appears twice in one
261
+ request (agents re-read files constantly), the second byte-identical copy
262
+ becomes a one-line pointer to the pages above. Exact repeats only.
222
263
 
223
- Responses stream through untouched. Savings land in `~/.pxpipe/events.jsonl`
224
- (the same file `tanuki_stats` reads), with the baseline named: what Anthropic
225
- billed, plus what the imaged blocks would have cost as text.
264
+ Responses stream through untouched. Savings land in
265
+ `~/.pxpipe/events.jsonl`, with the baseline named: what was billed plus
266
+ what the imaged blocks would have cost as text.
226
267
 
227
268
  Knobs: `--port N` `--upstream URL` `--level 0-4` `--distill` `--codebook`
228
269
  `--font tiny` `--min-chars N` `--ratio X` `--min-save N` `--max-pages N`.
229
270
  Defaults are conservative: level 0, nothing lossy on.
230
271
 
272
+ ## Clients
273
+
274
+ **OMP (oh-my-pi)** — `~/.omp/agent/mcp.json`, or `.omp/mcp.json` in the project:
275
+
276
+ ```json
277
+ { "mcpServers": { "tanuki-context": { "command": "npx", "args": ["-y", "tanuki-context"] } } }
278
+ ```
279
+
280
+ **jcode** — `~/.jcode/mcp.json`, or `.jcode/mcp.json` in the project. jcode
281
+ speaks stdio only; `"shared": true` reuses one server across sessions:
282
+
283
+ ```json
284
+ { "mcpServers": { "tanuki-context": { "command": "npx", "args": ["-y", "tanuki-context"], "shared": true } } }
285
+ ```
286
+
287
+ **pi** — pi has no MCP layer, so this package doubles as a pi extension (the
288
+ `"pi"` manifest field in package.json):
289
+
290
+ ```
291
+ pi install npm:tanuki-context
292
+ ```
293
+
294
+ **Claude Code**:
295
+
296
+ ```
297
+ claude mcp add tanuki-context -- npx -y tanuki-context
298
+ ```
299
+
300
+ **Rust instead of Node?** Same pipeline, same numbers, one 5.7 MB static
301
+ binary:
302
+
303
+ ```
304
+ cargo install --git https://github.com/Osyna/tanuki-context --branch rust
305
+ ```
306
+
307
+ Then `"command": "tanuki-context", "args": []` in any snippet above, or
308
+ `TANUKI_BIN=~/.cargo/bin/tanuki-context` for the pi extension.
309
+
231
310
  ## Claude Agent SDK
232
311
 
233
312
  `tanuki-context/agent` wires the pipeline into agents built on the
234
313
  [Claude Agent SDK](https://github.com/anthropics/claude-agent-sdk-typescript).
235
314
 
236
- External (subprocess per session, zero extra dependencies):
315
+ One agent (subprocess per session, zero extra dependencies):
237
316
 
238
317
  ```ts
239
318
  import { query } from "@anthropic-ai/claude-agent-sdk";
@@ -244,7 +323,7 @@ for await (const msg of query({ prompt: task, options: withTanuki({ model: "clau
244
323
  }
245
324
  ```
246
325
 
247
- In-process (one server instance shared by every agent in the process):
326
+ A team of agents (one in-process server shared by all of them):
248
327
 
249
328
  ```ts
250
329
  import { tanukiSdkServer, tanukiAllowedTools, TANUKI_INSTRUCTIONS } from "tanuki-context/agent";
@@ -258,11 +337,11 @@ const options = {
258
337
  // hand the same `options` to every agent in the team
259
338
  ```
260
339
 
261
- `withTanuki(options?)` merges into existing options without clobbering other
262
- servers or tools. `TANUKI_INSTRUCTIONS` is a canned prompt block that teaches
263
- agents the estimate-first workflow and the page decode grammar. The core
264
- package stays zero-dependency: the SDK and zod are optional peers, touched
265
- only inside `tanukiSdkServer()`.
340
+ `withTanuki(options?)` merges into your existing options without clobbering
341
+ other servers or tools. `TANUKI_INSTRUCTIONS` teaches agents the
342
+ estimate-first workflow, the `recommend` shortcut, and the page decode
343
+ grammar. The core package stays zero-dependency: the SDK and zod are
344
+ optional peers, touched only inside `tanukiSdkServer()`.
266
345
 
267
346
  Python SDK, plain dict config:
268
347
 
@@ -276,40 +355,30 @@ options = ClaudeAgentOptions(
276
355
  )
277
356
  ```
278
357
 
279
- ## CLI
358
+ ## CLI reference
280
359
 
281
360
  ```
282
361
  npx tanuki-context # MCP stdio server (default)
283
- npx tanuki-context proxy [--port 8484] [--upstream URL] [knobs] # implicit middlebox
362
+ npx tanuki-context proxy [--port 8484] [--upstream URL] [knobs] # implicit relay
284
363
  npx tanuki-context distill <file> [query] # stats JSON to stdout
285
364
  npx tanuki-context estimate <file> [level] [--distill] [--no-pack] [--font tiny] [--codebook]
286
- npx tanuki-context render <file> [level] [outdir] [--no-pack] [--font tiny] [--codebook]
365
+ npx tanuki-context render <file> [level] [outdir] [--distill] [--no-pack] [--font tiny] [--codebook]
287
366
  npx tanuki-context bench <file> <distill|pipeline> [level] [runs] # in-process timing
288
367
  ```
289
368
 
290
- ## Footprint
291
-
292
- Measured 2026-07-26 on a Ryzen 7 9700X, Linux. Spawn time is the median of 5
293
- cold starts to the first MCP response; the reference server is the original
294
- node MCP that wraps pxpipe's library (kept in `reference/node-mcp/`).
295
-
296
- | metric | node | bun | rust | node ref (pxpipe lib) |
297
- | ----------------------- | ------- | ------- | ------- | --------------------- |
298
- | spawn to first response | 35 ms | 27 ms | 0.4 ms | 158 ms |
299
- | idle server RSS | 87 MB | 50 MB | 3.8 MB | 177 MB |
300
- | distill a 12 MB log | 0.42 s | 0.31 s | 0.28 s | - |
301
- | install | 0.98 MB tarball, zero deps | same | 5.7 MB static binary | node_modules tree |
369
+ The example page above is one command:
370
+ `npx tanuki-context render your.log 0 ./pages --distill`.
302
371
 
303
372
  ## Two engines
304
373
 
305
374
  `main` is this TypeScript package. The [`rust` branch](../../tree/rust) is
306
375
  the same pipeline in Rust: same patch-grid token model, same escapes, same
307
376
  glyph atlas, same proxy rules. `reference/parity-ts.mjs` holds them to
308
- byte-identical JSON and pixel-identical PNGs on every knob combination, plus
309
- a full MCP session including error paths. When one engine changes, the
310
- other follows, or the parity harness fails loudly. The npm packaging and
311
- the Agent SDK / pi glue are TypeScript-only; everything the model sees is
312
- identical.
377
+ byte-identical JSON and pixel-identical PNGs 92 checks, including
378
+ distilled renders (a check that exists because it once caught the Rust
379
+ engine emitting its dedupe summary in nondeterministic order; fixed, tested,
380
+ and now permanently covered). The npm packaging and the Agent SDK / pi glue
381
+ are TypeScript-only; everything the model sees is identical.
313
382
 
314
383
  The imaging engine is a remake of [pxpipe](https://github.com/teamchong/pxpipe):
315
384
  page geometry and glyphs come from pxpipe's own generated atlas (Spleen 5x8
@@ -331,7 +400,7 @@ unassigned codepoints.
331
400
  | `src/ladder.ts` | stage 1: levels 0-4 with the exact-recall guard |
332
401
  | `src/codebook.ts` | repeated tokens and path prefixes to sigils plus `·legend·` (opt-in) |
333
402
  | `src/render.ts` | stage 2: reflow, pack, wrap, page split, AA blit, tiny 4x6 font |
334
- | `src/proxy.ts` | implicit mode: local Anthropic middlebox, in-place block imaging |
403
+ | `src/proxy.ts` | implicit mode: local Anthropic middlebox, in-place block imaging, dedupe |
335
404
  | `src/atlas.ts` | glyph atlas (92,812 codepoints): metadata eager, pixels inflated lazily |
336
405
  | `src/png.ts` | minimal grayscale PNG encoder (`node:zlib`, filter-0 rows) |
337
406
  | `src/stats.ts` | event log summary |
@@ -349,8 +418,8 @@ Node-compatible files:
349
418
 
350
419
  ```
351
420
  bun run build # dist/cli.js + dist/agent.js + dist/pi.js
352
- bun test # 35 tests
353
- bun run parity # TS vs rust binary, byte/pixel-exact (needs TANUKI_BIN)
421
+ bun test # 39 tests
422
+ bun run parity # TS vs rust binary, 92 checks (needs TANUKI_BIN)
354
423
  ```
355
424
 
356
425
  Regenerating glyphs after a pxpipe atlas rebuild (needs a pxpipe checkout
@@ -359,3 +428,20 @@ with `dist/` built; the generator fetches `unifont_upper` on first run):
359
428
  ```
360
429
  PXPIPE_DIST=~/Projects/pxpipe/dist node tools/gen-glyphs.mjs
361
430
  ```
431
+
432
+ ## Credits
433
+
434
+ - [pxpipe](https://github.com/teamchong/pxpipe) is where the idea and the
435
+ engine come from: an image is billed by its pixels, not by how much text
436
+ is inside it, and dense text survives the trip. tanuki-context began as a
437
+ Rust rewrite of their MCP. The page geometry and glyphs are extracted
438
+ from pxpipe's own generated atlas, the default render path is still
439
+ byte-identical to their production renderer, and their
440
+ [benchmarks](https://github.com/teamchong/pxpipe#benchmark-results-and-receipts)
441
+ are the read-back evidence this README leans on. If you want the
442
+ whole-bill transparent proxy with per-model profiles and eval receipts,
443
+ use pxpipe itself; the two compose fine.
444
+ - The bitmap fonts inside the atlas are
445
+ [Spleen](https://github.com/fcambus/spleen) 5x8 by Frederic Cambus and
446
+ [GNU Unifont](https://unifoundry.com/unifont/) (BMP coverage plus
447
+ `unifont_upper` for the astral planes).
package/dist/agent.js CHANGED
@@ -1,19 +1,19 @@
1
- import{createRequire as $Q}from"node:module";var PJ=$Q(import.meta.url);import{existsSync as QZ}from"node:fs";import{fileURLToPath as ZZ}from"node:url";function VQ(J){if(J===32||J>=9&&J<=13)return!0;if(J<133)return!1;return J===133||J===160||J===5760||J>=8192&&J<=8202||J===8232||J===8233||J===8239||J===8287||J===12288}function $J(J){let Q=0;for(let Z=0;Z<J.length;Z++){let $=J.charCodeAt(Z);if($>=55296&&$<=56319&&Z+1<J.length){let V=J.charCodeAt(Z+1);if(V>=56320&&V<=57343)Z++}Q++}return Q}function GQ(J,Q){let Z=0,$=0;while(Z<J.length&&$<Q.length){let V=J.codePointAt(Z),G=Q.codePointAt($);if(V!==G)return V-G;Z+=V>65535?2:1,$+=G>65535?2:1}return J.length-Z-(Q.length-$)}function VJ(J){let Q=new Map,Z=(B)=>{Q.set(B,(Q.get(B)??0)+1)},$=(B)=>{if($J(B)>=12)Z(B);if(B.includes("/")){let F=B.split("/"),O="";for(let D=0;D<F.length;D++){if(D>0)O+="/";if(O+=F[D],D>=2){let I=O+"/";if($J(I)>=12)Z(I)}}}},V=-1;for(let B=0;B<J.length;){let F=J.codePointAt(B),O=F>65535?2:1;if(VQ(F)){if(V>=0)$(J.slice(V,B)),V=-1}else if(V<0)V=B;B+=O}if(V>=0)$(J.slice(V));let G=[];for(let[B,F]of Q)if(F>=3){let O=$J(B);G.push({k:B,c:F,len:O,saved:(O-1)*F})}G.sort((B,F)=>F.saved-B.saved||GQ(B.k,F.k));let Y=[];for(let B of"§¤¢£¥µ¶ª°±¬×÷ØÞßæðøþ¡¿")if(!J.includes(B))Y.push(B);let H=[],z=[];for(let{k:B,c:F,len:O}of G){if(H.length>=Y.length)break;if((O-1)*F<=O+3)continue;let D=!1;for(let R=0;R<z.length;R++){let T=z[R];if(T.startsWith(B)||B.startsWith(T)){D=!0;break}}if(D)continue;let I=Y[H.length];z.push(B),H.push({sig:I,val:B,len:O})}if(H.length===0)return{text:J,entries:0};let K=H.map((B,F)=>F);K.sort((B,F)=>H[F].len-H[B].len);let q=J;for(let B=0;B<K.length;B++){let{sig:F,val:O}=H[K[B]];q=q.replaceAll(O,F)}let X=`
2
- ·legend· `;for(let B=0;B<H.length;B++)X+=H[B].sig+"="+H[B].val+" ";return q+=X.slice(0,-1),{text:q,entries:H.length}}import{Buffer as AJ}from"node:buffer";var YQ=/\x1b\[[0-9;]*[A-Za-z]/g,GJ=/\b([0-9A-Za-z_]*(error|exception)s?|err|warn(ing)?s?|fail(s|ed|ure|ures)?|panic(s|ked)?|fatal|critical|traceback|denied|refused|timeouts?|timed.?out|assert(s|ed|ion|ions)?|segfault(s|ed)?)\b/i,HQ=/[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9]{2}:[0-9]{2}:[0-9]{2}([.,][0-9]+)?(Z|[+-][0-9]{2}:?[0-9]{2})?/g,zQ=/\b[0-9]{2}:[0-9]{2}:[0-9]{2}([.,][0-9]+)?\b/g,KQ=/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi,XQ=/\b[0-9a-f]{7,64}\b/gi,BQ=/\b[0-9]+(\.[0-9]+)?[ \t\u00a0]?(ms|us|µs|ns|s|m|h|%|[KMGT]i?B)?\b/gi,qQ=/[0-9a-f]/i,UQ=3,OQ=8,YJ=2,_J=40;function FQ(J){if(!qQ.test(J))return J;let Q=J.replace(HQ,"<ts>");return Q=Q.replace(zQ,"<time>"),Q=Q.replace(KQ,"<uuid>"),Q=Q.replace(XQ,"<hex>"),Q.replace(BQ,"<n>")}function h(J){if(J===32)return!0;if(J<9)return!1;if(J<=13)return!0;if(J<133)return!1;return J===133||J===160||J===5760||J>=8192&&J<=8202||J===8232||J===8233||J===8239||J===8287||J===12288}function EJ(J){let Q=0,Z=J.length;while(Q<Z&&h(J.charCodeAt(Q)))Q++;while(Z>Q&&h(J.charCodeAt(Z-1)))Z--;return Q===0&&Z===J.length?J:J.slice(Q,Z)}function DQ(J,Q){let Z=0,$=J.length;while(Z<$&&h(J.charCodeAt(Z)))Z++;while($>Z&&h(J.charCodeAt($-1)))$--;let V=0;for(let G=Z;G<$&&V<Q;V++){let Y=J.charCodeAt(G);G+=Y>=55296&&Y<=56319&&G+1<$?2:1}return V<Q}function MQ(J){let Q=0,Z=J.length;while(Q<Z&&h(J.charCodeAt(Q)))Q++;return J.charCodeAt(Q)===91&&J.charCodeAt(Q+1)===215}function WQ(J){let Q=[],Z=-1,$=!0,V=!1,G=!1,Y=!0;for(let H=0;H<J.length;){let z=J.codePointAt(H),K=z>65535?2:1,q=h(z);if(Y){if(Y=!1,q)Q.push("<v>")}if(q){if(Z>=0)Q.push($?J.slice(Z,H):"<v>"),Z=-1;G=!0}else{if(V=!0,G=!1,Z<0)Z=H,$=!0;if($){let X=z|32;if(X<97||X>122||z>127)$=!1}}H+=K}if(Z>=0)Q.push($?J.slice(Z):"<v>");if(V&&G)Q.push("<v>");return Q.join(" ")}function SJ(J,Q){let Z=0,$=0,V=J.length;while(Z<V&&$<Q){let G=J.charCodeAt(Z);Z+=G>=55296&&G<=56319&&Z+1<V?2:1,$++}return Z>=V?J:J.slice(0,Z)}var jQ=/[.*+?^${}()|[\]\\]/g;function c(J,Q=null,Z=2){let $=Z,G=J.replace(YQ,"").split(`
3
- `),Y=G.length,H=Y,z=Array(Y),K=Array(Y),q=0;for(let U=0;U<Y;U++){z[U]=FQ(G[U]);let M=GJ.test(G[U]);if(K[U]=M,M)q++}let X=[],B=[],F=[],O=0,D=0;while(D<Y){let U=0,M=0;for(let W=1;W<=OQ&&D+2*W<=Y;W++){if(K[D+W-1])break;let N=1;J:for(;;){let j=D+N*W;if(j+W>Y)break;for(let A=0;A<W;A++)if(K[j+A]||z[j+A]!==z[D+A])break J;N++}if(N>=UQ&&W*(N-1)>U*(M>0?M-1:0))U=W,M=N}if(U>0){for(let W=D;W<D+U;W++)X.push(G[W]),B.push(K[W]),F.push(z[W]);X.push(U===1?` [×${M} similar]`:` [×${M} similar ${U}-line blocks]`),B.push(!1),F.push(null),O++,D+=U*M}else X.push(G[D]),B.push(K[D]),F.push(z[D]),D++}let I=new Map,R=new Map,T=YJ+1,_=[],P=[],L=0,y=0;for(let U=0;U<X.length;U++){let M=X[U],W=B[U];if(W||MQ(M)||DQ(M,4)){_.push(M),P.push(W);continue}let N=F[U],j=I.get(N);if(j!==void 0){if(j.count++,j.count<=YJ)_.push(M),P.push(!1);else L++;continue}let A=WQ(N);I.set(N,{count:1,exemplar:M});let g=R.get(A);if(g!==void 0)if(g.count++,g.count<=T)_.push(M),P.push(!1);else y++;else R.set(A,{count:1,exemplar:M}),_.push(M),P.push(!1)}let b=[];for(let U of I.values())if(U.count>YJ)b.push([U.count,"exact",U.exemplar]);for(let U of R.values())if(U.count>T)b.push([U.count,"template",U.exemplar]);if(b.sort((U,M)=>M[0]-U[0]),b.length>_J)b.length=_J;if(L+y>0){let U=`── ${L+y} repeated lines suppressed (${L} exact ×N, ${y} same-template; first occurrences kept above) ──`;_.push(U),P.push(GJ.test(U));for(let[M,W,N]of b){let A=` ×${M}${W==="template"?" (template)":""} ${SJ(EJ(N),160)}`;_.push(A),P.push(GJ.test(A))}}let v;if(Q!=null){let U;try{U=new RegExp(Q,"i")}catch{U=new RegExp(Q.replace(jQ,"\\$&"),"i")}let M=_.length,W=new Uint8Array(M);for(let j=0;j<M;j++)if(P[j]||U.test(_[j])){let A=j>$?j-$:0,g=Math.min(j+$,M-1);W.fill(1,A,g+1)}v=[];let N=0;for(let j=0;j<M;j++)if(W[j]){if(N>0)v.push(`… ${N} lines omitted`),N=0;v.push(_[j])}else N++;if(N>0)v.push(`… ${N} lines omitted`)}else v=_;let NJ=v.join(`
4
- `),IJ=AJ.byteLength(J),RJ=AJ.byteLength(NJ),ZJ=(1-RJ/IJ)*100,JQ=J.length===0?0:ZJ<0?-Math.round(-ZJ):Math.round(ZJ),QQ=b.map(([U,M,W])=>({count:U,exemplar:SJ(EJ(W),160),kind:M})),ZQ={collapsedRuns:O,importantKept:q,origChars:IJ,origLines:H,outChars:RJ,outLines:v.length,query:Q??null,savedPct:JQ,suppressedLines:L,templateSuppressed:y,topRepeats:QQ};return{distilled:NJ,stats:ZQ}}var o=[["none","none","passthrough (baseline)"],["whitespace","lossless","trailing whitespace + blank-line runs collapsed; safe for code"],["prose","light","L1 + prose lines: collapse spaces, cut redundant filler phrases (code/IDs protected)"],["dense","medium","L2 + prose: drop articles & intensifiers"],["caveman","heavy","L3 + prose: telegraphic — drop function words; gist only, NOT verbatim"]];var E=(J)=>new RegExp(`(?<![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])(?:${J})(?![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])`,"giu"),HJ=[[E("in order to"),"to"],[E("due to the fact that"),"because"],[E("at this point in time"),"now"],[E("in the event that"),"if"],[E("for the purpose of"),"for"],[E("with regard to"),"about"],[E("a large number of"),"many"],[E("it is important to note that"),""],[E("please note that"),""],[E("as a matter of fact"),""],[E("in terms of"),"for"],[E("the fact that"),"that"]],NQ=new RegExp("(?<![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])(?:the|an|a)[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+","giu"),IQ=new RegExp("(?<![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])(?:very|really|just|actually|basically|simply|quite|rather|essentially|literally)[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+","giu"),RQ=new RegExp("(?<![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])(?:is|are|was|were|am|be|been|being|do|does|did|have|has|had|will|would|shall|should|can|could|may|might|of|to|in|on|at|for|with|that|this|these|those|it|its|there|here)(?![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]*","giu"),CJ=/ {2,}/g,PQ=new RegExp("[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+([.,;:!?])","gu"),AQ=/\n{3,}/g;function zJ(J){if(J===32||J>=9&&J<=13)return!0;if(J<133)return!1;return J===133||J===160||J===5760||J>=8192&&J<=8202||J===8232||J===8233||J===8239||J===8287||J===12288}function _Q(J){let Q=0,Z=J.length;while(Q<Z){let $=J.codePointAt(Q);if(!zJ($))break;Q+=$>65535?2:1}while(Z>Q){let $=J.charCodeAt(Z-1);if(!zJ($))break;Z--}return Q===0&&Z===J.length?J:J.slice(Q,Z)}function EQ(J){if(J.length===0)return!1;let Q=J.charCodeAt(0);if(Q===32||Q===9)return!0;let Z=0,$=0,V=0,G=!1;for(let Y=0;Y<J.length;){let H=J.codePointAt(Y);if(Y+=H>65535?2:1,Z++,zJ(H)){V=0;continue}if(V++,V>=24)G=!0;if(H>=48&&H<=57||H>=65&&H<=90||H>=97&&H<=122)continue;switch(H){case 46:case 44:case 59:case 58:case 39:case 34:case 33:case 63:case 40:case 41:case 45:continue}$++}if($/Z>0.3)return!0;return G}function SQ(J,Q){let Z=J.replace(CJ," ");for(let V=0;V<HJ.length;V++)Z=Z.replace(HJ[V][0],HJ[V][1]);if(Q>=3)Z=Z.replace(NQ,""),Z=Z.replace(IQ,"");if(Q>=4)Z=Z.replace(RQ,"");Z=Z.replace(CJ," "),Z=_Q(Z.replace(PQ,"$1"));let $=Z.charCodeAt(0);if($>=97&&$<=122)Z=String.fromCharCode($-32)+Z.slice(1);return Z}function i(J,Q){let Z=Math.min(Q,4);if(Z===0)return{compressed:J,protectedLines:0,level:Z};let $=0,V=J.split(`
1
+ import{createRequire as $Q}from"node:module";var AJ=$Q(import.meta.url);import{existsSync as ZZ}from"node:fs";import{fileURLToPath as $Z}from"node:url";function VQ(J){if(J===32||J>=9&&J<=13)return!0;if(J<133)return!1;return J===133||J===160||J===5760||J>=8192&&J<=8202||J===8232||J===8233||J===8239||J===8287||J===12288}function YJ(J){let Q=0;for(let Z=0;Z<J.length;Z++){let $=J.charCodeAt(Z);if($>=55296&&$<=56319&&Z+1<J.length){let V=J.charCodeAt(Z+1);if(V>=56320&&V<=57343)Z++}Q++}return Q}function GQ(J,Q){let Z=0,$=0;while(Z<J.length&&$<Q.length){let V=J.codePointAt(Z),G=Q.codePointAt($);if(V!==G)return V-G;Z+=V>65535?2:1,$+=G>65535?2:1}return J.length-Z-(Q.length-$)}function i(J){let Q=new Map,Z=(B)=>{Q.set(B,(Q.get(B)??0)+1)},$=(B)=>{if(YJ(B)>=12)Z(B);if(B.includes("/")){let D=B.split("/"),O="";for(let F=0;F<D.length;F++){if(F>0)O+="/";if(O+=D[F],F>=2){let I=O+"/";if(YJ(I)>=12)Z(I)}}}},V=-1;for(let B=0;B<J.length;){let D=J.codePointAt(B),O=D>65535?2:1;if(VQ(D)){if(V>=0)$(J.slice(V,B)),V=-1}else if(V<0)V=B;B+=O}if(V>=0)$(J.slice(V));let G=[];for(let[B,D]of Q)if(D>=3){let O=YJ(B);G.push({k:B,c:D,len:O,saved:(O-1)*D})}G.sort((B,D)=>D.saved-B.saved||GQ(B.k,D.k));let Y=[];for(let B of"§¤¢£¥µ¶ª°±¬×÷ØÞßæðøþ¡¿")if(!J.includes(B))Y.push(B);let H=[],z=[];for(let{k:B,c:D,len:O}of G){if(H.length>=Y.length)break;if((O-1)*D<=O+3)continue;let F=!1;for(let R=0;R<z.length;R++){let T=z[R];if(T.startsWith(B)||B.startsWith(T)){F=!0;break}}if(F)continue;let I=Y[H.length];z.push(B),H.push({sig:I,val:B,len:O})}if(H.length===0)return{text:J,entries:0};let K=H.map((B,D)=>D);K.sort((B,D)=>H[D].len-H[B].len);let q=J;for(let B=0;B<K.length;B++){let{sig:D,val:O}=H[K[B]];q=q.replaceAll(O,D)}let X=`
2
+ ·legend· `;for(let B=0;B<H.length;B++)X+=H[B].sig+"="+H[B].val+" ";return q+=X.slice(0,-1),{text:q,entries:H.length}}import{Buffer as _J}from"node:buffer";var YQ=/\x1b\[[0-9;]*[A-Za-z]/g,HJ=/\b([0-9A-Za-z_]*(error|exception)s?|err|warn(ing)?s?|fail(s|ed|ure|ures)?|panic(s|ked)?|fatal|critical|traceback|denied|refused|timeouts?|timed.?out|assert(s|ed|ion|ions)?|segfault(s|ed)?)\b/i,HQ=/[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9]{2}:[0-9]{2}:[0-9]{2}([.,][0-9]+)?(Z|[+-][0-9]{2}:?[0-9]{2})?/g,zQ=/\b[0-9]{2}:[0-9]{2}:[0-9]{2}([.,][0-9]+)?\b/g,KQ=/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi,XQ=/\b[0-9a-f]{7,64}\b/gi,BQ=/\b[0-9]+(\.[0-9]+)?[ \t\u00a0]?(ms|us|µs|ns|s|m|h|%|[KMGT]i?B)?\b/gi,qQ=/[0-9a-f]/i,UQ=3,OQ=8,zJ=2,EJ=40;function DQ(J){if(!qQ.test(J))return J;let Q=J.replace(HQ,"<ts>");return Q=Q.replace(zQ,"<time>"),Q=Q.replace(KQ,"<uuid>"),Q=Q.replace(XQ,"<hex>"),Q.replace(BQ,"<n>")}function h(J){if(J===32)return!0;if(J<9)return!1;if(J<=13)return!0;if(J<133)return!1;return J===133||J===160||J===5760||J>=8192&&J<=8202||J===8232||J===8233||J===8239||J===8287||J===12288}function SJ(J){let Q=0,Z=J.length;while(Q<Z&&h(J.charCodeAt(Q)))Q++;while(Z>Q&&h(J.charCodeAt(Z-1)))Z--;return Q===0&&Z===J.length?J:J.slice(Q,Z)}function FQ(J,Q){let Z=0,$=J.length;while(Z<$&&h(J.charCodeAt(Z)))Z++;while($>Z&&h(J.charCodeAt($-1)))$--;let V=0;for(let G=Z;G<$&&V<Q;V++){let Y=J.charCodeAt(G);G+=Y>=55296&&Y<=56319&&G+1<$?2:1}return V<Q}function MQ(J){let Q=0,Z=J.length;while(Q<Z&&h(J.charCodeAt(Q)))Q++;return J.charCodeAt(Q)===91&&J.charCodeAt(Q+1)===215}function WQ(J){let Q=[],Z=-1,$=!0,V=!1,G=!1,Y=!0;for(let H=0;H<J.length;){let z=J.codePointAt(H),K=z>65535?2:1,q=h(z);if(Y){if(Y=!1,q)Q.push("<v>")}if(q){if(Z>=0)Q.push($?J.slice(Z,H):"<v>"),Z=-1;G=!0}else{if(V=!0,G=!1,Z<0)Z=H,$=!0;if($){let X=z|32;if(X<97||X>122||z>127)$=!1}}H+=K}if(Z>=0)Q.push($?J.slice(Z):"<v>");if(V&&G)Q.push("<v>");return Q.join(" ")}function CJ(J,Q){let Z=0,$=0,V=J.length;while(Z<V&&$<Q){let G=J.charCodeAt(Z);Z+=G>=55296&&G<=56319&&Z+1<V?2:1,$++}return Z>=V?J:J.slice(0,Z)}var jQ=/[.*+?^${}()|[\]\\]/g;function d(J,Q=null,Z=2){let $=Z,G=J.replace(YQ,"").split(`
3
+ `),Y=G.length,H=Y,z=Array(Y),K=Array(Y),q=0;for(let U=0;U<Y;U++){z[U]=DQ(G[U]);let M=HJ.test(G[U]);if(K[U]=M,M)q++}let X=[],B=[],D=[],O=0,F=0;while(F<Y){let U=0,M=0;for(let W=1;W<=OQ&&F+2*W<=Y;W++){if(K[F+W-1])break;let N=1;J:for(;;){let j=F+N*W;if(j+W>Y)break;for(let A=0;A<W;A++)if(K[j+A]||z[j+A]!==z[F+A])break J;N++}if(N>=UQ&&W*(N-1)>U*(M>0?M-1:0))U=W,M=N}if(U>0){for(let W=F;W<F+U;W++)X.push(G[W]),B.push(K[W]),D.push(z[W]);X.push(U===1?` [×${M} similar]`:` [×${M} similar ${U}-line blocks]`),B.push(!1),D.push(null),O++,F+=U*M}else X.push(G[F]),B.push(K[F]),D.push(z[F]),F++}let I=new Map,R=new Map,T=zJ+1,_=[],P=[],L=0,y=0;for(let U=0;U<X.length;U++){let M=X[U],W=B[U];if(W||MQ(M)||FQ(M,4)){_.push(M),P.push(W);continue}let N=D[U],j=I.get(N);if(j!==void 0){if(j.count++,j.count<=zJ)_.push(M),P.push(!1);else L++;continue}let A=WQ(N);I.set(N,{count:1,exemplar:M});let g=R.get(A);if(g!==void 0)if(g.count++,g.count<=T)_.push(M),P.push(!1);else y++;else R.set(A,{count:1,exemplar:M}),_.push(M),P.push(!1)}let v=[];for(let U of I.values())if(U.count>zJ)v.push([U.count,"exact",U.exemplar]);for(let U of R.values())if(U.count>T)v.push([U.count,"template",U.exemplar]);if(v.sort((U,M)=>M[0]-U[0]),v.length>EJ)v.length=EJ;if(L+y>0){let U=`── ${L+y} repeated lines suppressed (${L} exact ×N, ${y} same-template; first occurrences kept above) ──`;_.push(U),P.push(HJ.test(U));for(let[M,W,N]of v){let A=` ×${M}${W==="template"?" (template)":""} ${CJ(SJ(N),160)}`;_.push(A),P.push(HJ.test(A))}}let b;if(Q!=null){let U;try{U=new RegExp(Q,"i")}catch{U=new RegExp(Q.replace(jQ,"\\$&"),"i")}let M=_.length,W=new Uint8Array(M);for(let j=0;j<M;j++)if(P[j]||U.test(_[j])){let A=j>$?j-$:0,g=Math.min(j+$,M-1);W.fill(1,A,g+1)}b=[];let N=0;for(let j=0;j<M;j++)if(W[j]){if(N>0)b.push(`… ${N} lines omitted`),N=0;b.push(_[j])}else N++;if(N>0)b.push(`… ${N} lines omitted`)}else b=_;let IJ=b.join(`
4
+ `),RJ=_J.byteLength(J),PJ=_J.byteLength(IJ),GJ=(1-PJ/RJ)*100,JQ=J.length===0?0:GJ<0?-Math.round(-GJ):Math.round(GJ),QQ=v.map(([U,M,W])=>({count:U,exemplar:CJ(SJ(W),160),kind:M})),ZQ={collapsedRuns:O,importantKept:q,origChars:RJ,origLines:H,outChars:PJ,outLines:b.length,query:Q??null,savedPct:JQ,suppressedLines:L,templateSuppressed:y,topRepeats:QQ};return{distilled:IJ,stats:ZQ}}var r=[["none","none","passthrough (baseline)"],["whitespace","lossless","trailing whitespace + blank-line runs collapsed; safe for code"],["prose","light","L1 + prose lines: collapse spaces, cut redundant filler phrases (code/IDs protected)"],["dense","medium","L2 + prose: drop articles & intensifiers"],["caveman","heavy","L3 + prose: telegraphic — drop function words; gist only, NOT verbatim"]];var E=(J)=>new RegExp(`(?<![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])(?:${J})(?![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])`,"giu"),KJ=[[E("in order to"),"to"],[E("due to the fact that"),"because"],[E("at this point in time"),"now"],[E("in the event that"),"if"],[E("for the purpose of"),"for"],[E("with regard to"),"about"],[E("a large number of"),"many"],[E("it is important to note that"),""],[E("please note that"),""],[E("as a matter of fact"),""],[E("in terms of"),"for"],[E("the fact that"),"that"]],NQ=new RegExp("(?<![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])(?:the|an|a)[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+","giu"),IQ=new RegExp("(?<![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])(?:very|really|just|actually|basically|simply|quite|rather|essentially|literally)[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+","giu"),RQ=new RegExp("(?<![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])(?:is|are|was|were|am|be|been|being|do|does|did|have|has|had|will|would|shall|should|can|could|may|might|of|to|in|on|at|for|with|that|this|these|those|it|its|there|here)(?![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]*","giu"),TJ=/ {2,}/g,PQ=new RegExp("[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+([.,;:!?])","gu"),AQ=/\n{3,}/g;function XJ(J){if(J===32||J>=9&&J<=13)return!0;if(J<133)return!1;return J===133||J===160||J===5760||J>=8192&&J<=8202||J===8232||J===8233||J===8239||J===8287||J===12288}function _Q(J){let Q=0,Z=J.length;while(Q<Z){let $=J.codePointAt(Q);if(!XJ($))break;Q+=$>65535?2:1}while(Z>Q){let $=J.charCodeAt(Z-1);if(!XJ($))break;Z--}return Q===0&&Z===J.length?J:J.slice(Q,Z)}function EQ(J){if(J.length===0)return!1;let Q=J.charCodeAt(0);if(Q===32||Q===9)return!0;let Z=0,$=0,V=0,G=!1;for(let Y=0;Y<J.length;){let H=J.codePointAt(Y);if(Y+=H>65535?2:1,Z++,XJ(H)){V=0;continue}if(V++,V>=24)G=!0;if(H>=48&&H<=57||H>=65&&H<=90||H>=97&&H<=122)continue;switch(H){case 46:case 44:case 59:case 58:case 39:case 34:case 33:case 63:case 40:case 41:case 45:continue}$++}if($/Z>0.3)return!0;return G}function SQ(J,Q){let Z=J.replace(TJ," ");for(let V=0;V<KJ.length;V++)Z=Z.replace(KJ[V][0],KJ[V][1]);if(Q>=3)Z=Z.replace(NQ,""),Z=Z.replace(IQ,"");if(Q>=4)Z=Z.replace(RQ,"");Z=Z.replace(TJ," "),Z=_Q(Z.replace(PQ,"$1"));let $=Z.charCodeAt(0);if($>=97&&$<=122)Z=String.fromCharCode($-32)+Z.slice(1);return Z}function a(J,Q){let Z=Math.min(Q,4);if(Z===0)return{compressed:J,protectedLines:0,level:Z};let $=0,V=J.split(`
5
5
  `),G=Array(V.length);for(let H=0;H<V.length;H++){let z=V[H],K=z.length;while(K>0){let X=z.charCodeAt(K-1);if(X!==32&&X!==9)break;K--}let q=K===z.length?z:z.slice(0,K);if(Z===1){G[H]=q;continue}if(EQ(q)){$++,G[H]=q;continue}G[H]=SQ(q,Z)}return{compressed:G.join(`
6
6
  `).replace(AQ,`
7
7
 
8
- `),protectedLines:$,level:Z}}import{readFileSync as XJ}from"node:fs";import{inflateSync as CQ}from"node:zlib";//! Full-BMP glyph atlas (Spleen 5x8 for ASCII/code + Unifont fallback),
8
+ `),protectedLines:$,level:Z}}import{readFileSync as qJ}from"node:fs";import{inflateSync as CQ}from"node:zlib";//! Full-BMP glyph atlas (Spleen 5x8 for ASCII/code + Unifont fallback),
9
9
  //! extracted from pxpipe's generated gray atlas by `tools/gen-glyphs.mjs`.
10
10
  //!
11
11
  //! Codepoints + wide flags load eagerly (~175 KB, needed for wrap math);
12
12
  //! coverage pixels stay zlib-packed on disk and decompress lazily on the
13
13
  //! first blit, so `estimate` never pays for them.
14
- var w=5,d=8,x=XJ(new URL("../assets/glyphs.cps",import.meta.url)),r=XJ(new URL("../assets/glyphs.wide",import.meta.url)),TJ=(()=>{let J=x.byteLength>>>2;if((x.byteOffset&3)===0)return new Uint32Array(x.buffer,x.byteOffset,J);return new Uint32Array(x.buffer.slice(x.byteOffset,x.byteOffset+(J<<2)))})(),TQ=(()=>{let J=new Uint32Array(r.length),Q=0;for(let Z=0;Z<r.length;Z++)J[Z]=Q,Q+=(r[Z]===1?2*w:w)*d;return J})(),KJ=null;function l(J){let Q=0,Z=TJ.length-1;while(Q<=Z){let $=Q+Z>>>1,V=TJ[$];if(V<J)Q=$+1;else if(V>J)Z=$-1;else return $}return-1}function k(J){return r[J]===1}function BJ(J){if(KJ===null)KJ=CQ(XJ(new URL("../assets/glyphs.pix.z",import.meta.url)));let Q=TQ[J],Z=k(J)?2*w:w;return KJ.subarray(Q,Q+Z*d)}function LJ(J,Q,Z){let $=BJ(J),V=k(J)?2*w:w;return LQ($,V,d,Q,Z)}function LQ(J,Q,Z,$,V){let G=new Uint8Array($*V),Y=Q/$,H=Z/V;for(let z=0;z<V;z++){let K=z*H,q=(z+1)*H;for(let X=0;X<$;X++){let B=X*Y,F=(X+1)*Y,O=0,D=0,I=Math.min(Math.ceil(q),Z);for(let R=Math.floor(K);R<I;R++){let T=Math.min(R+1,q)-Math.max(R,K);if(T<=0)continue;let _=Math.min(Math.ceil(F),Q);for(let P=Math.floor(B);P<_;P++){let L=Math.min(P+1,F)-Math.max(P,B);if(L<=0)continue;let y=L*T;O+=y*J[R*Q+P],D+=y}}G[z*$+X]=D>0?Math.min(255,Math.max(0,Math.round(O/D))):0}}return G}import{deflateSync as yQ}from"node:zlib";//! Minimal grayscale PNG encoder (bit depth 8, color type 0), mirroring
14
+ var w=5,l=8,x=qJ(new URL("../assets/glyphs.cps",import.meta.url)),t=qJ(new URL("../assets/glyphs.wide",import.meta.url)),LJ=(()=>{let J=x.byteLength>>>2;if((x.byteOffset&3)===0)return new Uint32Array(x.buffer,x.byteOffset,J);return new Uint32Array(x.buffer.slice(x.byteOffset,x.byteOffset+(J<<2)))})(),TQ=(()=>{let J=new Uint32Array(t.length),Q=0;for(let Z=0;Z<t.length;Z++)J[Z]=Q,Q+=(t[Z]===1?2*w:w)*l;return J})(),BJ=null;function p(J){let Q=0,Z=LJ.length-1;while(Q<=Z){let $=Q+Z>>>1,V=LJ[$];if(V<J)Q=$+1;else if(V>J)Z=$-1;else return $}return-1}function k(J){return t[J]===1}function UJ(J){if(BJ===null)BJ=CQ(qJ(new URL("../assets/glyphs.pix.z",import.meta.url)));let Q=TQ[J],Z=k(J)?2*w:w;return BJ.subarray(Q,Q+Z*l)}function yJ(J,Q,Z){let $=UJ(J),V=k(J)?2*w:w;return LQ($,V,l,Q,Z)}function LQ(J,Q,Z,$,V){let G=new Uint8Array($*V),Y=Q/$,H=Z/V;for(let z=0;z<V;z++){let K=z*H,q=(z+1)*H;for(let X=0;X<$;X++){let B=X*Y,D=(X+1)*Y,O=0,F=0,I=Math.min(Math.ceil(q),Z);for(let R=Math.floor(K);R<I;R++){let T=Math.min(R+1,q)-Math.max(R,K);if(T<=0)continue;let _=Math.min(Math.ceil(D),Q);for(let P=Math.floor(B);P<_;P++){let L=Math.min(P+1,D)-Math.max(P,B);if(L<=0)continue;let y=L*T;O+=y*J[R*Q+P],F+=y}}G[z*$+X]=F>0?Math.min(255,Math.max(0,Math.round(O/F))):0}}return G}import{deflateSync as yQ}from"node:zlib";//! Minimal grayscale PNG encoder (bit depth 8, color type 0), mirroring
15
15
  //! pxpipe's png.ts: IHDR + one IDAT (zlib) + IEND, filter byte 0 per row.
16
- var wQ=(()=>{let J=new Uint32Array(256);for(let Q=0;Q<256;Q++){let Z=Q;for(let $=0;$<8;$++)Z=(Z&1)!==0?3988292384^Z>>>1:Z>>>1;J[Q]=Z>>>0}return J})();function qJ(J,Q,Z,$){let V=$.length;J[Q]=V>>>24&255,J[Q+1]=V>>>16&255,J[Q+2]=V>>>8&255,J[Q+3]=V&255;let G=Q+4;for(let z=0;z<4;z++)J[G+z]=Z.charCodeAt(z);J.set($,G+4);let Y=4294967295;for(let z=G;z<G+4+V;z++)Y=wQ[(Y^J[z])&255]^Y>>>8;Y=~Y>>>0;let H=G+4+V;return J[H]=Y>>>24&255,J[H+1]=Y>>>16&255,J[H+2]=Y>>>8&255,J[H+3]=Y&255,H+4}function yJ(J,Q,Z){let $=new Uint8Array(Z*(Q+1));for(let z=0,K=0,q=0;z<Z;z++)$[K++]=0,$.set(J.subarray(q,q+Q),K),K+=Q,q+=Q;let V=yQ($,{level:6}),G=new Uint8Array(33+(12+V.length)+12);G[0]=137,G[1]=80,G[2]=78,G[3]=71,G[4]=13,G[5]=10,G[6]=26,G[7]=10;let Y=new Uint8Array(13);Y[0]=Q>>>24&255,Y[1]=Q>>>16&255,Y[2]=Q>>>8&255,Y[3]=Q&255,Y[4]=Z>>>24&255,Y[5]=Z>>>16&255,Y[6]=Z>>>8&255,Y[7]=Z&255,Y[8]=8,Y[9]=0;let H=qJ(G,8,"IHDR",Y);return H=qJ(G,H,"IDAT",V),qJ(G,H,"IEND",new Uint8Array(0)),G}//! Stage 2: the `pxpipe` imaging engine, ported from pxpipe's render.ts
16
+ var wQ=(()=>{let J=new Uint32Array(256);for(let Q=0;Q<256;Q++){let Z=Q;for(let $=0;$<8;$++)Z=(Z&1)!==0?3988292384^Z>>>1:Z>>>1;J[Q]=Z>>>0}return J})();function OJ(J,Q,Z,$){let V=$.length;J[Q]=V>>>24&255,J[Q+1]=V>>>16&255,J[Q+2]=V>>>8&255,J[Q+3]=V&255;let G=Q+4;for(let z=0;z<4;z++)J[G+z]=Z.charCodeAt(z);J.set($,G+4);let Y=4294967295;for(let z=G;z<G+4+V;z++)Y=wQ[(Y^J[z])&255]^Y>>>8;Y=~Y>>>0;let H=G+4+V;return J[H]=Y>>>24&255,J[H+1]=Y>>>16&255,J[H+2]=Y>>>8&255,J[H+3]=Y&255,H+4}function wJ(J,Q,Z){let $=new Uint8Array(Z*(Q+1));for(let z=0,K=0,q=0;z<Z;z++)$[K++]=0,$.set(J.subarray(q,q+Q),K),K+=Q,q+=Q;let V=yQ($,{level:6}),G=new Uint8Array(33+(12+V.length)+12);G[0]=137,G[1]=80,G[2]=78,G[3]=71,G[4]=13,G[5]=10,G[6]=26,G[7]=10;let Y=new Uint8Array(13);Y[0]=Q>>>24&255,Y[1]=Q>>>16&255,Y[2]=Q>>>8&255,Y[3]=Q&255,Y[4]=Z>>>24&255,Y[5]=Z>>>16&255,Y[6]=Z>>>8&255,Y[7]=Z&255,Y[8]=8,Y[9]=0;let H=OJ(G,8,"IHDR",Y);return H=OJ(G,H,"IDAT",V),OJ(G,H,"IEND",new Uint8Array(0)),G}//! Stage 2: the `pxpipe` imaging engine, ported from pxpipe's render.ts
17
17
  //! production dense path (bare 5x8 AA cell, 312 cols, 1568x728 pages).
18
18
  //! Glyphs cover BMP (pxpipe's atlas: Spleen for ASCII/code, Unifont fallback)
19
19
  //! PLUS the astral planes (unifont_upper, incl. emoji) — beyond pxpipe, which
@@ -28,19 +28,19 @@ var wQ=(()=>{let J=new Uint32Array(256);for(let Q=0;Q<256;Q++){let Z=Q;for(let $
28
28
  //! * `font` — `Tiny` renders the same atlas box-filtered into a 4x6 cell
29
29
  //! (390 cols x 120 rows/page), ~40% fewer image-tokens; opt-in,
30
30
  //! transcription-accuracy gated.
31
- var p=4,a=4,bQ=1568,vQ=728,t="↵",xQ="⏎",s="→",hQ="⇢",UJ="⇥",kQ="⇨",fQ=9647,wJ=4,mQ=3,bJ="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",uQ=/\n{4,}/g;function OJ(J){return J.length===4&&J.replace(/[A-Z]/g,(Q)=>Q.toLowerCase())==="tiny"?"tiny":"normal"}function kJ(J){let Q=J==="tiny"?4:w,Z=J==="tiny"?6:d,$=Math.floor((bQ-2*p)/Q),V=Math.floor((vQ-2*a)/Z);return{cw:Q,ch:Z,cols:$,maxLines:V,maxChars:$*V}}var gQ=(()=>{let J=new Uint8Array(128);for(let Q=0;Q<128;Q++){let Z=l(Q);J[Q]=Z>=0&&k(Z)?2:1}return J})();function FJ(J){if(J<128)return gQ[J];let Q=l(J);return Q>=0&&k(Q)?2:1}function dQ(J){let Q=0;for(let Z=0;Z<J.length;Z++){let $=J.charCodeAt(Z);if($>=55296&&$<56320&&Z+1<J.length){let V=J.charCodeAt(Z+1);if(V>=56320&&V<57344)Z++}Q++}return Q}function DJ(J){let Q=J.split(`
31
+ var n=4,s=4,vQ=1568,bQ=728,e="↵",xQ="⏎",JJ="→",hQ="⇢",DJ="⇥",kQ="⇨",fQ=9647,vJ=4,mQ=3,bJ="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",uQ=/\n{4,}/g;function c(J){return J.length===4&&J.replace(/[A-Z]/g,(Q)=>Q.toLowerCase())==="tiny"?"tiny":"normal"}function fJ(J){let Q=J==="tiny"?4:w,Z=J==="tiny"?6:l,$=Math.floor((vQ-2*n)/Q),V=Math.floor((bQ-2*s)/Z);return{cw:Q,ch:Z,cols:$,maxLines:V,maxChars:$*V}}var gQ=(()=>{let J=new Uint8Array(128);for(let Q=0;Q<128;Q++){let Z=p(Q);J[Q]=Z>=0&&k(Z)?2:1}return J})();function FJ(J){if(J<128)return gQ[J];let Q=p(J);return Q>=0&&k(Q)?2:1}function dQ(J){let Q=0;for(let Z=0;Z<J.length;Z++){let $=J.charCodeAt(Z);if($>=55296&&$<56320&&Z+1<J.length){let V=J.charCodeAt(Z+1);if(V>=56320&&V<57344)Z++}Q++}return Q}function MJ(J){let Q=J.split(`
32
32
  `);for(let Z=0;Z<Q.length;Z++){let $=Q[Z],V=$.length;while(V>0){let G=$.charCodeAt(V-1);if(G!==32&&G!==9)break;V--}if(V!==$.length)Q[Z]=$.slice(0,V)}return Q.join(`
33
33
  `).replace(uQ,`
34
34
 
35
35
 
36
- `)}function fJ(J){if(!J.includes("\t"))return J;let Q="",Z=0;for(let $ of J)if($==="\t"){let V=wJ-Z%wJ;Q+=s;for(let G=1;G<V;G++)Q+=" ";Z+=V}else Q+=$,Z+=FJ($.codePointAt(0));return Q}function lQ(J){let Q=J.includes("\t")?J.replaceAll("\t",s):J,Z=0;while(Z<Q.length&&Q.charCodeAt(Z)===32)Z++;if(Z>=mQ&&Z<bJ.length)return UJ+bJ[Z]+Q.slice(Z);return Q}function mJ(J){return J.includes(t)?J.replaceAll(t,xQ):J}function pQ(J){let Q=mJ(J);if(Q.includes(s))Q=Q.replaceAll(s,hQ);if(Q.includes(UJ))Q=Q.replaceAll(UJ,kQ);return Q}function nQ(J){let Q=DJ(J).split(`
37
- `);for(let Z=0;Z<Q.length;Z++)Q[Z]=fJ(Q[Z]);return Q.join(t)}function cQ(J){let Q=DJ(J).split(`
38
- `);for(let Z=0;Z<Q.length;Z++)Q[Z]=lQ(Q[Z]);return Q.join(t)}function oQ(J,Q){let Z=[],$=DJ(J);for(let V of $.split(`
39
- `)){let G=fJ(V);if(G.length===0){Z.push("");continue}let Y="",H=0;for(let z of G){let K=FJ(z.codePointAt(0));if(H+K>Q)Z.push(Y),Y=z,H=K;else Y+=z,H+=K}if(Y.length!==0)Z.push(Y)}return Z}function iQ(J,Q,Z){let $=[],V=[],G=0;for(let Y of J){let H=dQ(Y),z=H+(V.length!==0?1:0);if(V.length!==0&&(V.length>=Q||G+z>Z))$.push(V),V=[],G=0;G+=H+(V.length!==0?1:0),V.push(Y)}if(V.length!==0)$.push(V);return $}function uJ(J){if(J<32)return!0;if(J>=127&&J<=159)return!0;if(J>=768&&J<=879)return!0;if(J===8203||J===8204||J===8205||J===8288||J===65279)return!0;if(J>=65024&&J<=65039)return!0;if(J>=917760&&J<=917999)return!0;return!1}function rQ(J){let Q=null,Z=0;for(let $ of J){let V=$.codePointAt(0);if(l(V)<0&&!uJ(V)){if(Q===null)Q=J.slice(0,Z);Q+=`[U+${V.toString(16).toUpperCase()}]`}else if(Q!==null)Q+=$;Z+=$.length}return Q??J}function gJ(J,Q,Z,$){let V=Q?Z?cQ(pQ(J)):nQ(mJ(J)):J;return iQ(oQ(rQ(V),$.cols),$.maxLines,$.maxChars)}function dJ(J,Q,Z){if(!Z)return 2*p+Q.cols*Q.cw;let $=0;for(let Y of J){let H=0;for(let z of Y)H+=FJ(z.codePointAt(0));if(H>Q.cols)H=Q.cols;if(H>$)$=H}let V=2*p+$*Q.cw,G=2*p+Q.cw;return V>G?V:G}var vJ=new Map;function xJ(J,Q,Z,$,V,G,Y){let H=l(V);if(H<0)return 0;let z=k(H),K=z?2*G.cw:G.cw,q;if(Y==="normal")q=BJ(H);else{let X=vJ.get(H);if(X===void 0)X=LJ(H,K,G.ch),vJ.set(H,X);q=X}for(let X=0;X<G.ch;X++){let B=($+X)*Q+Z,F=X*K;for(let O=0;O<K;O++){let D=q[F+O];if(D>0){let I=B+O;if(D>J[I])J[I]=D}}}return z?2:1}function aQ(J,Q,Z,$){let V=dJ(J,Q,Z),G=2*a+J.length*Q.ch,Y=new Uint8Array(V*G),H=0;for(let z=0;z<J.length;z++){let K=a+z*Q.ch,q=0;for(let X of J[z]){if(q>=Q.cols)break;let B=p+q*Q.cw,F=xJ(Y,V,B,K,X.codePointAt(0),Q,$);if(F===0){if(F=1,uJ(X.codePointAt(0)));else if(H++,X!==" ")xJ(Y,V,B,K,fQ,Q,$)}q+=F}}for(let z=0;z<Y.length;z++)Y[z]=255-Y[z];return{png:yJ(Y,V,G),width:V,height:G,dropped:H}}function MJ(J,Q,Z,$){let V=kJ($),G=gJ(J,Q,Z,V).map((K)=>aQ(K,V,Z,$)),Y=0,H=0,z=0;for(let K of G)Y+=K.width*K.height,H+=pJ(K.width,K.height),z+=K.dropped;return{pages:G,pixels:Y,tokens:H,dropped:z}}function lJ(J,Q,Z,$){let V=kJ($),G=gJ(J,Q,Z,V),Y=0,H=0;for(let z of G){let K=dJ(z,V,Z),q=2*a+z.length*V.ch;Y+=K*q,H+=pJ(K,q)}return{pages:G.length,pixels:Y,tokens:H}}var hJ=28;function pJ(J,Q){return Math.ceil(J/hJ)*Math.ceil(Q/hJ)}import{readFileSync as tQ}from"node:fs";import{join as sQ}from"node:path";//! pxpipe measurement-log summary (~/.pxpipe/events.jsonl), same math as the
36
+ `)}function mJ(J){if(!J.includes("\t"))return J;let Q="",Z=0;for(let $ of J)if($==="\t"){let V=vJ-Z%vJ;Q+=JJ;for(let G=1;G<V;G++)Q+=" ";Z+=V}else Q+=$,Z+=FJ($.codePointAt(0));return Q}function lQ(J){let Q=J.includes("\t")?J.replaceAll("\t",JJ):J,Z=0;while(Z<Q.length&&Q.charCodeAt(Z)===32)Z++;if(Z>=mQ&&Z<bJ.length)return DJ+bJ[Z]+Q.slice(Z);return Q}function uJ(J){return J.includes(e)?J.replaceAll(e,xQ):J}function pQ(J){let Q=uJ(J);if(Q.includes(JJ))Q=Q.replaceAll(JJ,hQ);if(Q.includes(DJ))Q=Q.replaceAll(DJ,kQ);return Q}function nQ(J){let Q=MJ(J).split(`
37
+ `);for(let Z=0;Z<Q.length;Z++)Q[Z]=mJ(Q[Z]);return Q.join(e)}function cQ(J){let Q=MJ(J).split(`
38
+ `);for(let Z=0;Z<Q.length;Z++)Q[Z]=lQ(Q[Z]);return Q.join(e)}function oQ(J,Q){let Z=[],$=MJ(J);for(let V of $.split(`
39
+ `)){let G=mJ(V);if(G.length===0){Z.push("");continue}let Y="",H=0;for(let z of G){let K=FJ(z.codePointAt(0));if(H+K>Q)Z.push(Y),Y=z,H=K;else Y+=z,H+=K}if(Y.length!==0)Z.push(Y)}return Z}function iQ(J,Q,Z){let $=[],V=[],G=0;for(let Y of J){let H=dQ(Y),z=H+(V.length!==0?1:0);if(V.length!==0&&(V.length>=Q||G+z>Z))$.push(V),V=[],G=0;G+=H+(V.length!==0?1:0),V.push(Y)}if(V.length!==0)$.push(V);return $}function gJ(J){if(J<32)return!0;if(J>=127&&J<=159)return!0;if(J>=768&&J<=879)return!0;if(J===8203||J===8204||J===8205||J===8288||J===65279)return!0;if(J>=65024&&J<=65039)return!0;if(J>=917760&&J<=917999)return!0;return!1}function rQ(J){let Q=null,Z=0;for(let $ of J){let V=$.codePointAt(0);if(p(V)<0&&!gJ(V)){if(Q===null)Q=J.slice(0,Z);Q+=`[U+${V.toString(16).toUpperCase()}]`}else if(Q!==null)Q+=$;Z+=$.length}return Q??J}function dJ(J,Q,Z,$){let V=Q?Z?cQ(pQ(J)):nQ(uJ(J)):J;return iQ(oQ(rQ(V),$.cols),$.maxLines,$.maxChars)}function lJ(J,Q,Z){if(!Z)return 2*n+Q.cols*Q.cw;let $=0;for(let Y of J){let H=0;for(let z of Y)H+=FJ(z.codePointAt(0));if(H>Q.cols)H=Q.cols;if(H>$)$=H}let V=2*n+$*Q.cw,G=2*n+Q.cw;return V>G?V:G}var xJ=new Map;function hJ(J,Q,Z,$,V,G,Y){let H=p(V);if(H<0)return 0;let z=k(H),K=z?2*G.cw:G.cw,q;if(Y==="normal")q=UJ(H);else{let X=xJ.get(H);if(X===void 0)X=yJ(H,K,G.ch),xJ.set(H,X);q=X}for(let X=0;X<G.ch;X++){let B=($+X)*Q+Z,D=X*K;for(let O=0;O<K;O++){let F=q[D+O];if(F>0){let I=B+O;if(F>J[I])J[I]=F}}}return z?2:1}function aQ(J,Q,Z,$){let V=lJ(J,Q,Z),G=2*s+J.length*Q.ch,Y=new Uint8Array(V*G),H=0;for(let z=0;z<J.length;z++){let K=s+z*Q.ch,q=0;for(let X of J[z]){if(q>=Q.cols)break;let B=n+q*Q.cw,D=hJ(Y,V,B,K,X.codePointAt(0),Q,$);if(D===0){if(D=1,gJ(X.codePointAt(0)));else if(H++,X!==" ")hJ(Y,V,B,K,fQ,Q,$)}q+=D}}for(let z=0;z<Y.length;z++)Y[z]=255-Y[z];return{png:wJ(Y,V,G),width:V,height:G,dropped:H}}function WJ(J,Q,Z,$){let V=fJ($),G=dJ(J,Q,Z,V).map((K)=>aQ(K,V,Z,$)),Y=0,H=0,z=0;for(let K of G)Y+=K.width*K.height,H+=pJ(K.width,K.height),z+=K.dropped;return{pages:G,pixels:Y,tokens:H,dropped:z}}function QJ(J,Q,Z,$){let V=fJ($),G=dJ(J,Q,Z,V),Y=0,H=0;for(let z of G){let K=lJ(z,V,Z),q=2*s+z.length*V.ch;Y+=K*q,H+=pJ(K,q)}return{pages:G.length,pixels:Y,tokens:H}}var kJ=28;function pJ(J,Q){return Math.ceil(J/kJ)*Math.ceil(Q/kJ)}import{readFileSync as tQ}from"node:fs";import{join as sQ}from"node:path";//! pxpipe measurement-log summary (~/.pxpipe/events.jsonl), same math as the
40
40
  //! node MCP: actual = every way input bytes get billed (input + cache reads +
41
41
  //! cache creates) — ignoring cache_read would fake the savings.
42
- class e{value;constructor(J){this.value=J}}function nJ(){let J=process.env.TANUKI_EVENTS;if(J!==void 0)return J;let Q=process.env.HOME??"";return sQ(Q,".pxpipe","events.jsonl")}function f(J){return typeof J==="number"&&Number.isSafeInteger(J)&&J>=0?J:0}function eQ(J){return J<0?-Math.round(-J):Math.round(J)}function WJ(){let J=nJ(),Q;try{Q=tQ(J,"utf8")}catch{return{available:!1,note:`no ${J} yet`}}let Z=0,$=0,V=0,G=0,Y=0,H=0;for(let K of Q.split(`
43
- `)){if(K.trim().length===0)continue;let q;try{q=JSON.parse(K)}catch{continue}Z+=1;let X=q!==null&&typeof q==="object"&&!Array.isArray(q)?q:{};if(X.compressed===!0)$+=1,V+=f(X.orig_chars),G+=f(X.image_count);Y+=f(X.baseline_tokens),H+=f(X.input_tokens)+f(X.cache_read_tokens)+f(X.cache_create_tokens)}let z=Y>0&&H>0?new e(eQ((1-H/Y)*1000)/10):null;return{available:!0,requests:Z,compressedRequests:$,imagedChars:V,imagesEmitted:G,baselineTokens:Y,actualInputTokens:H,estInputSavedPct:z}}//! Implicit mode: a local Anthropic middlebox, the pxpipe deployment shape
42
+ class ZJ{value;constructor(J){this.value=J}}function nJ(){let J=process.env.TANUKI_EVENTS;if(J!==void 0)return J;let Q=process.env.HOME??"";return sQ(Q,".pxpipe","events.jsonl")}function f(J){return typeof J==="number"&&Number.isSafeInteger(J)&&J>=0?J:0}function eQ(J){return J<0?-Math.round(-J):Math.round(J)}function jJ(){let J=nJ(),Q;try{Q=tQ(J,"utf8")}catch{return{available:!1,note:`no ${J} yet`}}let Z=0,$=0,V=0,G=0,Y=0,H=0;for(let K of Q.split(`
43
+ `)){if(K.trim().length===0)continue;let q;try{q=JSON.parse(K)}catch{continue}Z+=1;let X=q!==null&&typeof q==="object"&&!Array.isArray(q)?q:{};if(X.compressed===!0)$+=1,V+=f(X.orig_chars),G+=f(X.image_count);Y+=f(X.baseline_tokens),H+=f(X.input_tokens)+f(X.cache_read_tokens)+f(X.cache_create_tokens)}let z=Y>0&&H>0?new ZJ(eQ((1-H/Y)*1000)/10):null;return{available:!0,requests:Z,compressedRequests:$,imagedChars:V,imagesEmitted:G,baselineTokens:Y,actualInputTokens:H,estInputSavedPct:z}}//! Implicit mode: a local Anthropic middlebox, the pxpipe deployment shape
44
44
  //! without pxpipe's structural flaw. Rules that keep it injection-shaped-free:
45
45
  //!
46
46
  //! 1. The system prompt and tool definitions are NEVER touched.
@@ -65,15 +65,15 @@ class e{value;constructor(J){this.value=J}}function nJ(){let J=process.env.TANUK
65
65
  //! tanuki-context estimate <file> [level] [--distill]
66
66
  //! tanuki-context render <file> [level] [outdir]
67
67
  //! tanuki-context proxy [--port N] [--upstream URL] [knobs] (implicit mode)
68
- var cJ="0.1.0",jJ=6;function JZ(J,Q){let Z=Math.min(J.length,Q.length),$=0;while($<Z&&J.charCodeAt($)===Q.charCodeAt($))$++;if($>=Z)return J.length-Q.length;return J.codePointAt($)-Q.codePointAt($)}function C(J,Q,Z=""){if(J===null||J===void 0)return"null";if(J instanceof e){let z=J.value;return Number.isFinite(z)&&Number.isInteger(z)?z.toFixed(1):String(z)}let $=typeof J;if($==="string")return JSON.stringify(J);if($==="number"||$==="boolean")return String(J);if(Array.isArray(J)){if(J.length===0)return"[]";if(!Q){let q="[";for(let X=0;X<J.length;X++){if(X>0)q+=",";q+=C(J[X],!1)}return q+"]"}let z=Z+" ",K=`[
68
+ var cJ="0.2.1",NJ=6;function JZ(J,Q){let Z=Math.min(J.length,Q.length),$=0;while($<Z&&J.charCodeAt($)===Q.charCodeAt($))$++;if($>=Z)return J.length-Q.length;return J.codePointAt($)-Q.codePointAt($)}function C(J,Q,Z=""){if(J===null||J===void 0)return"null";if(J instanceof ZJ){let z=J.value;return Number.isFinite(z)&&Number.isInteger(z)?z.toFixed(1):String(z)}let $=typeof J;if($==="string")return JSON.stringify(J);if($==="number"||$==="boolean")return String(J);if(Array.isArray(J)){if(J.length===0)return"[]";if(!Q){let q="[";for(let X=0;X<J.length;X++){if(X>0)q+=",";q+=C(J[X],!1)}return q+"]"}let z=Z+" ",K=`[
69
69
  `;for(let q=0;q<J.length;q++){if(q>0)K+=`,
70
70
  `;K+=z+C(J[q],!0,z)}return K+`
71
71
  `+Z+"]"}let V=J,G=Object.keys(V).sort(JZ);if(G.length===0)return"{}";if(!Q){let z="{";for(let K=0;K<G.length;K++){if(K>0)z+=",";z+=JSON.stringify(G[K])+":"+C(V[G[K]],!1)}return z+"}"}let Y=Z+" ",H=`{
72
72
  `;for(let z=0;z<G.length;z++){if(z>0)H+=`,
73
73
  `;H+=Y+JSON.stringify(G[z])+": "+C(V[G[z]],!0,Y)}return H+`
74
- `+Z+"}"}function S(J,Q){return J!==null&&typeof J==="object"&&!Array.isArray(J)?J[Q]:void 0}function m(J){return typeof J==="string"?J:null}function JJ(J){return typeof J==="boolean"?J:null}function oJ(J){return typeof J==="number"&&Number.isSafeInteger(J)&&J>=0?J:null}function u(J){let Q=0;for(let Z=0;Z<J.length;Z++){Q++;let $=J.charCodeAt(Z);if($>=55296&&$<=56319&&Z+1<J.length){let V=J.charCodeAt(Z+1);if(V>=56320&&V<=57343)Z++}}return Q}function iJ(J,Q,Z,$,V){let G,Y=null;if(Z||$!==null){let K=c(J,$,2);G=K.distilled,Y=K.stats}else G=J;let H=0;if(V){let K=VJ(G);G=K.text,H=K.entries}let z=i(G,Q);return{stage0:Y,compressed:z.compressed,protectedLines:z.protectedLines,level:z.level,cbEntries:H}}function QJ(J){return Math.round(J/4)}function n(J,Q){if(J===0)return 0;let Z=(1-Q/J)*100;return Z<0?-Math.round(-Z):Math.round(Z)}function rJ(J){return{text:m(S(J,"text"))??"",level:(oJ(S(J,"level"))??0)%256,distill:JJ(S(J,"distill"))??!1,query:m(S(J,"query")),reflow:JJ(S(J,"reflow"))??!0,pack:JJ(S(J,"pack"))??!0,font:m(S(J,"font"))??"normal",codebook:JJ(S(J,"codebook"))??!1}}function aJ(J){let Q=rJ(J),Z=iJ(Q.text,Q.level,Q.distill,Q.query,Q.codebook),$=OJ(Q.font),V=lJ(Z.compressed,Q.reflow,Q.pack,$),G=V.tokens,Y=u(Q.text),H=u(Z.compressed),z=QJ(Y),[K,q]=o[Z.level];return{engine:"pxpipe",level:`${Z.level} ${K}`,loss:q,distill:Z.stage0,origChars:Y,stage1Chars:H,stage1SavedPct:n(Y,H),pages:V.pages,imageTokens:G,rawTextTokens:z,totalSavedPct:n(z,G),protectedLines:Z.protectedLines,pack:Q.pack,font:$==="tiny"?"tiny":"normal",codebook:Q.codebook?Z.cbEntries:!1,verdict:G<z?"PIPELINE cheaper":"TEXT cheaper"}}function tJ(J){let Q=rJ(J),Z=iJ(Q.text,Q.level,Q.distill,Q.query,Q.codebook),$=OJ(Q.font),V=MJ(Z.compressed,Q.reflow,Q.pack,$),G=V.tokens,Y=u(Q.text),H=u(Z.compressed),z=QJ(Y),[K,q]=o[Z.level],X="";if(Z.stage0!==null){let O=Z.stage0;X+=`distill: ${O.origLines} -> ${O.outLines} lines (-${O.savedPct}% chars, ${O.collapsedRuns} runs, ${O.suppressedLines} exact + ${O.templateSuppressed} template suppressed, ${O.importantKept} error/warn kept)
75
- `}if(X+=`L${Z.level} ${K} (${q}): ${Y} chars -> ${H} chars (stage1 -${n(Y,H)}%) -> ${V.pages.length} page(s), ~${G} image-tokens
76
- vs ~${z} text-tokens raw = TOTAL -${n(z,G)}%`,Z.protectedLines>0)X+=` · ${Z.protectedLines} lines kept verbatim`;if(V.dropped>0)X+=` · ${V.dropped} unmapped glyphs -> ▯`;if(Z.cbEntries>0)X+=` · codebook: ${Z.cbEntries} sigils (see ·legend·)`;if($==="tiny")X+=" · font: tiny 4x6";if(Q.pack)X+=" · packed (⇥N indent, → tab)";if(Q.reflow)X+=" · ↵ = newline · engine: pxpipe";let B=[{type:"text",text:X}],F=Math.min(V.pages.length,jJ);for(let O=0;O<F;O++){let D=V.pages[O].png;B.push({type:"image",data:Buffer.from(D.buffer,D.byteOffset,D.byteLength).toString("base64"),mimeType:"image/png"})}if(V.pages.length>jJ)B.push({type:"text",text:`(+${V.pages.length-jJ} more page(s))`});return B}function sJ(J){let Q=m(S(J,"text"))??"",Z=c(Q,m(S(J,"query")),2);return[{type:"text",text:C(Z.stats,!0)},{type:"text",text:Z.distilled}]}function eJ(J){let Q=m(S(J,"text"))??"",Z=(oJ(S(J,"level"))??1)%256,$=i(Q,Z),[V,G,Y]=o[$.level],H=u(Q),z=u($.compressed),K=QJ(H),q=QJ(z),X={level:`${$.level} ${V}`,loss:G,note:Y,origChars:H,outChars:z,approxOrigTokens:K,approxOutTokens:q,savedPct:n(K,q),protectedLines:$.protectedLines};return[{type:"text",text:C(X,!0)},{type:"text",text:$.compressed}]}//! Claude Agent SDK integration (`tanuki-context/agent`).
74
+ `+Z+"}"}function S(J,Q){return J!==null&&typeof J==="object"&&!Array.isArray(J)?J[Q]:void 0}function m(J){return typeof J==="string"?J:null}function $J(J){return typeof J==="boolean"?J:null}function oJ(J){return typeof J==="number"&&Number.isSafeInteger(J)&&J>=0?J:null}function u(J){let Q=0;for(let Z=0;Z<J.length;Z++){Q++;let $=J.charCodeAt(Z);if($>=55296&&$<=56319&&Z+1<J.length){let V=J.charCodeAt(Z+1);if(V>=56320&&V<=57343)Z++}}return Q}function iJ(J,Q,Z,$,V){let G,Y=null;if(Z||$!==null){let K=d(J,$,2);G=K.distilled,Y=K.stats}else G=J;let H=0;if(V){let K=i(G);G=K.text,H=K.entries}let z=a(G,Q);return{stage0:Y,compressed:z.compressed,protectedLines:z.protectedLines,level:z.level,cbEntries:H}}function VJ(J){return Math.round(J/4)}function o(J,Q){if(J===0)return 0;let Z=(1-Q/J)*100;return Z<0?-Math.round(-Z):Math.round(Z)}function rJ(J){return{text:m(S(J,"text"))??"",level:(oJ(S(J,"level"))??0)%256,distill:$J(S(J,"distill"))??!1,query:m(S(J,"query")),reflow:$J(S(J,"reflow"))??!0,pack:$J(S(J,"pack"))??!0,font:m(S(J,"font"))??"normal",codebook:$J(S(J,"codebook"))??!1}}function QZ(J){let Q=d(J,null,2).distilled,Z=[[!1,J],[!0,Q]],$={distill:!1,codebook:!1,text:J,tokens:1/0,pages:0};for(let[G,Y]of Z)for(let H of[!1,!0]){let z=H?i(Y).text:Y,K=QJ(z,!0,!0,c("normal"));if(K.tokens<$.tokens)$={distill:G,codebook:H,text:z,tokens:K.tokens,pages:K.pages}}let V=QJ($.text,!0,!0,c("tiny"));return{codebook:$.codebook,distill:$.distill,imageTokens:$.tokens,pages:$.pages,tinyImageTokens:V.tokens}}function aJ(J){let Q=rJ(J),Z=iJ(Q.text,Q.level,Q.distill,Q.query,Q.codebook),$=c(Q.font),V=QJ(Z.compressed,Q.reflow,Q.pack,$),G=V.tokens,Y=u(Q.text),H=u(Z.compressed),z=VJ(Y),[K,q]=r[Z.level];return{engine:"pxpipe",level:`${Z.level} ${K}`,loss:q,distill:Z.stage0,origChars:Y,stage1Chars:H,stage1SavedPct:o(Y,H),pages:V.pages,imageTokens:G,rawTextTokens:z,totalSavedPct:o(z,G),protectedLines:Z.protectedLines,pack:Q.pack,font:$==="tiny"?"tiny":"normal",codebook:Q.codebook?Z.cbEntries:!1,verdict:G<z?"PIPELINE cheaper":"TEXT cheaper",recommend:QZ(Q.text)}}function tJ(J){let Q=rJ(J),Z=iJ(Q.text,Q.level,Q.distill,Q.query,Q.codebook),$=c(Q.font),V=WJ(Z.compressed,Q.reflow,Q.pack,$),G=V.tokens,Y=u(Q.text),H=u(Z.compressed),z=VJ(Y),[K,q]=r[Z.level],X="";if(Z.stage0!==null){let O=Z.stage0;X+=`distill: ${O.origLines} -> ${O.outLines} lines (-${O.savedPct}% chars, ${O.collapsedRuns} runs, ${O.suppressedLines} exact + ${O.templateSuppressed} template suppressed, ${O.importantKept} error/warn kept)
75
+ `}if(X+=`L${Z.level} ${K} (${q}): ${Y} chars -> ${H} chars (stage1 -${o(Y,H)}%) -> ${V.pages.length} page(s), ~${G} image-tokens
76
+ vs ~${z} text-tokens raw = TOTAL -${o(z,G)}%`,Z.protectedLines>0)X+=` · ${Z.protectedLines} lines kept verbatim`;if(V.dropped>0)X+=` · ${V.dropped} unmapped glyphs -> ▯`;if(Z.cbEntries>0)X+=` · codebook: ${Z.cbEntries} sigils (see ·legend·)`;if($==="tiny")X+=" · font: tiny 4x6";if(Q.pack)X+=" · packed (⇥N indent, → tab)";if(Q.reflow)X+=" · ↵ = newline · engine: pxpipe";let B=[{type:"text",text:X}],D=Math.min(V.pages.length,NJ);for(let O=0;O<D;O++){let F=V.pages[O].png;B.push({type:"image",data:Buffer.from(F.buffer,F.byteOffset,F.byteLength).toString("base64"),mimeType:"image/png"})}if(V.pages.length>NJ)B.push({type:"text",text:`(+${V.pages.length-NJ} more page(s))`});return B}function sJ(J){let Q=m(S(J,"text"))??"",Z=d(Q,m(S(J,"query")),2);return[{type:"text",text:C(Z.stats,!0)},{type:"text",text:Z.distilled}]}function eJ(J){let Q=m(S(J,"text"))??"",Z=(oJ(S(J,"level"))??1)%256,$=a(Q,Z),[V,G,Y]=r[$.level],H=u(Q),z=u($.compressed),K=VJ(H),q=VJ(z),X={level:`${$.level} ${V}`,loss:G,note:Y,origChars:H,outChars:z,approxOrigTokens:K,approxOutTokens:q,savedPct:o(K,q),protectedLines:$.protectedLines};return[{type:"text",text:C(X,!0)},{type:"text",text:$.compressed}]}//! Claude Agent SDK integration (`tanuki-context/agent`).
77
77
  //!
78
78
  //! Two flavours, both one-liners from an agent's point of view:
79
79
  //!
@@ -90,7 +90,7 @@ vs ~${z} text-tokens raw = TOTAL -${n(z,G)}%`,Z.protectedLines>0)X+=` · ${Z.pro
90
90
  //! The core package stays zero-dependency: the SDK and zod are touched only
91
91
  //! inside `tanukiSdkServer()` via dynamic import, and both are already present
92
92
  //! in any Agent SDK project (zod is the SDK's own peer dependency).
93
- var $Z=["tanuki_render","tanuki_estimate","tanuki_distill","tanuki_compress","tanuki_stats"],VZ=`tanuki-context turns bulky text (logs, command output, docs) into dense PNG pages that cost a fraction of the text tokens.
94
- Workflow: call tanuki_estimate first (instant, exact, never renders pixels). If the verdict says "PIPELINE cheaper", call tanuki_render with the same arguments and use the returned pages instead of pasting the text.
93
+ var VZ=["tanuki_render","tanuki_estimate","tanuki_distill","tanuki_compress","tanuki_stats"],GZ=`tanuki-context turns bulky text (logs, command output, docs) into dense PNG pages that cost a fraction of the text tokens.
94
+ Workflow: call tanuki_estimate first (instant, exact, never renders pixels). Its "recommend" field already names the cheapest safe knob set, priced - do not probe combos by hand. If the verdict says "PIPELINE cheaper", call tanuki_render with the recommended knobs and use the returned pages instead of pasting the text.
95
95
  For logs, pass distill:true (repeats collapse, error/warn lines stay verbatim; add query:"regex" to slice). For prose you will not quote verbatim, level 2-3 shrinks it further. codebook:true helps path-heavy logs. Never image content you must quote byte-exact at level 4 or font tiny.
96
- Pages decode as: ↵ = newline, → = tab, ⇥N = N leading spaces, a trailing ·legend· line maps sigils back to full tokens.`;function GZ(){let J=new URL("./cli.js",import.meta.url);if(J.protocol==="file:"){let Q=ZZ(J);if(QZ(Q))return{type:"stdio",command:process.execPath,args:[Q]}}return{type:"stdio",command:"npx",args:["-y","tanuki-context"]}}function YZ(J="tanuki"){return $Z.map((Q)=>`mcp__${J}__${Q}`)}function pZ(J,Q={}){let Z=J??{},$=Q.key??"tanuki";return{...Z,mcpServers:{...Z.mcpServers??{},[$]:Q.server??GZ()},allowedTools:[...Z.allowedTools??[],...YZ($)]}}function HZ(J){let Q=J.string().describe("the bulky text to process"),Z=J.number().int().min(0).max(4).optional().describe("ladder level 0-4 (default 0)"),$={text:Q,level:Z,distill:J.boolean().optional().describe("stage 0 log distiller"),query:J.string().optional().describe("distill: keep matching lines +context"),reflow:J.boolean().optional().describe("pack short lines into full rows (default true)"),pack:J.boolean().optional().describe("indent RLE + width trim, lossless (default true)"),font:J.enum(["normal","tiny"]).optional().describe("tiny = 4x6 cells, ~40% fewer tokens, gated"),codebook:J.boolean().optional().describe("repeated tokens/paths -> sigils + legend")},V=(Y)=>({content:Y}),G=async(Y)=>{try{return V(Y())}catch(H){return{content:[{type:"text",text:`tanuki-context error: ${H instanceof Error?H.message:String(H)}`}],isError:!0}}};return[{name:"tanuki_render",description:"Render text through the pipeline (optional distill/level/codebook) into dense PNG pages. Call after tanuki_estimate says PIPELINE cheaper.",inputSchema:$,handler:(Y)=>G(()=>tJ(Y))},{name:"tanuki_estimate",description:"Exact page/token math for the same arguments as tanuki_render, without touching pixels. Instant; call this first.",inputSchema:$,handler:(Y)=>G(()=>[{type:"text",text:C(aJ(Y),!0)}])},{name:"tanuki_distill",description:"Stage 0 alone: collapse repeated log lines/blocks and template near-dupes; error/warn lines kept verbatim. Output stays greppable text.",inputSchema:{text:Q,query:$.query},handler:(Y)=>G(()=>sJ(Y))},{name:"tanuki_compress",description:"Stage 1 alone: graded text compression, levels 0-4, code/paths/hashes protected from level 2 up.",inputSchema:{text:Q,level:Z},handler:(Y)=>G(()=>eJ(Y))},{name:"tanuki_stats",description:"Session savings summary from the events log (honest denominator: input + cache reads + cache creates).",inputSchema:{},handler:()=>G(()=>[{type:"text",text:C(WJ(),!0)}])}]}async function nZ(){let J,Q;try{J=await import("@anthropic-ai/claude-agent-sdk");let $=await import("zod");Q=$.z??$}catch($){throw Error(`tanukiSdkServer() needs the host project's @anthropic-ai/claude-agent-sdk and zod (npm i @anthropic-ai/claude-agent-sdk zod). Import failed: ${$ instanceof Error?$.message:String($)}`)}let Z=HZ(Q).map(($)=>J.tool($.name,$.description,$.inputSchema,(V)=>$.handler(V)));return J.createSdkMcpServer({name:"tanuki-context",version:cJ,instructions:VZ,tools:Z})}export{pZ as withTanuki,HZ as tanukiSdkToolSpecs,nZ as tanukiSdkServer,GZ as tanukiMcpServer,YZ as tanukiAllowedTools,$Z as TANUKI_TOOL_NAMES,VZ as TANUKI_INSTRUCTIONS};
96
+ Pages decode as: ↵ = newline, → = tab, ⇥N = N leading spaces, a trailing ·legend· line maps sigils back to full tokens.`;function YZ(){let J=new URL("./cli.js",import.meta.url);if(J.protocol==="file:"){let Q=$Z(J);if(ZZ(Q))return{type:"stdio",command:process.execPath,args:[Q]}}return{type:"stdio",command:"npx",args:["-y","tanuki-context"]}}function HZ(J="tanuki"){return VZ.map((Q)=>`mcp__${J}__${Q}`)}function nZ(J,Q={}){let Z=J??{},$=Q.key??"tanuki";return{...Z,mcpServers:{...Z.mcpServers??{},[$]:Q.server??YZ()},allowedTools:[...Z.allowedTools??[],...HZ($)]}}function zZ(J){let Q=J.string().describe("the bulky text to process"),Z=J.number().int().min(0).max(4).optional().describe("ladder level 0-4 (default 0)"),$={text:Q,level:Z,distill:J.boolean().optional().describe("stage 0 log distiller"),query:J.string().optional().describe("distill: keep matching lines +context"),reflow:J.boolean().optional().describe("pack short lines into full rows (default true)"),pack:J.boolean().optional().describe("indent RLE + width trim, lossless (default true)"),font:J.enum(["normal","tiny"]).optional().describe("tiny = 4x6 cells, ~40% fewer tokens, gated"),codebook:J.boolean().optional().describe("repeated tokens/paths -> sigils + legend")},V=(Y)=>({content:Y}),G=async(Y)=>{try{return V(Y())}catch(H){return{content:[{type:"text",text:`tanuki-context error: ${H instanceof Error?H.message:String(H)}`}],isError:!0}}};return[{name:"tanuki_render",description:"Render text through the pipeline (optional distill/level/codebook) into dense PNG pages. Call after tanuki_estimate says PIPELINE cheaper.",inputSchema:$,handler:(Y)=>G(()=>tJ(Y))},{name:"tanuki_estimate",description:"Exact page/token math for the same arguments as tanuki_render, without touching pixels. Instant; call this first.",inputSchema:$,handler:(Y)=>G(()=>[{type:"text",text:C(aJ(Y),!0)}])},{name:"tanuki_distill",description:"Stage 0 alone: collapse repeated log lines/blocks and template near-dupes; error/warn lines kept verbatim. Output stays greppable text.",inputSchema:{text:Q,query:$.query},handler:(Y)=>G(()=>sJ(Y))},{name:"tanuki_compress",description:"Stage 1 alone: graded text compression, levels 0-4, code/paths/hashes protected from level 2 up.",inputSchema:{text:Q,level:Z},handler:(Y)=>G(()=>eJ(Y))},{name:"tanuki_stats",description:"Session savings summary from the events log (honest denominator: input + cache reads + cache creates).",inputSchema:{},handler:()=>G(()=>[{type:"text",text:C(jJ(),!0)}])}]}async function cZ(){let J,Q;try{J=await import("@anthropic-ai/claude-agent-sdk");let $=await import("zod");Q=$.z??$}catch($){throw Error(`tanukiSdkServer() needs the host project's @anthropic-ai/claude-agent-sdk and zod (npm i @anthropic-ai/claude-agent-sdk zod). Import failed: ${$ instanceof Error?$.message:String($)}`)}let Z=zZ(Q).map(($)=>J.tool($.name,$.description,$.inputSchema,(V)=>$.handler(V)));return J.createSdkMcpServer({name:"tanuki-context",version:cJ,instructions:GZ,tools:Z})}export{nZ as withTanuki,zZ as tanukiSdkToolSpecs,cZ as tanukiSdkServer,YZ as tanukiMcpServer,HZ as tanukiAllowedTools,VZ as TANUKI_TOOL_NAMES,GZ as TANUKI_INSTRUCTIONS};
package/dist/cli.js CHANGED
@@ -1,20 +1,20 @@
1
1
  #!/usr/bin/env node
2
- import{readFileSync as FZ,mkdirSync as MZ,writeFileSync as WZ}from"node:fs";import S from"node:process";function UQ(J){if(J===32||J>=9&&J<=13)return!0;if(J<133)return!1;return J===133||J===160||J===5760||J>=8192&&J<=8202||J===8232||J===8233||J===8239||J===8287||J===12288}function OJ(J){let Q=0;for(let Z=0;Z<J.length;Z++){let $=J.charCodeAt(Z);if($>=55296&&$<=56319&&Z+1<J.length){let V=J.charCodeAt(Z+1);if(V>=56320&&V<=57343)Z++}Q++}return Q}function XQ(J,Q){let Z=0,$=0;while(Z<J.length&&$<Q.length){let V=J.codePointAt(Z),z=Q.codePointAt($);if(V!==z)return V-z;Z+=V>65535?2:1,$+=z>65535?2:1}return J.length-Z-(Q.length-$)}function QJ(J){let Q=new Map,Z=(X)=>{Q.set(X,(Q.get(X)??0)+1)},$=(X)=>{if(OJ(X)>=12)Z(X);if(X.includes("/")){let O=X.split("/"),q="";for(let F=0;F<O.length;F++){if(F>0)q+="/";if(q+=O[F],F>=2){let R=q+"/";if(OJ(R)>=12)Z(R)}}}},V=-1;for(let X=0;X<J.length;){let O=J.codePointAt(X),q=O>65535?2:1;if(UQ(O)){if(V>=0)$(J.slice(V,X)),V=-1}else if(V<0)V=X;X+=q}if(V>=0)$(J.slice(V));let z=[];for(let[X,O]of Q)if(O>=3){let q=OJ(X);z.push({k:X,c:O,len:q,saved:(q-1)*O})}z.sort((X,O)=>O.saved-X.saved||XQ(X.k,O.k));let Y=[];for(let X of"§¤¢£¥µ¶ª°±¬×÷ØÞßæðøþ¡¿")if(!J.includes(X))Y.push(X);let G=[],H=[];for(let{k:X,c:O,len:q}of z){if(G.length>=Y.length)break;if((q-1)*O<=q+3)continue;let F=!1;for(let A=0;A<H.length;A++){let y=H[A];if(y.startsWith(X)||X.startsWith(y)){F=!0;break}}if(F)continue;let R=Y[G.length];H.push(X),G.push({sig:R,val:X,len:q})}if(G.length===0)return{text:J,entries:0};let K=G.map((X,O)=>O);K.sort((X,O)=>G[O].len-G[X].len);let B=J;for(let X=0;X<K.length;X++){let{sig:O,val:q}=G[K[X]];B=B.replaceAll(q,O)}let U=`
3
- ·legend· `;for(let X=0;X<G.length;X++)U+=G[X].sig+"="+G[X].val+" ";return B+=U.slice(0,-1),{text:B,entries:G.length}}import{Buffer as LJ}from"node:buffer";var BQ=/\x1b\[[0-9;]*[A-Za-z]/g,qJ=/\b([0-9A-Za-z_]*(error|exception)s?|err|warn(ing)?s?|fail(s|ed|ure|ures)?|panic(s|ked)?|fatal|critical|traceback|denied|refused|timeouts?|timed.?out|assert(s|ed|ion|ions)?|segfault(s|ed)?)\b/i,OQ=/[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9]{2}:[0-9]{2}:[0-9]{2}([.,][0-9]+)?(Z|[+-][0-9]{2}:?[0-9]{2})?/g,qQ=/\b[0-9]{2}:[0-9]{2}:[0-9]{2}([.,][0-9]+)?\b/g,DQ=/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi,FQ=/\b[0-9a-f]{7,64}\b/gi,MQ=/\b[0-9]+(\.[0-9]+)?[ \t\u00a0]?(ms|us|µs|ns|s|m|h|%|[KMGT]i?B)?\b/gi,WQ=/[0-9a-f]/i,NQ=3,jQ=8,DJ=2,yJ=40;function IQ(J){if(!WQ.test(J))return J;let Q=J.replace(OQ,"<ts>");return Q=Q.replace(qQ,"<time>"),Q=Q.replace(DQ,"<uuid>"),Q=Q.replace(FQ,"<hex>"),Q.replace(MQ,"<n>")}function g(J){if(J===32)return!0;if(J<9)return!1;if(J<=13)return!0;if(J<133)return!1;return J===133||J===160||J===5760||J>=8192&&J<=8202||J===8232||J===8233||J===8239||J===8287||J===12288}function wJ(J){let Q=0,Z=J.length;while(Q<Z&&g(J.charCodeAt(Q)))Q++;while(Z>Q&&g(J.charCodeAt(Z-1)))Z--;return Q===0&&Z===J.length?J:J.slice(Q,Z)}function RQ(J,Q){let Z=0,$=J.length;while(Z<$&&g(J.charCodeAt(Z)))Z++;while($>Z&&g(J.charCodeAt($-1)))$--;let V=0;for(let z=Z;z<$&&V<Q;V++){let Y=J.charCodeAt(z);z+=Y>=55296&&Y<=56319&&z+1<$?2:1}return V<Q}function AQ(J){let Q=0,Z=J.length;while(Q<Z&&g(J.charCodeAt(Q)))Q++;return J.charCodeAt(Q)===91&&J.charCodeAt(Q+1)===215}function PQ(J){let Q=[],Z=-1,$=!0,V=!1,z=!1,Y=!0;for(let G=0;G<J.length;){let H=J.codePointAt(G),K=H>65535?2:1,B=g(H);if(Y){if(Y=!1,B)Q.push("<v>")}if(B){if(Z>=0)Q.push($?J.slice(Z,G):"<v>"),Z=-1;z=!0}else{if(V=!0,z=!1,Z<0)Z=G,$=!0;if($){let U=H|32;if(U<97||U>122||H>127)$=!1}}G+=K}if(Z>=0)Q.push($?J.slice(Z):"<v>");if(V&&z)Q.push("<v>");return Q.join(" ")}function bJ(J,Q){let Z=0,$=0,V=J.length;while(Z<V&&$<Q){let z=J.charCodeAt(Z);Z+=z>=55296&&z<=56319&&Z+1<V?2:1,$++}return Z>=V?J:J.slice(0,Z)}var _Q=/[.*+?^${}()|[\]\\]/g;function m(J,Q=null,Z=2){let $=Z,z=J.replace(BQ,"").split(`
4
- `),Y=z.length,G=Y,H=Array(Y),K=Array(Y),B=0;for(let D=0;D<Y;D++){H[D]=IQ(z[D]);let M=qJ.test(z[D]);if(K[D]=M,M)B++}let U=[],X=[],O=[],q=0,F=0;while(F<Y){let D=0,M=0;for(let W=1;W<=jQ&&F+2*W<=Y;W++){if(K[F+W-1])break;let j=1;J:for(;;){let N=F+j*W;if(N+W>Y)break;for(let _=0;_<W;_++)if(K[N+_]||H[N+_]!==H[F+_])break J;j++}if(j>=NQ&&W*(j-1)>D*(M>0?M-1:0))D=W,M=j}if(D>0){for(let W=F;W<F+D;W++)U.push(z[W]),X.push(K[W]),O.push(H[W]);U.push(D===1?` [×${M} similar]`:` [×${M} similar ${D}-line blocks]`),X.push(!1),O.push(null),q++,F+=D*M}else U.push(z[F]),X.push(K[F]),O.push(H[F]),F++}let R=new Map,A=new Map,y=DJ+1,T=[],P=[],w=0,b=0;for(let D=0;D<U.length;D++){let M=U[D],W=X[D];if(W||AQ(M)||RQ(M,4)){T.push(M),P.push(W);continue}let j=O[D],N=R.get(j);if(N!==void 0){if(N.count++,N.count<=DJ)T.push(M),P.push(!1);else w++;continue}let _=PQ(j);R.set(j,{count:1,exemplar:M});let c=A.get(_);if(c!==void 0)if(c.count++,c.count<=y)T.push(M),P.push(!1);else b++;else A.set(_,{count:1,exemplar:M}),T.push(M),P.push(!1)}let h=[];for(let D of R.values())if(D.count>DJ)h.push([D.count,"exact",D.exemplar]);for(let D of A.values())if(D.count>y)h.push([D.count,"template",D.exemplar]);if(h.sort((D,M)=>M[0]-D[0]),h.length>yJ)h.length=yJ;if(w+b>0){let D=`── ${w+b} repeated lines suppressed (${w} exact ×N, ${b} same-template; first occurrences kept above) ──`;T.push(D),P.push(qJ.test(D));for(let[M,W,j]of h){let _=` ×${M}${W==="template"?" (template)":""} ${bJ(wJ(j),160)}`;T.push(_),P.push(qJ.test(_))}}let f;if(Q!=null){let D;try{D=new RegExp(Q,"i")}catch{D=new RegExp(Q.replace(_Q,"\\$&"),"i")}let M=T.length,W=new Uint8Array(M);for(let N=0;N<M;N++)if(P[N]||D.test(T[N])){let _=N>$?N-$:0,c=Math.min(N+$,M-1);W.fill(1,_,c+1)}f=[];let j=0;for(let N=0;N<M;N++)if(W[N]){if(j>0)f.push(`… ${j} lines omitted`),j=0;f.push(T[N])}else j++;if(j>0)f.push(`… ${j} lines omitted`)}else f=T;let EJ=f.join(`
5
- `),TJ=LJ.byteLength(J),CJ=LJ.byteLength(EJ),BJ=(1-CJ/TJ)*100,YQ=J.length===0?0:BJ<0?-Math.round(-BJ):Math.round(BJ),HQ=h.map(([D,M,W])=>({count:D,exemplar:bJ(wJ(W),160),kind:M})),KQ={collapsedRuns:q,importantKept:B,origChars:TJ,origLines:G,outChars:CJ,outLines:f.length,query:Q??null,savedPct:YQ,suppressedLines:w,templateSuppressed:b,topRepeats:HQ};return{distilled:EJ,stats:KQ}}var ZJ=[["none","none","passthrough (baseline)"],["whitespace","lossless","trailing whitespace + blank-line runs collapsed; safe for code"],["prose","light","L1 + prose lines: collapse spaces, cut redundant filler phrases (code/IDs protected)"],["dense","medium","L2 + prose: drop articles & intensifiers"],["caveman","heavy","L3 + prose: telegraphic — drop function words; gist only, NOT verbatim"]];var C=(J)=>new RegExp(`(?<![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])(?:${J})(?![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])`,"giu"),FJ=[[C("in order to"),"to"],[C("due to the fact that"),"because"],[C("at this point in time"),"now"],[C("in the event that"),"if"],[C("for the purpose of"),"for"],[C("with regard to"),"about"],[C("a large number of"),"many"],[C("it is important to note that"),""],[C("please note that"),""],[C("as a matter of fact"),""],[C("in terms of"),"for"],[C("the fact that"),"that"]],SQ=new RegExp("(?<![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])(?:the|an|a)[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+","giu"),EQ=new RegExp("(?<![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])(?:very|really|just|actually|basically|simply|quite|rather|essentially|literally)[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+","giu"),TQ=new RegExp("(?<![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])(?:is|are|was|were|am|be|been|being|do|does|did|have|has|had|will|would|shall|should|can|could|may|might|of|to|in|on|at|for|with|that|this|these|those|it|its|there|here)(?![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]*","giu"),vJ=/ {2,}/g,CQ=new RegExp("[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+([.,;:!?])","gu"),LQ=/\n{3,}/g;function MJ(J){if(J===32||J>=9&&J<=13)return!0;if(J<133)return!1;return J===133||J===160||J===5760||J>=8192&&J<=8202||J===8232||J===8233||J===8239||J===8287||J===12288}function yQ(J){let Q=0,Z=J.length;while(Q<Z){let $=J.codePointAt(Q);if(!MJ($))break;Q+=$>65535?2:1}while(Z>Q){let $=J.charCodeAt(Z-1);if(!MJ($))break;Z--}return Q===0&&Z===J.length?J:J.slice(Q,Z)}function wQ(J){if(J.length===0)return!1;let Q=J.charCodeAt(0);if(Q===32||Q===9)return!0;let Z=0,$=0,V=0,z=!1;for(let Y=0;Y<J.length;){let G=J.codePointAt(Y);if(Y+=G>65535?2:1,Z++,MJ(G)){V=0;continue}if(V++,V>=24)z=!0;if(G>=48&&G<=57||G>=65&&G<=90||G>=97&&G<=122)continue;switch(G){case 46:case 44:case 59:case 58:case 39:case 34:case 33:case 63:case 40:case 41:case 45:continue}$++}if($/Z>0.3)return!0;return z}function bQ(J,Q){let Z=J.replace(vJ," ");for(let V=0;V<FJ.length;V++)Z=Z.replace(FJ[V][0],FJ[V][1]);if(Q>=3)Z=Z.replace(SQ,""),Z=Z.replace(EQ,"");if(Q>=4)Z=Z.replace(TQ,"");Z=Z.replace(vJ," "),Z=yQ(Z.replace(CQ,"$1"));let $=Z.charCodeAt(0);if($>=97&&$<=122)Z=String.fromCharCode($-32)+Z.slice(1);return Z}function o(J,Q){let Z=Math.min(Q,4);if(Z===0)return{compressed:J,protectedLines:0,level:Z};let $=0,V=J.split(`
6
- `),z=Array(V.length);for(let G=0;G<V.length;G++){let H=V[G],K=H.length;while(K>0){let U=H.charCodeAt(K-1);if(U!==32&&U!==9)break;K--}let B=K===H.length?H:H.slice(0,K);if(Z===1){z[G]=B;continue}if(wQ(B)){$++,z[G]=B;continue}z[G]=bQ(B,Z)}return{compressed:z.join(`
7
- `).replace(LQ,`
2
+ import{readFileSync as FZ,mkdirSync as MZ,writeFileSync as WZ}from"node:fs";import S from"node:process";function XQ(J){if(J===32||J>=9&&J<=13)return!0;if(J<133)return!1;return J===133||J===160||J===5760||J>=8192&&J<=8202||J===8232||J===8233||J===8239||J===8287||J===12288}function qJ(J){let Q=0;for(let Z=0;Z<J.length;Z++){let $=J.charCodeAt(Z);if($>=55296&&$<=56319&&Z+1<J.length){let V=J.charCodeAt(Z+1);if(V>=56320&&V<=57343)Z++}Q++}return Q}function BQ(J,Q){let Z=0,$=0;while(Z<J.length&&$<Q.length){let V=J.codePointAt(Z),z=Q.codePointAt($);if(V!==z)return V-z;Z+=V>65535?2:1,$+=z>65535?2:1}return J.length-Z-(Q.length-$)}function i(J){let Q=new Map,Z=(U)=>{Q.set(U,(Q.get(U)??0)+1)},$=(U)=>{if(qJ(U)>=12)Z(U);if(U.includes("/")){let O=U.split("/"),q="";for(let F=0;F<O.length;F++){if(F>0)q+="/";if(q+=O[F],F>=2){let R=q+"/";if(qJ(R)>=12)Z(R)}}}},V=-1;for(let U=0;U<J.length;){let O=J.codePointAt(U),q=O>65535?2:1;if(XQ(O)){if(V>=0)$(J.slice(V,U)),V=-1}else if(V<0)V=U;U+=q}if(V>=0)$(J.slice(V));let z=[];for(let[U,O]of Q)if(O>=3){let q=qJ(U);z.push({k:U,c:O,len:q,saved:(q-1)*O})}z.sort((U,O)=>O.saved-U.saved||BQ(U.k,O.k));let Y=[];for(let U of"§¤¢£¥µ¶ª°±¬×÷ØÞßæðøþ¡¿")if(!J.includes(U))Y.push(U);let G=[],H=[];for(let{k:U,c:O,len:q}of z){if(G.length>=Y.length)break;if((q-1)*O<=q+3)continue;let F=!1;for(let A=0;A<H.length;A++){let y=H[A];if(y.startsWith(U)||U.startsWith(y)){F=!0;break}}if(F)continue;let R=Y[G.length];H.push(U),G.push({sig:R,val:U,len:q})}if(G.length===0)return{text:J,entries:0};let K=G.map((U,O)=>O);K.sort((U,O)=>G[O].len-G[U].len);let B=J;for(let U=0;U<K.length;U++){let{sig:O,val:q}=G[K[U]];B=B.replaceAll(q,O)}let X=`
3
+ ·legend· `;for(let U=0;U<G.length;U++)X+=G[U].sig+"="+G[U].val+" ";return B+=X.slice(0,-1),{text:B,entries:G.length}}import{Buffer as wJ}from"node:buffer";var OQ=/\x1b\[[0-9;]*[A-Za-z]/g,DJ=/\b([0-9A-Za-z_]*(error|exception)s?|err|warn(ing)?s?|fail(s|ed|ure|ures)?|panic(s|ked)?|fatal|critical|traceback|denied|refused|timeouts?|timed.?out|assert(s|ed|ion|ions)?|segfault(s|ed)?)\b/i,qQ=/[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9]{2}:[0-9]{2}:[0-9]{2}([.,][0-9]+)?(Z|[+-][0-9]{2}:?[0-9]{2})?/g,DQ=/\b[0-9]{2}:[0-9]{2}:[0-9]{2}([.,][0-9]+)?\b/g,FQ=/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi,MQ=/\b[0-9a-f]{7,64}\b/gi,WQ=/\b[0-9]+(\.[0-9]+)?[ \t\u00a0]?(ms|us|µs|ns|s|m|h|%|[KMGT]i?B)?\b/gi,NQ=/[0-9a-f]/i,jQ=3,IQ=8,FJ=2,vJ=40;function RQ(J){if(!NQ.test(J))return J;let Q=J.replace(qQ,"<ts>");return Q=Q.replace(DQ,"<time>"),Q=Q.replace(FQ,"<uuid>"),Q=Q.replace(MQ,"<hex>"),Q.replace(WQ,"<n>")}function d(J){if(J===32)return!0;if(J<9)return!1;if(J<=13)return!0;if(J<133)return!1;return J===133||J===160||J===5760||J>=8192&&J<=8202||J===8232||J===8233||J===8239||J===8287||J===12288}function bJ(J){let Q=0,Z=J.length;while(Q<Z&&d(J.charCodeAt(Q)))Q++;while(Z>Q&&d(J.charCodeAt(Z-1)))Z--;return Q===0&&Z===J.length?J:J.slice(Q,Z)}function AQ(J,Q){let Z=0,$=J.length;while(Z<$&&d(J.charCodeAt(Z)))Z++;while($>Z&&d(J.charCodeAt($-1)))$--;let V=0;for(let z=Z;z<$&&V<Q;V++){let Y=J.charCodeAt(z);z+=Y>=55296&&Y<=56319&&z+1<$?2:1}return V<Q}function PQ(J){let Q=0,Z=J.length;while(Q<Z&&d(J.charCodeAt(Q)))Q++;return J.charCodeAt(Q)===91&&J.charCodeAt(Q+1)===215}function _Q(J){let Q=[],Z=-1,$=!0,V=!1,z=!1,Y=!0;for(let G=0;G<J.length;){let H=J.codePointAt(G),K=H>65535?2:1,B=d(H);if(Y){if(Y=!1,B)Q.push("<v>")}if(B){if(Z>=0)Q.push($?J.slice(Z,G):"<v>"),Z=-1;z=!0}else{if(V=!0,z=!1,Z<0)Z=G,$=!0;if($){let X=H|32;if(X<97||X>122||H>127)$=!1}}G+=K}if(Z>=0)Q.push($?J.slice(Z):"<v>");if(V&&z)Q.push("<v>");return Q.join(" ")}function xJ(J,Q){let Z=0,$=0,V=J.length;while(Z<V&&$<Q){let z=J.charCodeAt(Z);Z+=z>=55296&&z<=56319&&Z+1<V?2:1,$++}return Z>=V?J:J.slice(0,Z)}var SQ=/[.*+?^${}()|[\]\\]/g;function b(J,Q=null,Z=2){let $=Z,z=J.replace(OQ,"").split(`
4
+ `),Y=z.length,G=Y,H=Array(Y),K=Array(Y),B=0;for(let D=0;D<Y;D++){H[D]=RQ(z[D]);let M=DJ.test(z[D]);if(K[D]=M,M)B++}let X=[],U=[],O=[],q=0,F=0;while(F<Y){let D=0,M=0;for(let W=1;W<=IQ&&F+2*W<=Y;W++){if(K[F+W-1])break;let j=1;J:for(;;){let N=F+j*W;if(N+W>Y)break;for(let _=0;_<W;_++)if(K[N+_]||H[N+_]!==H[F+_])break J;j++}if(j>=jQ&&W*(j-1)>D*(M>0?M-1:0))D=W,M=j}if(D>0){for(let W=F;W<F+D;W++)X.push(z[W]),U.push(K[W]),O.push(H[W]);X.push(D===1?` [×${M} similar]`:` [×${M} similar ${D}-line blocks]`),U.push(!1),O.push(null),q++,F+=D*M}else X.push(z[F]),U.push(K[F]),O.push(H[F]),F++}let R=new Map,A=new Map,y=FJ+1,T=[],P=[],w=0,v=0;for(let D=0;D<X.length;D++){let M=X[D],W=U[D];if(W||PQ(M)||AQ(M,4)){T.push(M),P.push(W);continue}let j=O[D],N=R.get(j);if(N!==void 0){if(N.count++,N.count<=FJ)T.push(M),P.push(!1);else w++;continue}let _=_Q(j);R.set(j,{count:1,exemplar:M});let o=A.get(_);if(o!==void 0)if(o.count++,o.count<=y)T.push(M),P.push(!1);else v++;else A.set(_,{count:1,exemplar:M}),T.push(M),P.push(!1)}let f=[];for(let D of R.values())if(D.count>FJ)f.push([D.count,"exact",D.exemplar]);for(let D of A.values())if(D.count>y)f.push([D.count,"template",D.exemplar]);if(f.sort((D,M)=>M[0]-D[0]),f.length>vJ)f.length=vJ;if(w+v>0){let D=`── ${w+v} repeated lines suppressed (${w} exact ×N, ${v} same-template; first occurrences kept above) ──`;T.push(D),P.push(DJ.test(D));for(let[M,W,j]of f){let _=` ×${M}${W==="template"?" (template)":""} ${xJ(bJ(j),160)}`;T.push(_),P.push(DJ.test(_))}}let m;if(Q!=null){let D;try{D=new RegExp(Q,"i")}catch{D=new RegExp(Q.replace(SQ,"\\$&"),"i")}let M=T.length,W=new Uint8Array(M);for(let N=0;N<M;N++)if(P[N]||D.test(T[N])){let _=N>$?N-$:0,o=Math.min(N+$,M-1);W.fill(1,_,o+1)}m=[];let j=0;for(let N=0;N<M;N++)if(W[N]){if(j>0)m.push(`… ${j} lines omitted`),j=0;m.push(T[N])}else j++;if(j>0)m.push(`… ${j} lines omitted`)}else m=T;let CJ=m.join(`
5
+ `),LJ=wJ.byteLength(J),yJ=wJ.byteLength(CJ),OJ=(1-yJ/LJ)*100,HQ=J.length===0?0:OJ<0?-Math.round(-OJ):Math.round(OJ),KQ=f.map(([D,M,W])=>({count:D,exemplar:xJ(bJ(W),160),kind:M})),UQ={collapsedRuns:q,importantKept:B,origChars:LJ,origLines:G,outChars:yJ,outLines:m.length,query:Q??null,savedPct:HQ,suppressedLines:w,templateSuppressed:v,topRepeats:KQ};return{distilled:CJ,stats:UQ}}var ZJ=[["none","none","passthrough (baseline)"],["whitespace","lossless","trailing whitespace + blank-line runs collapsed; safe for code"],["prose","light","L1 + prose lines: collapse spaces, cut redundant filler phrases (code/IDs protected)"],["dense","medium","L2 + prose: drop articles & intensifiers"],["caveman","heavy","L3 + prose: telegraphic — drop function words; gist only, NOT verbatim"]];var C=(J)=>new RegExp(`(?<![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])(?:${J})(?![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])`,"giu"),MJ=[[C("in order to"),"to"],[C("due to the fact that"),"because"],[C("at this point in time"),"now"],[C("in the event that"),"if"],[C("for the purpose of"),"for"],[C("with regard to"),"about"],[C("a large number of"),"many"],[C("it is important to note that"),""],[C("please note that"),""],[C("as a matter of fact"),""],[C("in terms of"),"for"],[C("the fact that"),"that"]],EQ=new RegExp("(?<![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])(?:the|an|a)[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+","giu"),TQ=new RegExp("(?<![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])(?:very|really|just|actually|basically|simply|quite|rather|essentially|literally)[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+","giu"),CQ=new RegExp("(?<![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])(?:is|are|was|were|am|be|been|being|do|does|did|have|has|had|will|would|shall|should|can|could|may|might|of|to|in|on|at|for|with|that|this|these|those|it|its|there|here)(?![\\p{Alphabetic}\\p{Mark}\\p{Decimal_Number}\\p{Connector_Punctuation}\\p{Join_Control}])[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]*","giu"),kJ=/ {2,}/g,LQ=new RegExp("[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+([.,;:!?])","gu"),yQ=/\n{3,}/g;function WJ(J){if(J===32||J>=9&&J<=13)return!0;if(J<133)return!1;return J===133||J===160||J===5760||J>=8192&&J<=8202||J===8232||J===8233||J===8239||J===8287||J===12288}function wQ(J){let Q=0,Z=J.length;while(Q<Z){let $=J.codePointAt(Q);if(!WJ($))break;Q+=$>65535?2:1}while(Z>Q){let $=J.charCodeAt(Z-1);if(!WJ($))break;Z--}return Q===0&&Z===J.length?J:J.slice(Q,Z)}function vQ(J){if(J.length===0)return!1;let Q=J.charCodeAt(0);if(Q===32||Q===9)return!0;let Z=0,$=0,V=0,z=!1;for(let Y=0;Y<J.length;){let G=J.codePointAt(Y);if(Y+=G>65535?2:1,Z++,WJ(G)){V=0;continue}if(V++,V>=24)z=!0;if(G>=48&&G<=57||G>=65&&G<=90||G>=97&&G<=122)continue;switch(G){case 46:case 44:case 59:case 58:case 39:case 34:case 33:case 63:case 40:case 41:case 45:continue}$++}if($/Z>0.3)return!0;return z}function bQ(J,Q){let Z=J.replace(kJ," ");for(let V=0;V<MJ.length;V++)Z=Z.replace(MJ[V][0],MJ[V][1]);if(Q>=3)Z=Z.replace(EQ,""),Z=Z.replace(TQ,"");if(Q>=4)Z=Z.replace(CQ,"");Z=Z.replace(kJ," "),Z=wQ(Z.replace(LQ,"$1"));let $=Z.charCodeAt(0);if($>=97&&$<=122)Z=String.fromCharCode($-32)+Z.slice(1);return Z}function r(J,Q){let Z=Math.min(Q,4);if(Z===0)return{compressed:J,protectedLines:0,level:Z};let $=0,V=J.split(`
6
+ `),z=Array(V.length);for(let G=0;G<V.length;G++){let H=V[G],K=H.length;while(K>0){let X=H.charCodeAt(K-1);if(X!==32&&X!==9)break;K--}let B=K===H.length?H:H.slice(0,K);if(Z===1){z[G]=B;continue}if(vQ(B)){$++,z[G]=B;continue}z[G]=bQ(B,Z)}return{compressed:z.join(`
7
+ `).replace(yQ,`
8
8
 
9
- `),protectedLines:$,level:Z}}import{appendFileSync as GZ,mkdirSync as YZ}from"node:fs";import{dirname as HZ}from"node:path";import sJ from"node:http";import KZ from"node:https";import{URL as UZ}from"node:url";import{readFileSync as NJ}from"node:fs";import{inflateSync as vQ}from"node:zlib";//! Full-BMP glyph atlas (Spleen 5x8 for ASCII/code + Unifont fallback),
9
+ `),protectedLines:$,level:Z}}import{appendFileSync as YZ,mkdirSync as HZ}from"node:fs";import{dirname as KZ}from"node:path";import eJ from"node:http";import UZ from"node:https";import{URL as XZ}from"node:url";import{readFileSync as jJ}from"node:fs";import{inflateSync as xQ}from"node:zlib";//! Full-BMP glyph atlas (Spleen 5x8 for ASCII/code + Unifont fallback),
10
10
  //! extracted from pxpipe's generated gray atlas by `tools/gen-glyphs.mjs`.
11
11
  //!
12
12
  //! Codepoints + wide flags load eagerly (~175 KB, needed for wrap math);
13
13
  //! coverage pixels stay zlib-packed on disk and decompress lazily on the
14
14
  //! first blit, so `estimate` never pays for them.
15
- var v=5,i=8,u=NJ(new URL("../assets/glyphs.cps",import.meta.url)),$J=NJ(new URL("../assets/glyphs.wide",import.meta.url)),xJ=(()=>{let J=u.byteLength>>>2;if((u.byteOffset&3)===0)return new Uint32Array(u.buffer,u.byteOffset,J);return new Uint32Array(u.buffer.slice(u.byteOffset,u.byteOffset+(J<<2)))})(),xQ=(()=>{let J=new Uint32Array($J.length),Q=0;for(let Z=0;Z<$J.length;Z++)J[Z]=Q,Q+=($J[Z]===1?2*v:v)*i;return J})(),WJ=null;function r(J){let Q=0,Z=xJ.length-1;while(Q<=Z){let $=Q+Z>>>1,V=xJ[$];if(V<J)Q=$+1;else if(V>J)Z=$-1;else return $}return-1}function d(J){return $J[J]===1}function jJ(J){if(WJ===null)WJ=vQ(NJ(new URL("../assets/glyphs.pix.z",import.meta.url)));let Q=xQ[J],Z=d(J)?2*v:v;return WJ.subarray(Q,Q+Z*i)}function kJ(J,Q,Z){let $=jJ(J),V=d(J)?2*v:v;return kQ($,V,i,Q,Z)}function kQ(J,Q,Z,$,V){let z=new Uint8Array($*V),Y=Q/$,G=Z/V;for(let H=0;H<V;H++){let K=H*G,B=(H+1)*G;for(let U=0;U<$;U++){let X=U*Y,O=(U+1)*Y,q=0,F=0,R=Math.min(Math.ceil(B),Z);for(let A=Math.floor(K);A<R;A++){let y=Math.min(A+1,B)-Math.max(A,K);if(y<=0)continue;let T=Math.min(Math.ceil(O),Q);for(let P=Math.floor(X);P<T;P++){let w=Math.min(P+1,O)-Math.max(P,X);if(w<=0)continue;let b=w*y;q+=b*J[A*Q+P],F+=b}}z[H*$+U]=F>0?Math.min(255,Math.max(0,Math.round(q/F))):0}}return z}import{deflateSync as hQ}from"node:zlib";//! Minimal grayscale PNG encoder (bit depth 8, color type 0), mirroring
15
+ var x=5,a=8,u=jJ(new URL("../assets/glyphs.cps",import.meta.url)),$J=jJ(new URL("../assets/glyphs.wide",import.meta.url)),hJ=(()=>{let J=u.byteLength>>>2;if((u.byteOffset&3)===0)return new Uint32Array(u.buffer,u.byteOffset,J);return new Uint32Array(u.buffer.slice(u.byteOffset,u.byteOffset+(J<<2)))})(),kQ=(()=>{let J=new Uint32Array($J.length),Q=0;for(let Z=0;Z<$J.length;Z++)J[Z]=Q,Q+=($J[Z]===1?2*x:x)*a;return J})(),NJ=null;function t(J){let Q=0,Z=hJ.length-1;while(Q<=Z){let $=Q+Z>>>1,V=hJ[$];if(V<J)Q=$+1;else if(V>J)Z=$-1;else return $}return-1}function l(J){return $J[J]===1}function IJ(J){if(NJ===null)NJ=xQ(jJ(new URL("../assets/glyphs.pix.z",import.meta.url)));let Q=kQ[J],Z=l(J)?2*x:x;return NJ.subarray(Q,Q+Z*a)}function fJ(J,Q,Z){let $=IJ(J),V=l(J)?2*x:x;return hQ($,V,a,Q,Z)}function hQ(J,Q,Z,$,V){let z=new Uint8Array($*V),Y=Q/$,G=Z/V;for(let H=0;H<V;H++){let K=H*G,B=(H+1)*G;for(let X=0;X<$;X++){let U=X*Y,O=(X+1)*Y,q=0,F=0,R=Math.min(Math.ceil(B),Z);for(let A=Math.floor(K);A<R;A++){let y=Math.min(A+1,B)-Math.max(A,K);if(y<=0)continue;let T=Math.min(Math.ceil(O),Q);for(let P=Math.floor(U);P<T;P++){let w=Math.min(P+1,O)-Math.max(P,U);if(w<=0)continue;let v=w*y;q+=v*J[A*Q+P],F+=v}}z[H*$+X]=F>0?Math.min(255,Math.max(0,Math.round(q/F))):0}}return z}import{deflateSync as fQ}from"node:zlib";//! Minimal grayscale PNG encoder (bit depth 8, color type 0), mirroring
16
16
  //! pxpipe's png.ts: IHDR + one IDAT (zlib) + IEND, filter byte 0 per row.
17
- var fQ=(()=>{let J=new Uint32Array(256);for(let Q=0;Q<256;Q++){let Z=Q;for(let $=0;$<8;$++)Z=(Z&1)!==0?3988292384^Z>>>1:Z>>>1;J[Q]=Z>>>0}return J})();function IJ(J,Q,Z,$){let V=$.length;J[Q]=V>>>24&255,J[Q+1]=V>>>16&255,J[Q+2]=V>>>8&255,J[Q+3]=V&255;let z=Q+4;for(let H=0;H<4;H++)J[z+H]=Z.charCodeAt(H);J.set($,z+4);let Y=4294967295;for(let H=z;H<z+4+V;H++)Y=fQ[(Y^J[H])&255]^Y>>>8;Y=~Y>>>0;let G=z+4+V;return J[G]=Y>>>24&255,J[G+1]=Y>>>16&255,J[G+2]=Y>>>8&255,J[G+3]=Y&255,G+4}function hJ(J,Q,Z){let $=new Uint8Array(Z*(Q+1));for(let H=0,K=0,B=0;H<Z;H++)$[K++]=0,$.set(J.subarray(B,B+Q),K),K+=Q,B+=Q;let V=hQ($,{level:6}),z=new Uint8Array(33+(12+V.length)+12);z[0]=137,z[1]=80,z[2]=78,z[3]=71,z[4]=13,z[5]=10,z[6]=26,z[7]=10;let Y=new Uint8Array(13);Y[0]=Q>>>24&255,Y[1]=Q>>>16&255,Y[2]=Q>>>8&255,Y[3]=Q&255,Y[4]=Z>>>24&255,Y[5]=Z>>>16&255,Y[6]=Z>>>8&255,Y[7]=Z&255,Y[8]=8,Y[9]=0;let G=IJ(z,8,"IHDR",Y);return G=IJ(z,G,"IDAT",V),IJ(z,G,"IEND",new Uint8Array(0)),z}//! Stage 2: the `pxpipe` imaging engine, ported from pxpipe's render.ts
17
+ var mQ=(()=>{let J=new Uint32Array(256);for(let Q=0;Q<256;Q++){let Z=Q;for(let $=0;$<8;$++)Z=(Z&1)!==0?3988292384^Z>>>1:Z>>>1;J[Q]=Z>>>0}return J})();function RJ(J,Q,Z,$){let V=$.length;J[Q]=V>>>24&255,J[Q+1]=V>>>16&255,J[Q+2]=V>>>8&255,J[Q+3]=V&255;let z=Q+4;for(let H=0;H<4;H++)J[z+H]=Z.charCodeAt(H);J.set($,z+4);let Y=4294967295;for(let H=z;H<z+4+V;H++)Y=mQ[(Y^J[H])&255]^Y>>>8;Y=~Y>>>0;let G=z+4+V;return J[G]=Y>>>24&255,J[G+1]=Y>>>16&255,J[G+2]=Y>>>8&255,J[G+3]=Y&255,G+4}function mJ(J,Q,Z){let $=new Uint8Array(Z*(Q+1));for(let H=0,K=0,B=0;H<Z;H++)$[K++]=0,$.set(J.subarray(B,B+Q),K),K+=Q,B+=Q;let V=fQ($,{level:6}),z=new Uint8Array(33+(12+V.length)+12);z[0]=137,z[1]=80,z[2]=78,z[3]=71,z[4]=13,z[5]=10,z[6]=26,z[7]=10;let Y=new Uint8Array(13);Y[0]=Q>>>24&255,Y[1]=Q>>>16&255,Y[2]=Q>>>8&255,Y[3]=Q&255,Y[4]=Z>>>24&255,Y[5]=Z>>>16&255,Y[6]=Z>>>8&255,Y[7]=Z&255,Y[8]=8,Y[9]=0;let G=RJ(z,8,"IHDR",Y);return G=RJ(z,G,"IDAT",V),RJ(z,G,"IEND",new Uint8Array(0)),z}//! Stage 2: the `pxpipe` imaging engine, ported from pxpipe's render.ts
18
18
  //! production dense path (bare 5x8 AA cell, 312 cols, 1568x728 pages).
19
19
  //! Glyphs cover BMP (pxpipe's atlas: Spleen for ASCII/code, Unifont fallback)
20
20
  //! PLUS the astral planes (unifont_upper, incl. emoji) — beyond pxpipe, which
@@ -29,19 +29,19 @@ var fQ=(()=>{let J=new Uint32Array(256);for(let Q=0;Q<256;Q++){let Z=Q;for(let $
29
29
  //! * `font` — `Tiny` renders the same atlas box-filtered into a 4x6 cell
30
30
  //! (390 cols x 120 rows/page), ~40% fewer image-tokens; opt-in,
31
31
  //! transcription-accuracy gated.
32
- var a=4,VJ=4,mQ=1568,uQ=728,zJ="↵",gQ="⏎",GJ="→",dQ="⇢",RJ="⇥",lQ="⇨",pQ=9647,fJ=4,nQ=3,mJ="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",cQ=/\n{4,}/g;function t(J){return J.length===4&&J.replace(/[A-Z]/g,(Q)=>Q.toLowerCase())==="tiny"?"tiny":"normal"}function lJ(J){let Q=J==="tiny"?4:v,Z=J==="tiny"?6:i,$=Math.floor((mQ-2*a)/Q),V=Math.floor((uQ-2*VJ)/Z);return{cw:Q,ch:Z,cols:$,maxLines:V,maxChars:$*V}}var oQ=(()=>{let J=new Uint8Array(128);for(let Q=0;Q<128;Q++){let Z=r(Q);J[Q]=Z>=0&&d(Z)?2:1}return J})();function AJ(J){if(J<128)return oQ[J];let Q=r(J);return Q>=0&&d(Q)?2:1}function iQ(J){let Q=0;for(let Z=0;Z<J.length;Z++){let $=J.charCodeAt(Z);if($>=55296&&$<56320&&Z+1<J.length){let V=J.charCodeAt(Z+1);if(V>=56320&&V<57344)Z++}Q++}return Q}function PJ(J){let Q=J.split(`
32
+ var s=4,VJ=4,uQ=1568,gQ=728,zJ="↵",dQ="⏎",GJ="→",lQ="⇢",AJ="⇥",pQ="⇨",nQ=9647,uJ=4,cQ=3,gJ="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",oQ=/\n{4,}/g;function g(J){return J.length===4&&J.replace(/[A-Z]/g,(Q)=>Q.toLowerCase())==="tiny"?"tiny":"normal"}function nJ(J){let Q=J==="tiny"?4:x,Z=J==="tiny"?6:a,$=Math.floor((uQ-2*s)/Q),V=Math.floor((gQ-2*VJ)/Z);return{cw:Q,ch:Z,cols:$,maxLines:V,maxChars:$*V}}var iQ=(()=>{let J=new Uint8Array(128);for(let Q=0;Q<128;Q++){let Z=t(Q);J[Q]=Z>=0&&l(Z)?2:1}return J})();function PJ(J){if(J<128)return iQ[J];let Q=t(J);return Q>=0&&l(Q)?2:1}function rQ(J){let Q=0;for(let Z=0;Z<J.length;Z++){let $=J.charCodeAt(Z);if($>=55296&&$<56320&&Z+1<J.length){let V=J.charCodeAt(Z+1);if(V>=56320&&V<57344)Z++}Q++}return Q}function _J(J){let Q=J.split(`
33
33
  `);for(let Z=0;Z<Q.length;Z++){let $=Q[Z],V=$.length;while(V>0){let z=$.charCodeAt(V-1);if(z!==32&&z!==9)break;V--}if(V!==$.length)Q[Z]=$.slice(0,V)}return Q.join(`
34
- `).replace(cQ,`
34
+ `).replace(oQ,`
35
35
 
36
36
 
37
- `)}function pJ(J){if(!J.includes("\t"))return J;let Q="",Z=0;for(let $ of J)if($==="\t"){let V=fJ-Z%fJ;Q+=GJ;for(let z=1;z<V;z++)Q+=" ";Z+=V}else Q+=$,Z+=AJ($.codePointAt(0));return Q}function rQ(J){let Q=J.includes("\t")?J.replaceAll("\t",GJ):J,Z=0;while(Z<Q.length&&Q.charCodeAt(Z)===32)Z++;if(Z>=nQ&&Z<mJ.length)return RJ+mJ[Z]+Q.slice(Z);return Q}function nJ(J){return J.includes(zJ)?J.replaceAll(zJ,gQ):J}function aQ(J){let Q=nJ(J);if(Q.includes(GJ))Q=Q.replaceAll(GJ,dQ);if(Q.includes(RJ))Q=Q.replaceAll(RJ,lQ);return Q}function tQ(J){let Q=PJ(J).split(`
38
- `);for(let Z=0;Z<Q.length;Z++)Q[Z]=pJ(Q[Z]);return Q.join(zJ)}function sQ(J){let Q=PJ(J).split(`
39
- `);for(let Z=0;Z<Q.length;Z++)Q[Z]=rQ(Q[Z]);return Q.join(zJ)}function eQ(J,Q){let Z=[],$=PJ(J);for(let V of $.split(`
40
- `)){let z=pJ(V);if(z.length===0){Z.push("");continue}let Y="",G=0;for(let H of z){let K=AJ(H.codePointAt(0));if(G+K>Q)Z.push(Y),Y=H,G=K;else Y+=H,G+=K}if(Y.length!==0)Z.push(Y)}return Z}function JZ(J,Q,Z){let $=[],V=[],z=0;for(let Y of J){let G=iQ(Y),H=G+(V.length!==0?1:0);if(V.length!==0&&(V.length>=Q||z+H>Z))$.push(V),V=[],z=0;z+=G+(V.length!==0?1:0),V.push(Y)}if(V.length!==0)$.push(V);return $}function cJ(J){if(J<32)return!0;if(J>=127&&J<=159)return!0;if(J>=768&&J<=879)return!0;if(J===8203||J===8204||J===8205||J===8288||J===65279)return!0;if(J>=65024&&J<=65039)return!0;if(J>=917760&&J<=917999)return!0;return!1}function QZ(J){let Q=null,Z=0;for(let $ of J){let V=$.codePointAt(0);if(r(V)<0&&!cJ(V)){if(Q===null)Q=J.slice(0,Z);Q+=`[U+${V.toString(16).toUpperCase()}]`}else if(Q!==null)Q+=$;Z+=$.length}return Q??J}function oJ(J,Q,Z,$){let V=Q?Z?sQ(aQ(J)):tQ(nJ(J)):J;return JZ(eQ(QZ(V),$.cols),$.maxLines,$.maxChars)}function iJ(J,Q,Z){if(!Z)return 2*a+Q.cols*Q.cw;let $=0;for(let Y of J){let G=0;for(let H of Y)G+=AJ(H.codePointAt(0));if(G>Q.cols)G=Q.cols;if(G>$)$=G}let V=2*a+$*Q.cw,z=2*a+Q.cw;return V>z?V:z}var uJ=new Map;function gJ(J,Q,Z,$,V,z,Y){let G=r(V);if(G<0)return 0;let H=d(G),K=H?2*z.cw:z.cw,B;if(Y==="normal")B=jJ(G);else{let U=uJ.get(G);if(U===void 0)U=kJ(G,K,z.ch),uJ.set(G,U);B=U}for(let U=0;U<z.ch;U++){let X=($+U)*Q+Z,O=U*K;for(let q=0;q<K;q++){let F=B[O+q];if(F>0){let R=X+q;if(F>J[R])J[R]=F}}}return H?2:1}function ZZ(J,Q,Z,$){let V=iJ(J,Q,Z),z=2*VJ+J.length*Q.ch,Y=new Uint8Array(V*z),G=0;for(let H=0;H<J.length;H++){let K=VJ+H*Q.ch,B=0;for(let U of J[H]){if(B>=Q.cols)break;let X=a+B*Q.cw,O=gJ(Y,V,X,K,U.codePointAt(0),Q,$);if(O===0){if(O=1,cJ(U.codePointAt(0)));else if(G++,U!==" ")gJ(Y,V,X,K,pQ,Q,$)}B+=O}}for(let H=0;H<Y.length;H++)Y[H]=255-Y[H];return{png:hJ(Y,V,z),width:V,height:z,dropped:G}}function l(J,Q,Z,$){let V=lJ($),z=oJ(J,Q,Z,V).map((K)=>ZZ(K,V,Z,$)),Y=0,G=0,H=0;for(let K of z)Y+=K.width*K.height,G+=aJ(K.width,K.height),H+=K.dropped;return{pages:z,pixels:Y,tokens:G,dropped:H}}function rJ(J,Q,Z,$){let V=lJ($),z=oJ(J,Q,Z,V),Y=0,G=0;for(let H of z){let K=iJ(H,V,Z),B=2*VJ+H.length*V.ch;Y+=K*B,G+=aJ(K,B)}return{pages:z.length,pixels:Y,tokens:G}}var dJ=28;function aJ(J,Q){return Math.ceil(J/dJ)*Math.ceil(Q/dJ)}import{readFileSync as $Z}from"node:fs";import{join as VZ}from"node:path";//! pxpipe measurement-log summary (~/.pxpipe/events.jsonl), same math as the
37
+ `)}function cJ(J){if(!J.includes("\t"))return J;let Q="",Z=0;for(let $ of J)if($==="\t"){let V=uJ-Z%uJ;Q+=GJ;for(let z=1;z<V;z++)Q+=" ";Z+=V}else Q+=$,Z+=PJ($.codePointAt(0));return Q}function aQ(J){let Q=J.includes("\t")?J.replaceAll("\t",GJ):J,Z=0;while(Z<Q.length&&Q.charCodeAt(Z)===32)Z++;if(Z>=cQ&&Z<gJ.length)return AJ+gJ[Z]+Q.slice(Z);return Q}function oJ(J){return J.includes(zJ)?J.replaceAll(zJ,dQ):J}function tQ(J){let Q=oJ(J);if(Q.includes(GJ))Q=Q.replaceAll(GJ,lQ);if(Q.includes(AJ))Q=Q.replaceAll(AJ,pQ);return Q}function sQ(J){let Q=_J(J).split(`
38
+ `);for(let Z=0;Z<Q.length;Z++)Q[Z]=cJ(Q[Z]);return Q.join(zJ)}function eQ(J){let Q=_J(J).split(`
39
+ `);for(let Z=0;Z<Q.length;Z++)Q[Z]=aQ(Q[Z]);return Q.join(zJ)}function JZ(J,Q){let Z=[],$=_J(J);for(let V of $.split(`
40
+ `)){let z=cJ(V);if(z.length===0){Z.push("");continue}let Y="",G=0;for(let H of z){let K=PJ(H.codePointAt(0));if(G+K>Q)Z.push(Y),Y=H,G=K;else Y+=H,G+=K}if(Y.length!==0)Z.push(Y)}return Z}function QZ(J,Q,Z){let $=[],V=[],z=0;for(let Y of J){let G=rQ(Y),H=G+(V.length!==0?1:0);if(V.length!==0&&(V.length>=Q||z+H>Z))$.push(V),V=[],z=0;z+=G+(V.length!==0?1:0),V.push(Y)}if(V.length!==0)$.push(V);return $}function iJ(J){if(J<32)return!0;if(J>=127&&J<=159)return!0;if(J>=768&&J<=879)return!0;if(J===8203||J===8204||J===8205||J===8288||J===65279)return!0;if(J>=65024&&J<=65039)return!0;if(J>=917760&&J<=917999)return!0;return!1}function ZZ(J){let Q=null,Z=0;for(let $ of J){let V=$.codePointAt(0);if(t(V)<0&&!iJ(V)){if(Q===null)Q=J.slice(0,Z);Q+=`[U+${V.toString(16).toUpperCase()}]`}else if(Q!==null)Q+=$;Z+=$.length}return Q??J}function rJ(J,Q,Z,$){let V=Q?Z?eQ(tQ(J)):sQ(oJ(J)):J;return QZ(JZ(ZZ(V),$.cols),$.maxLines,$.maxChars)}function aJ(J,Q,Z){if(!Z)return 2*s+Q.cols*Q.cw;let $=0;for(let Y of J){let G=0;for(let H of Y)G+=PJ(H.codePointAt(0));if(G>Q.cols)G=Q.cols;if(G>$)$=G}let V=2*s+$*Q.cw,z=2*s+Q.cw;return V>z?V:z}var dJ=new Map;function lJ(J,Q,Z,$,V,z,Y){let G=t(V);if(G<0)return 0;let H=l(G),K=H?2*z.cw:z.cw,B;if(Y==="normal")B=IJ(G);else{let X=dJ.get(G);if(X===void 0)X=fJ(G,K,z.ch),dJ.set(G,X);B=X}for(let X=0;X<z.ch;X++){let U=($+X)*Q+Z,O=X*K;for(let q=0;q<K;q++){let F=B[O+q];if(F>0){let R=U+q;if(F>J[R])J[R]=F}}}return H?2:1}function $Z(J,Q,Z,$){let V=aJ(J,Q,Z),z=2*VJ+J.length*Q.ch,Y=new Uint8Array(V*z),G=0;for(let H=0;H<J.length;H++){let K=VJ+H*Q.ch,B=0;for(let X of J[H]){if(B>=Q.cols)break;let U=s+B*Q.cw,O=lJ(Y,V,U,K,X.codePointAt(0),Q,$);if(O===0){if(O=1,iJ(X.codePointAt(0)));else if(G++,X!==" ")lJ(Y,V,U,K,nQ,Q,$)}B+=O}}for(let H=0;H<Y.length;H++)Y[H]=255-Y[H];return{png:mJ(Y,V,z),width:V,height:z,dropped:G}}function p(J,Q,Z,$){let V=nJ($),z=rJ(J,Q,Z,V).map((K)=>$Z(K,V,Z,$)),Y=0,G=0,H=0;for(let K of z)Y+=K.width*K.height,G+=tJ(K.width,K.height),H+=K.dropped;return{pages:z,pixels:Y,tokens:G,dropped:H}}function YJ(J,Q,Z,$){let V=nJ($),z=rJ(J,Q,Z,V),Y=0,G=0;for(let H of z){let K=aJ(H,V,Z),B=2*VJ+H.length*V.ch;Y+=K*B,G+=tJ(K,B)}return{pages:z.length,pixels:Y,tokens:G}}var pJ=28;function tJ(J,Q){return Math.ceil(J/pJ)*Math.ceil(Q/pJ)}import{readFileSync as VZ}from"node:fs";import{join as zZ}from"node:path";//! pxpipe measurement-log summary (~/.pxpipe/events.jsonl), same math as the
41
41
  //! node MCP: actual = every way input bytes get billed (input + cache reads +
42
42
  //! cache creates) — ignoring cache_read would fake the savings.
43
- class s{value;constructor(J){this.value=J}}function _J(){let J=process.env.TANUKI_EVENTS;if(J!==void 0)return J;let Q=process.env.HOME??"";return VZ(Q,".pxpipe","events.jsonl")}function p(J){return typeof J==="number"&&Number.isSafeInteger(J)&&J>=0?J:0}function zZ(J){return J<0?-Math.round(-J):Math.round(J)}function tJ(){let J=_J(),Q;try{Q=$Z(J,"utf8")}catch{return{available:!1,note:`no ${J} yet`}}let Z=0,$=0,V=0,z=0,Y=0,G=0;for(let K of Q.split(`
44
- `)){if(K.trim().length===0)continue;let B;try{B=JSON.parse(K)}catch{continue}Z+=1;let U=B!==null&&typeof B==="object"&&!Array.isArray(B)?B:{};if(U.compressed===!0)$+=1,V+=p(U.orig_chars),z+=p(U.image_count);Y+=p(U.baseline_tokens),G+=p(U.input_tokens)+p(U.cache_read_tokens)+p(U.cache_create_tokens)}let H=Y>0&&G>0?new s(zZ((1-G/Y)*1000)/10):null;return{available:!0,requests:Z,compressedRequests:$,imagedChars:V,imagesEmitted:z,baselineTokens:Y,actualInputTokens:G,estInputSavedPct:H}}//! Implicit mode: a local Anthropic middlebox, the pxpipe deployment shape
43
+ class e{value;constructor(J){this.value=J}}function SJ(){let J=process.env.TANUKI_EVENTS;if(J!==void 0)return J;let Q=process.env.HOME??"";return zZ(Q,".pxpipe","events.jsonl")}function n(J){return typeof J==="number"&&Number.isSafeInteger(J)&&J>=0?J:0}function GZ(J){return J<0?-Math.round(-J):Math.round(J)}function sJ(){let J=SJ(),Q;try{Q=VZ(J,"utf8")}catch{return{available:!1,note:`no ${J} yet`}}let Z=0,$=0,V=0,z=0,Y=0,G=0;for(let K of Q.split(`
44
+ `)){if(K.trim().length===0)continue;let B;try{B=JSON.parse(K)}catch{continue}Z+=1;let X=B!==null&&typeof B==="object"&&!Array.isArray(B)?B:{};if(X.compressed===!0)$+=1,V+=n(X.orig_chars),z+=n(X.image_count);Y+=n(X.baseline_tokens),G+=n(X.input_tokens)+n(X.cache_read_tokens)+n(X.cache_create_tokens)}let H=Y>0&&G>0?new e(GZ((1-G/Y)*1000)/10):null;return{available:!0,requests:Z,compressedRequests:$,imagedChars:V,imagesEmitted:z,baselineTokens:Y,actualInputTokens:G,estInputSavedPct:H}}//! Implicit mode: a local Anthropic middlebox, the pxpipe deployment shape
45
45
  //! without pxpipe's structural flaw. Rules that keep it injection-shaped-free:
46
46
  //!
47
47
  //! 1. The system prompt and tool definitions are NEVER touched.
@@ -57,10 +57,10 @@ class s{value;constructor(J){this.value=J}}function _J(){let J=process.env.TANUK
57
57
  //!
58
58
  //! Responses stream through untouched; usage is scraped from the stream for
59
59
  //! the ~/.pxpipe/events.jsonl savings log (same format tanuki_stats reads).
60
- var n={level:0,distill:!1,codebook:!1,font:"normal",minChars:4000,ratio:0.75,minSave:300,maxPages:20};function XZ(J){let Q=0;for(let Z=0;Z<J.length;Z++){let $=J.charCodeAt(Z);if($<55296||$>56319)Q++}return Q}function BZ(J,Q){let Z=XZ(J);if(Z<Q.minChars)return null;let $=J;if(Q.distill)$=m($,null,2).distilled;let V=0;if(Q.codebook){let K=QJ($);$=K.text,V=K.entries}if(Q.level>0)$=o($,Q.level).compressed;let z=Math.round(Z/4),Y=l($,!0,!0,Q.font);if(Y.pages.length>Q.maxPages)return null;if(Y.tokens>z*Q.ratio||z-Y.tokens<Q.minSave)return null;let H=[{type:"text",text:`[tanuki-context: ${Z} chars imaged in place as ${Y.pages.length} PNG page(s), ~${Y.tokens} vs ~${z} text tokens. ↵=newline →=tab ⇥N=indent`+(V>0?`; ·legend· line maps ${V} sigils`:"")+"]"}];for(let K of Y.pages)H.push({type:"image",source:{type:"base64",media_type:"image/png",data:Buffer.from(K.png.buffer,K.png.byteOffset,K.png.byteLength).toString("base64")}});return{blocks:H,origChars:Z,pages:Y.pages.length,savedTokens:z-Y.tokens}}function YJ(J){return J!==null&&typeof J==="object"&&!Array.isArray(J)}function OZ(J,Q){let Z;try{Z=JSON.parse(J)}catch{return null}if(!YJ(Z)||!Array.isArray(Z.messages))return null;let $=0,V=0,z=0,Y=0,G=(H)=>{let K=BZ(H,Q);if(K)$++,V+=K.origChars,z+=K.pages,Y+=K.savedTokens;return K};for(let H=0;H<Z.messages.length-1;H++){let K=Z.messages[H];if(!YJ(K)||K.role!=="user")continue;if(typeof K.content==="string"){let U=G(K.content);if(U)K.content=U.blocks;continue}if(!Array.isArray(K.content))continue;let B=[];for(let U of K.content){if(!YJ(U)||U.cache_control!==void 0){B.push(U);continue}if(U.type==="text"&&typeof U.text==="string"){let X=G(U.text);if(X)B.push(...X.blocks);else B.push(U);continue}if(U.type==="tool_result"){if(typeof U.content==="string"){let X=G(U.content);if(X)U.content=X.blocks}else if(Array.isArray(U.content)){let X=[];for(let O of U.content){if(YJ(O)&&O.type==="text"&&typeof O.text==="string"&&O.cache_control===void 0){let q=G(O.text);if(q){X.push(...q.blocks);continue}}X.push(O)}U.content=X}}B.push(U)}K.content=B}if($===0)return null;return{body:JSON.stringify(Z),imagedBlocks:$,origChars:V,imageCount:z,savedTokens:Y}}function qZ(J){let Q=(Z)=>{let $=Z.exec(J);return $?Number($[1]):0};return{input:Q(/"input_tokens"\s*:\s*(\d+)/),cacheRead:Q(/"cache_read_input_tokens"\s*:\s*(\d+)/),cacheCreate:Q(/"cache_creation_input_tokens"\s*:\s*(\d+)/)}}function DZ(J){try{let Q=_J();YZ(HZ(Q),{recursive:!0}),GZ(Q,JSON.stringify(J)+`
61
- `)}catch{}}function eJ(J){let Q=new UZ(J.upstream),Z=Q.protocol==="https:"?KZ:sJ,$=sJ.createServer((V,z)=>{let Y=[];V.on("data",(G)=>Y.push(G)),V.on("end",()=>{let G=Buffer.concat(Y),H=V.method==="POST"&&(V.url??"").startsWith("/v1/messages")&&!(V.url??"").includes("count_tokens")&&V.headers["content-encoding"]===void 0,K=null;if(H){if(K=OZ(G.toString("utf8"),J),K)G=Buffer.from(K.body,"utf8")}let B={...V.headers};if(delete B.host,delete B.connection,B["content-length"]=String(G.length),H)delete B["accept-encoding"];let U=Z.request({protocol:Q.protocol,hostname:Q.hostname,port:Q.port||(Q.protocol==="https:"?443:80),path:V.url,method:V.method,headers:B},(X)=>{if(z.writeHead(X.statusCode??502,X.headers),H){let O=[];X.on("data",(q)=>{O.push(q),z.write(q)}),X.on("end",()=>{z.end();let q=qZ(Buffer.concat(O).toString("utf8")),F=q.input+q.cacheRead+q.cacheCreate;DZ({ts:Date.now(),tool:"proxy",compressed:K!==null,orig_chars:K?.origChars??0,image_count:K?.imageCount??0,baseline_tokens:F+(K?.savedTokens??0),input_tokens:q.input,cache_read_tokens:q.cacheRead,cache_create_tokens:q.cacheCreate})})}else X.pipe(z)});U.on("error",(X)=>{z.writeHead(502,{"content-type":"application/json"}),z.end(JSON.stringify({type:"error",error:{type:"api_error",message:`tanuki proxy: upstream unreachable (${X.message})`}}))}),U.end(G)})});return $.listen(J.port,"127.0.0.1",()=>{let V=$.address(),z=V!==null&&typeof V==="object"?V.port:J.port,Y=`level=${J.level} distill=${J.distill} codebook=${J.codebook} font=${J.font} minChars=${J.minChars} ratio=${J.ratio} minSave=${J.minSave}`;process.stderr.write(`tanuki-context proxy on http://127.0.0.1:${z} -> ${J.upstream}
60
+ var c={level:0,distill:!1,codebook:!1,font:"normal",minChars:4000,ratio:0.75,minSave:300,maxPages:20};function EJ(J){let Q=0;for(let Z=0;Z<J.length;Z++){let $=J.charCodeAt(Z);if($<55296||$>56319)Q++}return Q}function BZ(J,Q){let Z=EJ(J);if(Z<Q.minChars)return null;let $=J;if(Q.distill)$=b($,null,2).distilled;let V=0;if(Q.codebook){let K=i($);$=K.text,V=K.entries}if(Q.level>0)$=r($,Q.level).compressed;let z=Math.round(Z/4),Y=p($,!0,!0,Q.font);if(Y.pages.length>Q.maxPages)return null;if(Y.tokens>z*Q.ratio||z-Y.tokens<Q.minSave)return null;let H=[{type:"text",text:`[tanuki-context: ${Z} chars imaged in place as ${Y.pages.length} PNG page(s), ~${Y.tokens} vs ~${z} text tokens. ↵=newline →=tab ⇥N=indent`+(V>0?`; ·legend· line maps ${V} sigils`:"")+"]"}];for(let K of Y.pages)H.push({type:"image",source:{type:"base64",media_type:"image/png",data:Buffer.from(K.png.buffer,K.png.byteOffset,K.png.byteLength).toString("base64")}});return{blocks:H,origChars:Z,pages:Y.pages.length,savedTokens:z-Y.tokens}}function HJ(J){return J!==null&&typeof J==="object"&&!Array.isArray(J)}function OZ(J,Q){let Z;try{Z=JSON.parse(J)}catch{return null}if(!HJ(Z)||!Array.isArray(Z.messages))return null;let $=0,V=0,z=0,Y=0,G=new Map,H=(K)=>{let B=G.get(K),X;if(B!==void 0){let U=EJ(K),O=`[tanuki-context: ${U} chars, byte-identical to a block imaged above (${B} PNG page(s)); not repeated]`;X={blocks:[{type:"text",text:O}],origChars:U,pages:0,savedTokens:Math.round(U/4)-Math.round(EJ(O)/4)}}else if(X=BZ(K,Q),X)G.set(K,X.pages);if(X)$++,V+=X.origChars,z+=X.pages,Y+=X.savedTokens;return X};for(let K=0;K<Z.messages.length-1;K++){let B=Z.messages[K];if(!HJ(B)||B.role!=="user")continue;if(typeof B.content==="string"){let U=H(B.content);if(U)B.content=U.blocks;continue}if(!Array.isArray(B.content))continue;let X=[];for(let U of B.content){if(!HJ(U)||U.cache_control!==void 0){X.push(U);continue}if(U.type==="text"&&typeof U.text==="string"){let O=H(U.text);if(O)X.push(...O.blocks);else X.push(U);continue}if(U.type==="tool_result"){if(typeof U.content==="string"){let O=H(U.content);if(O)U.content=O.blocks}else if(Array.isArray(U.content)){let O=[];for(let q of U.content){if(HJ(q)&&q.type==="text"&&typeof q.text==="string"&&q.cache_control===void 0){let F=H(q.text);if(F){O.push(...F.blocks);continue}}O.push(q)}U.content=O}}X.push(U)}B.content=X}if($===0)return null;return{body:JSON.stringify(Z),imagedBlocks:$,origChars:V,imageCount:z,savedTokens:Y}}function qZ(J){let Q=(Z)=>{let $=Z.exec(J);return $?Number($[1]):0};return{input:Q(/"input_tokens"\s*:\s*(\d+)/),cacheRead:Q(/"cache_read_input_tokens"\s*:\s*(\d+)/),cacheCreate:Q(/"cache_creation_input_tokens"\s*:\s*(\d+)/)}}function DZ(J){try{let Q=SJ();HZ(KZ(Q),{recursive:!0}),YZ(Q,JSON.stringify(J)+`
61
+ `)}catch{}}function JQ(J){let Q=new XZ(J.upstream),Z=Q.protocol==="https:"?UZ:eJ,$=eJ.createServer((V,z)=>{let Y=[];V.on("data",(G)=>Y.push(G)),V.on("end",()=>{let G=Buffer.concat(Y),H=V.method==="POST"&&(V.url??"").startsWith("/v1/messages")&&!(V.url??"").includes("count_tokens")&&V.headers["content-encoding"]===void 0,K=null;if(H){if(K=OZ(G.toString("utf8"),J),K)G=Buffer.from(K.body,"utf8")}let B={...V.headers};if(delete B.host,delete B.connection,B["content-length"]=String(G.length),H)delete B["accept-encoding"];let X=Z.request({protocol:Q.protocol,hostname:Q.hostname,port:Q.port||(Q.protocol==="https:"?443:80),path:V.url,method:V.method,headers:B},(U)=>{if(z.writeHead(U.statusCode??502,U.headers),H){let O=[];U.on("data",(q)=>{O.push(q),z.write(q)}),U.on("end",()=>{z.end();let q=qZ(Buffer.concat(O).toString("utf8")),F=q.input+q.cacheRead+q.cacheCreate;DZ({ts:Date.now(),tool:"proxy",compressed:K!==null,orig_chars:K?.origChars??0,image_count:K?.imageCount??0,baseline_tokens:F+(K?.savedTokens??0),input_tokens:q.input,cache_read_tokens:q.cacheRead,cache_create_tokens:q.cacheCreate})})}else U.pipe(z)});X.on("error",(U)=>{z.writeHead(502,{"content-type":"application/json"}),z.end(JSON.stringify({type:"error",error:{type:"api_error",message:`tanuki proxy: upstream unreachable (${U.message})`}}))}),X.end(G)})});return $.listen(J.port,"127.0.0.1",()=>{let V=$.address(),z=V!==null&&typeof V==="object"?V.port:J.port,Y=`level=${J.level} distill=${J.distill} codebook=${J.codebook} font=${J.font} minChars=${J.minChars} ratio=${J.ratio} minSave=${J.minSave}`;process.stderr.write(`tanuki-context proxy on http://127.0.0.1:${z} -> ${J.upstream}
62
62
  ${Y}
63
- `+` rules: system prompt & tools untouched · in-place blocks only · latest message never imaged · cache_control skipped
63
+ `+` rules: system prompt & tools untouched · in-place blocks only · latest message never imaged · cache_control skipped · identical blocks imaged once
64
64
  `+` point your client at it: export ANTHROPIC_BASE_URL=http://127.0.0.1:${z}
65
65
  `)}),$}//! tanuki-context — token-cutting context pipeline.
66
66
  //! pipeline: text -> distill (stage 0, logs) -> ladder level 0-4 (stage 1)
@@ -71,22 +71,22 @@ var n={level:0,distill:!1,codebook:!1,font:"normal",minChars:4000,ratio:0.75,min
71
71
  //! tanuki-context estimate <file> [level] [--distill]
72
72
  //! tanuki-context render <file> [level] [outdir]
73
73
  //! tanuki-context proxy [--port N] [--upstream URL] [knobs] (implicit mode)
74
- var NZ="0.1.0",SJ=6;function jZ(J,Q){let Z=Math.min(J.length,Q.length),$=0;while($<Z&&J.charCodeAt($)===Q.charCodeAt($))$++;if($>=Z)return J.length-Q.length;return J.codePointAt($)-Q.codePointAt($)}function E(J,Q,Z=""){if(J===null||J===void 0)return"null";if(J instanceof s){let H=J.value;return Number.isFinite(H)&&Number.isInteger(H)?H.toFixed(1):String(H)}let $=typeof J;if($==="string")return JSON.stringify(J);if($==="number"||$==="boolean")return String(J);if(Array.isArray(J)){if(J.length===0)return"[]";if(!Q){let B="[";for(let U=0;U<J.length;U++){if(U>0)B+=",";B+=E(J[U],!1)}return B+"]"}let H=Z+" ",K=`[
74
+ var NZ="0.2.1",TJ=6;function jZ(J,Q){let Z=Math.min(J.length,Q.length),$=0;while($<Z&&J.charCodeAt($)===Q.charCodeAt($))$++;if($>=Z)return J.length-Q.length;return J.codePointAt($)-Q.codePointAt($)}function E(J,Q,Z=""){if(J===null||J===void 0)return"null";if(J instanceof e){let H=J.value;return Number.isFinite(H)&&Number.isInteger(H)?H.toFixed(1):String(H)}let $=typeof J;if($==="string")return JSON.stringify(J);if($==="number"||$==="boolean")return String(J);if(Array.isArray(J)){if(J.length===0)return"[]";if(!Q){let B="[";for(let X=0;X<J.length;X++){if(X>0)B+=",";B+=E(J[X],!1)}return B+"]"}let H=Z+" ",K=`[
75
75
  `;for(let B=0;B<J.length;B++){if(B>0)K+=`,
76
76
  `;K+=H+E(J[B],!0,H)}return K+`
77
77
  `+Z+"]"}let V=J,z=Object.keys(V).sort(jZ);if(z.length===0)return"{}";if(!Q){let H="{";for(let K=0;K<z.length;K++){if(K>0)H+=",";H+=JSON.stringify(z[K])+":"+E(V[z[K]],!1)}return H+"}"}let Y=Z+" ",G=`{
78
78
  `;for(let H=0;H<z.length;H++){if(H>0)G+=`,
79
79
  `;G+=Y+JSON.stringify(z[H])+": "+E(V[z[H]],!0,Y)}return G+`
80
- `+Z+"}"}function I(J,Q){return J!==null&&typeof J==="object"&&!Array.isArray(J)?J[Q]:void 0}function L(J){return typeof J==="string"?J:null}function HJ(J){return typeof J==="boolean"?J:null}function $Q(J){return typeof J==="number"&&Number.isSafeInteger(J)&&J>=0?J:null}function k(J){let Q=0;for(let Z=0;Z<J.length;Z++){Q++;let $=J.charCodeAt(Z);if($>=55296&&$<=56319&&Z+1<J.length){let V=J.charCodeAt(Z+1);if(V>=56320&&V<=57343)Z++}}return Q}function XJ(J,Q,Z,$,V){let z,Y=null;if(Z||$!==null){let K=m(J,$,2);z=K.distilled,Y=K.stats}else z=J;let G=0;if(V){let K=QJ(z);z=K.text,G=K.entries}let H=o(z,Q);return{stage0:Y,compressed:H.compressed,protectedLines:H.protectedLines,level:H.level,cbEntries:G}}function e(J){return Math.round(J/4)}function JJ(J,Q){if(J===0)return 0;let Z=(1-Q/J)*100;return Z<0?-Math.round(-Z):Math.round(Z)}function VQ(J){return{text:L(I(J,"text"))??"",level:($Q(I(J,"level"))??0)%256,distill:HJ(I(J,"distill"))??!1,query:L(I(J,"query")),reflow:HJ(I(J,"reflow"))??!0,pack:HJ(I(J,"pack"))??!0,font:L(I(J,"font"))??"normal",codebook:HJ(I(J,"codebook"))??!1}}function zQ(J){let Q=VQ(J),Z=XJ(Q.text,Q.level,Q.distill,Q.query,Q.codebook),$=t(Q.font),V=rJ(Z.compressed,Q.reflow,Q.pack,$),z=V.tokens,Y=k(Q.text),G=k(Z.compressed),H=e(Y),[K,B]=ZJ[Z.level];return{engine:"pxpipe",level:`${Z.level} ${K}`,loss:B,distill:Z.stage0,origChars:Y,stage1Chars:G,stage1SavedPct:JJ(Y,G),pages:V.pages,imageTokens:z,rawTextTokens:H,totalSavedPct:JJ(H,z),protectedLines:Z.protectedLines,pack:Q.pack,font:$==="tiny"?"tiny":"normal",codebook:Q.codebook?Z.cbEntries:!1,verdict:z<H?"PIPELINE cheaper":"TEXT cheaper"}}function IZ(J){let Q=VQ(J),Z=XJ(Q.text,Q.level,Q.distill,Q.query,Q.codebook),$=t(Q.font),V=l(Z.compressed,Q.reflow,Q.pack,$),z=V.tokens,Y=k(Q.text),G=k(Z.compressed),H=e(Y),[K,B]=ZJ[Z.level],U="";if(Z.stage0!==null){let q=Z.stage0;U+=`distill: ${q.origLines} -> ${q.outLines} lines (-${q.savedPct}% chars, ${q.collapsedRuns} runs, ${q.suppressedLines} exact + ${q.templateSuppressed} template suppressed, ${q.importantKept} error/warn kept)
81
- `}if(U+=`L${Z.level} ${K} (${B}): ${Y} chars -> ${G} chars (stage1 -${JJ(Y,G)}%) -> ${V.pages.length} page(s), ~${z} image-tokens
82
- vs ~${H} text-tokens raw = TOTAL -${JJ(H,z)}%`,Z.protectedLines>0)U+=` · ${Z.protectedLines} lines kept verbatim`;if(V.dropped>0)U+=` · ${V.dropped} unmapped glyphs -> ▯`;if(Z.cbEntries>0)U+=` · codebook: ${Z.cbEntries} sigils (see ·legend·)`;if($==="tiny")U+=" · font: tiny 4x6";if(Q.pack)U+=" · packed (⇥N indent, → tab)";if(Q.reflow)U+=" · ↵ = newline · engine: pxpipe";let X=[{type:"text",text:U}],O=Math.min(V.pages.length,SJ);for(let q=0;q<O;q++){let F=V.pages[q].png;X.push({type:"image",data:Buffer.from(F.buffer,F.byteOffset,F.byteLength).toString("base64"),mimeType:"image/png"})}if(V.pages.length>SJ)X.push({type:"text",text:`(+${V.pages.length-SJ} more page(s))`});return X}function RZ(J){let Q=L(I(J,"text"))??"",Z=m(Q,L(I(J,"query")),2);return[{type:"text",text:E(Z.stats,!0)},{type:"text",text:Z.distilled}]}function AZ(J){let Q=L(I(J,"text"))??"",Z=($Q(I(J,"level"))??1)%256,$=o(Q,Z),[V,z,Y]=ZJ[$.level],G=k(Q),H=k($.compressed),K=e(G),B=e(H),U={level:`${$.level} ${V}`,loss:z,note:Y,origChars:G,outChars:H,approxOrigTokens:K,approxOutTokens:B,savedPct:JJ(K,B),protectedLines:$.protectedLines};return[{type:"text",text:E(U,!0)},{type:"text",text:$.compressed}]}function PZ(){let J={type:"string"},Q={type:"integer",minimum:0,maximum:4};return{tools:[{name:"tanuki_render",description:"Token-cut pipeline: optional log distillation (dedupe noise, keep errors verbatim, optional query filter), optional codebook (repeated long tokens/path prefixes -> 1-cell sigils + a ·legend· line), then a ladder level, then dense PNG page(s) via the pxpipe imaging engine. level 0 raw · 1 whitespace (lossless) · 2 prose · 3 dense · 4 caveman (gist only). From level 2 up code/IDs/hashes/paths stay verbatim. pack (default true) = lossless tight reflow (single-cell tabs, ⇥N indent runs, width-trimmed pages). font 'tiny' = 4x6 cell, ~40% fewer image-tokens (opt-in). Image tokens are pixel-priced, so every earlier cut compounds. Returns image blocks + a breakdown.",inputSchema:{type:"object",properties:{text:J,level:Q,distill:{type:"boolean"},query:{type:"string"},reflow:{type:"boolean"},pack:{type:"boolean"},font:{type:"string",enum:["normal","tiny"]},codebook:{type:"boolean"}},required:["text"]}},{name:"tanuki_estimate",description:"Estimate tokens for the pipeline (distill -> codebook -> level -> pxpipe imaging) vs sending the raw text as text. Exact page geometry, no image data returned. Compare levels/pack/font/codebook to pick a loss/size tradeoff.",inputSchema:{type:"object",properties:{text:J,level:Q,distill:{type:"boolean"},query:{type:"string"},reflow:{type:"boolean"},pack:{type:"boolean"},font:{type:"string",enum:["normal","tiny"]},codebook:{type:"boolean"}},required:["text"]}},{name:"tanuki_distill",description:"Stage 0 alone: make noisy logs/output small and readable WITHOUT imaging. Strips ANSI, collapses runs of near-identical lines/blocks into '[×N similar]', suppresses global near-dupes (exact + same-template) with exact counts, always keeps error/warn/fail lines verbatim, optional query (regex) returns only the relevant slice. Deterministic, order-preserving.",inputSchema:{type:"object",properties:{text:J,query:{type:"string"}},required:["text"]}},{name:"tanuki_compress",description:"Stage 1 alone: graded text compression for content that stays TEXT. level 0 none · 1 whitespace (lossless, safe for code) · 2 prose · 3 dense · 4 caveman (gist only). From level 2 up code/IDs/hashes/paths are preserved verbatim.",inputSchema:{type:"object",properties:{text:J,level:Q},required:["text"]}},{name:"tanuki_stats",description:"Summarize the pxpipe measurement log (~/.pxpipe/events.jsonl): requests, compression counts, honest input-token savings (input + cache reads + cache creates).",inputSchema:{type:"object",properties:{}}}]}}function _Z(J){let Q=L(I(J,"name"))??"",Z=I(J,"arguments"),$;switch(Q){case"tanuki_render":$=IZ(Z);break;case"tanuki_estimate":$=[{type:"text",text:E(zQ(Z),!0)}];break;case"tanuki_distill":$=RZ(Z);break;case"tanuki_compress":$=AZ(Z);break;case"tanuki_stats":$=[{type:"text",text:E(tJ(),!0)}];break;default:return{ok:!1,error:`unknown tool: ${Q}`}}return{ok:!0,value:{content:$}}}function JQ(J){let Q=J.endsWith("\r")?J.slice(0,-1):J;if(Q.trim().length===0)return;let Z;try{Z=JSON.parse(Q)}catch{return}let $=Z!==null&&typeof Z==="object"&&!Array.isArray(Z)&&Object.prototype.hasOwnProperty.call(Z,"id"),V=$?Z.id??null:null,z=L(I(Z,"method")),Y;switch(z){case"initialize":{let G=L(I(I(Z,"params"),"protocolVersion"))??"2025-06-18";Y={jsonrpc:"2.0",id:V,result:{protocolVersion:G,capabilities:{tools:{}},serverInfo:{name:"tanuki-context",version:NZ}}};break}case"notifications/initialized":case"notifications/cancelled":Y=void 0;break;case"ping":Y={jsonrpc:"2.0",id:V,result:{}};break;case"tools/list":Y={jsonrpc:"2.0",id:V,result:PZ()};break;case"tools/call":{let G=_Z(I(Z,"params"));Y=G.ok?{jsonrpc:"2.0",id:V,result:G.value}:{jsonrpc:"2.0",id:V,error:{code:-32602,message:G.error}};break}default:Y=$?{jsonrpc:"2.0",id:V,error:{code:-32601,message:"method not found"}}:void 0}if(Y!==void 0)S.stdout.write(E(Y,!1)+`
83
- `)}function SZ(){let J=Buffer.alloc(0);S.stdin.on("data",(Q)=>{let Z=J.length>0?Buffer.concat([J,Q]):Q,$=0,V;while((V=Z.indexOf(10,$))!==-1)JQ(Z.toString("utf8",$,V)),$=V+1;J=Z.subarray($)}),S.stdin.on("end",()=>{if(J.length>0)JQ(J.toString("utf8")),J=Buffer.alloc(0)})}function x(J){S.stderr.write(J+`
84
- `),S.exit(101)}function KJ(J){try{return FZ(J,"utf8")}catch{x("read file")}}function UJ(J,Q){if(!/^\+?[0-9]+$/.test(J))return null;let Z=BigInt(J.startsWith("+")?J.slice(1):J);return Z>Q?null:Number(Z)}var QQ=255n,ZQ=0xffffffffffffffffn;function GQ(){let J=S.argv.slice(1);switch(J[1]){case"distill":{let Q=J[2]??x("usage: tanuki-context distill <file> [query]"),Z=KJ(Q),$=m(Z,J[3]??null,2);S.stdout.write(E($.stats,!1)+`
85
- `);break}case"estimate":{let Q=J[2]??x("usage: tanuki-context estimate <file> [level] [--distill] [--no-pack] [--font tiny] [--codebook]"),Z=KJ(Q),$=J.slice(3).filter((H)=>!H.startsWith("--")),V=$.length>0?UJ($[0],ZQ)??0:0,z=J.indexOf("--font"),Y=z!==-1&&J[z+1]!==void 0?J[z+1]:"normal",G=zQ({text:Z,level:V,distill:J.includes("--distill"),pack:!J.includes("--no-pack"),font:Y,codebook:J.includes("--codebook")});S.stdout.write(E(G,!1)+`
86
- `);break}case"render":{let Q=J[2]??x("usage: tanuki-context render <file> [level] [outdir] [--no-pack] [--font tiny] [--codebook]"),Z=KJ(Q),$=J.slice(3).filter((O)=>!O.startsWith("--")),V=$.length>0?UJ($[0],QQ)??0:0,z=!J.includes("--no-pack"),Y=J.includes("--codebook"),G=J.indexOf("--font"),H=t(G!==-1&&J[G+1]!==void 0?J[G+1]:"normal"),K=XJ(Z,V,!1,null,Y),B=l(K.compressed,!0,z,H),U=B.tokens;S.stdout.write(E({pages:B.pages.length,imageTokens:U,dropped:B.dropped,rawTextTokens:e(k(Z))},!1)+`
87
- `);let X=$[1];if(X!==void 0){try{MZ(X,{recursive:!0})}catch{x("mkdir")}for(let O=0;O<B.pages.length;O++)try{WZ(`${X}/page${O}.png`,B.pages[O].png)}catch{x("write png")}}break}case"bench":{let Q=J[2]??x("usage: tanuki-context bench <file> <op> [level] [runs] [--distill]"),Z=J[3]??"pipeline",$=J[4]!==void 0?UJ(J[4],QQ)??0:0,V=J[5]!==void 0?UJ(J[5],ZQ)??3:3,z=J.includes("--distill"),Y=KJ(Q),G=[],H=null;for(let K=0;K<=V;K++){let B=performance.now();if(Z==="distill")H=m(Y,null,2).stats;else{let U=XJ(Y,$,z,null,!1),X=l(U.compressed,!0,!1,"normal");H={pages:X.pages.length,imageTokens:X.tokens,stage1Chars:k(U.compressed),dropped:X.dropped}}if(K>0)G.push(performance.now()-B)}if(G.sort((K,B)=>K-B),G.length===0)x("index out of bounds: the len is 0 but the index is 0");S.stdout.write(E({medianMs:new s(G[Math.floor(G.length/2)]),runs:V,result:H},!1)+`
88
- `);break}case"proxy":{let Q=(V,z)=>{let Y=J.indexOf(V);if(Y===-1||J[Y+1]===void 0)return z;let G=Number(J[Y+1]);return Number.isFinite(G)?G:z},Z=J.indexOf("--upstream"),$=J.indexOf("--font");eJ({port:Q("--port",8484),upstream:Z!==-1&&J[Z+1]!==void 0?J[Z+1]:S.env.TANUKI_UPSTREAM??"https://api.anthropic.com",level:Q("--level",n.level),distill:J.includes("--distill"),codebook:J.includes("--codebook"),font:t($!==-1&&J[$+1]!==void 0?J[$+1]:"normal"),minChars:Q("--min-chars",n.minChars),ratio:Q("--ratio",n.ratio),minSave:Q("--min-save",n.minSave),maxPages:Q("--max-pages",n.maxPages)});break}case"serve":case void 0:SZ();break;default:S.stderr.write(`unknown command: ${J[1]}
80
+ `+Z+"}"}function I(J,Q){return J!==null&&typeof J==="object"&&!Array.isArray(J)?J[Q]:void 0}function L(J){return typeof J==="string"?J:null}function KJ(J){return typeof J==="boolean"?J:null}function VQ(J){return typeof J==="number"&&Number.isSafeInteger(J)&&J>=0?J:null}function h(J){let Q=0;for(let Z=0;Z<J.length;Z++){Q++;let $=J.charCodeAt(Z);if($>=55296&&$<=56319&&Z+1<J.length){let V=J.charCodeAt(Z+1);if(V>=56320&&V<=57343)Z++}}return Q}function BJ(J,Q,Z,$,V){let z,Y=null;if(Z||$!==null){let K=b(J,$,2);z=K.distilled,Y=K.stats}else z=J;let G=0;if(V){let K=i(z);z=K.text,G=K.entries}let H=r(z,Q);return{stage0:Y,compressed:H.compressed,protectedLines:H.protectedLines,level:H.level,cbEntries:G}}function JJ(J){return Math.round(J/4)}function QJ(J,Q){if(J===0)return 0;let Z=(1-Q/J)*100;return Z<0?-Math.round(-Z):Math.round(Z)}function zQ(J){return{text:L(I(J,"text"))??"",level:(VQ(I(J,"level"))??0)%256,distill:KJ(I(J,"distill"))??!1,query:L(I(J,"query")),reflow:KJ(I(J,"reflow"))??!0,pack:KJ(I(J,"pack"))??!0,font:L(I(J,"font"))??"normal",codebook:KJ(I(J,"codebook"))??!1}}function IZ(J){let Q=b(J,null,2).distilled,Z=[[!1,J],[!0,Q]],$={distill:!1,codebook:!1,text:J,tokens:1/0,pages:0};for(let[z,Y]of Z)for(let G of[!1,!0]){let H=G?i(Y).text:Y,K=YJ(H,!0,!0,g("normal"));if(K.tokens<$.tokens)$={distill:z,codebook:G,text:H,tokens:K.tokens,pages:K.pages}}let V=YJ($.text,!0,!0,g("tiny"));return{codebook:$.codebook,distill:$.distill,imageTokens:$.tokens,pages:$.pages,tinyImageTokens:V.tokens}}function GQ(J){let Q=zQ(J),Z=BJ(Q.text,Q.level,Q.distill,Q.query,Q.codebook),$=g(Q.font),V=YJ(Z.compressed,Q.reflow,Q.pack,$),z=V.tokens,Y=h(Q.text),G=h(Z.compressed),H=JJ(Y),[K,B]=ZJ[Z.level];return{engine:"pxpipe",level:`${Z.level} ${K}`,loss:B,distill:Z.stage0,origChars:Y,stage1Chars:G,stage1SavedPct:QJ(Y,G),pages:V.pages,imageTokens:z,rawTextTokens:H,totalSavedPct:QJ(H,z),protectedLines:Z.protectedLines,pack:Q.pack,font:$==="tiny"?"tiny":"normal",codebook:Q.codebook?Z.cbEntries:!1,verdict:z<H?"PIPELINE cheaper":"TEXT cheaper",recommend:IZ(Q.text)}}function RZ(J){let Q=zQ(J),Z=BJ(Q.text,Q.level,Q.distill,Q.query,Q.codebook),$=g(Q.font),V=p(Z.compressed,Q.reflow,Q.pack,$),z=V.tokens,Y=h(Q.text),G=h(Z.compressed),H=JJ(Y),[K,B]=ZJ[Z.level],X="";if(Z.stage0!==null){let q=Z.stage0;X+=`distill: ${q.origLines} -> ${q.outLines} lines (-${q.savedPct}% chars, ${q.collapsedRuns} runs, ${q.suppressedLines} exact + ${q.templateSuppressed} template suppressed, ${q.importantKept} error/warn kept)
81
+ `}if(X+=`L${Z.level} ${K} (${B}): ${Y} chars -> ${G} chars (stage1 -${QJ(Y,G)}%) -> ${V.pages.length} page(s), ~${z} image-tokens
82
+ vs ~${H} text-tokens raw = TOTAL -${QJ(H,z)}%`,Z.protectedLines>0)X+=` · ${Z.protectedLines} lines kept verbatim`;if(V.dropped>0)X+=` · ${V.dropped} unmapped glyphs -> ▯`;if(Z.cbEntries>0)X+=` · codebook: ${Z.cbEntries} sigils (see ·legend·)`;if($==="tiny")X+=" · font: tiny 4x6";if(Q.pack)X+=" · packed (⇥N indent, → tab)";if(Q.reflow)X+=" · ↵ = newline · engine: pxpipe";let U=[{type:"text",text:X}],O=Math.min(V.pages.length,TJ);for(let q=0;q<O;q++){let F=V.pages[q].png;U.push({type:"image",data:Buffer.from(F.buffer,F.byteOffset,F.byteLength).toString("base64"),mimeType:"image/png"})}if(V.pages.length>TJ)U.push({type:"text",text:`(+${V.pages.length-TJ} more page(s))`});return U}function AZ(J){let Q=L(I(J,"text"))??"",Z=b(Q,L(I(J,"query")),2);return[{type:"text",text:E(Z.stats,!0)},{type:"text",text:Z.distilled}]}function PZ(J){let Q=L(I(J,"text"))??"",Z=(VQ(I(J,"level"))??1)%256,$=r(Q,Z),[V,z,Y]=ZJ[$.level],G=h(Q),H=h($.compressed),K=JJ(G),B=JJ(H),X={level:`${$.level} ${V}`,loss:z,note:Y,origChars:G,outChars:H,approxOrigTokens:K,approxOutTokens:B,savedPct:QJ(K,B),protectedLines:$.protectedLines};return[{type:"text",text:E(X,!0)},{type:"text",text:$.compressed}]}function _Z(){let J={type:"string"},Q={type:"integer",minimum:0,maximum:4};return{tools:[{name:"tanuki_render",description:"Token-cut pipeline: optional log distillation (dedupe noise, keep errors verbatim, optional query filter), optional codebook (repeated long tokens/path prefixes -> 1-cell sigils + a ·legend· line), then a ladder level, then dense PNG page(s) via the pxpipe imaging engine. level 0 raw · 1 whitespace (lossless) · 2 prose · 3 dense · 4 caveman (gist only). From level 2 up code/IDs/hashes/paths stay verbatim. pack (default true) = lossless tight reflow (single-cell tabs, ⇥N indent runs, width-trimmed pages). font 'tiny' = 4x6 cell, ~40% fewer image-tokens (opt-in). Image tokens are pixel-priced, so every earlier cut compounds. Returns image blocks + a breakdown.",inputSchema:{type:"object",properties:{text:J,level:Q,distill:{type:"boolean"},query:{type:"string"},reflow:{type:"boolean"},pack:{type:"boolean"},font:{type:"string",enum:["normal","tiny"]},codebook:{type:"boolean"}},required:["text"]}},{name:"tanuki_estimate",description:"Estimate tokens for the pipeline (distill -> codebook -> level -> pxpipe imaging) vs sending the raw text as text. Exact page geometry, no image data returned. Compare levels/pack/font/codebook to pick a loss/size tradeoff. The result's 'recommend' field names the cheapest safe knob set (level 0), so one call replaces manual knob probing.",inputSchema:{type:"object",properties:{text:J,level:Q,distill:{type:"boolean"},query:{type:"string"},reflow:{type:"boolean"},pack:{type:"boolean"},font:{type:"string",enum:["normal","tiny"]},codebook:{type:"boolean"}},required:["text"]}},{name:"tanuki_distill",description:"Stage 0 alone: make noisy logs/output small and readable WITHOUT imaging. Strips ANSI, collapses runs of near-identical lines/blocks into '[×N similar]', suppresses global near-dupes (exact + same-template) with exact counts, always keeps error/warn/fail lines verbatim, optional query (regex) returns only the relevant slice. Deterministic, order-preserving.",inputSchema:{type:"object",properties:{text:J,query:{type:"string"}},required:["text"]}},{name:"tanuki_compress",description:"Stage 1 alone: graded text compression for content that stays TEXT. level 0 none · 1 whitespace (lossless, safe for code) · 2 prose · 3 dense · 4 caveman (gist only). From level 2 up code/IDs/hashes/paths are preserved verbatim.",inputSchema:{type:"object",properties:{text:J,level:Q},required:["text"]}},{name:"tanuki_stats",description:"Summarize the pxpipe measurement log (~/.pxpipe/events.jsonl): requests, compression counts, honest input-token savings (input + cache reads + cache creates).",inputSchema:{type:"object",properties:{}}}]}}function SZ(J){let Q=L(I(J,"name"))??"",Z=I(J,"arguments"),$;switch(Q){case"tanuki_render":$=RZ(Z);break;case"tanuki_estimate":$=[{type:"text",text:E(GQ(Z),!0)}];break;case"tanuki_distill":$=AZ(Z);break;case"tanuki_compress":$=PZ(Z);break;case"tanuki_stats":$=[{type:"text",text:E(sJ(),!0)}];break;default:return{ok:!1,error:`unknown tool: ${Q}`}}return{ok:!0,value:{content:$}}}function QQ(J){let Q=J.endsWith("\r")?J.slice(0,-1):J;if(Q.trim().length===0)return;let Z;try{Z=JSON.parse(Q)}catch{return}let $=Z!==null&&typeof Z==="object"&&!Array.isArray(Z)&&Object.prototype.hasOwnProperty.call(Z,"id"),V=$?Z.id??null:null,z=L(I(Z,"method")),Y;switch(z){case"initialize":{let G=L(I(I(Z,"params"),"protocolVersion"))??"2025-06-18";Y={jsonrpc:"2.0",id:V,result:{protocolVersion:G,capabilities:{tools:{}},serverInfo:{name:"tanuki-context",version:NZ}}};break}case"notifications/initialized":case"notifications/cancelled":Y=void 0;break;case"ping":Y={jsonrpc:"2.0",id:V,result:{}};break;case"tools/list":Y={jsonrpc:"2.0",id:V,result:_Z()};break;case"tools/call":{let G=SZ(I(Z,"params"));Y=G.ok?{jsonrpc:"2.0",id:V,result:G.value}:{jsonrpc:"2.0",id:V,error:{code:-32602,message:G.error}};break}default:Y=$?{jsonrpc:"2.0",id:V,error:{code:-32601,message:"method not found"}}:void 0}if(Y!==void 0)S.stdout.write(E(Y,!1)+`
83
+ `)}function EZ(){let J=Buffer.alloc(0);S.stdin.on("data",(Q)=>{let Z=J.length>0?Buffer.concat([J,Q]):Q,$=0,V;while((V=Z.indexOf(10,$))!==-1)QQ(Z.toString("utf8",$,V)),$=V+1;J=Z.subarray($)}),S.stdin.on("end",()=>{if(J.length>0)QQ(J.toString("utf8")),J=Buffer.alloc(0)})}function k(J){S.stderr.write(J+`
84
+ `),S.exit(101)}function UJ(J){try{return FZ(J,"utf8")}catch{k("read file")}}function XJ(J,Q){if(!/^\+?[0-9]+$/.test(J))return null;let Z=BigInt(J.startsWith("+")?J.slice(1):J);return Z>Q?null:Number(Z)}var ZQ=255n,$Q=0xffffffffffffffffn;function YQ(){let J=S.argv.slice(1);switch(J[1]){case"distill":{let Q=J[2]??k("usage: tanuki-context distill <file> [query]"),Z=UJ(Q),$=b(Z,J[3]??null,2);S.stdout.write(E($.stats,!1)+`
85
+ `);break}case"estimate":{let Q=J[2]??k("usage: tanuki-context estimate <file> [level] [--distill] [--no-pack] [--font tiny] [--codebook]"),Z=UJ(Q),$=J.slice(3).filter((H)=>!H.startsWith("--")),V=$.length>0?XJ($[0],$Q)??0:0,z=J.indexOf("--font"),Y=z!==-1&&J[z+1]!==void 0?J[z+1]:"normal",G=GQ({text:Z,level:V,distill:J.includes("--distill"),pack:!J.includes("--no-pack"),font:Y,codebook:J.includes("--codebook")});S.stdout.write(E(G,!1)+`
86
+ `);break}case"render":{let Q=J[2]??k("usage: tanuki-context render <file> [level] [outdir] [--distill] [--no-pack] [--font tiny] [--codebook]"),Z=UJ(Q),$=J.slice(3).filter((O)=>!O.startsWith("--")),V=$.length>0?XJ($[0],ZQ)??0:0,z=!J.includes("--no-pack"),Y=J.includes("--codebook"),G=J.indexOf("--font"),H=g(G!==-1&&J[G+1]!==void 0?J[G+1]:"normal"),K=BJ(Z,V,J.includes("--distill"),null,Y),B=p(K.compressed,!0,z,H),X=B.tokens;S.stdout.write(E({pages:B.pages.length,imageTokens:X,dropped:B.dropped,rawTextTokens:JJ(h(Z))},!1)+`
87
+ `);let U=$[1];if(U!==void 0){try{MZ(U,{recursive:!0})}catch{k("mkdir")}for(let O=0;O<B.pages.length;O++)try{WZ(`${U}/page${O}.png`,B.pages[O].png)}catch{k("write png")}}break}case"bench":{let Q=J[2]??k("usage: tanuki-context bench <file> <op> [level] [runs] [--distill]"),Z=J[3]??"pipeline",$=J[4]!==void 0?XJ(J[4],ZQ)??0:0,V=J[5]!==void 0?XJ(J[5],$Q)??3:3,z=J.includes("--distill"),Y=UJ(Q),G=[],H=null;for(let K=0;K<=V;K++){let B=performance.now();if(Z==="distill")H=b(Y,null,2).stats;else{let X=BJ(Y,$,z,null,!1),U=p(X.compressed,!0,!1,"normal");H={pages:U.pages.length,imageTokens:U.tokens,stage1Chars:h(X.compressed),dropped:U.dropped}}if(K>0)G.push(performance.now()-B)}if(G.sort((K,B)=>K-B),G.length===0)k("index out of bounds: the len is 0 but the index is 0");S.stdout.write(E({medianMs:new e(G[Math.floor(G.length/2)]),runs:V,result:H},!1)+`
88
+ `);break}case"proxy":{let Q=(V,z)=>{let Y=J.indexOf(V);if(Y===-1||J[Y+1]===void 0)return z;let G=Number(J[Y+1]);return Number.isFinite(G)?G:z},Z=J.indexOf("--upstream"),$=J.indexOf("--font");JQ({port:Q("--port",8484),upstream:Z!==-1&&J[Z+1]!==void 0?J[Z+1]:S.env.TANUKI_UPSTREAM??"https://api.anthropic.com",level:Q("--level",c.level),distill:J.includes("--distill"),codebook:J.includes("--codebook"),font:g($!==-1&&J[$+1]!==void 0?J[$+1]:"normal"),minChars:Q("--min-chars",c.minChars),ratio:Q("--ratio",c.ratio),minSave:Q("--min-save",c.minSave),maxPages:Q("--max-pages",c.maxPages)});break}case"serve":case void 0:EZ();break;default:S.stderr.write(`unknown command: ${J[1]}
89
89
  usage: tanuki-context [serve|proxy|distill|estimate|render] ...
90
90
  `),S.exit(1)}}//! Bin entry: keeps src/main.ts importable as a library (src/agent.ts) without
91
91
  //! starting the MCP server as an import side effect.
92
- GQ();
92
+ YQ();
package/dist/pi.js CHANGED
@@ -11,5 +11,5 @@ import{spawn as W}from"node:child_process";import{fileURLToPath as X}from"node:u
11
11
  //! ToolResult content shape, so results pass through untouched.
12
12
  function Y(){let F=process.env.TANUKI_BIN;if(F)return{cmd:F,args:[]};return{cmd:process.execPath,args:[X(new URL("./cli.js",import.meta.url))]}}class Q{proc;buf="";nextId=1;pending=new Map;ready;constructor(){let{cmd:F,args:B}=Y();this.proc=W(F,B,{stdio:["pipe","pipe","inherit"]}),this.proc.stdout.setEncoding("utf8"),this.proc.stdout.on("data",(G)=>{this.buf+=G;let z;while((z=this.buf.indexOf(`
13
13
  `))!==-1){let H=this.buf.slice(0,z).trim();if(this.buf=this.buf.slice(z+1),!H)continue;let D;try{D=JSON.parse(H)}catch{continue}let J=D.id!==void 0?this.pending.get(D.id):void 0;if(!J)continue;if(this.pending.delete(D.id),D.error)J.reject(Error(D.error.message??"tanuki-context error"));else J.resolve(D.result)}}),this.proc.on("exit",(G)=>{let z=Error(`tanuki-context server exited (code ${G})`);for(let H of this.pending.values())H.reject(z);this.pending.clear()}),this.ready=this.request("initialize",{})}request(F,B){let G=this.nextId++,{promise:z,resolve:H,reject:D}=Promise.withResolvers();if(!this.proc.stdin?.writable)return D(Error("tanuki-context server is gone")),z;return this.pending.set(G,{resolve:H,reject:D}),this.proc.stdin.write(JSON.stringify({jsonrpc:"2.0",id:G,method:F,params:B})+`
14
- `),z}async call(F,B){return await this.ready,this.request("tools/call",{name:F,arguments:B})}kill(){this.proc.kill()}get alive(){return this.proc.exitCode===null&&!this.proc.killed}}var K=q.String({description:"The text to process"}),V=q.Optional(q.Integer({minimum:0,maximum:4,description:"0 raw · 1 whitespace · 2 prose · 3 dense · 4 caveman"})),N=q.Object({text:K,level:V,distill:q.Optional(q.Boolean()),query:q.Optional(q.String()),reflow:q.Optional(q.Boolean()),pack:q.Optional(q.Boolean()),font:q.Optional(q.String({enum:["normal","tiny"]})),codebook:q.Optional(q.Boolean())}),Z=[{name:"tanuki_render",label:"Tanuki Render",description:"Token-cut pipeline: optional log distillation (dedupe noise, keep errors verbatim, optional query filter), optional codebook (repeated long tokens/path prefixes -> 1-cell sigils + a ·legend· line), then a ladder level, then dense PNG page(s) via the pxpipe imaging engine. level 0 raw · 1 whitespace (lossless) · 2 prose · 3 dense · 4 caveman (gist only). From level 2 up code/IDs/hashes/paths stay verbatim. pack (default true) = lossless tight reflow. font 'tiny' = 4x6 cell, ~40% fewer image-tokens (opt-in). Image tokens are pixel-priced, so every earlier cut compounds. Returns image blocks + a breakdown.",parameters:N,snippet:"Render bulky text/logs as dense PNG pages that cost a fraction of the tokens"},{name:"tanuki_estimate",label:"Tanuki Estimate",description:"Estimate tokens for the pipeline (distill -> codebook -> level -> pxpipe imaging) vs sending the raw text as text. Exact page geometry, no image data returned. Compare levels/pack/font/codebook to pick a loss/size tradeoff.",parameters:N,snippet:"Instant token verdict: would imaging this text beat sending it as text?"},{name:"tanuki_distill",label:"Tanuki Distill",description:"Stage 0 alone: make noisy logs/output small and readable WITHOUT imaging. Strips ANSI, collapses runs of near-identical lines/blocks into '[×N similar]', suppresses global near-dupes with exact counts, always keeps error/warn/fail lines verbatim, optional query (regex) returns only the relevant slice. Deterministic, order-preserving.",parameters:q.Object({text:K,query:q.Optional(q.String())}),snippet:"Deterministically dedupe noisy logs, keeping every error line verbatim"},{name:"tanuki_compress",label:"Tanuki Compress",description:"Stage 1 alone: graded text compression for content that stays TEXT. level 0 none · 1 whitespace (lossless, safe for code) · 2 prose · 3 dense · 4 caveman (gist only). From level 2 up code/IDs/hashes/paths are preserved verbatim.",parameters:q.Object({text:K,level:V}),snippet:"Graded text compression (lossless whitespace up to gist-only)"},{name:"tanuki_stats",label:"Tanuki Stats",description:"Summarize the pxpipe measurement log (~/.pxpipe/events.jsonl): requests, compression counts, honest input-token savings (input + cache reads + cache creates).",parameters:q.Object({}),snippet:"Session savings summary from the tanuki/pxpipe event log"}];function $(F){let B=null,G=()=>{if(!B||!B.alive)B=new Q;return B};F.on("session_shutdown",async()=>{B?.kill(),B=null});for(let z of Z)F.registerTool({name:z.name,label:z.label,description:z.description,promptSnippet:z.snippet,parameters:z.parameters,async execute(H,D){let J=await G().call(z.name,D??{}),M=(J.content??[]).map((I)=>I.type==="image"?{type:"image",data:I.data??"",mimeType:I.mimeType??"image/png"}:{type:"text",text:I.text??""});if(J.isError)throw Error(M.map((I)=>("text"in I)?I.text:"").join(`
14
+ `),z}async call(F,B){return await this.ready,this.request("tools/call",{name:F,arguments:B})}kill(){this.proc.kill()}get alive(){return this.proc.exitCode===null&&!this.proc.killed}}var K=q.String({description:"The text to process"}),V=q.Optional(q.Integer({minimum:0,maximum:4,description:"0 raw · 1 whitespace · 2 prose · 3 dense · 4 caveman"})),N=q.Object({text:K,level:V,distill:q.Optional(q.Boolean()),query:q.Optional(q.String()),reflow:q.Optional(q.Boolean()),pack:q.Optional(q.Boolean()),font:q.Optional(q.String({enum:["normal","tiny"]})),codebook:q.Optional(q.Boolean())}),Z=[{name:"tanuki_render",label:"Tanuki Render",description:"Token-cut pipeline: optional log distillation (dedupe noise, keep errors verbatim, optional query filter), optional codebook (repeated long tokens/path prefixes -> 1-cell sigils + a ·legend· line), then a ladder level, then dense PNG page(s) via the pxpipe imaging engine. level 0 raw · 1 whitespace (lossless) · 2 prose · 3 dense · 4 caveman (gist only). From level 2 up code/IDs/hashes/paths stay verbatim. pack (default true) = lossless tight reflow. font 'tiny' = 4x6 cell, ~40% fewer image-tokens (opt-in). Image tokens are pixel-priced, so every earlier cut compounds. Returns image blocks + a breakdown.",parameters:N,snippet:"Render bulky text/logs as dense PNG pages that cost a fraction of the tokens"},{name:"tanuki_estimate",label:"Tanuki Estimate",description:"Estimate tokens for the pipeline (distill -> codebook -> level -> pxpipe imaging) vs sending the raw text as text. Exact page geometry, no image data returned. Compare levels/pack/font/codebook to pick a loss/size tradeoff. The result's 'recommend' field names the cheapest safe knob set (level 0), so one call replaces manual knob probing.",parameters:N,snippet:"Instant token verdict: would imaging this text beat sending it as text?"},{name:"tanuki_distill",label:"Tanuki Distill",description:"Stage 0 alone: make noisy logs/output small and readable WITHOUT imaging. Strips ANSI, collapses runs of near-identical lines/blocks into '[×N similar]', suppresses global near-dupes with exact counts, always keeps error/warn/fail lines verbatim, optional query (regex) returns only the relevant slice. Deterministic, order-preserving.",parameters:q.Object({text:K,query:q.Optional(q.String())}),snippet:"Deterministically dedupe noisy logs, keeping every error line verbatim"},{name:"tanuki_compress",label:"Tanuki Compress",description:"Stage 1 alone: graded text compression for content that stays TEXT. level 0 none · 1 whitespace (lossless, safe for code) · 2 prose · 3 dense · 4 caveman (gist only). From level 2 up code/IDs/hashes/paths are preserved verbatim.",parameters:q.Object({text:K,level:V}),snippet:"Graded text compression (lossless whitespace up to gist-only)"},{name:"tanuki_stats",label:"Tanuki Stats",description:"Summarize the pxpipe measurement log (~/.pxpipe/events.jsonl): requests, compression counts, honest input-token savings (input + cache reads + cache creates).",parameters:q.Object({}),snippet:"Session savings summary from the tanuki/pxpipe event log"}];function $(F){let B=null,G=()=>{if(!B||!B.alive)B=new Q;return B};F.on("session_shutdown",async()=>{B?.kill(),B=null});for(let z of Z)F.registerTool({name:z.name,label:z.label,description:z.description,promptSnippet:z.snippet,parameters:z.parameters,async execute(H,D){let J=await G().call(z.name,D??{}),M=(J.content??[]).map((I)=>I.type==="image"?{type:"image",data:I.data??"",mimeType:I.mimeType??"image/png"}:{type:"text",text:I.text??""});if(J.isError)throw Error(M.map((I)=>("text"in I)?I.text:"").join(`
15
15
  `)||z.name+" failed");return{content:M,details:{}}}})}export{$ as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tanuki-context",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Token-cutting context pipeline as an MCP server: distill logs, compress text, render dense PNG pages (pxpipe imaging engine). Zero dependencies.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -61,6 +61,7 @@
61
61
  "context",
62
62
  "compression",
63
63
  "tokens",
64
- "llm"
64
+ "llm",
65
+ "pxpipe"
65
66
  ]
66
67
  }