tanuki-context 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tanuki-context contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ Glyph data in assets/ is derived from the Spleen font (c) Frederic Cambus
16
+ (BSD 2-Clause) and GNU Unifont (SIL OFL 1.1 / GPLv2+ with font embedding
17
+ exception), via the pxpipe project's generated atlas.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # tanuki-context
2
+
3
+ Token-cutting context pipeline as a zero-dependency MCP server.
4
+ TypeScript, built with Bun, runs anywhere Node ≥18 runs:
5
+
6
+ ```
7
+ npx tanuki-context
8
+ ```
9
+
10
+ The original Rust implementation lives on the [`rust` branch](../../tree/rust)
11
+ (single ~2.6 MB static binary, same behavior). This branch is a 1:1 port of it —
12
+ byte-identical output, verified (see below). Architecture and reasoning live in
13
+ [DESIGN.md](DESIGN.md).
14
+
15
+ ```
16
+ text/logs ──► stage 0: distill ──► stage 1: ladder ──► stage 2: pxpipe imaging ──► PNG pages
17
+ (dedupe ×N, keep (levels 0–4: (312-col 5×8 pages, 1568×728,
18
+ errors verbatim, whitespace/prose/ pixel-priced — engine name kept
19
+ query slice) dense/caveman) from the original pxpipe mechanic)
20
+ ```
21
+
22
+ Remake of the [pxpipe](https://github.com/teamchong/pxpipe) MCP. Page geometry
23
+ and BMP glyphs are extracted from pxpipe's generated gray atlas (Spleen 5×8 for
24
+ ASCII/code, Unifont for CJK/Cyrillic/etc.), so pages are pixel-faithful to
25
+ pxpipe's production renderer. **Astral planes (emoji, plane-1+ symbols) render
26
+ too** — from GNU `unifont_upper`, box-filtered to the same AA cells — which goes
27
+ beyond pxpipe (it drops astral). 92,812 codepoints total; only unassigned
28
+ codepoints fall back to `▯` (counted + reported).
29
+
30
+ ## Measured (same machine; node reference = pre-rewrite pxpipe MCP)
31
+
32
+ | metric | node reference | tanuki (this branch) | tanuki (rust branch) |
33
+ |---|---:|---:|---:|
34
+ | distill 113 MB log | ~4 s | **3.35 s** (bun) | 3.27 s |
35
+ | MCP spawn → first response | 152 ms | **86 ms** (bun) / 106 ms (node) | 3 ms |
36
+ | idle server RSS | 177 MB | **50 MB** (bun) / 80 MB (node) | ~3 MB |
37
+ | install | node + node_modules | **`npx tanuki-context`** (0.97 MB tarball, zero deps) | build from source |
38
+ | output parity | — | **byte/pixel-identical to rust** on all fixtures | pages + tokens exact vs pxpipe |
39
+
40
+ ## Tools (MCP, stdio)
41
+
42
+ - `tanuki_render` — `{ text, level?, distill?, query?, reflow?, pack?, font?, codebook? }` → PNG page blocks + breakdown
43
+ - `tanuki_estimate` — same args; exact page geometry, numbers only (never decompresses glyphs)
44
+ - `tanuki_distill` — stage 0 alone (logs stay text; error/warn lines always verbatim)
45
+ - `tanuki_compress` — stage 1 alone (levels 0–4; code/IDs/hashes/paths verbatim from L2 up)
46
+ - `tanuki_stats` — honest savings summary from `~/.pxpipe/events.jsonl` (env `TANUKI_EVENTS`)
47
+
48
+ ### Density knobs (measured, image-tokens vs the pxpipe-faithful baseline)
49
+
50
+ All lossless or legend-decodable, all off the parity path (`pack=false,
51
+ font=normal, codebook=false` = byte-identical to pxpipe).
52
+
53
+ | knob | what | code | prose | log |
54
+ |---|---|---:|---:|---:|
55
+ | `pack` (default on) | single-cell tabs, `⇥N` indent runs, width-trimmed pages — byte-exact | −14% | −0% | −0% |
56
+ | `codebook` | repeated tokens/path prefixes → 1-cell sigils + a `·legend·` line — reversible | −19% | −0% | −37% |
57
+ | `font:"tiny"` | atlas box-filtered into a 4×6 cell — experimental, 99.7% read-back accuracy | −40% | −38% | −40% |
58
+ | **all three** | stacked | **−51%** | **−38%** | **−62%** |
59
+
60
+ ## CLI
61
+
62
+ ```
63
+ npx tanuki-context # MCP stdio server (default)
64
+ npx tanuki-context distill <file> [query] # stats JSON to stdout
65
+ npx tanuki-context estimate <file> [level] [--distill] [--no-pack] [--font tiny] [--codebook]
66
+ npx tanuki-context render <file> [level] [outdir] [--no-pack] [--font tiny] [--codebook]
67
+ ```
68
+
69
+ ## Register (MCP)
70
+
71
+ ```json
72
+ { "mcpServers": { "tanuki-context": { "command": "npx", "args": ["-y", "tanuki-context"] } } }
73
+ ```
74
+
75
+ ## Layout
76
+
77
+ | path | role |
78
+ |---|---|
79
+ | `src/main.ts` | MCP stdio server (hand-rolled JSON-RPC, serde_json-compatible serializer) + CLI |
80
+ | `src/distill.ts` | stage 0: 3-pass log distiller (runs/blocks ×N, exact+template near-dupes, query) |
81
+ | `src/ladder.ts` | stage 1: levels 0–4 with the exact-recall guard |
82
+ | `src/codebook.ts` | stage 0.5: repeated tokens/path prefixes → sigils + `·legend·` (opt-in, reversible) |
83
+ | `src/render.ts` | stage 2: reflow (`↵`), pack (`⇥N`/single-cell tab/width-trim), wrap, page split, AA blit, tiny 4×6 font |
84
+ | `src/atlas.ts` | full-Unicode glyph atlas (92,812 cps): codepoints/wide eager, pixels lazily inflated |
85
+ | `src/png.ts` | minimal grayscale PNG encoder (zlib via `node:zlib`, filter-0 rows) |
86
+ | `src/stats.ts` | events.jsonl summary |
87
+ | `assets/glyphs.*` | generated glyph data (35,501 codepoints, 0.4 MB packed) |
88
+ | `tools/gen-glyphs.mjs` | regenerates `assets/` from pxpipe's atlas |
89
+ | `reference/parity-ts.mjs` | parity harness: this port vs the rust binary — CLI JSON deep-equal, MCP replies, PNG pixels |
90
+ | `reference/parity.mjs` | original harness: rust vs pxpipe |
91
+ | `reference/node-mcp/` | the pre-rewrite node implementation, kept for comparison |
92
+
93
+ ## Build & test
94
+
95
+ Everything runs from source with Bun (`bun src/main.ts`) or as the bundled,
96
+ Node-compatible single file:
97
+
98
+ ```
99
+ bun install --frozen-lockfile # nothing to install — zero deps; safe to skip
100
+ bun run build # dist/cli.js (bun build --target=node --minify)
101
+ ```
102
+
103
+ Parity against the Rust implementation (build it from the `rust` branch first —
104
+ `git worktree add /tmp/tanuki-rust rust && cargo build --release
105
+ --manifest-path /tmp/tanuki-rust/Cargo.toml`, or set `TANUKI_BIN`):
106
+
107
+ ```
108
+ node reference/parity-ts.mjs # 76 checks: distill/estimate/render/MCP, pixel-exact PNGs
109
+ TANUKI_TS="bun src/main.ts" node reference/parity-ts.mjs # same, unbundled
110
+ ```
111
+
112
+ Regenerating glyphs after a pxpipe atlas rebuild (requires a pxpipe checkout
113
+ with `dist/` built):
114
+
115
+ ```
116
+ PXPIPE_DIST=~/Projects/pxpipe/dist node tools/gen-glyphs.mjs
117
+ ```
Binary file
Binary file
Binary file
package/dist/cli.js ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ import{readFileSync as sQ,mkdirSync as eQ,writeFileSync as JZ}from"node:fs";import C from"node:process";function ZQ(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 BJ(J){let Z=0;for(let Q=0;Q<J.length;Q++){let $=J.charCodeAt(Q);if($>=55296&&$<=56319&&Q+1<J.length){let V=J.charCodeAt(Q+1);if(V>=56320&&V<=57343)Q++}Z++}return Z}function $Q(J,Z){let Q=0,$=0;while(Q<J.length&&$<Z.length){let V=J.codePointAt(Q),z=Z.codePointAt($);if(V!==z)return V-z;Q+=V>65535?2:1,$+=z>65535?2:1}return J.length-Q-(Z.length-$)}function SJ(J){let Z=new Map,Q=(O)=>{Z.set(O,(Z.get(O)??0)+1)},$=(O)=>{if(BJ(O)>=12)Q(O);if(O.includes("/")){let X=O.split("/"),F="";for(let D=0;D<X.length;D++){if(D>0)F+="/";if(F+=X[D],D>=2){let R=F+"/";if(BJ(R)>=12)Q(R)}}}},V=-1;for(let O=0;O<J.length;){let X=J.codePointAt(O),F=X>65535?2:1;if(ZQ(X)){if(V>=0)$(J.slice(V,O)),V=-1}else if(V<0)V=O;O+=F}if(V>=0)$(J.slice(V));let z=[];for(let[O,X]of Z)if(X>=3){let F=BJ(O);z.push({k:O,c:X,len:F,saved:(F-1)*X})}z.sort((O,X)=>X.saved-O.saved||$Q(O.k,X.k));let Y=[];for(let O of"§¤¢£¥µ¶ª°±¬×÷ØÞßæðøþ¡¿")if(!J.includes(O))Y.push(O);let G=[],q=[];for(let{k:O,c:X,len:F}of z){if(G.length>=Y.length)break;if((F-1)*X<=F+3)continue;let D=!1;for(let P=0;P<q.length;P++){let L=q[P];if(L.startsWith(O)||O.startsWith(L)){D=!0;break}}if(D)continue;let R=Y[G.length];q.push(O),G.push({sig:R,val:O,len:F})}if(G.length===0)return{text:J,entries:0};let H=G.map((O,X)=>X);H.sort((O,X)=>G[X].len-G[O].len);let U=J;for(let O=0;O<H.length;O++){let{sig:X,val:F}=G[H[O]];U=U.replaceAll(F,X)}let B=`
3
+ ·legend· `;for(let O=0;O<G.length;O++)B+=G[O].sig+"="+G[O].val+" ";return U+=B.slice(0,-1),{text:U,entries:G.length}}import{Buffer as TJ}from"node:buffer";var VQ=/\x1b\[[0-9;]*[A-Za-z]/g,OJ=/\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,zQ=/[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,GQ=/\b[0-9]{2}:[0-9]{2}:[0-9]{2}([.,][0-9]+)?\b/g,YQ=/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi,qQ=/\b[0-9a-f]{7,64}\b/gi,HQ=/\b[0-9]+(\.[0-9]+)?[ \t\u00a0]?(ms|us|µs|ns|s|m|h|%|[KMGT]i?B)?\b/gi,BQ=/[0-9a-f]/i,OQ=3,UQ=8,UJ=2,CJ=40;function XQ(J){if(!BQ.test(J))return J;let Z=J.replace(zQ,"<ts>");return Z=Z.replace(GQ,"<time>"),Z=Z.replace(YQ,"<uuid>"),Z=Z.replace(qQ,"<hex>"),Z.replace(HQ,"<n>")}function u(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 yJ(J){let Z=0,Q=J.length;while(Z<Q&&u(J.charCodeAt(Z)))Z++;while(Q>Z&&u(J.charCodeAt(Q-1)))Q--;return Z===0&&Q===J.length?J:J.slice(Z,Q)}function KQ(J,Z){let Q=0,$=J.length;while(Q<$&&u(J.charCodeAt(Q)))Q++;while($>Q&&u(J.charCodeAt($-1)))$--;let V=0;for(let z=Q;z<$&&V<Z;V++){let Y=J.charCodeAt(z);z+=Y>=55296&&Y<=56319&&z+1<$?2:1}return V<Z}function FQ(J){let Z=0,Q=J.length;while(Z<Q&&u(J.charCodeAt(Z)))Z++;return J.charCodeAt(Z)===91&&J.charCodeAt(Z+1)===215}function DQ(J){let Z=[],Q=-1,$=!0,V=!1,z=!1,Y=!0;for(let G=0;G<J.length;){let q=J.codePointAt(G),H=q>65535?2:1,U=u(q);if(Y){if(Y=!1,U)Z.push("<v>")}if(U){if(Q>=0)Z.push($?J.slice(Q,G):"<v>"),Q=-1;z=!0}else{if(V=!0,z=!1,Q<0)Q=G,$=!0;if($){let B=q|32;if(B<97||B>122||q>127)$=!1}}G+=H}if(Q>=0)Z.push($?J.slice(Q):"<v>");if(V&&z)Z.push("<v>");return Z.join(" ")}function LJ(J,Z){let Q=0,$=0,V=J.length;while(Q<V&&$<Z){let z=J.charCodeAt(Q);Q+=z>=55296&&z<=56319&&Q+1<V?2:1,$++}return Q>=V?J:J.slice(0,Q)}var MQ=/[.*+?^${}()|[\]\\]/g;function p(J,Z=null,Q=2){let $=Q,z=J.replace(VQ,"").split(`
4
+ `),Y=z.length,G=Y,q=Array(Y),H=Array(Y),U=0;for(let K=0;K<Y;K++){q[K]=XQ(z[K]);let M=OJ.test(z[K]);if(H[K]=M,M)U++}let B=[],O=[],X=[],F=0,D=0;while(D<Y){let K=0,M=0;for(let W=1;W<=UQ&&D+2*W<=Y;W++){if(H[D+W-1])break;let N=1;J:for(;;){let j=D+N*W;if(j+W>Y)break;for(let _=0;_<W;_++)if(H[j+_]||q[j+_]!==q[D+_])break J;N++}if(N>=OQ&&W*(N-1)>K*(M>0?M-1:0))K=W,M=N}if(K>0){for(let W=D;W<D+K;W++)B.push(z[W]),O.push(H[W]),X.push(q[W]);B.push(K===1?` [×${M} similar]`:` [×${M} similar ${K}-line blocks]`),O.push(!1),X.push(null),F++,D+=K*M}else B.push(z[D]),O.push(H[D]),X.push(q[D]),D++}let R=new Map,P=new Map,L=UJ+1,S=[],A=[],b=0,w=0;for(let K=0;K<B.length;K++){let M=B[K],W=O[K];if(W||FQ(M)||KQ(M,4)){S.push(M),A.push(W);continue}let N=X[K],j=R.get(N);if(j!==void 0){if(j.count++,j.count<=UJ)S.push(M),A.push(!1);else b++;continue}let _=DQ(N);R.set(N,{count:1,exemplar:M});let l=P.get(_);if(l!==void 0)if(l.count++,l.count<=L)S.push(M),A.push(!1);else w++;else P.set(_,{count:1,exemplar:M}),S.push(M),A.push(!1)}let h=[];for(let K of R.values())if(K.count>UJ)h.push([K.count,"exact",K.exemplar]);for(let K of P.values())if(K.count>L)h.push([K.count,"template",K.exemplar]);if(h.sort((K,M)=>M[0]-K[0]),h.length>CJ)h.length=CJ;if(b+w>0){let K=`── ${b+w} repeated lines suppressed (${b} exact ×N, ${w} same-template; first occurrences kept above) ──`;S.push(K),A.push(OJ.test(K));for(let[M,W,N]of h){let _=` ×${M}${W==="template"?" (template)":""} ${LJ(yJ(N),160)}`;S.push(_),A.push(OJ.test(_))}}let x;if(Z!=null){let K;try{K=new RegExp(Z,"i")}catch{K=new RegExp(Z.replace(MQ,"\\$&"),"i")}let M=S.length,W=new Uint8Array(M);for(let j=0;j<M;j++)if(A[j]||K.test(S[j])){let _=j>$?j-$:0,l=Math.min(j+$,M-1);W.fill(1,_,l+1)}x=[];let N=0;for(let j=0;j<M;j++)if(W[j]){if(N>0)x.push(`… ${N} lines omitted`),N=0;x.push(S[j])}else N++;if(N>0)x.push(`… ${N} lines omitted`)}else x=S;let AJ=x.join(`
5
+ `),_J=TJ.byteLength(J),EJ=TJ.byteLength(AJ),HJ=(1-EJ/_J)*100,eJ=J.length===0?0:HJ<0?-Math.round(-HJ):Math.round(HJ),JQ=h.map(([K,M,W])=>({count:K,exemplar:LJ(yJ(W),160),kind:M})),QQ={collapsedRuns:F,importantKept:U,origChars:_J,origLines:G,outChars:EJ,outLines:x.length,query:Z??null,savedPct:eJ,suppressedLines:b,templateSuppressed:w,topRepeats:JQ};return{distilled:AJ,stats:QQ}}var t=[["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 T=(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"),XJ=[[T("in order to"),"to"],[T("due to the fact that"),"because"],[T("at this point in time"),"now"],[T("in the event that"),"if"],[T("for the purpose of"),"for"],[T("with regard to"),"about"],[T("a large number of"),"many"],[T("it is important to note that"),""],[T("please note that"),""],[T("as a matter of fact"),""],[T("in terms of"),"for"],[T("the fact that"),"that"]],WQ=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"),jQ=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"),NQ=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"),bJ=/ {2,}/g,IQ=new RegExp("[\\t\\n\\x0B\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+([.,;:!?])","gu"),RQ=/\n{3,}/g;function KJ(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 PQ(J){let Z=0,Q=J.length;while(Z<Q){let $=J.codePointAt(Z);if(!KJ($))break;Z+=$>65535?2:1}while(Q>Z){let $=J.charCodeAt(Q-1);if(!KJ($))break;Q--}return Z===0&&Q===J.length?J:J.slice(Z,Q)}function AQ(J){if(J.length===0)return!1;let Z=J.charCodeAt(0);if(Z===32||Z===9)return!0;let Q=0,$=0,V=0,z=!1;for(let Y=0;Y<J.length;){let G=J.codePointAt(Y);if(Y+=G>65535?2:1,Q++,KJ(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($/Q>0.3)return!0;return z}function _Q(J,Z){let Q=J.replace(bJ," ");for(let V=0;V<XJ.length;V++)Q=Q.replace(XJ[V][0],XJ[V][1]);if(Z>=3)Q=Q.replace(WQ,""),Q=Q.replace(jQ,"");if(Z>=4)Q=Q.replace(NQ,"");Q=Q.replace(bJ," "),Q=PQ(Q.replace(IQ,"$1"));let $=Q.charCodeAt(0);if($>=97&&$<=122)Q=String.fromCharCode($-32)+Q.slice(1);return Q}function FJ(J,Z){let Q=Math.min(Z,4);if(Q===0)return{compressed:J,protectedLines:0,level:Q};let $=0,V=J.split(`
6
+ `),z=Array(V.length);for(let G=0;G<V.length;G++){let q=V[G],H=q.length;while(H>0){let B=q.charCodeAt(H-1);if(B!==32&&B!==9)break;H--}let U=H===q.length?q:q.slice(0,H);if(Q===1){z[G]=U;continue}if(AQ(U)){$++,z[G]=U;continue}z[G]=_Q(U,Q)}return{compressed:z.join(`
7
+ `).replace(RQ,`
8
+
9
+ `),protectedLines:$,level:Q}}import{readFileSync as MJ}from"node:fs";import{inflateSync as EQ}from"node:zlib";//! Full-BMP glyph atlas (Spleen 5x8 for ASCII/code + Unifont fallback),
10
+ //! extracted from pxpipe's generated gray atlas by `tools/gen-glyphs.mjs`.
11
+ //!
12
+ //! Codepoints + wide flags load eagerly (~175 KB, needed for wrap math);
13
+ //! coverage pixels stay zlib-packed on disk and decompress lazily on the
14
+ //! first blit, so `estimate` never pays for them.
15
+ var v=5,c=8,m=MJ(new URL("../assets/glyphs.cps",import.meta.url)),s=MJ(new URL("../assets/glyphs.wide",import.meta.url)),wJ=(()=>{let J=m.byteLength>>>2;if((m.byteOffset&3)===0)return new Uint32Array(m.buffer,m.byteOffset,J);return new Uint32Array(m.buffer.slice(m.byteOffset,m.byteOffset+(J<<2)))})(),SQ=(()=>{let J=new Uint32Array(s.length),Z=0;for(let Q=0;Q<s.length;Q++)J[Q]=Z,Z+=(s[Q]===1?2*v:v)*c;return J})(),DJ=null;function e(J){let Z=0,Q=wJ.length-1;while(Z<=Q){let $=Z+Q>>>1,V=wJ[$];if(V<J)Z=$+1;else if(V>J)Q=$-1;else return $}return-1}function g(J){return s[J]===1}function WJ(J){if(DJ===null)DJ=EQ(MJ(new URL("../assets/glyphs.pix.z",import.meta.url)));let Z=SQ[J],Q=g(J)?2*v:v;return DJ.subarray(Z,Z+Q*c)}function vJ(J,Z,Q){let $=WJ(J),V=g(J)?2*v:v;return TQ($,V,c,Z,Q)}function TQ(J,Z,Q,$,V){let z=new Uint8Array($*V),Y=Z/$,G=Q/V;for(let q=0;q<V;q++){let H=q*G,U=(q+1)*G;for(let B=0;B<$;B++){let O=B*Y,X=(B+1)*Y,F=0,D=0,R=Math.min(Math.ceil(U),Q);for(let P=Math.floor(H);P<R;P++){let L=Math.min(P+1,U)-Math.max(P,H);if(L<=0)continue;let S=Math.min(Math.ceil(X),Z);for(let A=Math.floor(O);A<S;A++){let b=Math.min(A+1,X)-Math.max(A,O);if(b<=0)continue;let w=b*L;F+=w*J[P*Z+A],D+=w}}z[q*$+B]=D>0?Math.min(255,Math.max(0,Math.round(F/D))):0}}return z}import{deflateSync as CQ}from"node:zlib";//! Minimal grayscale PNG encoder (bit depth 8, color type 0), mirroring
16
+ //! pxpipe's png.ts: IHDR + one IDAT (zlib) + IEND, filter byte 0 per row.
17
+ var yQ=(()=>{let J=new Uint32Array(256);for(let Z=0;Z<256;Z++){let Q=Z;for(let $=0;$<8;$++)Q=(Q&1)!==0?3988292384^Q>>>1:Q>>>1;J[Z]=Q>>>0}return J})();function jJ(J,Z,Q,$){let V=$.length;J[Z]=V>>>24&255,J[Z+1]=V>>>16&255,J[Z+2]=V>>>8&255,J[Z+3]=V&255;let z=Z+4;for(let q=0;q<4;q++)J[z+q]=Q.charCodeAt(q);J.set($,z+4);let Y=4294967295;for(let q=z;q<z+4+V;q++)Y=yQ[(Y^J[q])&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 fJ(J,Z,Q){let $=new Uint8Array(Q*(Z+1));for(let q=0,H=0,U=0;q<Q;q++)$[H++]=0,$.set(J.subarray(U,U+Z),H),H+=Z,U+=Z;let V=CQ($,{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]=Z>>>24&255,Y[1]=Z>>>16&255,Y[2]=Z>>>8&255,Y[3]=Z&255,Y[4]=Q>>>24&255,Y[5]=Q>>>16&255,Y[6]=Q>>>8&255,Y[7]=Q&255,Y[8]=8,Y[9]=0;let G=jJ(z,8,"IHDR",Y);return G=jJ(z,G,"IDAT",V),jJ(z,G,"IEND",new Uint8Array(0)),z}//! Stage 2: the `pxpipe` imaging engine, ported from pxpipe's render.ts
18
+ //! production dense path (bare 5x8 AA cell, 312 cols, 1568x728 pages).
19
+ //! Glyphs cover BMP (pxpipe's atlas: Spleen for ASCII/code, Unifont fallback)
20
+ //! PLUS the astral planes (unifont_upper, incl. emoji) — beyond pxpipe, which
21
+ //! drops astral. Only unassigned codepoints fall back to `▯` and are counted
22
+ //! as dropped.
23
+ //!
24
+ //! Two tanuki-only extensions over the faithful port, both behind knobs so the
25
+ //! `pack=false, font=Normal` path stays byte-identical to pxpipe (parity):
26
+ //! * `pack` — lossless reflow tighter than pxpipe: single-cell tabs (no
27
+ //! 4-col padding) + indent run-length (`⇥N`), plus per-page
28
+ //! width-trim so short payloads stop paying for 1568px rows.
29
+ //! * `font` — `Tiny` renders the same atlas box-filtered into a 4x6 cell
30
+ //! (390 cols x 120 rows/page), ~40% fewer image-tokens; opt-in,
31
+ //! transcription-accuracy gated.
32
+ var n=4,JJ=4,LQ=1568,bQ=728,QJ="↵",wQ="⏎",ZJ="→",vQ="⇢",NJ="⇥",fQ="⇨",kQ=9647,kJ=4,hQ=3,hJ="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",xQ=/\n{4,}/g;function $J(J){return J.length===4&&J.replace(/[A-Z]/g,(Z)=>Z.toLowerCase())==="tiny"?"tiny":"normal"}function uJ(J){let Z=J==="tiny"?4:v,Q=J==="tiny"?6:c,$=Math.floor((LQ-2*n)/Z),V=Math.floor((bQ-2*JJ)/Q);return{cw:Z,ch:Q,cols:$,maxLines:V,maxChars:$*V}}var mQ=(()=>{let J=new Uint8Array(128);for(let Z=0;Z<128;Z++){let Q=e(Z);J[Z]=Q>=0&&g(Q)?2:1}return J})();function IJ(J){if(J<128)return mQ[J];let Z=e(J);return Z>=0&&g(Z)?2:1}function uQ(J){let Z=0;for(let Q=0;Q<J.length;Q++){let $=J.charCodeAt(Q);if($>=55296&&$<56320&&Q+1<J.length){let V=J.charCodeAt(Q+1);if(V>=56320&&V<57344)Q++}Z++}return Z}function RJ(J){let Z=J.split(`
33
+ `);for(let Q=0;Q<Z.length;Q++){let $=Z[Q],V=$.length;while(V>0){let z=$.charCodeAt(V-1);if(z!==32&&z!==9)break;V--}if(V!==$.length)Z[Q]=$.slice(0,V)}return Z.join(`
34
+ `).replace(xQ,`
35
+
36
+
37
+ `)}function gJ(J){if(!J.includes("\t"))return J;let Z="",Q=0;for(let $ of J)if($==="\t"){let V=kJ-Q%kJ;Z+=ZJ;for(let z=1;z<V;z++)Z+=" ";Q+=V}else Z+=$,Q+=IJ($.codePointAt(0));return Z}function gQ(J){let Z=J.includes("\t")?J.replaceAll("\t",ZJ):J,Q=0;while(Q<Z.length&&Z.charCodeAt(Q)===32)Q++;if(Q>=hQ&&Q<hJ.length)return NJ+hJ[Q]+Z.slice(Q);return Z}function dJ(J){return J.includes(QJ)?J.replaceAll(QJ,wQ):J}function dQ(J){let Z=dJ(J);if(Z.includes(ZJ))Z=Z.replaceAll(ZJ,vQ);if(Z.includes(NJ))Z=Z.replaceAll(NJ,fQ);return Z}function lQ(J){let Z=RJ(J).split(`
38
+ `);for(let Q=0;Q<Z.length;Q++)Z[Q]=gJ(Z[Q]);return Z.join(QJ)}function pQ(J){let Z=RJ(J).split(`
39
+ `);for(let Q=0;Q<Z.length;Q++)Z[Q]=gQ(Z[Q]);return Z.join(QJ)}function cQ(J,Z){let Q=[],$=RJ(J);for(let V of $.split(`
40
+ `)){let z=gJ(V);if(z.length===0){Q.push("");continue}let Y="",G=0;for(let q of z){let H=IJ(q.codePointAt(0));if(G+H>Z)Q.push(Y),Y=q,G=H;else Y+=q,G+=H}if(Y.length!==0)Q.push(Y)}return Q}function nQ(J,Z,Q){let $=[],V=[],z=0;for(let Y of J){let G=uQ(Y),q=G+(V.length!==0?1:0);if(V.length!==0&&(V.length>=Z||z+q>Q))$.push(V),V=[],z=0;z+=G+(V.length!==0?1:0),V.push(Y)}if(V.length!==0)$.push(V);return $}function lJ(J,Z,Q,$){let V=Z?Q?pQ(dQ(J)):lQ(dJ(J)):J;return nQ(cQ(V,$.cols),$.maxLines,$.maxChars)}function pJ(J,Z,Q){if(!Q)return 2*n+Z.cols*Z.cw;let $=0;for(let Y of J){let G=0;for(let q of Y)G+=IJ(q.codePointAt(0));if(G>Z.cols)G=Z.cols;if(G>$)$=G}let V=2*n+$*Z.cw,z=2*n+Z.cw;return V>z?V:z}var xJ=new Map;function mJ(J,Z,Q,$,V,z,Y){let G=e(V);if(G<0)return 0;let q=g(G),H=q?2*z.cw:z.cw,U;if(Y==="normal")U=WJ(G);else{let B=xJ.get(G);if(B===void 0)B=vJ(G,H,z.ch),xJ.set(G,B);U=B}for(let B=0;B<z.ch;B++){let O=($+B)*Z+Q,X=B*H;for(let F=0;F<H;F++){let D=U[X+F];if(D>0){let R=O+F;if(D>J[R])J[R]=D}}}return q?2:1}function oQ(J,Z,Q,$){let V=pJ(J,Z,Q),z=2*JJ+J.length*Z.ch,Y=new Uint8Array(V*z),G=0;for(let q=0;q<J.length;q++){let H=JJ+q*Z.ch,U=0;for(let B of J[q]){if(U>=Z.cols)break;let O=n+U*Z.cw,X=mJ(Y,V,O,H,B.codePointAt(0),Z,$);if(X===0){if(G++,B!==" ")mJ(Y,V,O,H,kQ,Z,$);X=1}U+=X}}for(let q=0;q<Y.length;q++)Y[q]=255-Y[q];return{png:fJ(Y,V,z),width:V,height:z,dropped:G}}function VJ(J,Z,Q,$){let V=uJ($),z=lJ(J,Z,Q,V).map((q)=>oQ(q,V,Q,$)),Y=0,G=0;for(let q of z)Y+=q.width*q.height,G+=q.dropped;return{pages:z,pixels:Y,dropped:G}}function cJ(J,Z,Q,$){let V=uJ($),z=lJ(J,Z,Q,V),Y=0;for(let G of z)Y+=pJ(G,V,Q)*(2*JJ+G.length*V.ch);return{pages:z.length,pixels:Y}}function o(J){return Math.round(J/750)}import{readFileSync as rQ}from"node:fs";import{join as iQ}from"node:path";//! pxpipe measurement-log summary (~/.pxpipe/events.jsonl), same math as the
41
+ //! node MCP: actual = every way input bytes get billed (input + cache reads +
42
+ //! cache creates) — ignoring cache_read would fake the savings.
43
+ class r{value;constructor(J){this.value=J}}function aQ(){let J=process.env.TANUKI_EVENTS;if(J!==void 0)return J;let Z=process.env.HOME??"";return iQ(Z,".pxpipe","events.jsonl")}function d(J){return typeof J==="number"&&Number.isSafeInteger(J)&&J>=0?J:0}function tQ(J){return J<0?-Math.round(-J):Math.round(J)}function nJ(){let J=aQ(),Z;try{Z=rQ(J,"utf8")}catch{return{available:!1,note:`no ${J} yet`}}let Q=0,$=0,V=0,z=0,Y=0,G=0;for(let H of Z.split(`
44
+ `)){if(H.trim().length===0)continue;let U;try{U=JSON.parse(H)}catch{continue}Q+=1;let B=U!==null&&typeof U==="object"&&!Array.isArray(U)?U:{};if(B.compressed===!0)$+=1,V+=d(B.orig_chars),z+=d(B.image_count);Y+=d(B.baseline_tokens),G+=d(B.input_tokens)+d(B.cache_read_tokens)+d(B.cache_create_tokens)}let q=Y>0&&G>0?new r(tQ((1-G/Y)*1000)/10):null;return{available:!0,requests:Q,compressedRequests:$,imagedChars:V,imagesEmitted:z,baselineTokens:Y,actualInputTokens:G,estInputSavedPct:q}}//! tanuki-context — token-cutting context pipeline, all Rust.
45
+ //! pipeline: text -> distill (stage 0, logs) -> ladder level 0-4 (stage 1)
46
+ //! -> pxpipe imaging (stage 2, name kept from the original mechanic)
47
+ //!
48
+ //! Default: MCP stdio server (newline-delimited JSON-RPC 2.0).
49
+ //! CLI: tanuki-context distill <file> [query]
50
+ //! tanuki-context estimate <file> [level] [--distill]
51
+ //! tanuki-context render <file> [level] [outdir]
52
+ var QZ="0.1.0",PJ=6;function ZZ(J,Z){let Q=Math.min(J.length,Z.length),$=0;while($<Q&&J.charCodeAt($)===Z.charCodeAt($))$++;if($>=Q)return J.length-Z.length;return J.codePointAt($)-Z.codePointAt($)}function E(J,Z,Q=""){if(J===null||J===void 0)return"null";if(J instanceof r){let q=J.value;return Number.isFinite(q)&&Number.isInteger(q)?q.toFixed(1):String(q)}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(!Z){let U="[";for(let B=0;B<J.length;B++){if(B>0)U+=",";U+=E(J[B],!1)}return U+"]"}let q=Q+" ",H=`[
53
+ `;for(let U=0;U<J.length;U++){if(U>0)H+=`,
54
+ `;H+=q+E(J[U],!0,q)}return H+`
55
+ `+Q+"]"}let V=J,z=Object.keys(V).sort(ZZ);if(z.length===0)return"{}";if(!Z){let q="{";for(let H=0;H<z.length;H++){if(H>0)q+=",";q+=JSON.stringify(z[H])+":"+E(V[z[H]],!1)}return q+"}"}let Y=Q+" ",G=`{
56
+ `;for(let q=0;q<z.length;q++){if(q>0)G+=`,
57
+ `;G+=Y+JSON.stringify(z[q])+": "+E(V[z[q]],!0,Y)}return G+`
58
+ `+Q+"}"}function I(J,Z){return J!==null&&typeof J==="object"&&!Array.isArray(J)?J[Z]:void 0}function y(J){return typeof J==="string"?J:null}function zJ(J){return typeof J==="boolean"?J:null}function aJ(J){return typeof J==="number"&&Number.isSafeInteger(J)&&J>=0?J:null}function k(J){let Z=0;for(let Q=0;Q<J.length;Q++){Z++;let $=J.charCodeAt(Q);if($>=55296&&$<=56319&&Q+1<J.length){let V=J.charCodeAt(Q+1);if(V>=56320&&V<=57343)Q++}}return Z}function qJ(J,Z,Q,$,V){let z,Y=null;if(Q||$!==null){let H=p(J,$,2);z=H.distilled,Y=H.stats}else z=J;let G=0;if(V){let H=SJ(z);z=H.text,G=H.entries}let q=FJ(z,Z);return{stage0:Y,compressed:q.compressed,protectedLines:q.protectedLines,level:q.level,cbEntries:G}}function i(J){return Math.round(J/4)}function a(J,Z){if(J===0)return 0;let Q=(1-Z/J)*100;return Q<0?-Math.round(-Q):Math.round(Q)}function tJ(J){return{text:y(I(J,"text"))??"",level:(aJ(I(J,"level"))??0)%256,distill:zJ(I(J,"distill"))??!1,query:y(I(J,"query")),reflow:zJ(I(J,"reflow"))??!0,pack:zJ(I(J,"pack"))??!0,font:y(I(J,"font"))??"normal",codebook:zJ(I(J,"codebook"))??!1}}function sJ(J){let Z=tJ(J),Q=qJ(Z.text,Z.level,Z.distill,Z.query,Z.codebook),$=$J(Z.font),V=cJ(Q.compressed,Z.reflow,Z.pack,$),z=o(V.pixels),Y=k(Z.text),G=k(Q.compressed),q=i(Y),[H,U]=t[Q.level];return{engine:"pxpipe",level:`${Q.level} ${H}`,loss:U,distill:Q.stage0,origChars:Y,stage1Chars:G,stage1SavedPct:a(Y,G),pages:V.pages,imageTokens:z,rawTextTokens:q,totalSavedPct:a(q,z),protectedLines:Q.protectedLines,pack:Z.pack,font:$==="tiny"?"tiny":"normal",codebook:Z.codebook?Q.cbEntries:!1,verdict:z<q?"PIPELINE cheaper":"TEXT cheaper"}}function $Z(J){let Z=tJ(J),Q=qJ(Z.text,Z.level,Z.distill,Z.query,Z.codebook),$=$J(Z.font),V=VJ(Q.compressed,Z.reflow,Z.pack,$),z=o(V.pixels),Y=k(Z.text),G=k(Q.compressed),q=i(Y),[H,U]=t[Q.level],B="";if(Q.stage0!==null){let F=Q.stage0;B+=`distill: ${F.origLines} -> ${F.outLines} lines (-${F.savedPct}% chars, ${F.collapsedRuns} runs, ${F.suppressedLines} exact + ${F.templateSuppressed} template suppressed, ${F.importantKept} error/warn kept)
59
+ `}if(B+=`L${Q.level} ${H} (${U}): ${Y} chars -> ${G} chars (stage1 -${a(Y,G)}%) -> ${V.pages.length} page(s), ~${z} image-tokens
60
+ vs ~${q} text-tokens raw = TOTAL -${a(q,z)}%`,Q.protectedLines>0)B+=` · ${Q.protectedLines} lines kept verbatim`;if(V.dropped>0)B+=` · ${V.dropped} unmapped glyphs -> ▯`;if(Q.cbEntries>0)B+=` · codebook: ${Q.cbEntries} sigils (see ·legend·)`;if($==="tiny")B+=" · font: tiny 4x6";if(Z.pack)B+=" · packed (⇥N indent, → tab)";if(Z.reflow)B+=" · ↵ = newline · engine: pxpipe";let O=[{type:"text",text:B}],X=Math.min(V.pages.length,PJ);for(let F=0;F<X;F++){let D=V.pages[F].png;O.push({type:"image",data:Buffer.from(D.buffer,D.byteOffset,D.byteLength).toString("base64"),mimeType:"image/png"})}if(V.pages.length>PJ)O.push({type:"text",text:`(+${V.pages.length-PJ} more page(s))`});return O}function VZ(J){let Z=y(I(J,"text"))??"",Q=p(Z,y(I(J,"query")),2);return[{type:"text",text:E(Q.stats,!0)},{type:"text",text:Q.distilled}]}function zZ(J){let Z=y(I(J,"text"))??"",Q=(aJ(I(J,"level"))??1)%256,$=FJ(Z,Q),[V,z,Y]=t[$.level],G=k(Z),q=k($.compressed),H=i(G),U=i(q),B={level:`${$.level} ${V}`,loss:z,note:Y,origChars:G,outChars:q,approxOrigTokens:H,approxOutTokens:U,savedPct:a(H,U),protectedLines:$.protectedLines};return[{type:"text",text:E(B,!0)},{type:"text",text:$.compressed}]}function GZ(){let J={type:"string"},Z={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:Z,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:Z,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:Z},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 YZ(J){let Z=y(I(J,"name"))??"",Q=I(J,"arguments"),$;switch(Z){case"tanuki_render":$=$Z(Q);break;case"tanuki_estimate":$=[{type:"text",text:E(sJ(Q),!0)}];break;case"tanuki_distill":$=VZ(Q);break;case"tanuki_compress":$=zZ(Q);break;case"tanuki_stats":$=[{type:"text",text:E(nJ(),!0)}];break;default:return{ok:!1,error:`unknown tool: ${Z}`}}return{ok:!0,value:{content:$}}}function oJ(J){let Z=J.endsWith("\r")?J.slice(0,-1):J;if(Z.trim().length===0)return;let Q;try{Q=JSON.parse(Z)}catch{return}let $=Q!==null&&typeof Q==="object"&&!Array.isArray(Q)&&Object.prototype.hasOwnProperty.call(Q,"id"),V=$?Q.id??null:null,z=y(I(Q,"method")),Y;switch(z){case"initialize":{let G=y(I(I(Q,"params"),"protocolVersion"))??"2025-06-18";Y={jsonrpc:"2.0",id:V,result:{protocolVersion:G,capabilities:{tools:{}},serverInfo:{name:"tanuki-context",version:QZ}}};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:GZ()};break;case"tools/call":{let G=YZ(I(Q,"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)C.stdout.write(E(Y,!1)+`
61
+ `)}function qZ(){let J=Buffer.alloc(0);C.stdin.on("data",(Z)=>{let Q=J.length>0?Buffer.concat([J,Z]):Z,$=0,V;while((V=Q.indexOf(10,$))!==-1)oJ(Q.toString("utf8",$,V)),$=V+1;J=Q.subarray($)}),C.stdin.on("end",()=>{if(J.length>0)oJ(J.toString("utf8")),J=Buffer.alloc(0)})}function f(J){C.stderr.write(J+`
62
+ `),C.exit(101)}function GJ(J){try{return sQ(J,"utf8")}catch{f("read file")}}function YJ(J,Z){if(!/^\+?[0-9]+$/.test(J))return null;let Q=BigInt(J.startsWith("+")?J.slice(1):J);return Q>Z?null:Number(Q)}var rJ=255n,iJ=0xffffffffffffffffn;function HZ(){let J=C.argv.slice(1);switch(J[1]){case"distill":{let Z=J[2]??f("usage: tanuki-context distill <file> [query]"),Q=GJ(Z),$=p(Q,J[3]??null,2);C.stdout.write(E($.stats,!1)+`
63
+ `);break}case"estimate":{let Z=J[2]??f("usage: tanuki-context estimate <file> [level] [--distill] [--no-pack] [--font tiny] [--codebook]"),Q=GJ(Z),$=J.slice(3).filter((q)=>!q.startsWith("--")),V=$.length>0?YJ($[0],iJ)??0:0,z=J.indexOf("--font"),Y=z!==-1&&J[z+1]!==void 0?J[z+1]:"normal",G=sJ({text:Q,level:V,distill:J.includes("--distill"),pack:!J.includes("--no-pack"),font:Y,codebook:J.includes("--codebook")});C.stdout.write(E(G,!1)+`
64
+ `);break}case"render":{let Z=J[2]??f("usage: tanuki-context render <file> [level] [outdir] [--no-pack] [--font tiny] [--codebook]"),Q=GJ(Z),$=J.slice(3).filter((X)=>!X.startsWith("--")),V=$.length>0?YJ($[0],rJ)??0:0,z=!J.includes("--no-pack"),Y=J.includes("--codebook"),G=J.indexOf("--font"),q=$J(G!==-1&&J[G+1]!==void 0?J[G+1]:"normal"),H=qJ(Q,V,!1,null,Y),U=VJ(H.compressed,!0,z,q),B=o(U.pixels);C.stdout.write(E({pages:U.pages.length,imageTokens:B,dropped:U.dropped,rawTextTokens:i(k(Q))},!1)+`
65
+ `);let O=$[1];if(O!==void 0){try{eQ(O,{recursive:!0})}catch{f("mkdir")}for(let X=0;X<U.pages.length;X++)try{JZ(`${O}/page${X}.png`,U.pages[X].png)}catch{f("write png")}}break}case"bench":{let Z=J[2]??f("usage: tanuki-context bench <file> <op> [level] [runs] [--distill]"),Q=J[3]??"pipeline",$=J[4]!==void 0?YJ(J[4],rJ)??0:0,V=J[5]!==void 0?YJ(J[5],iJ)??3:3,z=J.includes("--distill"),Y=GJ(Z),G=[],q=null;for(let H=0;H<=V;H++){let U=performance.now();if(Q==="distill")q=p(Y,null,2).stats;else{let B=qJ(Y,$,z,null,!1),O=VJ(B.compressed,!0,!1,"normal");q={pages:O.pages.length,imageTokens:o(O.pixels),stage1Chars:k(B.compressed),dropped:O.dropped}}if(H>0)G.push(performance.now()-U)}if(G.sort((H,U)=>H-U),G.length===0)f("index out of bounds: the len is 0 but the index is 0");C.stdout.write(E({medianMs:new r(G[Math.floor(G.length/2)]),runs:V,result:q},!1)+`
66
+ `);break}case"serve":case void 0:qZ();break;default:C.stderr.write(`unknown command: ${J[1]}
67
+ usage: tanuki-context [serve|distill|estimate|render] ...
68
+ `),C.exit(1)}}HZ();
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "tanuki-context",
3
+ "version": "0.1.0",
4
+ "description": "Token-cutting context pipeline as an MCP server: distill logs, compress text, render dense PNG pages (pxpipe imaging engine). Zero dependencies.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "tanuki-context": "dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "assets",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "build": "bun build src/main.ts --target=node --minify --outfile=dist/cli.js",
18
+ "prepublishOnly": "bun run build",
19
+ "parity": "bun reference/parity-ts.mjs"
20
+ },
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/Osyna/tanuki-context.git"
27
+ },
28
+ "keywords": [
29
+ "mcp",
30
+ "model-context-protocol",
31
+ "context",
32
+ "compression",
33
+ "tokens",
34
+ "llm"
35
+ ]
36
+ }