@sanlabs/sanbox-cli 0.0.5 → 0.0.10

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 CHANGED
@@ -15,23 +15,61 @@ installed_cli_version="$(sanbox --version)"
15
15
  test "$installed_cli_version" = "$latest_cli_version"
16
16
  ```
17
17
 
18
- Always use the latest published CLI. CLI 0.0.5 adds organization discovery from the API key,
19
- Hermes-aware template metadata, permanent paused sandboxes, and same-sandbox follow-ups.
18
+ Always use the latest published CLI. CLI 0.0.10 adds per-run Hermes email and Telegram channels,
19
+ Supabase user authorization, and removes the retired run-chat commands.
20
+ CLI 0.0.9 added user login for private SSH access to running Hermes Computers.
21
+ CLI 0.0.6 added Anthropic self-hosted environment inspection and administration.
20
22
 
21
23
  ## Configure
22
24
 
25
+ For an interactive local terminal, sign in with your Sanbox account:
26
+
27
+ ```bash
28
+ sanbox login
29
+ ```
30
+
31
+ The CLI opens the console for approval, binds the session to one organization, and stores it in
32
+ `~/.sanbox/credentials.json`. The file is written atomically and limited to the current user on
33
+ POSIX systems. Run `sanbox logout` to revoke and remove that session.
34
+
35
+ For non-interactive automation, continue to use an organization API key:
36
+
23
37
  ```bash
24
38
  export SANBOX_API_URL=https://console.sanbox.cloud
25
39
  export SANBOX_API_KEY=sbx_live_...
26
40
  export SANBOX_TEMPLATE=<template-id-or-slug>
27
41
  ```
28
42
 
29
- `SANBOX_API_URL` is optional for the hosted service. `SANBOX_TEMPLATE` can instead come from `--template` or `.sanbox/config.json` `default_template`.
30
- The CLI derives the organization automatically from `SANBOX_API_KEY`; use an organization-scoped
31
- key that has access to exactly one organization.
43
+ `SANBOX_API_URL` is optional for the hosted service. `SANBOX_TEMPLATE` can instead come from
44
+ `--template` or `.sanbox/config.json` `default_template`. Organization API keys are for automation;
45
+ they cannot open terminal or SSH sessions.
32
46
 
33
47
  An org admin configures provider credentials and templates in the console. Do not pass provider keys to the CLI or a runner.
34
48
 
49
+ ## Connect An Anthropic Self-Hosted Environment
50
+
51
+ Create a `self_hosted` environment in Claude Console and generate its environment key. Then connect
52
+ it to Sanbox:
53
+
54
+ ```bash
55
+ export ANTHROPIC_ENVIRONMENT_KEY='<environment-key>'
56
+ sanbox anthropic-environments connect env_example --json
57
+ unset ANTHROPIC_ENVIRONMENT_KEY
58
+
59
+ sanbox anthropic-environments get env_example --json
60
+ sanbox anthropic-environments list --json
61
+ ```
62
+
63
+ The environment key is read only from `ANTHROPIC_ENVIRONMENT_KEY`; there is no secret-bearing CLI
64
+ flag. Connect, key replacement, and disconnect require an organization-admin user session or
65
+ operator credential. Regular organization-scoped member keys can list and inspect connections.
66
+
67
+ Disconnecting requires explicit confirmation and preserves existing session-to-workspace mappings:
68
+
69
+ ```bash
70
+ sanbox anthropic-environments disconnect env_example --force --json
71
+ ```
72
+
35
73
  ## Check Readiness
36
74
 
37
75
  ```bash
@@ -44,7 +82,46 @@ sanbox doctor --json
44
82
 
45
83
  Model IDs are provider-scoped. The CLI never guesses or silently substitutes a provider, model, or template.
46
84
  For waited task runs, choose a template with `runnable: true`, `template_type: "runner"`, and
47
- `runner_config.harness: "opencode"`. Hermes service templates are always-on.
85
+ `runner_config.harness: "opencode"` or `"browser-use"`. Browser Use is for one-shot web tasks whose
86
+ target domains are already approved. Hermes service templates are always-on. A Hermes template
87
+ declares which optional channels its runs may activate:
88
+
89
+ ```bash
90
+ sanbox templates create \
91
+ --name "Customer assistant" \
92
+ --harness hermes \
93
+ --model-provider openai \
94
+ --model '<model-id>' \
95
+ --channel email \
96
+ --channel telegram
97
+ ```
98
+
99
+ Omit both `--channel` flags for a WebUI-only computer. Channel credentials never belong to the
100
+ template.
101
+
102
+ ## Create A Browser Use Template
103
+
104
+ Browser Use runs local headless Chromium inside the Firecracker sandbox. It requires OpenAI,
105
+ Anthropic, Google Gemini, or Hetzner Inference and at least one explicit browser target:
106
+
107
+ ```bash
108
+ sanbox templates create \
109
+ --name "Web research" \
110
+ --harness browser-use \
111
+ --model-provider openai \
112
+ --model '<model-id>' \
113
+ --browser-domain example.com \
114
+ --browser-domain '*.example.org' \
115
+ --browser-max-steps 30 \
116
+ --browser-step-timeout-seconds 120 \
117
+ --browser-vision-mode auto \
118
+ --browser-viewport 1280x720 \
119
+ --browser-download-policy allow
120
+ ```
121
+
122
+ Targets are limited to hostnames and leading wildcards on ports 80 and 443. The remaining browser
123
+ security controls are fixed. Runs return `browser-use-response.md`, a sanitized run ledger, and any
124
+ allowed downloads. Browser Use templates are one-shot agent executions.
48
125
 
49
126
  ## Run Idempotently
50
127
 
@@ -64,6 +141,51 @@ sanbox run "Review this repo and write output/report.md" \
64
141
 
65
142
  Repeat `--input` for files, directories, or globs. The CLI excludes common secrets and applies `.sanboxignore`. `--include` is a deprecated compatibility alias.
66
143
 
144
+ For a Hermes template that allows `email`, optionally request an address from one of the AgentMail
145
+ domains listed in the Sanbox console:
146
+
147
+ ```bash
148
+ sanbox run "Handle customer email for this project" \
149
+ --template "$SANBOX_TEMPLATE" \
150
+ --email-address research-agent@agentmail.to \
151
+ --json
152
+ ```
153
+
154
+ `--email-address` is optional. When supplied, AgentMail must be connected and Sanbox does not
155
+ generate a fallback address; use `agentmail.to` or one of the domains listed for the connected Pod.
156
+
157
+ For a Hermes template that allows `telegram`, pass the bot token and at least one numeric user ID on
158
+ the run. Both values are optional unless the run needs Telegram:
159
+
160
+ ```bash
161
+ sanbox run "Handle Telegram messages for this project" \
162
+ --template "$SANBOX_TEMPLATE" \
163
+ --telegram-bot-token '<telegram-bot-token>' \
164
+ --telegram-allowed-user '<numeric-telegram-user-id>' \
165
+ --json
166
+ ```
167
+
168
+ You can set `SANBOX_TELEGRAM_BOT_TOKEN` instead of passing `--telegram-bot-token`. Do not use both.
169
+
170
+ For a long-running Hermes Computer that should act as one Supabase user, provide that user's
171
+ Supabase Auth UUID. Sanbox automatically uses the organization's connected Supabase project:
172
+
173
+ ```bash
174
+ sanbox run "Act as this user's procurement assistant" \
175
+ --template "$SANBOX_TEMPLATE" \
176
+ --external-run-id "hermes:<tenant-id>:<user-id>" \
177
+ --supabase-user-id "<supabase-auth-user-uuid>" \
178
+ --json
179
+
180
+ sanbox runs supabase authorize <run-id> --open
181
+ ```
182
+
183
+ The run stays in `awaiting_grant` until the expected Supabase user approves the browser consent
184
+ flow. Without `--open`, the authorize command prints the URL for copying into any browser. The
185
+ callback lands on a Sanbox-hosted completion page, so a CLI demo does not need a customer web app.
186
+ Customer integrations may use `--return-url <https-url>` when its origin matches the return origin
187
+ configured on the organization Supabase connection.
188
+
67
189
  Reuse the same external ID when retrying an ambiguous submission. To stream activity, replace `--wait --json` with `--jsonl`. Ctrl-C detaches without canceling unless `--cancel-on-interrupt` is supplied.
68
190
 
69
191
  ## Inspect And Recover
@@ -91,21 +213,53 @@ sanbox runs download <run-id> \
91
213
 
92
214
  Downloads are path-safe and return their byte counts and SHA-256 digests. Existing files are preserved unless `--overwrite` is explicit.
93
215
 
94
- ## Continue A Run
216
+ ## Share A Running Filesystem
217
+
218
+ Create one read-only bearer URL for the running sandbox's entire root filesystem:
95
219
 
96
220
  ```bash
97
- sanbox runs messages <run-id> --json
98
- sanbox runs message <run-id> "Summarize the retained output" --wait --json
221
+ sanbox runs share <run-id> --expires 1h --json
222
+ sanbox runs shares <run-id> --json
223
+ sanbox runs unshare <run-id> <access-point-id> --json
99
224
  ```
100
225
 
101
- The JSON response includes the user `message`, submitted `chat_job`, matching terminal `followup_event`, and conversation messages. The waiter matches `payload.chat_job_id`, so another concurrent follow-up cannot complete the wrong command.
226
+ The URL always starts at `/`; there is no root or output selector. Append a path to read an arbitrary
227
+ ordinary file, or add `?format=json` to a directory URL for agent-friendly navigation. The URL stops
228
+ working when the exact sandbox session stops, the link expires, or it is revoked. It is shown only
229
+ when created and must be handled as a bearer secret. See
230
+ [Live Filesystem Access](../docs/live-filesystem-access.md) for the HTTP contract and exclusions.
231
+
232
+ ## Resume Or Pause A Sandbox
233
+
234
+ ```bash
235
+ sanbox runs get <run-id> --json
236
+ sanbox runs resume <run-id> --wait --json
237
+ sanbox runs share <run-id> --expires 1h --json
238
+ sanbox runs pause <run-id> --wait --json
239
+ ```
240
+
241
+ Resume restores the latest writable Firecracker snapshot without starting the configured agent
242
+ harness. It acquires an exclusive manual lease and leaves the sandbox running for live filesystem
243
+ access until Pause creates the next snapshot generation. Before resuming, require
244
+ `sandbox_state: "paused"` and a positive `snapshot_generation`. Do not submit agent work while that
245
+ lease is active. Paused state has no retention TTL and remains available until explicitly deleted.
246
+
247
+ ## SSH Into A Hermes Computer
248
+
249
+ ```bash
250
+ sanbox login
251
+ sanbox ssh <run-id>
252
+ ```
102
253
 
103
- Completed runs pause into a durable Firecracker snapshot. `sanbox runs message <run-id> --message "..."`
104
- resumes the same writable sandbox and OpenCode session, then pauses it again. There is no retention
105
- TTL; paused sandboxes and their artifacts remain available until explicitly deleted.
254
+ The run must be a currently running Hermes Computer created from the SSH-capable template artifact.
255
+ The signed-in user must own the run or be an organization admin. The command uses your local OpenSSH
256
+ client, but it does not expose the microVM on a public IP or port. The CLI generates a temporary
257
+ Ed25519 identity, pins the microVM's runtime host key, and tunnels the SSH stream over a one-time
258
+ authenticated WebSocket. The temporary key is deleted when the connection closes, and the saved
259
+ user token is not passed to the OpenSSH child process.
106
260
 
107
- Check `sanbox runs get <run-id> --json` before a follow-up and require `sandbox_state: "paused"` with
108
- a positive `snapshot_generation`. Do not submit concurrent follow-ups to one run.
261
+ OpenCode and Browser Use runs are one-shot agent executions. Create a new run for additional agent
262
+ work. A persisted sandbox can still be resumed manually for inspection when a snapshot exists.
109
263
 
110
264
  ## Batch Work
111
265
 
package/dist/api.js CHANGED
@@ -150,6 +150,21 @@ export class SanboxClient {
150
150
  async listProviderModels(providerId) {
151
151
  return this.request(await this.orgPath(`/model-providers/${encodeURIComponent(providerId)}/models`));
152
152
  }
153
+ async listAnthropicEnvironments() {
154
+ return this.request(await this.orgPath("/anthropic-environments"));
155
+ }
156
+ async getAnthropicEnvironment(environmentId) {
157
+ return this.request(await this.orgPath(`/anthropic-environments/${encodeURIComponent(environmentId)}`));
158
+ }
159
+ async connectAnthropicEnvironment(environmentId, environmentKey) {
160
+ return this.request(await this.orgPath(`/anthropic-environments/${encodeURIComponent(environmentId)}`), {
161
+ method: "PUT",
162
+ body: JSON.stringify({ environment_key: environmentKey })
163
+ });
164
+ }
165
+ async disconnectAnthropicEnvironment(environmentId) {
166
+ return this.request(await this.orgPath(`/anthropic-environments/${encodeURIComponent(environmentId)}`), { method: "DELETE" });
167
+ }
153
168
  async listTemplates() {
154
169
  return this.request(await this.orgPath("/templates"));
155
170
  }
@@ -168,6 +183,18 @@ export class SanboxClient {
168
183
  async getRun(runId, signal) {
169
184
  return this.request(await this.orgPath(`/runs/${encodeURIComponent(runId)}`), { signal });
170
185
  }
186
+ async authorizeRunSupabase(runId, returnUrl) {
187
+ return this.request(await this.orgPath(`/runs/${encodeURIComponent(runId)}/supabase/authorize`), {
188
+ method: "POST",
189
+ body: JSON.stringify(returnUrl ? { return_url: returnUrl } : {})
190
+ });
191
+ }
192
+ async createSSHSession(runId, publicKey) {
193
+ return this.request(await this.orgPath(`/runs/${encodeURIComponent(runId)}/ssh-sessions`), {
194
+ method: "POST",
195
+ body: JSON.stringify({ public_key: publicKey })
196
+ });
197
+ }
171
198
  async listEvents(runId, afterEventId = 0, limit = 200, signal) {
172
199
  return this.request(`${await this.orgPath(`/runs/${encodeURIComponent(runId)}/events`)}?after_event_id=${afterEventId}&limit=${limit}`, { signal });
173
200
  }
@@ -177,18 +204,33 @@ export class SanboxClient {
177
204
  body: "{}"
178
205
  });
179
206
  }
180
- async sendMessage(runId, message, payload = {}) {
181
- return this.request(await this.orgPath(`/runs/${encodeURIComponent(runId)}/messages`), {
207
+ async resumeRun(runId) {
208
+ return this.request(await this.orgPath(`/runs/${encodeURIComponent(runId)}/resume`), {
182
209
  method: "POST",
183
- body: JSON.stringify({ message, payload })
210
+ body: "{}"
184
211
  });
185
212
  }
186
- async listMessages(runId) {
187
- return this.request(await this.orgPath(`/runs/${encodeURIComponent(runId)}/messages`));
213
+ async pauseRun(runId) {
214
+ return this.request(await this.orgPath(`/runs/${encodeURIComponent(runId)}/pause`), {
215
+ method: "POST",
216
+ body: "{}"
217
+ });
188
218
  }
189
219
  async listArtifacts(runId) {
190
220
  return this.request(await this.orgPath(`/runs/${encodeURIComponent(runId)}/artifacts`));
191
221
  }
222
+ async listFileAccessPoints(runId) {
223
+ return this.request(await this.orgPath(`/runs/${encodeURIComponent(runId)}/file-access-points`));
224
+ }
225
+ async createFileAccessPoint(runId, input) {
226
+ return this.request(await this.orgPath(`/runs/${encodeURIComponent(runId)}/file-access-points`), {
227
+ method: "POST",
228
+ body: JSON.stringify(input)
229
+ });
230
+ }
231
+ async revokeFileAccessPoint(runId, accessPointId) {
232
+ return this.request(await this.orgPath(`/runs/${encodeURIComponent(runId)}/file-access-points/${encodeURIComponent(accessPointId)}`), { method: "DELETE" });
233
+ }
192
234
  async downloadArtifact(runId, artifactPath) {
193
235
  return this.rawRequest(`${await this.orgPath(`/runs/${encodeURIComponent(runId)}/artifacts`)}?path=${encodeURIComponent(artifactPath)}`);
194
236
  }
package/dist/args.js CHANGED
@@ -1,4 +1,11 @@
1
- const multiFlags = new Set(["input", "include", "artifact", "telegram-allowed-user"]);
1
+ const multiFlags = new Set([
2
+ "input",
3
+ "include",
4
+ "artifact",
5
+ "channel",
6
+ "telegram-allowed-user",
7
+ "browser-domain"
8
+ ]);
2
9
  export const booleanFlags = new Set([
3
10
  "help",
4
11
  "version",
@@ -12,7 +19,9 @@ export const booleanFlags = new Set([
12
19
  "verbose",
13
20
  "force",
14
21
  "write",
15
- "overwrite"
22
+ "overwrite",
23
+ "open",
24
+ "no-browser"
16
25
  ]);
17
26
  export const parseArgs = (argv) => {
18
27
  const command = [];