* feat(npm): ship the native binary as platform optionalDependencies
Replace the postinstall downloader with Codex-style platform packages:
suffixed versions of @gitlawb/zero (X.Y.Z-<platform>-<arch>) carrying the
binary, sandbox helpers, and the vendored agent-browser/tuistory tree,
referenced from the wrapper via npm: aliases in optionalDependencies.
The published wrapper has no lifecycle scripts and no dependencies, so
npm/Bun/pnpm installs are silent: no EBADENGINE from agent-browser's
node>=24 engines pin, no allow-scripts warnings, no network outside the
npm registry. The old downloader remains as a checksum-verified first-run
fallback for --omit=optional installs and unsupported platforms.
scripts/npm/build-platform-packages.mjs assembles the payloads from the
release archives at publish time: it materializes npm's symlink .bin
shims into regular sh shims (npm pack silently drops symlinks) and prunes
agent-browser's other-platform binaries (~65 MB per package). The publish
job uploads platform versions first under --tag platform, asserts the
latest dist-tag survived (platform versions are semver prereleases of the
wrapper version), then publishes the wrapper.
See docs/NPM_PACKAGING.md for the architecture and publishing rules.
* fix(npm): address PR review — LICENSE in payloads, dist-tag lookup guard, accurate fallback and platform docs
- Ship the repo LICENSE in both the wrapper and platform payloads (npm only
includes a license file physically present in the packed directory).
- Guard the platform-publish dist-tag lookup with || true so a transient
npm view failure (or a bootstrap publish with no latest tag) cannot abort
the job between the platform and wrapper publishes.
- Gate the wrapper's fallback downloader on the release matrix so platforms
with no asset (windows-arm64 and unknown combos) skip the spawn instead of
attempting it every run; keep deliberate retry-on-failure for supported
platforms and say so honestly in the notice and docs.
- Correct Windows support wording everywhere: x64 only, ARM runs under
emulation (README, README_ZH, INSTALL.md incl. the release-target list).
`npm install -g @gitlawb/zero` failed on Android/Termux with EBADPLATFORM
because `package.json` `os` array lacked `"android"`. Termux provides a
Linux userspace — the existing `linux-arm64` binary works natively.
- Add `"android"` to the `os` array in package.json.
- Map `android` platform to `linux` in postinstall.mjs resolvePlatform().
Fixes#449
* chore(launch): MIT license field, windows-arm64 install guard, TUI TTY guard
Three launch-readiness fixes the docs cleanup doesn't cover:
- package.json: set "license": "MIT" (was the placeholder "SEE LICENSE IN
LICENSE"), matching the LICENSE file and the README badge.
- scripts/postinstall.mjs: skip gracefully on windows-arm64. (win32, arm64)
resolves to a valid platform/arch but the release matrix ships no
windows-arm64 artifact, so the installer would download a 404 and hard-fail
npm install. Treat it like any unsupported target (warnSkip, exit 0) and point
to the x64 build (which runs under emulation on Windows on ARM) or source.
- internal/tui/run.go: fail fast when stdin is not a terminal. The interactive
shell otherwise blocks forever in the Bubble Tea event loop on piped/redirected
input (e.g. `echo "" | zero`); now it prints guidance toward `zero exec` and
exits non-zero. Dependency-free ModeCharDevice check (no x/term added).
Tests: windows-arm64 skips with exit 0; the interactive shell returns non-zero
(not a hang) on non-TTY stdin.
* fix(tui): use term.IsTerminal for the stdin guard; pin exit code in test
Address review on the no-TTY guard:
- Replace the os.Stdin.Stat()/ModeCharDevice heuristic with term.IsTerminal
(github.com/charmbracelet/x/term, already in the module graph via bubbletea).
It is a true TTY check — it rejects pipes, regular files, and non-terminal
char devices like /dev/null — and fails closed: anything not a verified
terminal blocks the interactive shell.
- The test now asserts the documented exit code 2 from the guard, not just a
non-zero result, so it proves the stdin guard fired.
* Add local control browser and terminal tools
Add configurable local browser, desktop, and terminal control helpers with packaged helper discovery for install and release flows.
Improve sandbox scope classification for local control commands and shared temporary paths.
Add structured browser actions, terminal session line input/key normalization/read support, and artifact capture for local-control outputs.
Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/localcontrol ./internal/tools ./internal/cli ./internal/config -run 'Test.*LocalControl|TestTerminal|TestBrowser|TestDesktop|TestCapture'
Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/localcontrol ./internal/tools ./internal/cli ./internal/config ./internal/agent
Tested: tuistory smoke launched bash, sent interactive input, waited for expected output, and captured a no-cursor snapshot
Tested: git diff --cached --check
* Fix npm wrapper helper manifest test on macOS
Canonicalize expected helper paths in the npm wrapper manifest test so macOS /var and /private/var temp path aliases compare correctly.
Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/npmwrapper
Tested: git diff --check
* Harden local control review fixes
Respect resolved local-control config before tool listing and filter validation while preserving stream-json usage error ordering.
Harden browser, desktop, and artifact tool validation; guard Linux-only app launch behavior; clear stale helper manifests from the npm wrapper.
Make helper packaging lockfile-based with npm ci and add installer/package tests for helper staging.
Tested: focused local-control/tool/CLI/release/install/sandbox suite
Tested: isolated full go test ./...
Tested: macOS and Windows compile-only checks for touched packages
Make `npm install -g @gitlawb/zero` deliver a working CLI. bin/zero.js already
execs an adjacent binary, but nothing fetched it and the name `zero` is taken
on npm. This wires up delivery:
- package.json: rename to @gitlawb/zero; add a postinstall hook, files, engines,
os/cpu, repository, and a license placeholder (the LICENSE file is still
pending and tracked separately).
- scripts/postinstall.mjs: detect platform/arch (mapping mirrors
internal/release/release.go — darwin->macos, amd64->x64), download
zero-v{version}-{os}-{arch}.{tar.gz|zip} from the matching GitHub Release,
verify its SHA-256, and extract into place. Extraction goes to a temp dir and
copies only known binary basenames, so a crafted archive cannot escape the
package (no zip-slip). Idempotent via a version marker; ZERO_SKIP_DOWNLOAD and
unsupported platforms are non-fatal skips.
Security hardening (from an adversarial review):
- require https for the download origin (the same-origin .sha256 makes TLS the
load-bearing integrity control); ZERO_ALLOW_INSECURE_DOWNLOAD=1 opts out for
local testing, and an https->http redirect is refused.
- the checksum parser is anchored per line and validates the filename field.
- cap the download size; warn when an optional sandbox helper is absent.
release-artifacts.yml: on a tag push, assert the git tag equals
v{package.json version} before packaging — zero-release names assets from
package.json while the release publishes under the tag, so any drift would 404
every install (install.sh, install.ps1, and npm alike).
Tests (internal/npmwrapper): package.json now requires exactly a postinstall
hook (no repo build scripts) plus name/license/repository/files; new
node-driven tests cover the per-platform asset plan, the skip env, and the
unsupported-platform path. Verified end-to-end against a local mirror:
download -> verify -> extract places the binary, a tampered checksum is
rejected, and http is refused without the opt-in.
Going live still needs two maintainer steps: create the `gitlawb` npm org +
NPM_TOKEN, and publish the first v0.1.0 release so there is an asset to fetch.
* feat: advance Go-first TUI and npm wrapper
* fix: address wrapper and footer review feedback
* feat: deepen Go TUI model and exec controls
* fix: address coderabbit exec and model list feedback
* fix plan mode and add test
* Address review: wire plan mode, harden test script, restore friendly errors
- Wire /plan into agent behavior: add PLAN_MODE_SYSTEM_PROMPT and a planMode
option on runAgent; App passes isPlanMode through so the agent actually
plans without editing instead of only changing UI color.
- Harden test script to "bun test ./tests --timeout 15000" for deterministic
discovery and a bounded runtime so it always exits.
- Restore friendly provider-setup/auth/network error mapping via
toFriendlyError(); debug mode still records the full error object.
- Add tests/agent-loop.test.ts covering prompt selection and the
tool-call -> result -> final-answer loop with a mock provider.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
---------
Co-authored-by: Kevin Codex <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>