Skip to content

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:

Terminal window
curl -fsSL https://hutch.blackboard.sh/hutch/install.sh | sh

Want to try prerelease builds without touching your daily setup? Install the canary channel alongside it as hutch-canary:

Terminal window
curl -fsSL https://hutch.blackboard.sh/hutch/install.sh |
sh -s -- --channel canary

On Windows PowerShell:

Terminal window
& ([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

Terminal window
# Choose a template from the invoking launcher's default catalog
hutch electrobun init
# Opt into the current beta template catalog
hutch electrobun init --beta
# Run the project's reproducible install task, if it has one
hutch 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 binary
hutch pm exec -- vite --version
# Run a script declared in hutch.config.ts
hutch run dev
# Build and launch an Electrobun app
hutch electrobun dev --watch
# Prepare the selected Electrobun devkit and toolchain without building
hutch electrobun prepare
# Update the nearest exact Electrobun pin to the latest stable release
# and sync this app's generated devkit
hutch electrobun update
# Deliberately advance an unpinned project to the current channel head
hutch electrobun sync
# Create distributable builds
hutch electrobun build --env=canary
hutch electrobun build --env=stable

Hutch’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:

hutch.config.ts
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.

Terminal window
hutch upgrade
hutch self version
hutch cottontail version

Interactive 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:

Terminal window
# Preview every currently unreachable release and toolchain without mutation
hutch prune --dry-run
# Remove every currently unreachable release and toolchain immediately
hutch prune
# Recreate the Hutch home immediately, without a prompt
hutch reset

Reset 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=production

Selectors 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.0

The 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:

Terminal window
# Pin the nearest hutch.config.ts to the exact version currently
# selected for the active channel
hutch self pin
hutch cottontail pin
# Pin an explicit selector, including the floating `latest`
hutch self pin 0.5.0
hutch 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 alone
hutch self pin --recursive
hutch cottontail pin --recursive

The 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.