Let OpenSpec update itself, and ship it without needing Node
Author: TabishBCreated Sep 28, 2026Updated Sep 28, 2026
Two things keep coming up together:
- Nothing can update OpenSpec without a person at a terminal. The only upgrade path is inside
openspec update, in a project, behind an "Upgrade to vX now?" prompt, and only for npm installs (shouldOfferUpgrade()bails unless both the input and the output are a terminal;runGlobalUpgrade()hard-codesnpm install -g). Tools that build on OpenSpec can't offer "Update now". - OpenSpec needs Node first. New users install Node before
npm install -gworks. And since npm is the only channel, OpenSpec never owns its install: updating means working out whether npm, pnpm, bun, Volta or yarn put it there, by elimination.
Today
$ curl -fsSL https://openspec.dev/install.sh | sh
(doesn't exist; you need Node, then npm install -g @fission-ai/openspec)
$ openspec upgrade --yes
error: unknown command 'upgrade'What we'd like
$ curl -fsSL https://openspec.dev/install.sh | sh # macOS, Linux
$ irm https://openspec.dev/install.ps1 | iex # Windows
Installed OpenSpec 1.13.2 to ~/.openspec/bin
$ openspec upgrade --yes
Updating OpenSpec 1.13.2 → 1.14.0…
Updated to 1.14.0.
$ openspec upgrade --yes --json
{ "schemaVersion": 1, "status": "upgraded", "from": "1.13.2", "to": "1.14.0", "installedBy": "openspec" }- A copy OpenSpec installed (it keeps a small receipt) updates itself. No prompts, no project needed.
- A copy a package manager installed runs that manager's own update (
npm,pnpm,bun,volta), headless. - If it can't tell who installed it, or can't write to that folder:
"status": "manual"with the exact command. It doesn't guess. - If the update fails:
"status": "failed"with the error, and the old version still works. Keep the existing check that the new version is really the one on PATH.
What we looked at
- Claude Code: started on npm, deprecated npm installs in v2.1.15 for a native installer that updates itself in the background. Its npm package now just delivers the same native binary through per-platform optional dependencies (v2.1.113). For Homebrew and WinGet it runs
brew upgrade/winget upgradeitself, opt-in (CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE=1), and warns when the npm folder isn't writable. setup docs, changelog - uv:
self updateneeds an install receipt written by its own installer. Without one it refuses: "If you installed uv with pip, brew, or another package manager, update uv with…". Proving ownership beats guessing it. source - rustup: distributions build it with self-update switched off at compile time. source
- Deno:
deno upgradereplaces its own executable; if it can't write there, it says to update through your package manager. source - Bun:
bun upgradeworks, but its docs send Homebrew and Scoop users to those tools. docs - update-notifier (what many npm CLIs use): only notifies, once a day, and always suggests
npm i -g. It knows no other package manager. source - Building a standalone binary:
bun build --compile(cross-compiles to 8 targets; macOS signing needs theallow-jitentitlement),deno compile(ad-hoc signed on macOS; Windows needs SignTool), and Node's own single-executable apps (node --build-seasince v25.5.0, still "active development"). For scale, Claude Code's Bun binary is about 340 MB raw and 75 MB compressed on Linux x64. bun, deno, node
What we debated
- Detecting the package manager better vs owning the install. Better detection still guesses, and a path it doesn't recognise falls through to npm. Tools that update smoothly own the copy they update and hand every other copy back to its package manager.
- Running the package manager's own update headlessly. It's an accepted pattern (Claude Code does it for Homebrew and WinGet) and gives an "Update now" for existing npm, pnpm, bun and Volta installs, so it's worth doing even before a standalone build exists.
- Dropping npm vs keeping it. Keep it, ideally delivering the same binary (as Claude Code and Biome do), so existing users aren't stranded and there's only one thing to build.
- A tool bundling its own OpenSpec vs updating the user's. A bundled copy is easiest to keep current, but can drift from the
openspecin the user's terminal. Not decided.
Explorations still to run
- Measure start-up of
openspec --versionon Windows and macOS: the npm shim (cmd.exe→ Node) vs running Node directly vs a compiled binary. We found no good published numbers. - Try compiling OpenSpec with
bun build --compileanddeno compile: does everything work (prompts, file writes, skill installs, telemetry)? Size and start-up per platform? - Cost out signing: Apple Developer ID and notarisation, a Windows signing certificate and SmartScreen reputation, and doing both in CI.
- Design the receipt: what it records (version, install path, channel), where it lives, and how
upgradeuses it to decide who owns the copy. - npm delivering the binary through per-platform optional dependencies: install size, and what happens with pnpm and Volta.
- Windows edge cases: replacing a running
.exe(Windows locks it), and how today's pnpm and Volta install paths look on Windows.
Questions to settle: Bun, Deno or Node's single executables? Who pays for and holds the signing identities? Does npm keep shipping JavaScript, or deliver the binary?
Related: #1988 (reporting the installed version and whether there's an update).
Source: Fission-AI/OpenSpec