v1.57.8.0 feat: browse js/eval --out render-to-file (canonical Chromium for offline rendering) (#1929)

* feat(browse): js/eval --out render-to-file with write-capability gate

Add --out <file> / --raw to js and eval so an evaluate result is written
straight to disk (base64 data URLs auto-decoded to bytes, charset-validated
before decode, parent dirs created) instead of serialized back through the
CLI. --out is modeled as a per-invocation WRITE: it requires write scope, is
never dispatchable over the pair-agent tunnel (canDispatchOverTunnel now
consults args), and counts as a mutation for watch-mode and tab-ownership.
Shared parseOutArgs/hasOutArg/resultToString helpers keep the handler and the
gate in sync. Tests cover the parser, render-to-file paths, and tunnel guards.

* docs(browse): offline render mode + canonical-Chromium guidance

Document the blessed offline-render path (headless, no proxy/Xvfb): visual
output via screenshot --selector, bytes a function returns via js --out.
Add the puppeteer->browse cheatsheet row, a "don't bundle your own Chromium"
note (browse skill + CONTRIBUTING), and the --out/--raw command descriptions.
Regenerate browse/SKILL.md, SKILL.md, and gstack/llms.txt from the templates.

* chore: bump version and changelog (v1.59.1.0)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: document js/eval --out render-to-file in BROWSER.md reference (v1.59.1.0)

The js and eval reference rows in BROWSER.md drifted: every other reference
surface (SKILL.md, gstack/llms.txt, browse/SKILL.md) already shows the new
[--out <file>] [--raw] flags from v1.59.1.0, but the complete browser
reference still showed the pre-feature signatures. Add the flags plus the
WRITE-capability / no-tunnel note so the reference matches what shipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: re-version 1.59.1.0 -> 1.57.8.0 (natural PATCH from 1.57.7.0)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-06-09 21:02:30 -07:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 1626d4857b
commit 421460f03a
14 changed files with 553 additions and 27 deletions
+67
View File
@@ -1,5 +1,72 @@
# Changelog
## [1.57.8.0] - 2026-06-09
## **`browse` is now the one Chromium on the box, for offline rendering too.**
## **`js`/`eval --out <file>` writes a render straight to disk, so skills stop bundling their own puppeteer.**
You can now turn your own local HTML or JSON into a PNG (or any bytes) on disk
through the same headless `browse` Chromium you already run, with no second
browser install. `js "<expr>" --out out.png` and `eval script.js --out out.png`
write the evaluate result to a file instead of returning it. When the result is a
base64 data URL (the shape Excalidraw exports, og-image generators, and card
renderers hand back), `--out` decodes it to raw bytes for you; pass `--raw` to
write the literal string. Malformed base64 errors loudly instead of writing a
corrupt file, and missing parent directories are created. This closes the gap that
made local-render skills each `npm i puppeteer` and download a drifting second
Chromium.
### The numbers that matter
No synthetic benchmark — these are structural facts of the change, verifiable from
the diff and a one-line smoke (`browse load-html``screenshot --selector` /
`js --out`).
| For a skill that rasterizes local HTML/JSON | Before | After |
|---|---|---|
| Chromium installs per box | 2+ (browse + each skill's own puppeteer) | 1 (shared `browse`) |
| Getting a PNG from a render function | `evaluate` → multi-MB data URL over the CLI channel → hand-decode base64 → write | `js --out` decodes and writes server-side; only a short status crosses the channel |
| Render-to-file primitive | none | `js`/`eval --out [--raw]` |
The blessed offline path is documented in the browse skill: visual output goes
through `screenshot --selector` (the picture never crosses the CDP wire), and bytes
a function returns go through `js --out`.
### What this means for you
If you write skills that draw diagrams, cards, or og-images, point them at `browse`
and delete the bundled Chromium. One version to pin, one daemon to manage. `--out`
is treated as a write everywhere it matters: it needs the `write` scope, is blocked
over the pair-agent tunnel, and is gated in watch mode, so a remote agent can never
use it to write to your disk.
### Itemized changes
#### Added
- **`js` / `eval --out <file>` render-to-file** (`browse/src/read-commands.ts`).
Writes the evaluate result to disk and returns a short `... result written: <path>
(<N> bytes)` status. A `data:<type>;base64,...` result is decoded to raw bytes
(case-insensitive header parse, split on the first comma, base64-charset validated
before decode); `--raw` forces a literal write. Parent directories are created.
- **`--raw` flag** to bypass data-URL decoding and write the literal result string.
- **Offline render mode docs** in the browse skill: an explicit headless, no-proxy,
no-Xvfb path with a worked example showing visual (`screenshot --selector`) vs
bytes (`js --out`), a puppeteer→browse cheatsheet row, and a "don't bundle your
own Chromium" note (also in CONTRIBUTING.md).
#### Changed
- **`--out` is a per-invocation WRITE capability** (`browse/src/server.ts`).
`js`/`eval` stay read commands, but an `--out` invocation requires the `write`
scope, is never dispatchable over the tunnel surface (`canDispatchOverTunnel` now
consults args), and counts as a mutation for watch-mode and tab-ownership gates.
#### For contributors
- New tests: `parseOutArgs`/`hasOutArg` unit coverage (`--out`/`--out=`, `--raw`,
repeats, missing value, ordering), `--out` render-to-file integration (large
string, data-URL→PNG, `--raw`, malformed-base64, outside-safe-dir, mkdir, eval
parity, byte-for-byte null/undefined), and tunnel-gate guards proving `--out`
is never tunnel-dispatchable.
## [1.57.7.0] - 2026-06-08
## **Every plan review now ends by telling you, in one line, whether anything is still unresolved.**