@sandbar-sdk/cli 0.0.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -2
- package/dist/main.js +2663 -0
- package/package.json +38 -7
package/README.md
CHANGED
|
@@ -1,5 +1,148 @@
|
|
|
1
1
|
# @sandbar-sdk/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`sandbar` creates and manages sandboxes on any Sandbar provider from the command line. It is built for agents first: it never prompts, every command has a stable `--json` form, and exit codes say what to do next.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Install it with `npm install -g @sandbar-sdk/cli` (or `bun add -g @sandbar-sdk/cli`); `sandbar --version` prints the installed version. Adapters are installed separately.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
export SANDBAR_PROVIDER=e2b E2B_API_KEY=...
|
|
11
|
+
sandbar create --name web
|
|
12
|
+
sandbar exec web -- uname -a
|
|
13
|
+
sandbar cp ./app.py web:/work/app.py
|
|
14
|
+
sandbar rm web
|
|
15
|
+
|
|
16
|
+
sandbar run -- python3 -c 'print(2 + 2)' # create, run, always destroy
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The adapter package must be installed where the CLI can find it: in `~/.config/sandbar/adapters` (`cd ~/.config/sandbar/adapters && npm install @sandbar-sdk/e2b`) or next to the CLI itself. Adapters receive your credentials, so the current project's `node_modules` is searched only when you opt in with `"adapters": { "searchProject": true }` or `SANDBAR_PROJECT_ADAPTERS=1`. `sandbar providers` shows what is installed and which credentials are set.
|
|
20
|
+
|
|
21
|
+
## Commands
|
|
22
|
+
|
|
23
|
+
| Command | What it does |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `create [--name] [--image <oci-ref> \| --prepared <id>] [--network] [--label k=v] [--if-not-exists]` | Create a sandbox, print its name |
|
|
26
|
+
| `ls [--offline]` | List store records with live state (`running`, `gone`, `unreachable`, ...) |
|
|
27
|
+
| `inspect <sandbox>` | State, expiry and reference |
|
|
28
|
+
| `rm <sandbox>... \| --all [--force]` | Destroy and forget; only sandboxes this CLI created unless `--force`. `--all` skips the others and reports them |
|
|
29
|
+
| `exec <sandbox> [--cwd] [--env K=V] [--stdin \| --stdin-file] [--timeout] [--max-output] -- cmd...` | Run argv; exit code passes through |
|
|
30
|
+
| `sh <sandbox> '<script>'` (or `-- '<script>'`) | Run a shell script; same flags as exec |
|
|
31
|
+
| `run [create and exec flags] [--keep] -- cmd...` | Create, run one command, destroy |
|
|
32
|
+
| `image build <oci-ref> [--name] [--refresh]` | Build an OCI image on a provider that needs a build first, and record it |
|
|
33
|
+
| `image ls`, `image inspect <name>`, `image forget <name>` | List, show or drop image records; `forget` lists what the provider still holds |
|
|
34
|
+
| `cp <src> <dst>` | Copy one file; one side is `<sandbox>:/path` |
|
|
35
|
+
| `cat <sandbox>:/path` | Print a file |
|
|
36
|
+
| `doctor` | Check config, credentials and the connection; creates nothing |
|
|
37
|
+
| `providers` | Official providers, install state and missing credentials |
|
|
38
|
+
| `schema` | Machine-readable description of every command, flag and exit code |
|
|
39
|
+
| `skill [install [--project]]` | Print the agent skill for this CLI, or install it for your coding agents |
|
|
40
|
+
| `completion <zsh\|bash\|fish>` | Print a shell completion script |
|
|
41
|
+
|
|
42
|
+
A `<sandbox>` is a store name, a native id held in the store, or `@file.json` holding a saved sandbox reference.
|
|
43
|
+
|
|
44
|
+
## Output and exit codes
|
|
45
|
+
|
|
46
|
+
With `--json` (or `SANDBAR_JSON=1`) stdout holds exactly one document: `{"ok":true,"data":...}` or `{"ok":false,"error":{"code","message","retryable","outcome","hint?","known?","details?"}}`. Diagnostics go to stderr. When `rm` stops partway, `error.details` lists what was `removed`, `skipped` and `failed`.
|
|
47
|
+
|
|
48
|
+
| Code | Meaning |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| 0 | success |
|
|
51
|
+
| 1 | other failure (including a taken `--name` or an existing `cp --no-clobber` target) |
|
|
52
|
+
| 2 | usage error |
|
|
53
|
+
| 3 | not found |
|
|
54
|
+
| 4 | unsupported, adapter missing (`ADAPTER_NOT_INSTALLED`) or failing to load (`ADAPTER_LOAD_FAILED`), password-manager CLI not installed, or `limits.maxSandboxes` reached |
|
|
55
|
+
| 5 | authentication or permission, including a password-manager secret that cannot be read |
|
|
56
|
+
| 6 | outcome unknown: an effect may have happened, inspect before retrying |
|
|
57
|
+
| 7 | retryable before any effect (rate limit, capacity, unavailable) |
|
|
58
|
+
| 8 | partial: some steps confirmed, see `error.known` |
|
|
59
|
+
|
|
60
|
+
`exec`, `sh` and `run` exit with the remote command's exit code. Their own failures always use 125, so they never look like a command's result; providers report deadlines differently, so there is no separate deadline code. A missing exit status is 125 with `OUTCOME_UNKNOWN` and the captured output in `error.known.exec`, never 0. If `run` cannot destroy its sandbox it exits 125 with `CLEANUP_FAILED`, and the command's result is inside that one error document.
|
|
61
|
+
|
|
62
|
+
SIGINT, SIGTERM and SIGHUP all stop local waiting and let `run` clean up, then exit 130, 143 or 129 (`INTERRUPTED` for `run`); a second signal exits at once.
|
|
63
|
+
|
|
64
|
+
## Shell completion
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
source <(sandbar completion zsh) # add to ~/.zshrc
|
|
68
|
+
source <(sandbar completion bash) # add to ~/.bashrc
|
|
69
|
+
sandbar completion fish > ~/.config/fish/completions/sandbar.fish
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Completion covers commands, flags, `--provider` ids, `--profile` names from `sandbar.json`, sandbox names for `exec`, `sh`, `inspect`, `rm`, `cp` and `cat`, and image record names for `image inspect` and `image forget`. Sandbox names come from the local store, so pressing tab never calls a provider.
|
|
73
|
+
|
|
74
|
+
## Agent skill
|
|
75
|
+
|
|
76
|
+
The CLI carries an agent skill that matches its own version. `sandbar skill` prints it; `sandbar skill install` writes it to `~/.agents/skills/sandbar-cli/` and, when `~/.claude` exists, to `~/.claude/skills/sandbar-cli/`. Add `--project` to install into the current folder instead. Rerunning updates a copy it wrote; a copy installed another way is left alone.
|
|
77
|
+
|
|
78
|
+
## Configuration
|
|
79
|
+
|
|
80
|
+
`~/.config/sandbar/sandbar.json` (`$XDG_CONFIG_HOME/sandbar`, or `--config` / `SANDBAR_CONFIG`):
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"default": "e2b",
|
|
85
|
+
"profiles": {
|
|
86
|
+
"e2b": { "provider": "e2b", "credentials": { "apiKey": "env:E2B_API_KEY" }, "config": { "network": "internet" } },
|
|
87
|
+
"local": { "module": "./my-adapter.js", "export": "myAdapter", "config": {}, "credentials": {} }
|
|
88
|
+
},
|
|
89
|
+
"state": { "kind": "fs", "dir": "~/.config/sandbar/state" },
|
|
90
|
+
"limits": { "maxSandboxes": null }
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- Profile selection: `--profile`, `SANDBAR_PROFILE`, `--provider`, `SANDBAR_PROVIDER`, then `default`.
|
|
95
|
+
- `provider` names an official adapter (`@sandbar-sdk/<provider>`); `config` and `credentials` are that adapter's own settings. `module` and `export` load any adapter, either a definition or a bound adapter.
|
|
96
|
+
- Credentials can be written in the profile directly, as `env:NAME` references, or as password-manager references (below). Keep the file private (`chmod 600`) if it holds keys. Env-only providers read `DAYTONA_API_KEY` and `DAYTONA_TARGET`; `E2B_API_KEY`; `BOXD_API_KEY`, `BOXD_ORG` and `BOXD_NETWORK=internet` (boxd always allows egress, so its adapter requires choosing it); `VERCEL_TOKEN`, `VERCEL_TEAM_ID` and `VERCEL_PROJECT_ID`; `MODAL_TOKEN_ID`, `MODAL_TOKEN_SECRET`, `MODAL_APP_NAME` and `MODAL_ENVIRONMENT`.
|
|
97
|
+
- `limits.maxSandboxes` (or `SANDBAR_MAX_SANDBOXES`) caps how many sandboxes the store may hold. Unset means uncapped.
|
|
98
|
+
|
|
99
|
+
## Password managers
|
|
100
|
+
|
|
101
|
+
Any `config` or `credentials` value (including one that comes from a provider's environment variable) can name a secret in a password manager. The CLI reads it with the manager's own command-line tool, using that tool's sign-in, only when the profile connects. It never writes the value anywhere.
|
|
102
|
+
|
|
103
|
+
| Reference | Read with | Sign-in |
|
|
104
|
+
| --- | --- | --- |
|
|
105
|
+
| `op://vault/item/field` | `op read` (1Password CLI) | desktop app integration, `op signin` or `OP_SERVICE_ACCOUNT_TOKEN` |
|
|
106
|
+
| `bws://<secret-id>` | `bws secret get` (Bitwarden Secrets Manager CLI) | `BWS_ACCESS_TOKEN` |
|
|
107
|
+
| `pass://path/to/entry` | `pass show` (first line of the entry) | your GPG agent |
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{ "profiles": { "e2b": { "provider": "e2b", "credentials": { "apiKey": "op://Dev/E2B/credential" } } } }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
If the tool is not installed the command exits 4 (`SECRET_TOOL_MISSING`); if it cannot read the secret it exits 5 (`SECRET_UNAVAILABLE`) with the tool's own message. Each reference is read at most once per command.
|
|
114
|
+
|
|
115
|
+
## Images
|
|
116
|
+
|
|
117
|
+
`--image` always takes an OCI reference, such as `python:3.12` or `ghcr.io/org/tool@sha256:...`. A provider-specific template or snapshot goes in the profile's `image` setting or `--prepared <id>`.
|
|
118
|
+
|
|
119
|
+
- boxd and Modal boot the OCI image directly. Nothing is built or kept.
|
|
120
|
+
- E2B and Daytona must build it into a prepared image first. A build is slow, may cost money, and leaves a provider-side template or snapshot behind that destroying a sandbox does not delete. So `create --image` and `run --image` only use a build already recorded for the same reference and profile; otherwise they fail with exit 4 before creating anything, naming the command to run:
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
sandbar image build python:3.12 --provider e2b # once
|
|
124
|
+
sandbar run --image python:3.12 --provider e2b -- python3 --version
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Pass `--build` to build a missing image during `create` or `run`, or set `"images": { "autoBuild": true }` in `sandbar.json` to always do so. Each reference is built once per profile either way.
|
|
128
|
+
- Vercel takes neither and rejects `--image`.
|
|
129
|
+
|
|
130
|
+
`sandbar image build` records the reference as typed, the profile, the prepared image (bound to the provider account that built it) and the provider-side resources it left. Running it again for a recorded reference does nothing unless you pass `--refresh`, which builds again, for example after a mutable tag such as `python:3.12` moved; pin a digest for an exact image. `image forget` removes the record and prints the resources the provider still holds; the CLI cannot delete them yet, so remove them with the provider's own tools when you no longer need them. A build whose outcome is unknown (exit 6) keeps its record, and `image inspect` asks the provider again how it ended. Providers do not report a resolved digest, so records do not have one.
|
|
131
|
+
|
|
132
|
+
## State store
|
|
133
|
+
|
|
134
|
+
Sandbox references are kept as one JSON file per sandbox in `~/.config/sandbar/state/sandboxes/<name>.json`, and image records in `images/<name>.json` next to it (override with `--state-dir` or `SANDBAR_STATE_DIR`, relative to the working directory, or `state.dir`, relative to the config file). Records hold references only, never credentials. A name is claimed before the provider call, so a crash or uncertain create leaves a record that `ls` shows. An uncertain create also keeps the SDK's recovery reference: `rm` and `inspect` use it to ask how the original request ended (never resending it) and `rm` destroys the sandbox if one was made; otherwise `rm --force` drops the record. Records are written whole (a temporary file linked or renamed into place) and updates take a short per-record lock, so concurrent CLI processes on one machine never see half-written files; `ls` skips an unreadable record with a warning. The `maxSandboxes` check is best effort under concurrent creates. The store sits behind a small per-record interface (create-if-absent, compare-and-swap update, paged list) so object storage can back it later.
|
|
135
|
+
|
|
136
|
+
## Not yet
|
|
137
|
+
|
|
138
|
+
Planned for later steps: `--lifetime`/`--idle`/`--port` create flags, recursive `cp`, detached processes and logs, preview URLs, suspend/resume/renew, `gc`, deleting built images (`image rm`), snapshots and volumes, `sandbar adapter add`, an S3-compatible store, KeePassXC and system-keyring references, and MCP mode.
|
|
139
|
+
|
|
140
|
+
## Development
|
|
141
|
+
|
|
142
|
+
Tests run the real CLI as a subprocess against the fake provider; they make no provider calls:
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
bun test packages/cli
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Inside this repository the official adapters are development dependencies of the CLI, so `bun packages/cli/src/main.ts` finds them from any directory without an adapters install.
|