# instabox (npm)

```
npx instabox@latest      # sign in, install the CLI and the agent skills (--no-skills: no skills)
npx instabox@latest sh   # the same, then start a new box and SSH into it
npx instabox@latest skills install -y   # only the agent skills: no sign-in, no network
```

`npx instabox@latest` signs you in to instabox with a device code (first run only: open the page it prints on any device and enter the code; it opens the page for you where there is a browser, never with `--no-browser`), installs the `instabox` CLI already signed in or updates an out-of-date one (skip with `--no-cli`), installs or updates the agent skills from [`../skills`](../skills) in `~/.claude/skills`, `~/.agents/skills` and `~/.gemini/config/skills` (skip with `--no-skills`), and prints the next steps. It does not touch your boxes.

`npx instabox@latest sh` does all that, then starts a new box (`box-<hex>`) and drops you into it over SSH, no questions. Run by an agent (no terminal), it starts the box and prints `instabox ssh <box> -- <command>` instead of opening a shell. At the box limit it starts nothing and lists your boxes, to remove one (`instabox rm --yes <box>`) or open one (`instabox ssh <box>`).

A GitHub and a Google sign-in are separate instabox accounts, each with its own boxes. Both this entry point and `instabox login` let the sign-in page offer both. `--google` asks for Google directly, as does `instabox login --provider google`. An agent with no browser and nobody at the keyboard signs itself up instead with `instabox login --agent` once the CLI is installed (docs/agent-auth.md): it prints a claim page and a code for the account owner, who approves later from any device.

Without a terminal (an agent, CI), nobody can enter a device code, so the sign-in comes from `INSTABOX_API_KEY` (an `ibk_...` key, as the CLI takes it: no browser, no device code, nothing saved), a sign-in already saved, or an agent key from `instabox login --agent`. With none of them it exits at once with status 3 and says how to go on, instead of waiting for a code nobody will enter. `--device-code` asks for the device code anyway, for an agent that relays it to a person. `-y` (`--yes`) asks nothing: no invite-code prompt.

`npx instabox@latest skills install [-y] [--claude|--codex|--cursor|--opencode|--antigravity|--all]` installs only the agent skills, with no sign-in: Claude Code's in `~/.claude/skills`, Codex's, Cursor's and OpenCode's in `~/.agents/skills` (all three read it, so one copy serves them), Antigravity's in `~/.gemini/config/skills`. With no agent named it installs for all of them, asking first at a terminal unless `-y`. An unknown flag is a usage error, status 2.

Any other command line is the instabox CLI's ([`cmd/instabox`](../cmd/instabox)): `npx instabox@latest ls` runs `instabox ls`, the build this package carries.

This package is the only place the CLI comes from, and `npx instabox@latest` (or `npm install -g instabox` in scripts and CI) is how to install it. Two older paths read it from the npm registry too: `curl -fsSL https://api.instabox.sh/install.sh | sh`, still served for existing scripts but no longer advertised (the server serves the script; the script asks `$INSTABOX_NPM_REGISTRY/instabox/$INSTABOX_VERSION`, default `https://registry.npmjs.org` and `latest`, for `dist.tarball`, verifies the tarball against `dist.integrity` (sha512), and extracts `package/cli/instabox-<os>-<arch>`), and `instabox update` (the same in Go). The control plane builds, stores and serves no CLI: a CLI change is on npm minutes after its merge, with no release and no deploy.

## How it works

- `bin/instabox.mjs` runs instabox's device-code sign-in (RFC 8628, `POST /v1/auth/device`, then polls `/v1/auth/device/token`), as `instabox login` does, and saves the tokens where the CLI keeps its own, `~/.config/instabox/credentials.json`, so both are signed in at once. There is one copy on purpose: the server rotates refresh tokens and ends the session when a rotated one comes back.
- `sh` always starts a new box, with no timeout and no idle auto-suspend of its own. An existing box is entered with the CLI: `instabox ssh <box>`.
- The package carries the Go CLI: all six builds (darwin, linux and windows; amd64 and arm64) under `cli/`, and `bin/instabox.mjs` picks the one for this machine ([`lib/platforms.mjs`](lib/platforms.mjs)). One package rather than one per platform keeps publishing to a single package and a single trusted-publisher setting; the price is that every install downloads the other platforms' builds too (about 22 MB compressed, cached by npx per version). It copies that binary onto PATH (`$INSTABOX_INSTALL_DIR`, else `/usr/local/bin` when writable, else `~/.local/bin`; Windows: `%LOCALAPPDATA%\Programs\instabox`), replaces an installed CLI whose content differs in place, and leaves a development build (`instabox dev`, or `-dirty`) alone. No network is needed for the CLI, and `npx instabox@latest <command>` runs the bundled binary directly, whatever is on PATH.
- Without a bundled binary (a platform with no build, or this directory run from a checkout, where `cli/` does not exist until `scripts/bundle-cli.mjs` fills it) nothing is installed and nothing is downloaded: the output says why in one line (`lib/cli.mjs`, `missingBundledCli`), an installed CLI is left as it is, and `npx instabox@latest <command>` runs the installed CLI if there is one.
- Skills are written only over copies marked `installed-by: instabox`; the output says which were installed, updated, already up to date, or kept because they are the user's own.
- On Windows the bundled `instabox.exe` goes to `%LOCALAPPDATA%\Programs\instabox`; the output gives the PowerShell line that puts that folder on the user's PATH, and `npx instabox@latest <command>` finds it without that.
- The bin is also called `instabox`, so `lib/cli.mjs` finds the real CLI the way the insta CLI does: PATH without `node_modules` dirs and without links back to this script, then the installer's own targets.
- `prepack` copies `../skills` into the package; every skill there ships on the next publish.

## Develop

```sh
npm ci
npm test
npm run format:check
npm pack            # copies ../skills, then builds instabox-<version>.tgz
npx --package=./instabox-0.4.3.tgz instabox --help
```

`INSTABOX_API_URL` points it at another instabox server. From a checkout there is no bundled binary (`cli/` is not in version control), so the wrapper installs nothing and says so; to try the real thing, fill `cli/` the way CI does:

```sh
../scripts/build-cli.sh --out ../out/cli --version 0.0.0-local
node scripts/bundle-cli.mjs --cli-dir ../out/cli   # -> cli/, checked against SHA256SUMS
node bin/instabox.mjs --version                     # runs cli/<build> for this machine
npm pack                                            # a tarball with the binaries, ~22 MB
```

## Release

Nothing to do by hand. [`npm-release.yml`](../.github/workflows/npm-release.yml) runs on every push to `main`. When anything the package ships has changed since the last `npm-v*` tag (`cmd/instabox` and the packages of this module it imports, `npm/`, `skills/`, `go.mod`, `go.sum`, `scripts/build-cli.sh`), it builds the six CLI binaries with `scripts/build-cli.sh`, puts them in `cli/` with [`scripts/bundle-cli.mjs`](scripts/bundle-cli.mjs) (each checked against `SHA256SUMS`), publishes `instabox` through npm trusted publishing (no npm token anywhere), and tags the commit `npm-v<version>`. Anyone who can merge can release.

The version is [`scripts/next-version.mjs`](scripts/next-version.mjs)'s: `package.json` names a line (`0.8.0`), and merges ship `0.8.0`, `0.8.1`, `0.8.2`, … on it. `package.json` changes only to start a new line (a new minor or major); a line below the highest published stable version is refused, so a reverted `package.json` cannot move `latest` back. A version with a `-` (`0.9.0-rc.1`) is published as written, once, under the `next` dist-tag. The workflow sets the version with `npm version` at publish time; the committed `package.json` keeps the line's base. `instabox --version` prints `<npm version>+<commit>`.

A commit is published once. npm records the commit of every version it publishes (`gitHead`), and the version step reads the registry's packument first: when a version built from this commit is already there (a rerun after the publish succeeded but the tag job failed), nothing is published again and only the missing `npm-v<version>` tag is created. So a failed run is simply re-run from the Actions tab (`workflow_dispatch`). Its `force` input publishes a new patch with nothing changed.

Trusted publishing is one setting on npmjs.com, package `instabox` → Settings → Trusted Publisher: GitHub Actions, `InsForge` / `instabox` / `npm-release.yml`. It is set. Renaming the workflow file breaks publishing until the setting is updated; npm does not follow renames.

The CLI a user gets, from npm, `install.sh` or `instabox update` alike, is the newest published, which can be ahead of the release a site runs.
