Skip to content

feat(dist): self-contained binary + brew/scoop/curl install paths - #128

Merged
click6067-ship-it merged 1 commit into
masterfrom
click6067-ship-it/distribution-binaries-2026-09-14
Sep 14, 2026
Merged

feat(dist): self-contained binary + brew/scoop/curl install paths#128
click6067-ship-it merged 1 commit into
masterfrom
click6067-ship-it/distribution-binaries-2026-09-14

Conversation

@click6067-ship-it

Copy link
Copy Markdown
Owner

Phase A of closing the distribution gap. Plan: fitllm-v2/docs/plans/2026-09-14-distribution-parity.md (contract PASS, 8 AC).

Why

Accuracy only counts if the tool is installable where its users are. Today: npx only, which requires Node. The comparable tool in this space ships through brew, scoop, uv, docker and curl | sh, and its latest release saw 8,751 downloads in four days.

What

No engine change. It works untouched because the package has zero runtime dependencies and exactly one non-relative import (node:child_process), so bun build --compile wraps the existing code. Porting to Rust/Go was rejectedengine.js is the asset; a port makes a second copy of the truth that drifts.

File Role
scripts/build-binaries.mjs 7 targets (macOS arm64/x64, Linux x64/arm64 × glibc/musl, Windows x64), .sha256 each, manifest
packaging/install.sh platform + libc detection, checksum verification, fail-closed
scripts/gen-packaging.mjs Homebrew formula + Scoop manifest derived from the build manifest
.github/workflows/binaries.yml test → build → attach on v*

Verified by execution, not assertion

  • Parity: host-target binary stdout is byte-identical to the node path on 3 cases; the build fails otherwise. A user seeing different numbers depending on install method is the worst failure this product has.
  • No Node needed: runs under PATH=/var/empty (no node, no sh) → exit 0, correct output.
  • install.sh happy path: resolved latest version, downloaded, verified, installed, ran.
  • install.sh fail-closed: unsupported OS / unsupported arch / missing release / tampered binary → each exits 1 with zero files written.
  • Packaging: every sha256 in the formula and Scoop manifest matches the published release asset.
  • npm test 292/292 (+6 new), vectors 33/33.

v2.16.0 assets are already attached, so the install paths work right now.

Stated limits, not buried

  • Binaries are 60–94 MB (embedded runtime) vs ~5–6 MB for a Rust equivalent.
  • This is a tap, not homebrew-core: core's self-submission bar is 225 stars / 90 forks / 90 watchers; this project has 8 stars.

Speed claims stay out of the new surfaces (2026-06-05 sealed decision) — a test asserts it.

🤖 Generated with Claude Code

…nd curl installs

The accuracy work only counts if the tool is installable where its users already
are. Today the only path is `npx`, which requires Node; the comparable tool in
this space ships prebuilt binaries through brew, scoop, uv, docker and a curl
one-liner, and its latest release saw 8,751 downloads in four days.

This adds the missing paths without touching the engine. It can be done without
touching it because the package already has zero runtime dependencies and one
non-relative import (`node:child_process`), so `bun build --compile` wraps the
existing code as-is. Porting the engine to Rust or Go was rejected: engine.js is
the asset, and a port makes a second copy of the truth that will drift from the
first.

- scripts/build-binaries.mjs builds seven targets — macOS arm64/x64, Linux
  x64/arm64 in both glibc and musl, Windows x64 — writes a .sha256 next to each
  and a manifest. For any target the host can actually execute it runs the
  binary against the node path on three cases and fails the build unless stdout
  is byte-identical, because a user seeing different numbers depending on how
  they installed is the worst failure this product has.
- packaging/install.sh detects platform and libc, verifies the published
  checksum, and installs nothing on any failure. Verified by execution: the
  happy path installs and runs; an unsupported OS, an unsupported arch, a
  missing release and a tampered binary each exit 1 with zero files written.
- scripts/gen-packaging.mjs derives the Homebrew formula and Scoop manifest from
  the build manifest rather than hand-written hashes, and throws on a missing
  target instead of emitting a file that points at nothing.
- .github/workflows/binaries.yml runs the engine tests, builds, and attaches the
  assets on a v* tag.

Two limits are stated in the README rather than buried: each binary is 60-94 MB
because it embeds its runtime (a Rust equivalent is 5-6 MB), and this is a
Homebrew tap, not homebrew-core — core's self-submission bar is 225 stars / 90
forks / 90 watchers and this project has 8 stars.

Speed claims stay out of the new surfaces; a test asserts it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@click6067-ship-it
click6067-ship-it merged commit da3ac9e into master Sep 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant