44 Commits

Author SHA1 Message Date
teamchong e57cc07c1b chore(publish): rename npm package to pxpipe-proxy (registry typo-squat filter blocked pxpipe); CLI command stays pxpipe 2026-06-10 07:06:22 -04:00
teamchong 63c1eab83e chore(publish): absolute README image URL, repo/homepage/bugs fields, Fable-scoped description+keywords, drop pnpm engine 2026-06-10 06:59:02 -04:00
teamchong 40a3eb5cdd docs: caption matches the real render measurements 2026-06-09 21:58:37 -04:00
teamchong e520749b84 docs: example render is now real transformRequest output (reflow + instruction banner) 2026-06-09 21:57:50 -04:00
teamchong 7a80bd1799 docs: fix image caption to match new dense example render 2026-06-09 21:45:46 -04:00
teamchong 898be465a3 docs: README example image now matches real dashboard density (full dense page, synthetic content) 2026-06-09 21:45:12 -04:00
teamchong 4ffb75e665 docs: drop GPT 5.5 route mention, Fable 5 only 2026-06-09 20:36:16 -04:00
teamchong c35b3ab2d4 docs: rewrite README for readability — hook first, real render example, honest-caveats section 2026-06-09 19:10:49 -04:00
teamchong 65cd8aab71 docs: hide unproven Cloudflare Workers quick start 2026-06-09 19:08:49 -04:00
teamchong 5eb7ec6156 docs: demote unmeasured OpenAI route to a footnote, fix stale test count and ocproxy mention 2026-06-09 19:08:33 -04:00
teamchong b5504d166e docs: drop redundant (npm: pxpipe) from README title 2026-06-09 19:05:56 -04:00
teamchong 99bb931014 chore: rename all pixelpipe references to pxpipe
- code, tests, docs, README, wrangler.toml, help text
- data dir ~/.pixelpipe -> ~/.pxpipe (PXPIPE_LOG env var)
- rebuilt dist; 323/323 tests
2026-06-09 19:04:09 -04:00
teamchong a1c51b3543 feat: publish as pxpipe on npm
- rename package to pxpipe (pixelpipe is an npm security placeholder), bin: pxpipe
- move @napi-rs/canvas to dependencies (runtime dep for npx consumers)
- drop only-allow pnpm preinstall (blocked npm/npx installs)
- add prepublishOnly guard (typecheck + test + build)
- exclude sourcemaps from tarball; README quick start: npx pxpipe
2026-06-09 19:01:22 -04:00
teamchong 0e3c95a079 docs: add CLI proxy quick start, fix stale constants table and test count 2026-06-09 18:33:34 -04:00
teamchong 3d0f9ef08a scope: narrow model gate to fable-5, drop opus; switch dense render to bare 5x8 cell
- gate regex: fable-5 only (opus 4.7/4.8 read tax gone on fable: 100/100 novel-arithmetic, identical image billing)
- remove dead opus-4.6 cpt fork in transform.ts
- DENSE_RENDER_STYLE: 7x10 padded -> bare 5x8 cell (~42% fewer image tokens/page, recall flat in A/B)
- dashboard: compression defaults ON
- README/FINDINGS/TRANSFORM_INFO updated with 2026-06-09 measurements
2026-06-09 18:33:34 -04:00
Steven Chong 613ca3bc71 feat(proxy): support gpt 5.5 chat completions 2026-06-05 00:35:26 -04:00
teamchong f6150ba2bd docs(readme): lead benchmark with the clean 93%, demote contaminated GSM8K to a footnote
One headline number (93% novel reading), 0/15 as the boundary, GSM8K 96%
moved to a flagged footnote so the result isn't ambiguous.
2026-05-31 18:50:31 -04:00
teamchong 05a5ac98dc docs(eval): reproducible reading-fidelity benchmark + honest README table
Does the model actually read pixelpipe's render, or coast on memorized answers?
- novel random-number problems (un-memorizable): text 100% vs image 93% (-7pp real reading tax)
- GSM8K (in training data): 97% vs 96% -- inflated ~3pp by recall
- verbatim recall from a dense render: 0/15

README 'Benchmarks' section leads with the clean 93% and reports the failure
rows too (no cherry-pick). Harness in eval/gsm8k/: gen_novel.py, render_cfg.mjs
(pixelpipe's real renderTextToPngs), bench.py -- claude -p, exact-match grading.
2026-05-31 18:48:19 -04:00
teamchong 378dd7f6c4 feat(applicability)!: scope to opus 4.7+ and enforce model gate at proxy
Widen the model gate to /^claude-opus-4-(?:[7-9]|[1-9]\d)(?:-|$)/ (Opus 4.7
and newer; previously 4.6/4.7). Wire isPixelpipeSupportedModel into the proxy
boundary in proxy.ts — the proxy previously gated on economics only and
ignored the model, so it compressed 4.8 traffic despite the docs claiming a
4.6/4.7 scope. Unsupported models now pass through with reason
'unsupported_model'.

Update public-api tests to the 4.7+ spec (320/320 pass).

Correct the docs to live measurement: reverse the POSTMORTEM "dead" verdict
(pixelpipe is a lossy gist-compressor saving ~68% on real dense Claude Code
traffic; the verbatim 0/15 needle finding stands as a caveat, not the
verdict), rewrite README Status/Limitations, and add a correction pointer to
the eval README. Original POSTMORTEM body preserved below the correction.

BREAKING CHANGE: Opus 4.6 and older are no longer supported.
Verbatim-risk guard (skip imaging unique IDs/hashes/exact values) still pending.
2026-05-29 22:34:55 -04:00
Steven Chong 2f78ecaf9f fix(transform): use conservative cpt defaults for opus 4.6 2026-05-26 15:46:19 -04:00
Steven Chong d3992c6bad feat(applicability): enable pixelpipe for opus 4.6 2026-05-26 15:35:12 -04:00
Steven Chong be545b7ccd feat(render): full-canvas single-column rendering, 50k chars/page
Pixelpipe now always uses the full 1568px canvas (cols=313) and packs up
to 50,000 chars per image. The multi-column code path and the
shrink-to-content path are no longer used by transform.ts - both were
sacrificing token savings on dense content to make sparse content marginally
more readable, and the readability gain wasn't real (the renderer's
output was unreadable in either layout once the content was actually dense).

Key changes:
- READABLE_CHARS_PER_IMAGE: 6_000 -> 50_000
- DEFAULT_COLS: 100 -> 313 (full 1568px / 5px cell)
- shrinkColsToContent() is now a no-op (returns cols unchanged)
- MinToolResultChars / MinReminderChars default to 50k (was 6k)
- transform.ts always renders single-column at full canvas

Practical impact: a 6 KB tool_result that previously rendered as 4 narrow
508x488 pages (1324 image tokens, 86% of text cost) now renders as 1
full-canvas 1568x488 page (~331 image tokens, 22% of text cost) - 4x
more savings on the same content with no quality change.

Rebuilt dist/. Tests: 315/315 pass.
2026-05-25 14:20:43 -04:00
Steven Chong 393f72ee7c docs(readme): reframe tagline — pixels instead of a transcript, context as UI 2026-05-22 23:38:20 -04:00
teamchong a8d2f62d63 docs(cell): update stale 7×10 references to current 5×8 production cell
The reflow-inimage finding (commit bb9c231) made 5×8 viable, so
DEFAULT_CELL_W_BONUS / DEFAULT_CELL_H_BONUS reverted to 0 — but many
comments and the README still said 7×10 was production.

Touched:
- README.md             — eval table, density bullet, sweep narrative,
                          cell-pitch section, measurement header,
                          Limitations section
- src/core/render.ts    — RenderStyle docstring + cell-bonus docstrings
                          + multicol intro comment
- src/core/transform.ts — TransformOptions.reflow docstring, DEFAULTS
                          comments (minReminderChars floor rationale,
                          maxImagesPerToolResult cap, reflow gate
                          rationale, anchor scaling formula, LINES_PER_IMAGE
                          worked example)

No code behavior changed — every constant still resolves through CELL_W /
CELL_H, and the eval harness still overrides the cell via cellWBonus /
cellHBonus when comparing variants. The 5×8 production path is what was
already shipped on 81550f6 (reflow + grayscale + L1/L2 harness) and
bb9c231 (reflow-inimage).
2026-05-22 22:59:20 -04:00
teamchong 4774aeb14c feat(eval): in-image instruction variant (reflow-inimage) — +1.04pp vs baseline
Co-render the OCR instruction into the same PNG as the content,
delimited by '===…===' bands, with the API system field dropped.

L1 OCR fidelity, Opus 4.7, 20 production blocks, 7×10 cell:
- baseline (text-only):       97.91% mean / 96.25% min
- reflow (separate system):   91.99% mean / 82.59% min  (-5.93pp)
- reflow-inimage:             98.95% mean / 96.42% min  (+1.04pp)

reflow-inimage wins on all 20/20 blocks vs reflow, and beats the
text-only baseline on 17/20 blocks. Three blocks hit 100%. The
-5.93pp reflow regression that the cell-pitch sweep partially
recovered disappears entirely when the instruction is co-rendered.

Mechanism: when system carries the instruction and the image
carries the content, the model does cross-modal binding to figure
out what the image is for. Co-rendering reduces it to a
single-modal task with an unambiguous parse rule.

Files:
- eval/eval-l1-ocr.mjs:        add reflow-inimage variant + prompt
- README.md:                   new section before history compression
- eval/EXPERIMENT_LOG.md:      attempt #2 writeup
- eval/results/{l1-report.md, l1-results.json}: regenerated
2026-05-22 22:04:10 -04:00
teamchong 81550f690e feat(render): pack reflow across newlines + grayscale atlas + L1/L2 eval harness
- wrapLines packs continuously; NL_SENTINEL stays as an inline marker, never breaks a row
- atlas-gray.ts: anti-aliased grayscale variant (gated via RenderStyle.aa, unused in production)
- gen-atlas.ts: optional ATLAS_GRAY=1 emission
- eval/: standalone Anthropic-client L1 OCR fidelity + L2 session replay harness, claude -p backed
- README: reflect packed reflow + measured Opus 4.7 numbers
2026-05-22 20:48:40 -04:00
Steven Chong d87aecec95 feat(render): use 5x8 code-font atlas 2026-05-21 17:41:30 -04:00
Steven Chong 6b508c367d docs(readme): summarize VLM text-density research for renderer tuning
Capture the practical conclusions from recent VLM/OCR papers:
ReadBench (arXiv:2505.19091), typographic perturbation work
(arXiv:2604.12371 / 2604.25102), and typography-gap work
(arXiv:2603.08497).

The main guidance for pixelpipe is: don't increase DPI; margin tweaks
are tiny; the real foundational experiment is a denser-but-readable
bitmap atlas (e.g. 5x11 -> 4x8/4x7) with exact retrieval tests before
shipping.
2026-05-21 16:39:21 -04:00
Steven Chong 3d5d27d0db fix: Opus 4.7 pricing is $5/$25/MTok, not $2.50/$12.50 — revert prior "fix"
Earlier commit a0f282f "corrected" Opus 4.7 rates downward to $2.50
input / $12.50 output based on a misread of the pricing source. Per
docs.claude.com/en/docs/about-claude/pricing (current), Opus 4.7 is:

  Base input:       $5    / MTok   (same as Opus 4.5, 4.6)
  Output:           $25   / MTok
  5m cache write:   $6.25 / MTok
  Cache hits/reads: $0.50 / MTok

Same dollar pricing as Opus 4.5 / 4.6 — what changed in 4.7 is the
tokenizer, not the per-token rate. The README now flags this so future
contributors don't assume hardcoded chars-per-token / image-token
constants tuned on earlier models are still safe on 4.7; only
count_tokens against the target model is honest.

Touches:
  - src/dashboard.ts: ASSUMED_INPUT_USD_PER_MTOK back to 5.0, with a
    long comment block citing the source and warning about tokenizer
    differences. Updated card subtext on the headline accordingly.
  - README.md: pricing table back to $5/$25/$6.25/$0.50, with a
    callout that the tokenizer changed in 4.7.
2026-05-21 12:38:10 -04:00
Steven Chong 09e4f635ea docs(readme): stop claiming '$ saved'; report token deltas only
The README headlined a '~76% fewer tokens' framing, a worked example
with a 'savings' column, and an Opus pricing table that used the old
Opus-4-base rates ($5/$25/MTok input/output) instead of Opus 4.7's
actual rates ($2.50/$12.50). Combined, it read like an aggregate
$-savings claim that the data does not yet support.

What's actually true and stays:
  - Pixelpipe ships fewer input tokens on cold-miss requests, measured
    by Anthropic's own count_tokens endpoint. The 173,783 -> 41,321
    anecdote is one real request, not a session average.
  - Image-tokens-vs-text is a real per-request encoding-density
    observation.

What was misleading and is removed/softened:
  - Opening tagline no longer claims '~76% fewer tokens, 100% quality'
    as if it generalizes. Replaced with an experimental-status note.
  - Worked-example 'savings' column renamed 'delta' and contextualized.
  - 'How we report numbers' section added that explains the
    baseline_probe_status gate, says we don't currently make an
    aggregate $ saved claim, and warns the reader to treat any host
    dashboard that surfaces $ saved without the gate as marketing.
  - Pricing table corrected to Opus 4.7 rates.
2026-05-21 12:26:26 -04:00
Steven Chong 3d22a04356 docs(readme): document multi-turn amortization problem and design space
The break-even gate in collapseHistory is per-turn — it answers 'is the
image cheaper than the text on this single request?' That question has
the wrong answer once Anthropic has cached the prior text-based prefix,
because text-at-10% beats image-at-40%-cold. But the prefix's lifetime
cost is what matters, not the per-turn cost, and lifetime cost favours
the image once the cache eventually expires.

This is the same shape of decision a JIT compiler, a DB optimiser, or
ZFS block compression makes. Documenting the analogues, the four
credible designs we considered (try-then-decide / session-state /
always-collapse / cache-bust-driven), and why we picked try-then-decide
as v1.

The Why-it's-hard section gets a new paragraph framing the asymmetric
cache invalidation. The history-compression section gets a new
'The unsolved part: multi-turn amortization' subsection with the full
design space and the decision criteria.

No code changes — this is design documentation for future contributors
so they don't reinvent the analysis.
2026-05-21 10:13:00 -04:00
Steven Chong 0aa2cce88f Expose pixelpipe library API 2026-05-20 23:47:32 -04:00
teamchong 51c8f10012 docs(README): drop duplicate blockquote, change headline to "pixels instead of text"
- Headline: "pixels instead of tokens" → "pixels instead of text" (images
  bill as tokens too — the honest framing is pixels vs text)
- Removed the redundant "Why this works in 2026" blockquote since the
  Opus 4.7 explanation already lives in the intro paragraph
2026-05-20 13:38:49 -04:00
teamchong 5325effb65 docs(README): reframe as context-as-UI, not cost arbitrage
The intro now leads with the hypothesis (pixels pack more semantic info
per unit of context than serialized text) and treats the token reduction
as measurable evidence rather than the headline.

Adds a "What this is NOT" section to address the ToS / billing-loophole
read directly:
- Not ToS evasion (uses the documented vision API as documented)
- Not a billing loophole (savings hold even if Anthropic re-prices)
- Not cost arbitrage (cost is a side effect of density)

Same numbers, same code, different stance: defensive about the framing
("I'm not paying for churn I didn't cause") rather than offensive about
the pricing.
2026-05-20 13:33:47 -04:00
teamchong 3c759ed662 docs(README): cite Opus 4.7 vision upgrade as what makes this possible
Pre-4.7 vision couldn't reliably OCR dense monospace glyphs - errors
would corrupt the prompt before the model read it. Opus 4.7 bumped
the long-edge image cap 1568->2576px (3.3x pixels) and document-OCR
benchmarks (DocVQA 87->94%, ChartQA 80->88%) per Anthropic's release
notes.

Pixelpipe still renders at 1568x1568 - this is a model fidelity
change, not a renderer change. But it's why this approach works now
when it didn't a year ago.

Cites anthropic.com/news/claude-opus-4-7.
2026-05-20 10:10:11 -04:00
teamchong 3173e8ad92 docs(README): drop Opus 4 column from rate table
Proxy targets Opus 4.7. Input pricing has been flat across Opus 4.5/4.6/4.7
($5/$25 per MTok), so the historical Opus 4 column ($15/$75) was framing
noise unrelated to actual cost.
2026-05-20 09:44:27 -04:00
teamchong 699ce3023f docs(README): explain history compression (Variant C)
~80-line section covering the 4-breakpoint cache cliff that history
collapse addresses, the closed-prefix walk, the 4 honesty gates, and
today's HISTORY_CHARS_PER_TOKEN=2.5 fix wired in b563751.

Links to the live data point in events.jsonl (12:30:01 event,
collapsed_turns=175, collapsed_chars=180,684) and the relevant
transform.ts / core/history.ts call sites.
2026-05-20 09:40:38 -04:00
teamchong 8cdda62779 docs(README): name every input bucket the proxy touches
Previous version only listed system+history+tool_result. Real input
shape also includes:

- tools field (built-in schemas + every MCP server's tool definitions
  - usually the largest single bucket; grows with each MCP server
  wired up)
- skills loaded into the system prompt (SKILL.md per skill)
- subagent definitions
- <system-reminder> blocks Claude Code injects into user messages

Headline math unchanged - all of these land in the same static slab or
closed-prefix history that pixelpipe image-compresses.
2026-05-20 09:10:09 -04:00
teamchong a66a558b55 refactor: remove all CLI flags and behavior env vars — single codepath
The proxy used to expose ~20 behavior toggles (--no-compress,
--no-tools, --no-schemas, --no-reminders, --no-tool-results,
--history, --no-history, --history-keep-tail N, --history-min-prefix N,
--min-chars N, --min-reminder-chars N, --min-tool-result-chars N,
--cols N, --multi-col N, --no-track, --events-file PATH, --port N,
--upstream URL) plus matching env vars. Each one was a dead branch in
the common case and a configuration knob nobody had a principled
reason to pick a value for.

Replaced with: exactly one way to run the proxy. Every compression
mode on, every tuning parameter at its measured-best value, history
compression always active (was opt-in dead code). The only adjustable
surface is deployment concerns — where to listen, what to forward,
where to log — env-var only:

  PORT                 default 47821
  ANTHROPIC_UPSTREAM   default https://api.anthropic.com
  PIXELPIPE_LOG        default ~/.pixelpipe/events.jsonl

CLI accepts only --help and --version. Anything else exits with
non-zero and a one-line error.

Notable behavior change:
- DEFAULTS.compressHistory flipped false → true. Variant-C history-
  image compression now runs on every request (was opt-in via
  --history / COMPRESS_HISTORY=1). The static-slab cache_control
  placement is on the system image, not on the history-replacement
  chain, so the published cache-topology risk does not apply.

Touched:
- src/node.ts: CliOpts → RuntimeConfig (3 fields). parseCli is now
  ~25 lines. printHelp reduced to env-var-only doc.
- src/core/transform.ts: DEFAULTS.compressHistory = true. Comment
  updated.
- scripts/restart.sh: drops PROXY_ARGS array entirely. Port read
  from PORT= env var. Unknown args rejected.
- tests/restart.test.sh: replaces flag-passthrough test with a
  reject-unknown-args test. 4/4 pass.
- tests/history.test.ts: renames the "compressHistory:false (default)"
  test to reflect the new always-on default; asserts the input-not-
  mutated invariant remains.
- README.md: configuration table reduced to 3 env vars; restart
  examples drop CLI flag passthrough.

Tests: 277/277. Typecheck + build clean. Restart script tests: 4/4.
2026-05-19 17:01:05 -04:00
teamchong f199640bdc ship Unifont @ 10px multilingual rendering + 400 diagnosis fixes
- Switch font: JetBrains Mono @ 15px → Unifont 16.0.04 @ 10px. Sparse
  atlas with double-width EAW dispatch for CJK. Full BMP profile
  (35,501 glyphs) covers Latin/Cyrillic/Greek/Hebrew/Arabic/CJK/Hangul
  plus Letterlike, Misc Technical, Block Elements, Geometric Shapes,
  Misc Symbols, Dingbats, Enclosed Alphanumerics, all box-drawing.
- Fix cell-height bug (was hardcoded 9; now probed from font metrics
  via Math.ceil(maxAscent+maxDescent)). Previously clipped the top
  2 rows off every CJK glyph since the original Unifont ship.
- Expand tabs to spaces with 4-column tab stops, EAW-aware. Was the
  silent-drop source for 99.6% of dropped chars in production.
- Add dropped_codepoints_top telemetry (top 20 per event, only emitted
  when drops > 0) so future coverage gaps surface in events.jsonl.
- Raise minToolResultChars 2000→5000 and minReminderChars 1000→2000
  to stop net-losing tokens on small compressions, per ~2,500 tok/img
  empirical measurement (N=33 cold-miss events).
- Fix dashboard per-image-cost formula: was pngBytes/375 (~190 tok),
  now imageCount * 2500 (the measured empirical rate). Reduces the
  inflated "tokens saved" headline to honest numbers.
- Schema-stub fix in compressSchemas path: preserveSchemaShell()
  retains type / properties / required / enum / oneOf / anyOf / $ref /
  numeric constraints recursively (depth 20), strips only descriptions
  and metadata. Prevents 400 errors on first-tool-call requests.
- Capture full gzipped 4xx body + per-event req_body_sha8 for upstream
  error diagnosis. Sidecar to ~/.pixelpipe/4xx-bodies/ when over 32 KB
  inline cap. Workers-safe via CompressionStream + Web Crypto.
- scripts/restart.sh: pnpm-aware one-command kill+rebuild+start with
  graceful SIGTERM→SIGKILL escalation, build-failure abort, port-in-use
  precheck, --no-build flag for fast iteration. Shell-tested.
- Bundle 1.58 MB gzipped on Node. 91/91 vitest + 4/4 shell tests.
2026-05-18 21:28:25 -04:00
teamchong be372a0c3a legacy: drop python reference oracle (live savings confirmed at 98.7%)
HANDOFF item #4 was "Delete legacy/python/ — but only after #1 confirms
live savings." Item #1 (live cache-rate validation) now passes: over 232
compressed POST /v1/messages calls in a 46-minute real-traffic window,
cache hit rate by tokens is 98.7%, clearing the HANDOFF 60% bar by 38
points. The Python reference has done its job.

Removes (1831 deletions):
  legacy/python/proxy.py        (975)  full Python proxy implementation
  legacy/python/diff_transform.py (213)  body-diff parity oracle
  legacy/python/cli.js          (170)  shim
  legacy/python/visual_compare.py (150) PNG-diff parity oracle
  legacy/python/diff_render.py  (136)  render-only parity oracle
  legacy/python/gen_atlas.py    ( 75)  atlas generator (TS port owns this now
                                       via scripts/gen-atlas.ts)
  legacy/python/install.js      ( 50)  installer shim
  legacy/README.md              ( 35)

Comment / doc updates to remove references:
  README.md            drops the "## Legacy" section
  CLAUDE.md            drops the "## Parity oracle" section + tree entry
  src/core/transform.ts  header comment no longer cites legacy/python
  src/dashboard.ts     header comment notes the formulas were ported,
                       not where from
  docs/TRANSFORM_INFO.md edits land in the separate TRANSFORM_INFO commit

The byte-output determinism the Python oracle existed to guard against
is now pinned by tests/render.test.ts ("renders identical input to byte-
identical output (determinism = cacheability)") and the systemSha8
fingerprint in every JSONL row. If a future change ever breaks
determinism, the test will catch it before merge, and the
production-deployed cache hit rate will catch it within minutes after.

Gates: typecheck clean, 38 tests pass, build clean dist/node.js.
2026-05-18 17:56:56 -04:00
teamchong 6b50f94e0b Rewrite README, CLAUDE.md, HANDOFF for TypeScript implementation
README: Node + Workers quickstart, config table, architecture diagram,
        limitations, dev commands. Legacy Python pointer.
CLAUDE.md: agent orientation — file layout, constraints (no node: in core,
        no new runtime deps, don't hand-edit atlas.ts), verification
        recipe, Anthropic API constraints we can't regress.
HANDOFF.md: current status (TS port lands, parity not yet verified),
        what works, what doesn't, key design decisions, prioritized
        list of follow-ups (parity test, live savings test, Workers
        deploy, delete legacy/).
2026-05-18 15:35:19 -04:00
teamchong f20207cd7b Rename: claude-image-proxy -> pixelpipe (Tier 1: code + docs)
Project rename for the Show HN launch with framing:
  'pixelpipe — what if Opus saw a UI instead of just append-only logs?'

Renamed everywhere:
  - kebab-case 'claude-image-proxy' -> 'pixelpipe' (42 occurrences)
  - snake_case 'claude_image_proxy' -> 'pixelpipe'  (5 occurrences)
  - 14 files touched: package.json, Cargo.toml, Cargo.lock, *.md,
    bin/cli.js, scripts/install.js, src/proxy.py, src/rust/src/main.rs,
    src/rust/examples/{render_sample,transform_only,diff_render}.rs,
    src/zig/build.zig

Effects on build artifacts:
  - npm package name -> 'pixelpipe'
  - Rust binary name -> target/release/pixelpipe (was claude-image-proxy)
  - Python cache dir -> ~/.cache/pixelpipe/ (was ~/.cache/claude-image-proxy/)

NOT YET DONE in this commit (Tier 2+3, handled in next commit):
  - Repo on-disk directory still ~/Downloads/repos/claude-image-proxy/
  - Cache dir on disk still ~/.cache/claude-image-proxy/ — Python proxy
    currently writes here. Will be migrated alongside the running services
    in a coordinated stop/move/restart sequence.

Validation: scripts/diff_transform.py still produces exactly 1 structural
diff (the PNG bytes, expected — different fonts Python/Menlo vs
Rust/JetBrains Mono). No regression from the rename.
2026-05-18 14:16:48 -04:00
teamchong 9fddbeb28a Initial commit: working Python proxy
Token-saving HTTP proxy for Claude Code that renders system prompt + tools
as 5pt Menlo monospace images. Achieves 65-73% token reduction with 100%
reasoning quality preserved on Opus 4.7.

Production state at this commit:
- src/proxy.py: 972 lines, battle-tested with 46+ real Claude Code sessions
  observed (~551k tokens saved, ~$8.27 in stats.json as of commit time)
- bin/cli.js: Node wrapper that spawns Python proxy on port 47821
- scripts/gen_atlas.py: generates the bundled Menlo 5pt glyph atlas
- scripts/install.js: postinstall — verifies Python + Pillow + httpx
- src/zig/: earlier Zig 0.16 renderer (kept for reference, not built)
- CLAUDE.md, HANDOFF.md: contributor notes
- package.json: npm entry point, version 0.1.0

Known issue (deferred for separate fix):
  src/proxy.py:125 — `tallest = max(lines_per_col, last_col_lines) if
  c_needed == 1 else lines_per_col` pads every single-column image to
  full MAX_EDGE=1568 height regardless of content, wasting image-token
  budget for small inputs. Tight bounding would improve savings further.

This is the rollback point. Any subsequent changes (Rust port, Python
fixes, etc.) build on this proven baseline.
2026-05-18 11:52:02 -04:00