withruntime 0.8.3 → 0.9.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/COMPATIBILITY.md +145 -0
- package/E2B.md +23 -13
- package/README.md +8 -5
- package/README.src.md +8 -5
- package/compatibility-lock.json +281 -0
- package/dist/ai-harness/index.js +2 -2
- package/dist/api-defaults.d.ts +17 -0
- package/dist/api-defaults.js +22 -0
- package/dist/blaxel/preview.d.ts +1 -1
- package/dist/blaxel/preview.js +1 -1
- package/dist/blaxel/process.d.ts +1 -1
- package/dist/blaxel/process.js +11 -2
- package/dist/cli-words.d.ts +27 -0
- package/dist/cli-words.js +135 -0
- package/dist/cli.js +162 -31
- package/dist/client.d.ts +4 -1
- package/dist/client.js +8 -2
- package/dist/cloudflare/index.d.ts +294 -0
- package/dist/cloudflare/index.js +554 -0
- package/dist/cloudflare/worker.d.ts +415 -0
- package/dist/cloudflare/worker.js +297 -0
- package/dist/codesandbox/guest-service.d.ts +31 -0
- package/dist/codesandbox/guest-service.js +453 -0
- package/dist/codesandbox/index.d.ts +386 -0
- package/dist/codesandbox/index.js +1313 -0
- package/dist/compat/core.d.ts +39 -0
- package/dist/compat/core.js +199 -0
- package/dist/compat/files.d.ts +26 -0
- package/dist/compat/files.js +115 -0
- package/dist/compat/shell.d.ts +4 -0
- package/dist/compat/shell.js +165 -0
- package/dist/daytona/context.js +2 -1
- package/dist/daytona/process.d.ts +1 -1
- package/dist/daytona/process.js +27 -4
- package/dist/e2b/code-interpreter.d.ts +4 -5
- package/dist/e2b/code-interpreter.js +54 -56
- package/dist/e2b/commands.d.ts +5 -4
- package/dist/e2b/commands.js +37 -29
- package/dist/e2b/errors.js +1 -1
- package/dist/e2b/filesystem.d.ts +8 -6
- package/dist/e2b/filesystem.js +127 -28
- package/dist/e2b/sandbox.d.ts +4 -3
- package/dist/e2b/sandbox.js +10 -10
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +15 -2
- package/dist/freestyle/index.d.ts +301 -0
- package/dist/freestyle/index.js +310 -0
- package/dist/index.d.ts +1 -0
- package/dist/mcp.js +2 -2
- package/dist/modal/index.d.ts +205 -0
- package/dist/modal/index.js +485 -0
- package/dist/network-cli.js +1 -1
- package/dist/openai-agents/index.js +9 -26
- package/dist/products/audit.d.ts +3 -0
- package/dist/products/index.d.ts +2 -0
- package/dist/products/index.js +2 -0
- package/dist/products/interpreter.d.ts +11 -4
- package/dist/products/interpreter.js +33 -9
- package/dist/products/jobs.d.ts +1 -1
- package/dist/products/jobs.js +5 -4
- package/dist/products/limits.d.ts +10 -0
- package/dist/products/previews.d.ts +10 -2
- package/dist/products/previews.js +3 -2
- package/dist/products/tailscale.d.ts +57 -0
- package/dist/products/tailscale.js +17 -0
- package/dist/products/watch.d.ts +25 -4
- package/dist/products/watch.js +75 -14
- package/dist/runloop/index.d.ts +340 -0
- package/dist/runloop/index.js +438 -0
- package/dist/runloop/pagination.d.ts +46 -0
- package/dist/runloop/pagination.js +82 -0
- package/dist/runloop/snapshots.d.ts +52 -0
- package/dist/runloop/snapshots.js +244 -0
- package/dist/sandbox.d.ts +40 -9
- package/dist/sandbox.js +273 -76
- package/dist/snapshots.d.ts +11 -1
- package/dist/snapshots.js +11 -1
- package/dist/sprites/index.d.ts +251 -0
- package/dist/sprites/index.js +552 -0
- package/dist/tar.d.ts +8 -1
- package/dist/tar.js +185 -35
- package/dist/tools/index.js +2 -1
- package/dist/transport.d.ts +20 -2
- package/dist/transport.js +48 -11
- package/dist/types.d.ts +52 -3
- package/dist/vercel/command.js +12 -1
- package/dist/vercel/errors.d.ts +2 -2
- package/dist/vercel/errors.js +12 -1
- package/dist/vercel/filesystem.d.ts +21 -18
- package/dist/vercel/filesystem.js +95 -48
- package/dist/vercel/sandbox.js +5 -5
- package/package.json +45 -3
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ import { Sandbox as SandboxHandle } from "./sandbox.js";
|
|
|
10
10
|
import { NotFoundError, RuntimeError } from "./errors.js";
|
|
11
11
|
import { NETWORK_PRODUCTS, networkProductCommand } from "./network-cli.js";
|
|
12
12
|
import { billingCommand } from "./billing-cli.js";
|
|
13
|
+
import { cliRefusal, describeRecord, newestCpu, topicHelp } from "./cli-words.js";
|
|
13
14
|
import { envFetch } from "./proxy.js";
|
|
14
15
|
import { VERSION } from "./transport.js";
|
|
15
16
|
const HELP = `Runtime Cloud CLI ${VERSION}
|
|
@@ -40,7 +41,9 @@ Account
|
|
|
40
41
|
[--allow '[GET,HEAD] /path/*']... Store a secret sandboxes use without seeing it; the
|
|
41
42
|
value is read from standard input, never an argument.
|
|
42
43
|
--allow (paid): only these methods and paths get it
|
|
43
|
-
secrets
|
|
44
|
+
secrets set <NAME> --jobs [--host ...] A copy scheduled jobs put in their environment
|
|
45
|
+
(job create --secret NAME); with --host, both
|
|
46
|
+
secrets ls | get <NAME> Names, hosts, placeholders and jobs versions, never values
|
|
44
47
|
secrets rotate <NAME> A new version of the jobs copy, from standard input
|
|
45
48
|
secrets reveal <NAME> [--version N] Print the jobs copy's value. The sandboxes' copy is
|
|
46
49
|
never readable, by anyone
|
|
@@ -86,6 +89,7 @@ Products
|
|
|
86
89
|
snapshot Saved sandboxes, disk and memory, to start or fork from
|
|
87
90
|
image Custom images from a Dockerfile, any public or private image, or a package list
|
|
88
91
|
volume Persistent disks to attach to sandboxes
|
|
92
|
+
job A command in a fresh sandbox, once or on a cron schedule, with its logs
|
|
89
93
|
domain Your own hostname for a sandbox's port, with HTTPS
|
|
90
94
|
port A public TCP port to a sandbox: databases, game servers
|
|
91
95
|
address A dedicated outbound address, for allow-lists
|
|
@@ -103,7 +107,7 @@ const SANDBOX_HELP = `runtime sandbox <command>
|
|
|
103
107
|
run [--keep] [create options] -- <command> A fresh sandbox runs one command, then stops
|
|
104
108
|
ssh <id|name> [-- <command...>] | ssh config [--install] Log in over SSH, also from VS Code and JetBrains
|
|
105
109
|
port-forward <id|name> <port|local:remote>... Forward local ports to ports in it
|
|
106
|
-
create [--name n] [--label k=v]... [--vcpu 2] [--memory 4096] [--disk 4096]
|
|
110
|
+
create [--name n] [--label k=v]... [--env K=V]... [--vcpu 2] [--memory 4096] [--disk 4096]
|
|
107
111
|
[--cpu shared|reserved] [--cpu-floor <thousandths>] [--max-cost <usd>] [--max-total-cost <usd>]
|
|
108
112
|
[--timeout <seconds>] [--on-timeout pause|stop] [--trial|--paid] [--no-wait]
|
|
109
113
|
[--no-internet] [--allow <host>]... [--deny <host>]... [--connect <host:port>]...
|
|
@@ -122,10 +126,15 @@ const SANDBOX_HELP = `runtime sandbox <command>
|
|
|
122
126
|
cp <src> <dst> Copy files or directories; name a sandbox
|
|
123
127
|
path as <id>:/path, e.g. cp ./app sbx:/workspace/app
|
|
124
128
|
cat <id> <path> Print a file
|
|
125
|
-
files <id> [path] [--depth
|
|
129
|
+
files <id> [path] [--depth 1] [--glob '**/*.py']
|
|
126
130
|
stop <id> | pause <id> | wake <id> | restart <id>
|
|
131
|
+
rm <id> Delete it for good: stops it, deletes its disk and
|
|
132
|
+
paused memory, revokes its previews; snapshots stay
|
|
133
|
+
switch-image <id> <image> --keep-workspace Move it to another image, keeping /workspace (your home),
|
|
134
|
+
volumes, env and previews; processes restart, the rest of
|
|
135
|
+
its disk is lost (snapshot it first to keep everything)
|
|
127
136
|
extend <id> <seconds> More time before the lease ends
|
|
128
|
-
update <id> [--name n] [--label k=v]... [--idle-pause <seconds>] [--auto-wake on|off]
|
|
137
|
+
update <id> [--name n] [--label k=v]... [--env K=V]... [--unset-env K]... [--idle-pause <seconds>] [--auto-wake on|off]
|
|
129
138
|
[--persistent on|off] [--max-total-cost <usd>|none]
|
|
130
139
|
Change its settings; persistent keeps it running
|
|
131
140
|
snapshot <id> [--name n] [--retention days] Keep its whole machine; prints the snapshot id
|
|
@@ -133,6 +142,9 @@ const SANDBOX_HELP = `runtime sandbox <command>
|
|
|
133
142
|
[--endpoint https://...] [--account-id id] [--read-only]
|
|
134
143
|
Your bucket as a directory; the proxy signs, the sandbox never sees the key
|
|
135
144
|
mounts <id> | unmount <id> <path> Its bucket mounts; unmount one
|
|
145
|
+
tailscale up <id> --auth-key-secret NAME [--hostname h] [--tag tag:x]...
|
|
146
|
+
Join your tailnet (paid); the key is a secret set with --jobs
|
|
147
|
+
tailscale status <id> | tailscale down <id> Its tailnet address; take it off the tailnet
|
|
136
148
|
fork <id> [--count 3] [--name n] [--keep-snapshot] [--trial|--paid]
|
|
137
149
|
Copies of it as it is now, running; prints their ids
|
|
138
150
|
run-code <id> <file|-> [--lang python|javascript|typescript|r|java|bash|go] [--context c] [--out-dir .]
|
|
@@ -385,6 +397,9 @@ const SANDBOX_VERBS = [
|
|
|
385
397
|
"ls",
|
|
386
398
|
"get",
|
|
387
399
|
"stop",
|
|
400
|
+
"rm",
|
|
401
|
+
"delete",
|
|
402
|
+
"switch-image",
|
|
388
403
|
"pause",
|
|
389
404
|
"wake",
|
|
390
405
|
"restart",
|
|
@@ -392,6 +407,7 @@ const SANDBOX_VERBS = [
|
|
|
392
407
|
"mount",
|
|
393
408
|
"mounts",
|
|
394
409
|
"unmount",
|
|
410
|
+
"tailscale",
|
|
395
411
|
"fork",
|
|
396
412
|
"extend",
|
|
397
413
|
"update",
|
|
@@ -484,6 +500,12 @@ function describe(info) {
|
|
|
484
500
|
["funding", info.funding],
|
|
485
501
|
["region", info.region],
|
|
486
502
|
["expires", info.expiresAt ?? "-"],
|
|
503
|
+
["at lease end", info.onLeaseEnd ?? "-"],
|
|
504
|
+
// Why a sandbox is no longer running (troubleshooting.md, "A sandbox
|
|
505
|
+
// stopped on its own"); `get` left both out until 30 September 2026.
|
|
506
|
+
...(info.pausedAt ? [["paused at", info.pausedAt]] : []),
|
|
507
|
+
...(info.endedAt ? [["ended", info.endedAt]] : []),
|
|
508
|
+
...(info.stopReason ? [["stop reason", info.stopReason]] : []),
|
|
487
509
|
["auto wake", info.autoWake === false ? "off" : "on"],
|
|
488
510
|
[
|
|
489
511
|
"idle pause",
|
|
@@ -523,6 +545,17 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
|
|
|
523
545
|
// `runtime sandbox create --help` is how an agent asks what a command takes:
|
|
524
546
|
// answer with the product's help, never by running the command.
|
|
525
547
|
const cut = remaining.indexOf("--");
|
|
548
|
+
const asked = (cut < 0 ? remaining : remaining.slice(0, cut)).some((arg) => arg === "--help" || arg === "-h") ||
|
|
549
|
+
(remaining.length === 1 && remaining[0] === "help");
|
|
550
|
+
// A command without a help of its own answers `help` with its lines of the
|
|
551
|
+
// top-level help, and never takes the word as its input.
|
|
552
|
+
if (first && first !== "help" && asked && !nested && !OWN_HELP.includes(first)) {
|
|
553
|
+
const lines = topicHelp(named(HELP), first);
|
|
554
|
+
if (lines) {
|
|
555
|
+
out.write(out.json ? JSON.stringify({ usage: lines }) : lines);
|
|
556
|
+
return 0;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
526
559
|
if (first &&
|
|
527
560
|
(cut < 0 ? remaining : remaining.slice(0, cut)).some((arg) => arg === "--help" || arg === "-h"))
|
|
528
561
|
return run(nested && command === "ssh"
|
|
@@ -671,7 +704,8 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
|
|
|
671
704
|
}
|
|
672
705
|
if (command === "limits") {
|
|
673
706
|
const l = await (await client(env)).limits.get();
|
|
674
|
-
|
|
707
|
+
// As `usage` shows money: $0.0014 of spend read $0.00 until 30 September 2026.
|
|
708
|
+
const usd = (micros) => dollars(micros);
|
|
675
709
|
const access = l.access === "read"
|
|
676
710
|
? "read only: reads every product, cannot start, change or spend"
|
|
677
711
|
: l.access === "full"
|
|
@@ -687,6 +721,7 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
|
|
|
687
721
|
],
|
|
688
722
|
["used, last 24 hours", usd(l.daily.usedMicros)],
|
|
689
723
|
...(l.daily.remainingMicros === null ? [] : [["left", usd(l.daily.remainingMicros)]]),
|
|
724
|
+
...(l.trial ? [["free trial", trialLeft(l.trial)]] : []),
|
|
690
725
|
]), l);
|
|
691
726
|
return 0;
|
|
692
727
|
}
|
|
@@ -975,11 +1010,15 @@ export function usageCsv(u) {
|
|
|
975
1010
|
.join(","));
|
|
976
1011
|
return [head.join(","), ...rows].join("\n");
|
|
977
1012
|
}
|
|
1013
|
+
/** The free trial's time left, as `runtime usage` and `runtime limits` say it. */
|
|
1014
|
+
function trialLeft(trial) {
|
|
1015
|
+
const hours = (ms) => (ms / 3_600_000).toLocaleString("en-US", { maximumFractionDigits: 1 });
|
|
1016
|
+
return `${hours(trial.availableMs)} of ${hours(trial.totalMs)} hours left${trial.reservedMs > 0 ? `, ${hours(trial.reservedMs)} held by running sandboxes` : ""}`;
|
|
1017
|
+
}
|
|
978
1018
|
function usageSummary(u) {
|
|
979
1019
|
const micros = (value) => typeof value === "string" || typeof value === "number" || typeof value === "bigint"
|
|
980
1020
|
? BigInt(value)
|
|
981
1021
|
: 0n;
|
|
982
|
-
const hours = (ms) => (ms / 3_600_000).toLocaleString("en-US", { maximumFractionDigits: 1 });
|
|
983
1022
|
const takenBack = micros(u.takenBack);
|
|
984
1023
|
const rows = [
|
|
985
1024
|
["available", dollars(micros(u.available))],
|
|
@@ -996,10 +1035,7 @@ function usageSummary(u) {
|
|
|
996
1035
|
if (micros(u.expired) > 0n)
|
|
997
1036
|
rows.push(["expired or taken back", dollars(micros(u.expired))]);
|
|
998
1037
|
if (u.trial)
|
|
999
|
-
rows.push([
|
|
1000
|
-
"free trial",
|
|
1001
|
-
`${hours(u.trial.availableMs)} of ${hours(u.trial.totalMs)} hours left${u.trial.reservedMs > 0 ? `, ${hours(u.trial.reservedMs)} held by running sandboxes` : ""}`,
|
|
1002
|
-
]);
|
|
1038
|
+
rows.push(["free trial", trialLeft(u.trial)]);
|
|
1003
1039
|
if (u.outbound) {
|
|
1004
1040
|
const gib = (bytes) => `${(bytes / 1_073_741_824).toLocaleString("en-US", { maximumFractionDigits: 1 })} GiB`;
|
|
1005
1041
|
const left = Math.max(0, u.outbound.allowanceBytes - u.outbound.freeBytes);
|
|
@@ -1357,8 +1393,12 @@ async function sandbox(argv, env, out) {
|
|
|
1357
1393
|
...(args.flags.has("state")
|
|
1358
1394
|
? { state: args.flags.get("state") }
|
|
1359
1395
|
: {}),
|
|
1396
|
+
limit: 100,
|
|
1360
1397
|
});
|
|
1361
|
-
|
|
1398
|
+
// Every page, oldest first, so the newest are last on the screen. Until
|
|
1399
|
+
// 30 September 2026 this stopped at 1,000 and silently left out the
|
|
1400
|
+
// newest sandboxes of a busy account.
|
|
1401
|
+
const all = await page.toArray(Infinity);
|
|
1362
1402
|
// Stopped sandboxes are hidden by default; say so, or an agent that
|
|
1363
1403
|
// stopped everything reads "No sandboxes" as "never had any".
|
|
1364
1404
|
const hidden = out.json || args.flags.size
|
|
@@ -1392,6 +1432,23 @@ async function sandbox(argv, env, out) {
|
|
|
1392
1432
|
print(describe(sbx.info), sbx.info);
|
|
1393
1433
|
return 0;
|
|
1394
1434
|
}
|
|
1435
|
+
case "switch-image": {
|
|
1436
|
+
const args = parse(rest, ["keep-workspace"], []);
|
|
1437
|
+
const [id, image] = args.positional;
|
|
1438
|
+
const sbx = await use(need(id, `a sandbox, e.g. ${me} sandbox switch-image <id> app:v2 --keep-workspace`));
|
|
1439
|
+
if (!args.flags.has("keep-workspace"))
|
|
1440
|
+
throw usage(`A switch keeps only /workspace (your home), volumes, env and previews; processes restart and everything else on its disk is lost. Add --keep-workspace to say so, or snapshot it first (${me} sandbox snapshot ${sbx.id}) to keep everything.`);
|
|
1441
|
+
await sbx.switchImage(need(image, "an image, e.g. app:v2"), { keep: "workspace" });
|
|
1442
|
+
print(describe(sbx.info), sbx.info);
|
|
1443
|
+
return 0;
|
|
1444
|
+
}
|
|
1445
|
+
case "rm":
|
|
1446
|
+
case "delete": {
|
|
1447
|
+
const sbx = await use(need(rest[0], `a sandbox, e.g. ${me} sandbox rm <id>`));
|
|
1448
|
+
const gone = await sbx.delete();
|
|
1449
|
+
print(`${gone.id} deleted`, gone);
|
|
1450
|
+
return 0;
|
|
1451
|
+
}
|
|
1395
1452
|
case "stop":
|
|
1396
1453
|
case "pause":
|
|
1397
1454
|
case "wake":
|
|
@@ -1445,6 +1502,35 @@ async function sandbox(argv, env, out) {
|
|
|
1445
1502
|
print(`${gone.path} unmounted`, gone);
|
|
1446
1503
|
return 0;
|
|
1447
1504
|
}
|
|
1505
|
+
case "tailscale": {
|
|
1506
|
+
const action = rest[0];
|
|
1507
|
+
if (action === "up") {
|
|
1508
|
+
const args = parse(rest.slice(1), [], ["auth-key-secret", "hostname", "tag"]);
|
|
1509
|
+
const sbx = await use(args.positional[0]);
|
|
1510
|
+
const joined = await sbx.tailscale.up({
|
|
1511
|
+
authKeySecret: need(flag(args, "auth-key-secret"), "--auth-key-secret, the name of a secret set with --jobs that holds your Tailscale auth key"),
|
|
1512
|
+
...(flag(args, "hostname") ? { hostname: flag(args, "hostname") } : {}),
|
|
1513
|
+
...(args.flags.has("tag") ? { tags: args.flags.get("tag") } : {}),
|
|
1514
|
+
});
|
|
1515
|
+
print(`${joined.hostname} ${joined.addresses.join(" ")}${joined.proxy ? ` (reach the tailnet through ${joined.proxy})` : ""}`, joined);
|
|
1516
|
+
return 0;
|
|
1517
|
+
}
|
|
1518
|
+
if (action === "status") {
|
|
1519
|
+
const sbx = await use(rest[1]);
|
|
1520
|
+
const now = await sbx.tailscale.status();
|
|
1521
|
+
print(now.tailnet
|
|
1522
|
+
? `${now.tailnet.hostname} ${now.running ? now.addresses.join(" ") : (now.state ?? "not running")}`
|
|
1523
|
+
: "Not on a tailnet.", now);
|
|
1524
|
+
return 0;
|
|
1525
|
+
}
|
|
1526
|
+
if (action === "down") {
|
|
1527
|
+
const sbx = await use(rest[1]);
|
|
1528
|
+
const gone = await sbx.tailscale.down();
|
|
1529
|
+
print(gone.left ? "Left the tailnet." : "Not on a tailnet.", gone);
|
|
1530
|
+
return 0;
|
|
1531
|
+
}
|
|
1532
|
+
throw usage("runtime sandbox tailscale up|status|down <id>");
|
|
1533
|
+
}
|
|
1448
1534
|
case "snapshot": {
|
|
1449
1535
|
const args = parse(rest, [], ["name", "retention"]);
|
|
1450
1536
|
const sbx = await get(args.positional[0]);
|
|
@@ -1472,7 +1558,16 @@ async function sandbox(argv, env, out) {
|
|
|
1472
1558
|
return 0;
|
|
1473
1559
|
}
|
|
1474
1560
|
case "update": {
|
|
1475
|
-
const args = parse(rest, [], [
|
|
1561
|
+
const args = parse(rest, [], [
|
|
1562
|
+
"name",
|
|
1563
|
+
"label",
|
|
1564
|
+
"env",
|
|
1565
|
+
"unset-env",
|
|
1566
|
+
"idle-pause",
|
|
1567
|
+
"auto-wake",
|
|
1568
|
+
"persistent",
|
|
1569
|
+
"max-total-cost",
|
|
1570
|
+
]);
|
|
1476
1571
|
const sbx = await get(args.positional[0]);
|
|
1477
1572
|
const onOff = (name) => {
|
|
1478
1573
|
const value = flag(args, name);
|
|
@@ -1485,9 +1580,14 @@ async function sandbox(argv, env, out) {
|
|
|
1485
1580
|
const cost = flag(args, "max-total-cost");
|
|
1486
1581
|
if (cost !== undefined && cost !== "none" && !/^\d+(\.\d{1,6})?$/.test(cost))
|
|
1487
1582
|
throw usage("--max-total-cost takes dollars, such as 25 or 2.50, or none.");
|
|
1583
|
+
const env = {
|
|
1584
|
+
...(args.flags.has("env") ? pairs(args, "env") : {}),
|
|
1585
|
+
...Object.fromEntries((args.flags.get("unset-env") ?? []).map((name) => [name, null])),
|
|
1586
|
+
};
|
|
1488
1587
|
const settings = {
|
|
1489
1588
|
...(flag(args, "name") ? { name: flag(args, "name") } : {}),
|
|
1490
1589
|
...(args.flags.has("label") ? { labels: pairs(args, "label") } : {}),
|
|
1590
|
+
...(Object.keys(env).length ? { env } : {}),
|
|
1491
1591
|
...(integer(args, "idle-pause") !== undefined
|
|
1492
1592
|
? { idlePauseSeconds: integer(args, "idle-pause") }
|
|
1493
1593
|
: {}),
|
|
@@ -1500,7 +1600,7 @@ async function sandbox(argv, env, out) {
|
|
|
1500
1600
|
}),
|
|
1501
1601
|
};
|
|
1502
1602
|
if (!Object.keys(settings).length)
|
|
1503
|
-
throw usage("Say what to change: --name, --label, --idle-pause, --auto-wake, --persistent or --max-total-cost.");
|
|
1603
|
+
throw usage("Say what to change: --name, --label, --env, --unset-env, --idle-pause, --auto-wake, --persistent or --max-total-cost.");
|
|
1504
1604
|
await sbx.update(settings);
|
|
1505
1605
|
print(describe(sbx.info), sbx.info);
|
|
1506
1606
|
return 0;
|
|
@@ -1576,8 +1676,21 @@ async function sandbox(argv, env, out) {
|
|
|
1576
1676
|
const args = parse(rest, [], ["signal"]);
|
|
1577
1677
|
const sbx = await use(args.positional[0]);
|
|
1578
1678
|
const proc = await sbx.processes.get(need(args.positional[1], "a process id"));
|
|
1679
|
+
// A process that has ended has nothing to signal; "Signalled" said it
|
|
1680
|
+
// had been until 30 September 2026.
|
|
1681
|
+
if (proc.info.state === "exited" ||
|
|
1682
|
+
proc.info.state === "killed" ||
|
|
1683
|
+
proc.info.state === "timed_out") {
|
|
1684
|
+
print(`${proc.id} had already ${proc.info.state === "exited" ? "exited" : "ended"}${proc.info.exitCode == null ? "" : ` (exit code ${proc.info.exitCode})`}; nothing to signal.`, {
|
|
1685
|
+
processId: proc.id,
|
|
1686
|
+
state: proc.info.state,
|
|
1687
|
+
exitCode: proc.info.exitCode ?? null,
|
|
1688
|
+
signalled: false,
|
|
1689
|
+
});
|
|
1690
|
+
return 0;
|
|
1691
|
+
}
|
|
1579
1692
|
await proc.kill((flag(args, "signal") ?? "SIGTERM"));
|
|
1580
|
-
print(`Signalled ${proc.id}.`, { processId: proc.id });
|
|
1693
|
+
print(`Signalled ${proc.id}.`, { processId: proc.id, signalled: true });
|
|
1581
1694
|
return 0;
|
|
1582
1695
|
}
|
|
1583
1696
|
case "shell": {
|
|
@@ -2256,7 +2369,7 @@ async function snapshot(argv, env, out) {
|
|
|
2256
2369
|
? { sandboxId: await sandboxIdOf(await client(env), flag(args, "sandbox")) }
|
|
2257
2370
|
: {}),
|
|
2258
2371
|
...(flag(args, "name") ? { name: flag(args, "name") } : {}),
|
|
2259
|
-
})).toArray(
|
|
2372
|
+
})).toArray(Infinity);
|
|
2260
2373
|
print(all.length
|
|
2261
2374
|
? table([
|
|
2262
2375
|
["ID", "NAME", "STATUS", "FROM", "EXPIRES"],
|
|
@@ -2426,7 +2539,7 @@ read = readSecret) {
|
|
|
2426
2539
|
case "list":
|
|
2427
2540
|
case "versions": {
|
|
2428
2541
|
const name = verb === "versions" ? need(rest[0], "a name") : undefined;
|
|
2429
|
-
const all = await (await api.list(name ? { name } : {})).toArray(
|
|
2542
|
+
const all = await (await api.list(name ? { name } : {})).toArray(Infinity);
|
|
2430
2543
|
print(all.length
|
|
2431
2544
|
? table([
|
|
2432
2545
|
["ID", "NAME", "VERSION", "TAGS", "STATE", "MIB", "CREATED"],
|
|
@@ -2447,7 +2560,7 @@ read = readSecret) {
|
|
|
2447
2560
|
}
|
|
2448
2561
|
case "get": {
|
|
2449
2562
|
const found = await api.resolve(need(rest[0], "an image"));
|
|
2450
|
-
print(
|
|
2563
|
+
print(describeRecord(found, table), found);
|
|
2451
2564
|
return 0;
|
|
2452
2565
|
}
|
|
2453
2566
|
case "logs": {
|
|
@@ -2672,7 +2785,7 @@ async function jobCommand(argv, env, out) {
|
|
|
2672
2785
|
}
|
|
2673
2786
|
case "ls":
|
|
2674
2787
|
case "list": {
|
|
2675
|
-
const all = await (await api.list({ limit: 100 })).toArray(
|
|
2788
|
+
const all = await (await api.list({ limit: 100 })).toArray(Infinity);
|
|
2676
2789
|
print(all.length
|
|
2677
2790
|
? table([
|
|
2678
2791
|
["ID", "NAME", "STATE", "SCHEDULE", "NEXT RUN", "BLOCKED"],
|
|
@@ -2681,7 +2794,8 @@ async function jobCommand(argv, env, out) {
|
|
|
2681
2794
|
j.name,
|
|
2682
2795
|
j.state,
|
|
2683
2796
|
scheduleText(j),
|
|
2684
|
-
|
|
2797
|
+
// A canceled or finished job keeps its last nextRunAt, but runs no more.
|
|
2798
|
+
j.state === "active" || j.state === "paused" ? when(j.nextRunAt) : "-",
|
|
2685
2799
|
j.blockedReason ?? "-",
|
|
2686
2800
|
]),
|
|
2687
2801
|
])
|
|
@@ -2695,7 +2809,7 @@ async function jobCommand(argv, env, out) {
|
|
|
2695
2809
|
}
|
|
2696
2810
|
case "runs": {
|
|
2697
2811
|
const jobId = need(rest[0], "a job id");
|
|
2698
|
-
const all = await (await api.runs(jobId, { limit: 100 })).toArray(
|
|
2812
|
+
const all = await (await api.runs(jobId, { limit: 100 })).toArray(Infinity);
|
|
2699
2813
|
print(all.length
|
|
2700
2814
|
? table([
|
|
2701
2815
|
["RUN", "ATTEMPT", "STATE", "EXIT", "SCHEDULED", "STARTED", "ENDED", "REASON"],
|
|
@@ -2724,7 +2838,7 @@ async function jobCommand(argv, env, out) {
|
|
|
2724
2838
|
if (!(error instanceof NotFoundError))
|
|
2725
2839
|
throw error;
|
|
2726
2840
|
// A job's id: its latest run.
|
|
2727
|
-
const runs = await (await api.runs(id, { limit: 100 })).toArray(
|
|
2841
|
+
const runs = await (await api.runs(id, { limit: 100 })).toArray(Infinity);
|
|
2728
2842
|
const latest = runs.at(-1);
|
|
2729
2843
|
if (!latest) {
|
|
2730
2844
|
const job = await api.get(id);
|
|
@@ -2823,7 +2937,7 @@ async function volume(argv, env, out) {
|
|
|
2823
2937
|
}
|
|
2824
2938
|
case "ls":
|
|
2825
2939
|
case "list": {
|
|
2826
|
-
const all = await (await api.list()).toArray(
|
|
2940
|
+
const all = await (await api.list()).toArray(Infinity);
|
|
2827
2941
|
print(all.length
|
|
2828
2942
|
? table([
|
|
2829
2943
|
["ID", "NAME", "STATE", "MIB", "BACKED UP", "ATTACHED"],
|
|
@@ -2841,7 +2955,7 @@ async function volume(argv, env, out) {
|
|
|
2841
2955
|
}
|
|
2842
2956
|
case "get": {
|
|
2843
2957
|
const found = await api.get(need(rest[0], "a volume id"));
|
|
2844
|
-
print(
|
|
2958
|
+
print(describeRecord(found, table), found);
|
|
2845
2959
|
return 0;
|
|
2846
2960
|
}
|
|
2847
2961
|
case "rm":
|
|
@@ -2862,7 +2976,7 @@ async function volume(argv, env, out) {
|
|
|
2862
2976
|
}
|
|
2863
2977
|
case "backups": {
|
|
2864
2978
|
const volumeId = rest[0];
|
|
2865
|
-
const all = await (await api.backups(volumeId ? { volumeId } : {})).toArray(
|
|
2979
|
+
const all = await (await api.backups(volumeId ? { volumeId } : {})).toArray(Infinity);
|
|
2866
2980
|
print(all.length
|
|
2867
2981
|
? table([
|
|
2868
2982
|
["ID", "VOLUME", "STATE", "TRIGGER", "MIB", "STORED", "TAKEN", "EXPIRES"],
|
|
@@ -2966,6 +3080,7 @@ const CREATE_VALUES = [
|
|
|
2966
3080
|
"image",
|
|
2967
3081
|
"snapshot",
|
|
2968
3082
|
"volume",
|
|
3083
|
+
"env",
|
|
2969
3084
|
];
|
|
2970
3085
|
/** Dollars from the command line as integer microdollars: 25, 2.50 or 0.000001. */
|
|
2971
3086
|
function usdMicros(args, name) {
|
|
@@ -2993,6 +3108,7 @@ function createInput(args) {
|
|
|
2993
3108
|
: {}),
|
|
2994
3109
|
...(flag(args, "name") ? { name: flag(args, "name") } : {}),
|
|
2995
3110
|
...(args.flags.has("label") ? { labels: pairs(args, "label") } : {}),
|
|
3111
|
+
...(args.flags.has("env") ? { env: pairs(args, "env") } : {}),
|
|
2996
3112
|
...(integer(args, "vcpu") ? { vcpu: integer(args, "vcpu") } : {}),
|
|
2997
3113
|
...(integer(args, "memory") ? { memoryMiB: integer(args, "memory") } : {}),
|
|
2998
3114
|
...(integer(args, "disk") ? { diskMiB: integer(args, "disk") } : {}),
|
|
@@ -3071,7 +3187,11 @@ ownInterrupt = true) {
|
|
|
3071
3187
|
const lost = extras.lostOutput(result, sbx.id);
|
|
3072
3188
|
if (lost && !out.json)
|
|
3073
3189
|
out.error(lost);
|
|
3074
|
-
|
|
3190
|
+
/* A command a signal ended comes back as minus the signal (-9 after the
|
|
3191
|
+
out-of-memory killer's SIGKILL); a shell says 128 + the signal. Clamped
|
|
3192
|
+
to 0 until 30 September 2026, a killed command exited 0. */
|
|
3193
|
+
const exit = result.exitCode ?? 1;
|
|
3194
|
+
const code = result.timedOut ? 124 : Math.min(255, exit < 0 ? 128 - exit : exit);
|
|
3075
3195
|
return lost && code === 0 ? 1 : code;
|
|
3076
3196
|
}
|
|
3077
3197
|
async function shell(sbx, command) {
|
|
@@ -3119,7 +3239,9 @@ export function describeError(error, json) {
|
|
|
3119
3239
|
details: error.details,
|
|
3120
3240
|
},
|
|
3121
3241
|
});
|
|
3122
|
-
|
|
3242
|
+
// In the command line's words: the option, not the request's field.
|
|
3243
|
+
const { message, hint } = cliRefusal(error);
|
|
3244
|
+
return `Error [${error.code}]: ${message}${hint ? `\nHint: ${hint}` : ""}${error.requestId
|
|
3123
3245
|
? // A refusal of what was sent is the caller's to fix; only a failure
|
|
3124
3246
|
// of ours is worth a report.
|
|
3125
3247
|
error.status >= 400 && error.status < 500
|
|
@@ -3160,17 +3282,26 @@ function metricsText(m) {
|
|
|
3160
3282
|
const cpu = m.points.map((p) => p.cpuPercent);
|
|
3161
3283
|
const memory = m.points.map((p) => p.memoryBytes);
|
|
3162
3284
|
const peak = Math.max(0, ...m.points.map((p) => p.cpuPeakPercent ?? p.cpuPercent ?? 0));
|
|
3163
|
-
|
|
3285
|
+
// `latest` is the newest reading of the last 15 minutes whatever the state,
|
|
3286
|
+
// so a sandbox paused a minute ago still has one from before it slept. Only a
|
|
3287
|
+
// running sandbox has a now.
|
|
3288
|
+
const latest = m.state === "running" ? m.latest : null;
|
|
3289
|
+
const still = m.state === "running" ? null : `none: it is ${m.state}`;
|
|
3290
|
+
const cpuNow = newestCpu(m);
|
|
3164
3291
|
return [
|
|
3165
3292
|
table([
|
|
3166
3293
|
["state", m.state],
|
|
3167
3294
|
[
|
|
3168
3295
|
"cpu now",
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3296
|
+
still ??
|
|
3297
|
+
(cpuNow
|
|
3298
|
+
? `${cpuNow.percent}% of ${m.vcpu} vCPU${cpuNow.cores == null ? "" : ` (${cpuNow.cores} cores)`}${cpuNow.ago ? `, ${cpuNow.ago}` : ""}`
|
|
3299
|
+
: "no reading in this range"),
|
|
3300
|
+
],
|
|
3301
|
+
[
|
|
3302
|
+
"memory now",
|
|
3303
|
+
still ?? (latest ? `${mib(latest.memoryBytes)} of ${mib(m.memoryLimitBytes)}` : "-"),
|
|
3172
3304
|
],
|
|
3173
|
-
["memory now", latest ? `${mib(latest.memoryBytes)} of ${mib(m.memoryLimitBytes)}` : "-"],
|
|
3174
3305
|
[`cpu, ${m.range}`, m.points.length ? `${spark(cpu)} peak ${peak}%` : "no readings"],
|
|
3175
3306
|
[
|
|
3176
3307
|
`memory, ${m.range}`,
|
package/dist/client.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { type ClientExtensions } from "./products/index.js";
|
|
|
4
4
|
import { Sandbox } from "./sandbox.js";
|
|
5
5
|
import { Snapshots } from "./snapshots.js";
|
|
6
6
|
import { Transport, type RequestOptions } from "./transport.js";
|
|
7
|
-
import type { CreateSandbox, FeedbackKind, SandboxInfo, Usage } from "./types.js";
|
|
7
|
+
import type { CreateSandbox, DeletedSandbox, FeedbackKind, SandboxInfo, Usage } from "./types.js";
|
|
8
8
|
export type RuntimeOptions = {
|
|
9
9
|
/** Default: the RUNTIME_API_KEY environment variable. */
|
|
10
10
|
apiKey?: string;
|
|
@@ -80,6 +80,9 @@ export declare class Sandboxes {
|
|
|
80
80
|
wait?: boolean;
|
|
81
81
|
}, options?: CreateOptions): Promise<Sandbox>;
|
|
82
82
|
get(id: string, options?: RequestOptions): Promise<Sandbox>;
|
|
83
|
+
/** Deletes a sandbox for good, by id, without reading it first: see
|
|
84
|
+
* `sandbox.delete()`. Deleting it again answers the same. */
|
|
85
|
+
delete(id: string, options?: RequestOptions): Promise<DeletedSandbox>;
|
|
83
86
|
/** Live sandboxes, oldest first. Await for a page, or `for await` over all. */
|
|
84
87
|
list(filter?: {
|
|
85
88
|
state?: SandboxInfo["state"][];
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { WAIT_FOR_TIMEOUT_SECONDS } from "./api-defaults.js";
|
|
1
2
|
import { RuntimeError } from "./errors.js";
|
|
2
3
|
import { clientFactories } from "./products/index.js";
|
|
3
|
-
import { Sandbox, sandboxPage } from "./sandbox.js";
|
|
4
|
+
import { Sandbox, deleteSandbox, sandboxPage } from "./sandbox.js";
|
|
4
5
|
import { Snapshots } from "./snapshots.js";
|
|
5
6
|
import { Transport, missingKey } from "./transport.js";
|
|
6
7
|
/** The key `runtime login` saved for this machine, found on first use. */
|
|
@@ -83,7 +84,7 @@ export class Sandboxes {
|
|
|
83
84
|
});
|
|
84
85
|
const sandbox = new Sandbox(this.t, info);
|
|
85
86
|
if (wait !== false && info.state !== "running") {
|
|
86
|
-
await sandbox.waitFor("running", { timeoutSeconds:
|
|
87
|
+
await sandbox.waitFor("running", { timeoutSeconds: WAIT_FOR_TIMEOUT_SECONDS });
|
|
87
88
|
if (sandbox.state !== "running")
|
|
88
89
|
throw new RuntimeError({
|
|
89
90
|
message: `Sandbox ${info.id} is ${sandbox.state}, not running.`,
|
|
@@ -108,6 +109,11 @@ export class Sandboxes {
|
|
|
108
109
|
...options,
|
|
109
110
|
}));
|
|
110
111
|
}
|
|
112
|
+
/** Deletes a sandbox for good, by id, without reading it first: see
|
|
113
|
+
* `sandbox.delete()`. Deleting it again answers the same. */
|
|
114
|
+
delete(id, options = {}) {
|
|
115
|
+
return deleteSandbox(this.t, id, options);
|
|
116
|
+
}
|
|
111
117
|
/** Live sandboxes, oldest first. Await for a page, or `for await` over all. */
|
|
112
118
|
async list(filter = {}) {
|
|
113
119
|
const query = {
|