Install CLI commands from a local workspace or an NPM package into Bun's global package store.

Supports command selection and Bun runtime override. Excellent for dev builds of MCPs and other tools. Automatically resolves dependencies within your project's workspace.

bun add -g bun-installClone the repo and run the entrypoint directly:

git clone https://github.com/joeycumines/bun-install.git

cd bun-install

bun src/index.tsThis installs bun-install into Bun's global package store just as the

published package would.

Run bun-install from your project root (or any subdirectory):

bun-installInstall only selected commands (by binary name):

bun-install my-command another-commandSelect a specific package, optionally filtering to specific commands from it:

bun-install --package my-cli # all commands from my-cli

bun-install -p my-cli tool-a tool-b # only tool-a and tool-bInstall from NPM by passing a package specifier to --package:

bun-install -p prettier # all commands from prettier

bun-install -p @scope/pkg@latest # all commands from a scoped package

bun-install -p pkg@2.0.0 cmd1 # only cmd1 from pkg@2.0.0

bun-install --bun -p pkg@latest # install under Bun runtimeAny specifier that bun add accepts works, including version ranges and

dist-tags. The package is fetched via bun add into a temporary project,

then packed and installed globally.

When a subset of a package's commands is selected, only those commands are

symlinked into Bun's global bin directory. bun add -g

overwrites existing symlinks without warning, so bun-install filters the

bin field in the packed tarball before installation, ensuring unselected

commands are never symlinked and cannot clobber existing commands from other

packages. This applies to both local and NPM packages.

Command-level selection does not prune dependencies. Determining which deps a specific command uses is undecidable for dynamic imports, and the risk of runtime failures outweighs the marginal benefit.

bun-install --bun

bun-install --bun my-command

bun-install --package my-cli --bun

bun-install --bun -p pkg@latestThe --bun flag rewrites node shebangs in the installed commands to

#!/usr/bin/env bun and injects a Bun shebang when a bin target has none, so

they run under the Bun runtime instead of Node.js. This works cross-platform:

on Unix the OS reads the shebang via the symlink; on Windows Bun's shim reads

it from the target file. Files that cannot be safely rewritten (native

binaries, non-node scripts) are skipped with a warning. The install proceeds.

Bun's own mechanism for forcing the Bun runtime is

bunx --bun, which resolves packages from the

current directory's node_modules first and does not consult globally

installed packages. This makes it unsuitable for commands that should be

available everywhere. bun-install --bun rewrites shebangs in the installed

bin targets so they run under Bun regardless of the working directory.

Bun blocks lifecycle scripts (postinstall, preinstall, etc.) for all

dependencies by default — a security measure against arbitrary code execution

during install. Some packages (e.g. esbuild, @swc/core, native addons)

require these scripts to function correctly. Use --trust to allow them:

bun-install -p pkg --trust esbuild

bun-install -p pkg --trust esbuild --trust @swc/core

bun-install --trust esbuild my-cli

bun-install --trust esbuild # standalone: persist trust, no install

bun-install --trust esbuild --trust @swc/core # trust multiple packagesThe --trust flag persists package names to a global sidecar file at

$BUN_INSTALL/trusted-dependencies.json (typically

~/.bun/trusted-dependencies.json). Before each bun add -g call, the tool

collects trust entries from the sidecar and applies them to Bun's global

package.json via bun pm trust — letting Bun modify its own state. The flag

may be specified multiple times and persists across invocations.

When run without an install target (no project, no --package), --trust

persists the trust entries and exits.

In local mode, the project's existing trustedDependencies in the root

package.json are inherited and applied alongside the CLI-specified values.

This happens automatically — even without --trust — and is cumulative:

each distinct local project permanently adds its trust entries to the global

store (~/.bun/install/global/package.json). When new entries are propagated,

a warning is printed naming the count and the target file.

Note: Defining

trustedDependenciesin the global package.json replaces Bun's built-in default trusted list (~300+ packages). This is Bun's standard behavior. If you need default-trusted packages (likesharporprisma) to also run their lifecycle scripts, add them via--trustas well.

bun-install honors Bun's minimum-release-age configuration (set in

~/.bunfig.toml as minimumReleaseAge in seconds, or ~/.npmrc as

min-release-age in days). Versions published more recently than the window

are not eligible for resolution at fetch time — a supply-chain safety feature

that is on by default for untrusted packages.

Trusting a package with --trust also exempts it from the release-age gate

at fetch time, so a trusted @latest resolves to the newest version (including

those published inside the window) rather than falling back to an older

unblocked version:

bun-install --trust @github/copilot # persist trust (also exempts release-age)

bun-install -p @github/copilot@latest # now resolves the true registry latestThis applies to registry specs (pkg, pkg@latest, @scope/pkg@^1.0.0). For

npm: alias specs (e.g. myalias@npm:realpkg) the package name is discovered

after fetch; if the resolved package is trusted, bun-install re-fetches it

with the release-age exemption automatically. Git URLs, file: paths, and

https: URLs are not re-fetched — they have no registry version to advance.

Blast radius:

--minimum-release-age=0disables the age gate for the ENTIRE temp dependency tree during the exemptedbun add, not just the trusted package. Nested and bundled dependencies fetched during thatbun addare also age-ungated, and because NPM-fetched packages preserve nestednode_modules(bundled deps), an age-ungated nested dependency can be packed and shipped globally. This is a deliberate trade-off: the user explicitly trusted the package, and Bun does not offer a per-package release-age exclusion flag. Users who need strict age gating for ALL dependencies should NOT use--trustfor packages with many transitive dependencies.

If an untrusted @latest (or range) resolves to an older version because of

the gate, bun-install prints a warning naming both the resolved and registry

latest versions, and suggests --trust. Non-registry specs (git URLs,

file:, https:) are skipped — they have no registry version to compare

against:

Warning: resolved @github/copilot@1.0.71 but registry latest is 1.0.75 (likely blocked by minimum-release-age (trust the package with --trust to exempt it)).

This warning is best-effort: it queries the registry via npm view, which

requires npm to be installed and reachable. If npm is unavailable, the

spawn times out (30s), or the lookup fails for any reason, the warning is

silently skipped — it never aborts the install.

Note on

ignore-scripts: if your global~/.npmrcsetsignore-scripts=true, bun-install cannot override it per-package for a trusted install — Bun's--ignore-scriptsflag is a boolean with no=falseform, and--no-ignore-scriptsdoes not override a.npmrcsetting. If you need lifecycle scripts to run for a trusted package, removeignore-scripts=truefrom your global~/.npmrcor scope it per-project.

Show help:

bun-install --helpbun-install supports two local project structures:

Discovery walks upwards from your current directory, so you can run

bun-install from any nested directory inside the project.

When --package is specified and the package is not found in the local

project, bun-install falls back to fetching it from NPM. If there is no

local project at all (no package.json in any parent directory), it also

falls back to NPM. However, if the local project is broken (e.g. malformed

package.json, empty workspace), the error is surfaced rather than silently

substituting a remote package.

- Bun 1.3.14 or newer

- A package.jsonin the project root with:- A "name"field

- At least one package that exposes a "bin"entry

- A

- Workspace mode only: a "workspaces"field pointing to package directories

- No local project required

- The package must expose a "bin"entry in itspackage.json

bun install

bun run check# bump version (pick one)

bun run version:patch

bun run version:minor

bun run version:major

bun run version:prerelease

# publish (dry-run first, then latest or next)

bun run publish:dry

bun run publish:latest

bun run publish:next

# push the tag

bun run release