tanuki-context 0.2.1 → 0.2.3

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
@@ -22,21 +22,25 @@ cleaned up and drawn. A vision model reads it directly:
22
22
 
23
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)
24
24
 
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.*
25
+ *Four pages like this replace 51,200 tokens of raw text with 5,264
26
+ a 90% cut. Zoom in: it's all still there, errors verbatim. (Hostnames,
27
+ IPs, MACs, and network names were rewritten to placeholders before
28
+ rendering; the structure and repetition are untouched.)*
27
29
 
28
30
  ## What you save
29
31
 
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):
32
+ Measured on that same 200 KB slice of a real system journal (1,434 lines
33
+ from tailscaled, NetworkManager, the kernel, and friends — mixed on
34
+ purpose; a plain `journalctl` tail is so repetitive the result looks fake.
35
+ Identifiers were rewritten before measuring; the repetition shape and
36
+ every error line are untouched):
33
37
 
34
38
  | how the log enters the conversation | tokens | saved |
35
39
  | ----------------------------------- | -----: | --------: |
36
40
  | pasted as raw text | 51,200 | 0 |
37
41
  | 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%** |
42
+ | noise removed first, then drawn | 5,264 | **-90%** |
43
+ | plus codebook and tiny font | 2,576 | **-95%** |
40
44
 
41
45
  Every row is one command on your own file:
42
46
 
@@ -81,8 +85,8 @@ call replaces a whole round of trial and error:
81
85
  ```
82
86
  npx tanuki-context estimate journal.log 0
83
87
  # { "imageTokens": 10752, "verdict": "PIPELINE cheaper",
84
- # "recommend": { "distill": true, "codebook": true, "imageTokens": 3920,
85
- # "pages": 3, "tinyImageTokens": 2352 }, ... }
88
+ # "recommend": { "distill": true, "codebook": true, "imageTokens": 4256,
89
+ # "pages": 3, "tinyImageTokens": 2576 }, ... }
86
90
  ```
87
91
 
88
92
  **Implicit (proxy) — for clients you can't modify.** A small local relay.
@@ -160,8 +164,8 @@ input (11 lines): distilled (deterministic):
160
164
  ```
161
165
 
162
166
  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.
167
+ always produces the same output. On the 200 KB journal: 1,434 lines to 641,
168
+ half the characters gone, all 310 error/warn lines kept verbatim.
165
169
 
166
170
  **Stage 1 — the ladder.** Five text-compression levels, 0 (off) to 4 (gist
167
171
  only). From level 2 up, an exact-recall guard keeps code, identifiers,
@@ -225,6 +229,49 @@ harness that holds the two engines to byte-identical JSON and
225
229
  pixel-identical PNGs on every knob combination, including distilled
226
230
  renders and a full MCP session with error paths.
227
231
 
232
+ ## How it compares
233
+
234
+ Five approaches to the same problem, measured on the same 200 KB journal
235
+ (51,200 tokens raw) on the same machine. They are not five competitors —
236
+ they are different answers to "does the model need to see this at all?",
237
+ and several of them compose.
238
+
239
+ | approach | what enters context | tokens | what the model can still do |
240
+ | --- | --- | ---: | --- |
241
+ | raw text (baseline) | everything, as text | 51,200 | everything, quotable |
242
+ | caveman-style compression (tanuki ladder L4, stays text) | reworded text | 51,198 (-0%) | on a log: nothing changes — the exact-recall guard protects every line, and unguarded caveman would corrupt them. On prose it manages -6% (DESIGN.md). Wrong tool for logs |
243
+ | ponytail strategy (send only the queried slice: `distill` + `query`) | matched + error lines, as text | 22,110 (-57%) | greppable, quotable text of everything important; the unmatched rest is gone |
244
+ | [pxpipe](https://github.com/teamchong/pxpipe) (its own `export` CLI on this corpus) | 8 PNG pages + prompt + factsheet | ~11,965 (-77%) | read everything back; 96 precision-critical strings ride verbatim in the factsheet |
245
+ | tanuki, distill + render | 4 PNG pages | 5,264 (-90%) | read everything that survives distill: repeats collapsed with exact counts, all 310 error/warn lines verbatim |
246
+ | tanuki, + codebook + tiny font | 2 PNG pages | 2,576 (-95%) | same content, lossy glyphs (99.7% read-back) — opt-in |
247
+ | [context-mode](https://www.npmjs.com/package/context-mode) (sandbox pass over the file) | one analysis result | ~270 per question (-99.5%) | only the answer it asked for; the file never enters context at all |
248
+
249
+ Reading that honestly:
250
+
251
+ - **context-mode wins whenever a question suffices.** If the model never
252
+ needs to *see* the log, ~270 tokens per query is unbeatable. The moment it
253
+ needs the material in front of it (debugging an unknown, quoting context,
254
+ "read all of this and tell me what's weird"), retrieval stops being the
255
+ same product. The two compose: keep the file in context-mode's sandbox,
256
+ render it with tanuki the day the model actually has to read it.
257
+ - **pxpipe and tanuki are the same engine**, so the gap between -77% and
258
+ -90% here is not the imaging — it is distill (pxpipe does not de-noise
259
+ logs) plus the sidecar text its export flow pastes alongside. On prose,
260
+ where distill does nothing, the two land in the same place. pxpipe's
261
+ factsheet is a fidelity feature tanuki lacks: guaranteed-verbatim strings
262
+ next to the pages.
263
+ - **Caveman-style prompt compression is the weakest option for logs**, and
264
+ tanuki's own level 4 proves it: the guard that makes it safe also makes
265
+ it a no-op on structured content. It exists for wordy prose.
266
+ - **The ponytail row is a strategy, not a tool**: send only what is needed.
267
+ It beats nothing on this corpus except raw text, but it composes — the
268
+ 22,110-token slice drawn as pages is 4,704 tokens, and `estimate` will
269
+ happily price that route for you.
270
+
271
+ Every row is reproducible: rows 1-3 and 5-6 are single `tanuki-context`
272
+ commands, the pxpipe row is `pxpipe export --stdin < your.log` on their
273
+ CLI, the context-mode row is one `ctx_execute_file` call in its sandbox.
274
+
228
275
  ## Limits, plainly
229
276
 
230
277
  - **Rendering is exact; reading is a model skill.** Pages are pixel-faithful
package/dist/agent.js CHANGED
@@ -65,7 +65,7 @@ class ZJ{value;constructor(J){this.value=J}}function nJ(){let J=process.env.TANU
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.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=`[
68
+ var cJ="0.2.3",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=`{
package/dist/cli.js CHANGED
@@ -71,7 +71,7 @@ var c={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.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=`[
74
+ var NZ="0.2.3",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=`{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tanuki-context",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
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",