Hutch
Hutch is Electrobun 2.x’s native build and workspace CLI. It runs project scripts, bundles TypeScript, resolves exact Electrobun devkits and compiler toolchains, and packages applications for distribution. It also installs JavaScript dependencies with a minimal built-in npm-compatible resolver, so a JavaScript project needs no separately installed package manager; projects that prefer npm, Bun, pnpm, or Yarn can delegate to them instead. Non-JS dependencies stay with their own tools: Cargo for Rust, Go modules for Go, and so on.
Hutch is build-time tooling — it’s what you run. It uses Cottontail for
TypeScript and shell execution during builds, but that does not select the
application runtime. Cottontail ships as the default JavaScript main process;
when build.mainProcess selects Bun, the actual Bun runtime ships instead.
Hutch itself stays out of the finished bundle.
Install Hutch
Regular npm commands need no separate machine-wide installation. There is one
dependency-free electrobun package, with no @electrobun/hutch-* platform
packages and no postinstall script. Each Electrobun GitHub Release carries four
mirrors from its exact paired Hutch release. hutch-artifacts.json binds every
host archive to its immutable GitHub Release URL, declared byte size, and
SHA-256 digest. On first use the npm command reads that index, downloads and
verifies the host archive, safely caches the extracted launcher and engine, then
runs it.
npx electrobun and package.json scripts that call electrobun ... therefore
need no shell-profile changes. The dependency version supplies exact paired
Hutch and Electrobun defaults, with Hutch’s paired build-time Cottontail, so
updating the one dependency moves the tested set tracked by your lockfile.
Direct hutch commands use the machine-wide installation below;
electrobun init ensures that compatible global launcher is present for the
generated project’s hutch run ... tasks while running init through the exact
private cache.
For machine-wide use outside npm, install the current production channel on macOS or Linux:
curl -fsSL https://hutch.blackboard.sh/hutch/install.sh | shWant to try prerelease builds without touching your daily setup? Install the
canary channel alongside it as hutch-canary:
curl -fsSL https://hutch.blackboard.sh/hutch/install.sh | sh -s -- --channel canaryOn Windows PowerShell:
& ([scriptblock]::Create((irm https://hutch.blackboard.sh/hutch/install.ps1)))Pass -Channel canary to install hutch-canary.exe. The installers also
accept an exact semantic version or build revision. Production and canary
live side by side under ~/.hutch/releases, with launchers in ~/.hutch/bin
and their exact local selections in ~/.hutch/state/selections.json. Set
HUTCH_HOME to relocate the store. stable is accepted as an alias for the
production channel.
Coming from npm or Bun? npx electrobun init and bunx electrobun init both
work, defaulting init to stable templates; pass --beta explicitly for beta
templates. Each published template pins the exact Electrobun release it was
tested with. The npm bootstrap still supplies its paired Hutch automatically;
direct Hutch validates the template catalog and installs the pinned release.
Everyday commands
# Choose a template from the invoking launcher's default cataloghutch electrobun init
# Opt into the current beta template cataloghutch electrobun init --beta
# Run the project's reproducible install task, if it has onehutch run install
# Install package.json dependencies (built-in resolver, or the# delegated manager when hutch.config.ts selects one)hutch install
# Run an installed project-local package binaryhutch pm exec -- vite --version
# Run a script declared in hutch.config.tshutch run dev
# Build and launch an Electrobun apphutch electrobun dev --watch
# Prepare the selected Electrobun devkit and toolchain without buildinghutch electrobun prepare
# Update the nearest exact Electrobun pin to the latest stable release# and sync this app's generated devkithutch electrobun update
# Deliberately advance an unpinned project to the current channel headhutch electrobun sync
# Create distributable buildshutch electrobun build --env=canaryhutch electrobun build --env=stableHutch’s Electrobun build environments are dev, canary, and stable.
The publisher guarantees that a published template’s exact Electrobun pin
matches its catalog. After extracting it, init syncs that release and the
required native toolchain, then runs the template’s configured install task
when one exists.
Pass --skip-install to defer that task. An unpinned hand-written template uses
the npm-paired default when supplied, otherwise the selected catalog release.
Initialization always requires network access because Hutch fetches the current
template catalog and chosen template instead of keeping persistent copies of
either one.
Scripts come only from the nearest hutch.config.ts. Running one does not infer
package.json scripts, add node_modules/.bin to PATH, or emulate npm
lifecycle hooks. Package installation and local-binary lookup happen only when
a task explicitly calls hutch install, hutch pm exec, or a selected external
manager. When a name is unambiguous, hutch <name> works as shorthand:
export default { // Optional exact override: // electrobun: { version: "2.0.1-beta.0" }, scripts: { install: ["hutch", "install", "--frozen-lockfile"], ui: ["hutch", "pm", "exec", "--", "vite", "build"], dev: ["hutch", "electrobun", "dev", "--watch"], build: ["hutch", "electrobun", "build", "--env=stable"], },};String values are shell expressions: pipelines, redirects, environment
expansion, and operators such as && are interpreted, as in
"hutch pm exec -- vite build && hutch electrobun dev". Argument arrays spawn
the first element with the remaining elements as exact arguments—there is no
shell parsing or implicit quoting—and are preferable for a single command.
Non-JavaScript projects do not need a package.json at all.
Its first line can also carry the version pragma that pins Hutch and Cottontail per project.
Without a packageManager selection, hutch install uses Hutch’s built-in
npm-compatible resolver. It installs package.json registry, file:, and git
dependencies (github:owner/repo#ref and git+<url>#ref, pinned to exact
commits and installed as checked out) into hutch.lock — the only lockfile
Hutch reads or writes; foreign lockfiles such as bun.lock or
package-lock.json are ignored, never migrated. Lifecycle scripts never run:
no postinstall, no prepare. Packages that must build on install belong to
an explicitly selected external manager.
In this built-in mode, hutch pm exec [--] <command> [args...] resolves only
the nearest package project’s node_modules/.bin/<command> and preserves its
arguments and exit status. It rejects path-like command names and never uses a
global executable, falls back to PATH, contacts a registry, or behaves like
npx. Bare hutch pm shows help, and hutch pm --version reports Hutch’s
version.
The top-level packageManager may be "npm", "bun", "pnpm", "yarn", or
a custom { name, executable? } selection. Selecting "bun" without an
explicit executable uses a Hutch-vendored Bun toolchain, so nothing extra needs
to be on PATH. With an external manager selected, hutch install spawns its
install command and hutch pm ... forwards raw arguments to it — for
example, with npm selected, hutch pm install zod and
hutch pm exec -- tsc --version delegate those operations to npm.
Versions and updates
The global launchers have independent production and canary selections. Each selection names an exact installed release. Hutch engines and Cottontail runtimes are stored side by side, so one project can pin an older release without replacing your global selection.
hutch upgradehutch self versionhutch cottontail versionInteractive use checks for updates periodically. CI and other non-interactive invocations never prompt.
Cottontail is paired with the Hutch release: an unpinned project runs the
Cottontail this launcher was built and tested with, and hutch upgrade
advances both together. (hutch self update remains as the explicit long
form of hutch upgrade.) This pairing covers the build-time runtime only —
the Cottontail bundled into an app by mainProcess: "cottontail" is a
runtime component pinned by the selected Electrobun release’s devkit
manifest, exactly like the bundled Bun. Projects that install through npm
receive their exact Hutch and Electrobun defaults from the one electrobun
dependency and its GitHub Release assets — updating it moves the tested set
tracked by your lockfile.
Updating moves only the global selection. A project whose pragma pins an exact
version keeps running that version — hutch upgrade says so and suggests
hutch self pin when the pin now trails the selection it just advanced.
hutch status shows the current directory’s pragma, the version it resolves
to, and the active channel’s selection side by side.
Local store and cleanup
Hutch keeps verified installations and authoritative local state under its home directory:
~/.hutch/|-- bin/|-- releases/| |-- hutch/| |-- cottontail/| `-- electrobun/|-- toolchains/|-- npm/| `-- electrobun/`-- state/ |-- selections.json |-- projects/ |-- leases/ |-- locks/ |-- trash/ `-- tmp/Remote channel manifests, template catalogs, artifact indexes, and template
indexes are fetched fresh and are not stored persistently. There is no local
channel directory tree. A download may use state/tmp/ while Hutch verifies
and installs it, but only the installed release or toolchain remains.
Hutch registers the exact releases and managed toolchains used by each prepared project. The currently executing Hutch release, local selections, resolvable registered projects, and live leases protect those installations. Missing projects and project dependency records that cannot be resolved protect nothing.
Every ordinary Hutch invocation lazily removes releases and toolchains after they have remained unreachable for 10 days. The explicit cleanup commands use no retention period:
# Preview every currently unreachable release and toolchain without mutationhutch prune --dry-run
# Remove every currently unreachable release and toolchain immediatelyhutch prune
# Recreate the Hutch home immediately, without a prompthutch resetReset reseeds the launcher, engine, and exact Hutch selection used to run it,
so Hutch itself remains usable. It removes every other Hutch-managed release,
toolchain, selection, project registration, lease, temporary object, and the
npm/electrobun bootstrap cache. Prune does not manage that bootstrap cache.
Neither command touches project files or package-manager caches owned outside
the Hutch home by npm, Bun, pnpm, Yarn, Cargo, or Go.
Offline use is limited to exact releases and managed toolchains that are already
installed, including an exact release named by a local selection. A missing
release or toolchain requires a fresh network lookup and download. Builds can
run offline when every required Hutch, Cottontail, Electrobun, and toolchain
installation is present and JavaScript dependencies are already materialized.
An explicitly selected external manager applies its own offline and cache
rules. hutch electrobun init always requires the network.
Pin a project
Need a project to build the same way for everyone, regardless of what’s
installed globally? Place an optional pragma on the first line of
hutch.config.ts:
// @hutch cli=production cottontail=productionSelectors may be production, stable, canary, latest, an exact semantic
version, or build:<full-git-revision>. stable and latest resolve to
production:
// @hutch cli=0.5.0 cottontail=0.3.0The pragma selects versions only for that project. It never rewrites the global installation’s local selections.
The pragma is optional. npm-launched commands use the exact Hutch and
Electrobun defaults paired with the installed electrobun dependency, plus
Hutch’s paired Cottontail. A direct Hutch invocation instead uses its active
channel and paired Cottontail; an unpinned Electrobun project follows the
release channel when explicitly synced and stays on the projected version
between syncs. Every explicit pin wins over these defaults.
The pin commands rewrite the pragma so it never needs hand editing:
# Pin the nearest hutch.config.ts to the exact version currently# selected for the active channelhutch self pinhutch cottontail pin
# Pin an explicit selector, including the floating `latest`hutch self pin 0.5.0hutch self pin latest
# Walk the tree below the current directory and move every config whose# pragma already pins an exact version or build — configs that track a# channel or carry no pragma are left alonehutch self pin --recursivehutch cottontail pin --recursiveThe recursive form is made for monorepos with many pinned projects — for example, bumping every explicitly pinned project after a Hutch release with one command.
Select, prepare, and sync Electrobun
electrobun.version is an optional exact override in hutch.config.ts,
alongside tasks and an optional external package-manager selection:
export default { // Optional: omit this object to use the npm or channel default. electrobun: { version: "2.0.1-beta.0" }, scripts: { dev: ["hutch", "electrobun", "dev", "--watch"], },};When present, the version must be exact; npm tags and semver ranges are not
Electrobun release selectors. Otherwise an npm-launched command uses the
installed package’s exact paired version. A direct Hutch command floats on the
active release channel: hutch electrobun sync advances to its current head,
while prepare, build, run, and dev reuse the version already projected
into .hutch/devkit so builds remain stable between syncs. An explicit pin
always wins.
Run hutch electrobun update from an app directory to upgrade an exact pin.
Hutch finds the nearest parent hutch.config.ts, resolves the latest stable
Electrobun release, rewrites only its exact electrobun.version string literal,
and then syncs the current app. The command always targets stable even when the
active Hutch or template channel is canary/beta. It requires network access and
fails instead of guessing when the pin is missing, computed, or ambiguous.
hutch electrobun prepare resolves and verifies the selected release in the global
~/.hutch/releases/electrobun store, then copies its SDKs and editor facade into
the project’s generated .hutch/devkit sysroot. It also resolves the selected
native compiler under ~/.hutch/toolchains when the project needs one. It
preserves an existing valid projection for a floating direct-Hutch project.
build, run, and dev prepare implicitly. The explicit command is useful
after changing the version pin, for editor setup, before Vite or a standalone
typecheck, or for preparing a build that will later run without network access.
Use hutch electrobun sync only when you intend to advance an unpinned
direct-Hutch project to the current channel head. init performs its initial
sync to a published template’s exact pin; npm or catalog defaults apply only to
an unpinned template.
Templates ignore .hutch/ by default. Hutch owns this generated state and may
replace it during sync; direct SDK edits and committing the projection are not
supported workflows. See
Project Ownership and the Devkit for
the complete boundary and external-bundler recipe.
Toolchains and installed releases
For Zig, Rust, Go, and Odin main processes, you don’t need to install the
compiler yourself. The selected Electrobun devkit declares a default exact
version for each language; build.zig.version, build.rust.version,
build.go.version, and build.odin.version can override it per project. Hutch
accepts an exact matching system compiler when supported, otherwise downloads
the selected toolchain into
~/.hutch/toolchains/<language>/<version>/<platform> and shares it across
projects.
The project still owns its build description and language dependencies:
build.zig for Zig, Cargo.toml/Cargo.lock for Rust, and go.mod/go.sum
for Go. Odin builds the configured project package directory. Hutch uses the
matching SDK copy in .hutch/devkit and passes its path to those normal build
tools; it does not generate build files or synthesize a GOPATH.