@sanlabs/sanbox-cli 0.0.13 → 0.0.14
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 +28 -2
- package/dist/args.js +1 -0
- package/dist/cli.js +50 -11
- package/dist/runs.js +1 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,11 @@ 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.
|
|
18
|
+
Always use the latest published CLI. CLI 0.0.14 adds repeatable `--allowed-model` options when
|
|
19
|
+
creating OpenCode Computer templates and `sanbox run --model` to select a configured same-provider
|
|
20
|
+
model at startup. The selected model is saved on the run and reused on Resume; SDK per-prompt
|
|
21
|
+
switching does not change that saved startup default.
|
|
22
|
+
CLI 0.0.13 allows service templates to start without an
|
|
19
23
|
initial task, including OpenCode Computers that are ready for later SDK steering, and supports
|
|
20
24
|
Custom OpenAI Proxy providers when creating templates, including Browser Use templates.
|
|
21
25
|
CLI 0.0.12 adopted the Stop, Resume, and Delete lifecycle,
|
|
@@ -99,9 +103,31 @@ sanbox templates create \
|
|
|
99
103
|
--harness opencode \
|
|
100
104
|
--mode computer \
|
|
101
105
|
--model-provider openai \
|
|
102
|
-
--model '<model-id>'
|
|
106
|
+
--model '<default-model-id>' \
|
|
107
|
+
--allowed-model '<additional-model-id>'
|
|
103
108
|
```
|
|
104
109
|
|
|
110
|
+
Repeat `--allowed-model` to make more models from the same provider available for native OpenCode
|
|
111
|
+
SDK prompt switching. The default `--model` is included automatically. At template creation, Sanbox
|
|
112
|
+
checks that each ID is in the organization's saved provider catalog; it does not infer model
|
|
113
|
+
compatibility or availability. Later catalog changes do not block Computer startup or resume, even
|
|
114
|
+
if the default model disappears. Configured IDs remain registered, and model errors surface when
|
|
115
|
+
a prompt uses them.
|
|
116
|
+
|
|
117
|
+
Choose a different startup model when creating an OpenCode Computer run:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
sanbox run --template <computer-template-id> --model '<additional-model-id>' --json
|
|
121
|
+
sanbox run "Inspect the repository" --template <computer-template-id> --model '<additional-model-id>' --json
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`sanbox run --model` accepts an exact ID from the template's configured models (`allowed_model_ids`),
|
|
125
|
+
using the template's provider. Omit it to use the template default. The selection is saved on the run;
|
|
126
|
+
`sanbox runs resume <run-id>` reuses it without another `--model` flag. Neither run creation nor SDK
|
|
127
|
+
per-prompt switching changes the template default, and SDK switches do not update the run's saved
|
|
128
|
+
startup model. This override is supported only for OpenCode Computer, not task or Hermes templates.
|
|
129
|
+
An `external_run_id` retry with a different explicit model returns `idempotency_conflict`.
|
|
130
|
+
|
|
105
131
|
Existing templates keep their current names, IDs, slugs, and task behavior. Browser Use is for one-shot
|
|
106
132
|
web tasks whose target domains are already approved. Hermes service templates are always-on. A Hermes template
|
|
107
133
|
declares which optional channels its runs may activate:
|
package/dist/args.js
CHANGED
package/dist/cli.js
CHANGED
|
@@ -40,11 +40,11 @@ Commands:
|
|
|
40
40
|
sanbox templates list [--json]
|
|
41
41
|
sanbox templates get <template-id> [--json]
|
|
42
42
|
sanbox templates validate <template-id> [--json]
|
|
43
|
-
sanbox templates create --name "..." --model-provider <provider-id> --model <model-id> [--harness opencode|hermes|browser-use] [--mode task|computer] [--channel email|telegram] [--browser-domain <hostname>] [--llm-budget-usd <amount>] [--json]
|
|
43
|
+
sanbox templates create --name "..." --model-provider <provider-id> --model <model-id> [--harness opencode|hermes|browser-use] [--mode task|computer] [--allowed-model <model-id>] [--channel email|telegram] [--browser-domain <hostname>] [--llm-budget-usd <amount>] [--json]
|
|
44
44
|
sanbox templates delete <template-id> --force [--json]
|
|
45
|
-
sanbox run "task" --template <template-id> [--email-address <address>] [--telegram-bot-token <token>] [--telegram-allowed-user <id>] [--input <path>] [--wait | --watch] [--json | --jsonl]
|
|
46
|
-
sanbox run --task "..." --template <template-id> [--email-address <address>] [--telegram-bot-token <token>] [--telegram-allowed-user <id>] [--input <path>] [--wait | --watch] [--json | --jsonl]
|
|
47
|
-
sanbox run --template <service-template-id> [--email-address <address>] [--telegram-bot-token <token>] [--telegram-allowed-user <id>] [--json]
|
|
45
|
+
sanbox run "task" --template <template-id> [--model <model-id>] [--email-address <address>] [--telegram-bot-token <token>] [--telegram-allowed-user <id>] [--input <path>] [--wait | --watch] [--json | --jsonl]
|
|
46
|
+
sanbox run --task "..." --template <template-id> [--model <model-id>] [--email-address <address>] [--telegram-bot-token <token>] [--telegram-allowed-user <id>] [--input <path>] [--wait | --watch] [--json | --jsonl]
|
|
47
|
+
sanbox run --template <service-template-id> [--model <model-id>] [--email-address <address>] [--telegram-bot-token <token>] [--telegram-allowed-user <id>] [--json]
|
|
48
48
|
sanbox batch --tasks tasks.json --template <template-id> [--input <path>] [--max-parallel 5] [--wait] [--json]
|
|
49
49
|
sanbox runs list [--limit 50] [--json]
|
|
50
50
|
sanbox runs get <run-id> [--json]
|
|
@@ -95,11 +95,14 @@ Usage:
|
|
|
95
95
|
sanbox run "Review these files" --template <template-id> --input report.pdf --input data/ --wait
|
|
96
96
|
sanbox run --task "Review these files" --template <template-id> --input report.pdf --wait --json
|
|
97
97
|
sanbox run --template <service-template-id> --json
|
|
98
|
+
sanbox run --template <opencode-computer-template-id> --model <model-id> --json
|
|
99
|
+
sanbox run "Inspect the repository" --template <opencode-computer-template-id> --model <model-id> --json
|
|
98
100
|
|
|
99
101
|
Options:
|
|
100
102
|
--task <instruction> Required for task templates; optional for service templates.
|
|
101
103
|
--input <path> File, directory, or glob to upload. Repeatable.
|
|
102
104
|
--template <id> Template id or slug. Required unless SANBOX_TEMPLATE or project config sets it.
|
|
105
|
+
--model <id> Startup model from the OpenCode Computer template's configured models.
|
|
103
106
|
--external-run-id <id> Idempotency key for retries.
|
|
104
107
|
--supabase-user-id <id> Supabase Auth user UUID expected during OAuth consent.
|
|
105
108
|
--email-address <address> Optional for Hermes computers; must use a listed AgentMail domain.
|
|
@@ -113,6 +116,10 @@ Options:
|
|
|
113
116
|
--after-event-id <id> Resume after an event cursor. Default: 0.
|
|
114
117
|
--stop-on-interrupt Request Stop when Ctrl-C is pressed.
|
|
115
118
|
--json Print JSON.
|
|
119
|
+
|
|
120
|
+
--model is supported only for OpenCode Computer, with or without an initial task.
|
|
121
|
+
Omitting --model uses the template's default. The provider stays fixed to the template.
|
|
122
|
+
The chosen startup model is saved on the run and reused on resume; SDK per-prompt switches do not change it.
|
|
116
123
|
`;
|
|
117
124
|
const runsSupabaseHelp = `Sanbox Supabase run authorization
|
|
118
125
|
|
|
@@ -163,13 +170,15 @@ Usage:
|
|
|
163
170
|
sanbox templates list [--json]
|
|
164
171
|
sanbox templates get <template-id> [--json]
|
|
165
172
|
sanbox templates validate <template-id> [--json]
|
|
166
|
-
sanbox templates create --name "Code review" --model-provider <provider-id> --model <model-id> [--harness opencode|hermes|browser-use] [--mode task|computer] [--browser-domain <hostname>] [--llm-budget-usd <amount>] [--json]
|
|
173
|
+
sanbox templates create --name "Code review" --model-provider <provider-id> --model <model-id> [--harness opencode|hermes|browser-use] [--mode task|computer] [--allowed-model <model-id>] [--browser-domain <hostname>] [--llm-budget-usd <amount>] [--json]
|
|
167
174
|
sanbox templates delete <template-id> --force [--json]
|
|
168
175
|
|
|
169
176
|
Template creation requires an exact provider id and that provider's exact model id.
|
|
170
177
|
LiteLLM budgets are optional USD amounts and apply separately to each run.
|
|
171
178
|
OpenCode defaults to --mode task. Use --mode computer for a retained private OpenCode server;
|
|
172
179
|
per-run LiteLLM budgets are not available for that retained mode yet.
|
|
180
|
+
Use repeatable --allowed-model with OpenCode Computer to make additional models from the selected
|
|
181
|
+
provider available for SDK prompt switching. --model remains the default and is included automatically.
|
|
173
182
|
Hermes templates are always-on computers. Use repeatable --channel email|telegram to select which
|
|
174
183
|
channels runs may activate. Email and Telegram credentials are supplied only when creating a run.
|
|
175
184
|
Browser Use templates run local headless Chromium inside a one-shot sandbox. They require OpenAI
|
|
@@ -216,6 +225,13 @@ a Hermes service template for a waited task because it is always-on. Select auto
|
|
|
216
225
|
template qualifies; otherwise ask the user. Provider credentials and template administration are
|
|
217
226
|
console-only.
|
|
218
227
|
|
|
228
|
+
For an explicitly requested OpenCode Computer, use a service template with harness opencode and
|
|
229
|
+
execution_mode computer. The task is optional; omit --wait. Use sanbox run --model <model-id> to
|
|
230
|
+
select an exact ID from the template's allowed_model_ids, keeping its provider unchanged. Omit
|
|
231
|
+
--model to use the template default. The startup model is saved on the run and reused on Resume
|
|
232
|
+
without re-specifying --model. SDK per-prompt switching changes neither the template default nor
|
|
233
|
+
the run's saved startup model. This flag is not supported for task or Hermes templates.
|
|
234
|
+
|
|
219
235
|
Use --json for request/response commands and --jsonl for streams. Parse the versioned envelope:
|
|
220
236
|
schema_version, ok, command, context, data or error, and next_actions. Execute command actions as
|
|
221
237
|
argv arrays, never shell strings. Exit 0 means command success, 1 local/API failure, 2 readiness or
|
|
@@ -316,7 +332,19 @@ const providerReadinessCodes = new Set([
|
|
|
316
332
|
"model_not_found_for_provider",
|
|
317
333
|
"template_provider_missing"
|
|
318
334
|
]);
|
|
319
|
-
const
|
|
335
|
+
const runModelSelectionCodes = new Set([
|
|
336
|
+
"invalid_run_model",
|
|
337
|
+
"run_model_not_configured",
|
|
338
|
+
"run_model_override_not_supported"
|
|
339
|
+
]);
|
|
340
|
+
const runCreationError = (error, client, template, modelId) => {
|
|
341
|
+
if (error instanceof SanboxApiError && runModelSelectionCodes.has(error.code)) {
|
|
342
|
+
return new CliError(error.code, error.message, {
|
|
343
|
+
status: error.status,
|
|
344
|
+
details: { template_id: template.id, model_id: modelId },
|
|
345
|
+
nextActions: [error.code === "run_model_override_not_supported" ? commandAction(["sanbox", "run", "--help"], "Remove --model and retry. Startup model overrides require an OpenCode Computer template.") : commandAction(["sanbox", "templates", "get", template.id, "--json"], "Inspect the template's configured models. Omit --model to use its default.")]
|
|
346
|
+
});
|
|
347
|
+
}
|
|
320
348
|
if (!(error instanceof SanboxApiError) || !runReadinessCodes.has(error.code))
|
|
321
349
|
return error;
|
|
322
350
|
const nextActions = [
|
|
@@ -348,7 +376,7 @@ const templateCreationError = (error, client, providerId, modelId) => {
|
|
|
348
376
|
return new CliError(error.code, error.message, {
|
|
349
377
|
status: error.status,
|
|
350
378
|
details: { provider_id: providerId, model_id: modelId },
|
|
351
|
-
nextActions: [commandAction(["sanbox", "model-providers", "models", providerId, "--json"], "List exact
|
|
379
|
+
nextActions: [commandAction(["sanbox", "model-providers", "models", providerId, "--json"], "List exact model IDs in the selected provider's catalog.")]
|
|
352
380
|
});
|
|
353
381
|
}
|
|
354
382
|
if (error.code === "template_name_conflict") {
|
|
@@ -461,6 +489,7 @@ const flagSets = {
|
|
|
461
489
|
"model",
|
|
462
490
|
"harness",
|
|
463
491
|
"mode",
|
|
492
|
+
"allowed-model",
|
|
464
493
|
"llm-budget-usd",
|
|
465
494
|
"web-access",
|
|
466
495
|
"channel",
|
|
@@ -473,7 +502,7 @@ const flagSets = {
|
|
|
473
502
|
"browser-additional-instructions"
|
|
474
503
|
],
|
|
475
504
|
run: [
|
|
476
|
-
...commonFlags, "task", "input", "template", "external-run-id", "email-address",
|
|
505
|
+
...commonFlags, "task", "input", "template", "model", "external-run-id", "email-address",
|
|
477
506
|
"supabase-user-id",
|
|
478
507
|
"telegram-bot-token", "telegram-allowed-user",
|
|
479
508
|
"dry-run", "wait", "watch", "jsonl", "view", "after-event-id", "stop-on-interrupt",
|
|
@@ -537,7 +566,7 @@ const requiredValueFlags = new Set([
|
|
|
537
566
|
"telegram-bot-token", "telegram-allowed-user", "channel",
|
|
538
567
|
"tasks", "max-parallel", "batch-id", "poll-interval-ms",
|
|
539
568
|
"event-page-size", "timeout-seconds", "view", "after-event-id", "limit", "name",
|
|
540
|
-
"model-provider", "model", "harness", "mode", "llm-budget-usd",
|
|
569
|
+
"model-provider", "model", "harness", "mode", "allowed-model", "llm-budget-usd",
|
|
541
570
|
"browser-domain", "browser-max-steps", "browser-step-timeout-seconds",
|
|
542
571
|
"browser-vision-mode", "browser-viewport", "browser-download-policy",
|
|
543
572
|
"browser-additional-instructions", "expires", "output", "artifact"
|
|
@@ -729,6 +758,7 @@ const commandRun = async (command, flags) => {
|
|
|
729
758
|
throw new CliError("conflicting_arguments", "Use either --task or a positional task, not both.");
|
|
730
759
|
}
|
|
731
760
|
const task = runTask(command, flags);
|
|
761
|
+
const modelId = flags.model === undefined ? undefined : flagString(flags, "model").trim();
|
|
732
762
|
if (hasFlag(flags, "wait") && wantsWatch(flags)) {
|
|
733
763
|
throw new Error("--wait cannot be combined with --watch or --jsonl.");
|
|
734
764
|
}
|
|
@@ -773,6 +803,7 @@ const commandRun = async (command, flags) => {
|
|
|
773
803
|
payload = await createRun(client, {
|
|
774
804
|
cwd: cwd(),
|
|
775
805
|
instruction: task || undefined,
|
|
806
|
+
modelId,
|
|
776
807
|
inputs: flagList(flags, "input"),
|
|
777
808
|
externalRunId: flagString(flags, "external-run-id") || undefined,
|
|
778
809
|
supabaseUserId: supabaseUserId || undefined,
|
|
@@ -783,7 +814,7 @@ const commandRun = async (command, flags) => {
|
|
|
783
814
|
});
|
|
784
815
|
}
|
|
785
816
|
catch (error) {
|
|
786
|
-
throw runCreationError(error, client, template);
|
|
817
|
+
throw runCreationError(error, client, template, modelId);
|
|
787
818
|
}
|
|
788
819
|
if (wantsWatch(flags)) {
|
|
789
820
|
const watched = await watchRunWithOutput(client, payload.run.id, flags, payload);
|
|
@@ -972,7 +1003,7 @@ const commandModelProviders = async (command, flags) => {
|
|
|
972
1003
|
for (const model of payload.models) {
|
|
973
1004
|
const modelId = String(model.model_id || model.id || "");
|
|
974
1005
|
const displayName = String(model.display_name || model.name || "");
|
|
975
|
-
process.stdout.write(`${modelId}${displayName && displayName !== modelId ? `\t${displayName}` : ""}
|
|
1006
|
+
process.stdout.write(`${modelId}${displayName && displayName !== modelId ? `\t${displayName}` : ""}\n`);
|
|
976
1007
|
}
|
|
977
1008
|
return;
|
|
978
1009
|
}
|
|
@@ -1184,6 +1215,13 @@ const commandTemplates = async (command, flags) => {
|
|
|
1184
1215
|
if (harness !== "opencode" && modeFlag) {
|
|
1185
1216
|
throw new CliError("opencode_mode_not_supported", "templates create --mode requires --harness opencode.");
|
|
1186
1217
|
}
|
|
1218
|
+
const additionalAllowedModels = [...new Set(flagList(flags, "allowed-model").map((value) => value.trim()).filter(Boolean))];
|
|
1219
|
+
if (additionalAllowedModels.length > 0 && (harness !== "opencode" || mode !== "computer")) {
|
|
1220
|
+
throw new CliError("opencode_allowed_models_not_supported", "templates create --allowed-model requires an OpenCode Computer template.");
|
|
1221
|
+
}
|
|
1222
|
+
const allowedModelIds = additionalAllowedModels.length > 0
|
|
1223
|
+
? [...new Set([model, ...additionalAllowedModels])]
|
|
1224
|
+
: undefined;
|
|
1187
1225
|
const budgetRaw = flagString(flags, "llm-budget-usd");
|
|
1188
1226
|
const parsedBudgetUsd = budgetRaw ? Number(budgetRaw) : undefined;
|
|
1189
1227
|
const llmBudgetUsd = parsedBudgetUsd === undefined
|
|
@@ -1241,6 +1279,7 @@ const commandTemplates = async (command, flags) => {
|
|
|
1241
1279
|
name,
|
|
1242
1280
|
provider_id: modelProvider,
|
|
1243
1281
|
model_id: model,
|
|
1282
|
+
...(allowedModelIds ? { allowed_model_ids: allowedModelIds } : {}),
|
|
1244
1283
|
...(harness === "opencode" && modeFlag ? { mode: mode } : {}),
|
|
1245
1284
|
...(llmBudgetUsd === undefined ? {} : { llm_budget_usd: llmBudgetUsd }),
|
|
1246
1285
|
...(harness === "browser-use" ? {
|
package/dist/runs.js
CHANGED
|
@@ -52,6 +52,7 @@ export const createRun = async (client, options) => {
|
|
|
52
52
|
return client.createRun({
|
|
53
53
|
external_run_id: options.externalRunId,
|
|
54
54
|
workload_id: options.templateId,
|
|
55
|
+
...(options.modelId !== undefined ? { model_id: options.modelId } : {}),
|
|
55
56
|
...(options.instruction ? { instruction: options.instruction } : {}),
|
|
56
57
|
...(options.supabaseUserId ? { supabase_user_id: options.supabaseUserId } : {}),
|
|
57
58
|
...(options.emailAddress ? { email_address: options.emailAddress } : {}),
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "0.0.
|
|
1
|
+
export const version = "0.0.14";
|