withruntime 0.9.0 → 0.11.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/BLAXEL.md +4 -4
- package/DAYTONA.md +52 -40
- package/E2B.md +65 -62
- package/README.md +15 -7
- package/README.src.md +15 -7
- package/VERCEL.md +34 -22
- package/compatibility-lock.json +16 -16
- package/dist/blaxel/errors.js +1 -1
- package/dist/blaxel/lookup.d.ts +3 -2
- package/dist/blaxel/lookup.js +3 -2
- package/dist/blaxel/pagination.d.ts +2 -2
- package/dist/blaxel/pagination.js +45 -17
- package/dist/blaxel/preview.d.ts +2 -1
- package/dist/blaxel/preview.js +15 -13
- package/dist/blaxel/process.js +56 -10
- package/dist/blaxel/sandbox.d.ts +2 -0
- package/dist/blaxel/sandbox.js +2 -0
- package/dist/blaxel/snapshot.js +3 -5
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +262 -56
- package/dist/client.d.ts +41 -3
- package/dist/client.js +67 -11
- package/dist/cloudflare/identity.d.ts +7 -0
- package/dist/cloudflare/identity.js +13 -0
- package/dist/cloudflare/index.d.ts +9 -0
- package/dist/cloudflare/index.js +47 -9
- package/dist/cloudflare/worker.d.ts +4 -0
- package/dist/cloudflare/worker.js +166 -16
- package/dist/codesandbox/guest-service.js +57 -20
- package/dist/codesandbox/index.js +42 -8
- package/dist/compat/core.d.ts +9 -8
- package/dist/compat/core.js +60 -21
- package/dist/compat/files.js +22 -4
- package/dist/compat/lease.d.ts +36 -0
- package/dist/compat/lease.js +87 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +107 -38
- package/dist/daytona/computer-use.d.ts +162 -0
- package/dist/daytona/computer-use.js +324 -0
- package/dist/daytona/context.d.ts +10 -0
- package/dist/daytona/context.js +24 -0
- package/dist/daytona/daytona.d.ts +20 -0
- package/dist/daytona/daytona.js +134 -20
- package/dist/daytona/filesystem.d.ts +17 -4
- package/dist/daytona/filesystem.js +129 -38
- package/dist/daytona/index.d.ts +3 -27
- package/dist/daytona/index.js +2 -7
- package/dist/daytona/process.d.ts +63 -6
- package/dist/daytona/process.js +224 -23
- package/dist/daytona/sandbox.d.ts +20 -6
- package/dist/daytona/sandbox.js +55 -32
- package/dist/e2b/client.d.ts +5 -0
- package/dist/e2b/client.js +6 -2
- package/dist/e2b/commands.d.ts +43 -12
- package/dist/e2b/commands.js +217 -64
- package/dist/e2b/errors.d.ts +7 -0
- package/dist/e2b/errors.js +20 -0
- package/dist/e2b/filesystem.d.ts +2 -1
- package/dist/e2b/filesystem.js +43 -6
- package/dist/e2b/index.d.ts +3 -2
- package/dist/e2b/index.js +2 -1
- package/dist/e2b/pty.d.ts +24 -0
- package/dist/e2b/pty.js +83 -0
- package/dist/e2b/sandbox.d.ts +17 -6
- package/dist/e2b/sandbox.js +239 -43
- package/dist/e2b/users.d.ts +30 -0
- package/dist/e2b/users.js +185 -0
- package/dist/file-source.d.ts +16 -0
- package/dist/file-source.js +162 -0
- package/dist/freestyle/index.d.ts +9 -1
- package/dist/freestyle/index.js +38 -5
- package/dist/index.d.ts +3 -1
- package/dist/login.js +16 -10
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.js +6 -1
- package/dist/modal/index.js +2 -1
- package/dist/network-cli.js +139 -43
- package/dist/openai-agents/index.js +9 -45
- package/dist/page.d.ts +1 -1
- package/dist/page.js +2 -2
- package/dist/products/browser.d.ts +60 -0
- package/dist/products/browser.js +37 -0
- package/dist/products/desktop.d.ts +2 -2
- package/dist/products/desktop.js +2 -2
- package/dist/products/images.d.ts +3 -1
- package/dist/products/images.js +14 -2
- package/dist/products/index.d.ts +14 -0
- package/dist/products/index.js +12 -0
- package/dist/products/network-products.d.ts +7 -0
- package/dist/products/network-products.js +3 -0
- package/dist/products/observability.d.ts +44 -0
- package/dist/products/observability.js +6 -0
- package/dist/products/previews.d.ts +8 -2
- package/dist/products/previews.js +11 -3
- package/dist/products/private-network.d.ts +22 -0
- package/dist/products/private-network.js +10 -0
- package/dist/products/volumes.d.ts +42 -0
- package/dist/products/volumes.js +36 -0
- package/dist/products/watch.js +61 -8
- package/dist/proxy.js +3 -1
- package/dist/runloop/index.d.ts +13 -27
- package/dist/runloop/index.js +149 -52
- package/dist/sandbox.d.ts +59 -8
- package/dist/sandbox.js +479 -68
- package/dist/snapshots.d.ts +9 -4
- package/dist/sprites/index.js +69 -33
- package/dist/tar.d.ts +3 -1
- package/dist/tar.js +140 -14
- package/dist/transport.d.ts +4 -7
- package/dist/transport.js +205 -122
- package/dist/types.d.ts +5 -2
- package/dist/vercel/command.d.ts +1 -1
- package/dist/vercel/command.js +3 -2
- package/dist/vercel/errors.d.ts +1 -1
- package/dist/vercel/errors.js +7 -1
- package/dist/vercel/index.d.ts +1 -7
- package/dist/vercel/index.js +1 -3
- package/dist/vercel/sandbox-user.d.ts +70 -0
- package/dist/vercel/sandbox-user.js +206 -0
- package/dist/vercel/sandbox.d.ts +55 -20
- package/dist/vercel/sandbox.js +353 -99
- package/dist/vercel/snapshot.d.ts +3 -2
- package/dist/vercel/snapshot.js +9 -5
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { realpathSync } from "node:fs";
|
|
3
|
+
import { once } from "node:events";
|
|
3
4
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
5
|
import { dirname, join, resolve } from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { connectionOrigins, connectionStore, resolveCredential } from "./credentials.js";
|
|
7
|
+
import { checkBrowserOrigins, connectionOrigins, connectionStore, resolveCredential, } from "./credentials.js";
|
|
7
8
|
import { me, named } from "./cli-name.js";
|
|
8
9
|
import { Runtime } from "./client.js";
|
|
9
10
|
import { Sandbox as SandboxHandle } from "./sandbox.js";
|
|
@@ -36,6 +37,8 @@ Account
|
|
|
36
37
|
sso Single sign-on and SCIM directory sync (owner or
|
|
37
38
|
admin keys); an owner changes it on the website
|
|
38
39
|
usage [--csv] Balance, trial time and charges; --csv, each resource
|
|
40
|
+
usage export --since <UTC time> --until <UTC time>
|
|
41
|
+
Every settlement in the range as CSV; --json, each page
|
|
39
42
|
limits Whether this key is read-only, and its daily spending limit
|
|
40
43
|
secrets set <NAME> --host <host>... [--header <name> [--format '<text {value}>']]
|
|
41
44
|
[--allow '[GET,HEAD] /path/*']... Store a secret sandboxes use without seeing it; the
|
|
@@ -85,7 +88,7 @@ Observability
|
|
|
85
88
|
Events and metrics to an OpenTelemetry endpoint
|
|
86
89
|
|
|
87
90
|
Products
|
|
88
|
-
sandbox Linux microVMs: exec, shell, files, code, previews, network, desktop
|
|
91
|
+
sandbox Linux microVMs: exec, shell, files, code, previews, network, desktop, browser
|
|
89
92
|
snapshot Saved sandboxes, disk and memory, to start or fork from
|
|
90
93
|
image Custom images from a Dockerfile, any public or private image, or a package list
|
|
91
94
|
volume Persistent disks to attach to sandboxes
|
|
@@ -94,7 +97,7 @@ Products
|
|
|
94
97
|
port A public TCP port to a sandbox: databases, game servers
|
|
95
98
|
address A dedicated outbound address, for allow-lists
|
|
96
99
|
tunnel A WireGuard tunnel from your own network into your sandboxes
|
|
97
|
-
network Account-wide network settings:
|
|
100
|
+
network Account-wide network settings: your own proxy, sandboxes reaching each other by name
|
|
98
101
|
Each has its own help: \`runtime sandbox help\`, \`runtime image help\`...
|
|
99
102
|
|
|
100
103
|
Every command takes --json. Keys: RUNTIME_API_KEY, or the connection from \`runtime login\`.
|
|
@@ -128,11 +131,15 @@ const SANDBOX_HELP = `runtime sandbox <command>
|
|
|
128
131
|
cat <id> <path> Print a file
|
|
129
132
|
files <id> [path] [--depth 1] [--glob '**/*.py']
|
|
130
133
|
stop <id> | pause <id> | wake <id> | restart <id>
|
|
134
|
+
stop --label k=v [--label k=v]... Stop every live sandbox with all those labels
|
|
131
135
|
rm <id> Delete it for good: stops it, deletes its disk and
|
|
132
136
|
paused memory, revokes its previews; snapshots stay
|
|
133
137
|
switch-image <id> <image> --keep-workspace Move it to another image, keeping /workspace (your home),
|
|
134
138
|
volumes, env and previews; processes restart, the rest of
|
|
135
139
|
its disk is lost (snapshot it first to keep everything)
|
|
140
|
+
resize <id> [--vcpu 2] [--memory 4096] --restart
|
|
141
|
+
Change its size by a restart: the whole disk stays,
|
|
142
|
+
programs stop (snapshot it first to keep memory)
|
|
136
143
|
extend <id> <seconds> More time before the lease ends
|
|
137
144
|
update <id> [--name n] [--label k=v]... [--env K=V]... [--unset-env K]... [--idle-pause <seconds>] [--auto-wake on|off]
|
|
138
145
|
[--persistent on|off] [--max-total-cost <usd>|none]
|
|
@@ -177,11 +184,13 @@ const SANDBOX_HELP = `runtime sandbox <command>
|
|
|
177
184
|
| type <text> | press <keys> | windows
|
|
178
185
|
desktop <id> record start [--fps 10] [--max-seconds 1800] [--max-mib 512] | record stop <recId>
|
|
179
186
|
| record ls | record fetch <recId> [file.mp4] | record rm <recId>
|
|
187
|
+
browser <id> start [--headed] [--width 1280] [--height 800] | get | stop
|
|
188
|
+
Chromium for CDP agents: prints its cdpUrl
|
|
180
189
|
|
|
181
190
|
Examples
|
|
182
191
|
id=$(runtime sandbox create)
|
|
183
|
-
runtime sandbox
|
|
184
|
-
runtime sandbox
|
|
192
|
+
runtime sandbox cp ./invoice.py $id:/workspace/invoice.py
|
|
193
|
+
runtime sandbox exec $id -- python3 /workspace/invoice.py
|
|
185
194
|
runtime sandbox stop $id
|
|
186
195
|
`;
|
|
187
196
|
/** A command's options. Reading one its parse() did not declare is a bug in
|
|
@@ -229,9 +238,13 @@ commandAfter) {
|
|
|
229
238
|
throw commandAfter && !meantOption(name, known)
|
|
230
239
|
? usage(`Unknown option ${arg.split("=")[0]}. If it belongs to the command, put the command after --: ${commandAfter}`)
|
|
231
240
|
: unknownOption(name, known);
|
|
232
|
-
const
|
|
233
|
-
|
|
241
|
+
const boolean = booleans.includes(name);
|
|
242
|
+
const value = inline ?? (boolean ? "true" : argv[++i]);
|
|
243
|
+
if (value === undefined ||
|
|
244
|
+
(!boolean && inline === undefined && (value === "--" || /^--?[^\d]/.test(value))))
|
|
234
245
|
throw usage(`--${name} needs a value.`);
|
|
246
|
+
if (boolean && value !== "true" && value !== "false")
|
|
247
|
+
throw usage(`--${name} takes true or false.`);
|
|
235
248
|
flags.set(name, [...(flags.get(name) ?? []), value]);
|
|
236
249
|
}
|
|
237
250
|
else
|
|
@@ -263,7 +276,10 @@ const integer = (args, name) => {
|
|
|
263
276
|
return undefined;
|
|
264
277
|
if (!/^\d+$/.test(value))
|
|
265
278
|
throw usage(`--${name} must be a whole number.`);
|
|
266
|
-
|
|
279
|
+
const parsed = Number(value);
|
|
280
|
+
if (!Number.isSafeInteger(parsed))
|
|
281
|
+
throw usage(`--${name} is too large to represent exactly.`);
|
|
282
|
+
return parsed;
|
|
267
283
|
};
|
|
268
284
|
/** A flag that takes one of a few words, checked before anything is sent. */
|
|
269
285
|
function oneOf(args, name, choices) {
|
|
@@ -400,6 +416,7 @@ const SANDBOX_VERBS = [
|
|
|
400
416
|
"rm",
|
|
401
417
|
"delete",
|
|
402
418
|
"switch-image",
|
|
419
|
+
"resize",
|
|
403
420
|
"pause",
|
|
404
421
|
"wake",
|
|
405
422
|
"restart",
|
|
@@ -427,6 +444,7 @@ const SANDBOX_VERBS = [
|
|
|
427
444
|
"session",
|
|
428
445
|
"network",
|
|
429
446
|
"desktop",
|
|
447
|
+
"browser",
|
|
430
448
|
"metrics",
|
|
431
449
|
"watch",
|
|
432
450
|
"mcp",
|
|
@@ -575,8 +593,8 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
|
|
|
575
593
|
? JSON.stringify({ connected, usage: HELP })
|
|
576
594
|
: `Runtime Cloud ${VERSION}. ${start}
|
|
577
595
|
|
|
578
|
-
${me} sandbox run -- python3
|
|
579
|
-
|
|
596
|
+
${me} sandbox run -- python3 < invoice.py
|
|
597
|
+
Run a local invoice script, then stop the sandbox
|
|
580
598
|
${me} sandbox create A sandbox that stays; prints its id
|
|
581
599
|
${me} sandbox exec <id> -- <command> Run in it
|
|
582
600
|
${me} help Everything else
|
|
@@ -588,10 +606,10 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
|
|
|
588
606
|
return 0;
|
|
589
607
|
}
|
|
590
608
|
if (command === "run") {
|
|
591
|
-
const args = parse(rest, [...CREATE_FLAGS, "keep"], [...CREATE_VALUES, "cwd", "env"], `${me} sandbox run -- python3
|
|
609
|
+
const args = parse(rest, [...CREATE_FLAGS, "keep"], [...CREATE_VALUES, "cwd", "env"], `${me} sandbox run -- python3 < invoice.py`);
|
|
592
610
|
const commandLine = args.rest ?? args.positional;
|
|
593
611
|
if (!commandLine.length)
|
|
594
|
-
throw usage(`Give the command after --: ${me} sandbox run -- python3
|
|
612
|
+
throw usage(`Give the command after --: ${me} sandbox run -- python3 < invoice.py`);
|
|
595
613
|
const runtime = await client(env);
|
|
596
614
|
const timeout = integer(args, "timeout");
|
|
597
615
|
const { timeoutSeconds: _lease, ...input } = createInput(args);
|
|
@@ -688,6 +706,40 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
|
|
|
688
706
|
return 0;
|
|
689
707
|
}
|
|
690
708
|
if (command === "usage") {
|
|
709
|
+
if (rest[0] === "export") {
|
|
710
|
+
const args = parse(rest.slice(1), [], ["since", "until"]);
|
|
711
|
+
if (args.positional.length || args.rest)
|
|
712
|
+
throw usage(`Use ${me} usage export --since <UTC time> --until <UTC time>.`);
|
|
713
|
+
const since = need(flag(args, "since"), "--since <UTC time>, such as 2026-09-01T00:00:00Z");
|
|
714
|
+
const until = need(flag(args, "until"), "--until <UTC time>, such as 2026-10-01T00:00:00Z");
|
|
715
|
+
const rt = await client(env);
|
|
716
|
+
const cursors = new Set();
|
|
717
|
+
let cursor;
|
|
718
|
+
for (;;) {
|
|
719
|
+
const page = await rt.usageExport({
|
|
720
|
+
since,
|
|
721
|
+
until,
|
|
722
|
+
...(cursor ? { cursor } : {}),
|
|
723
|
+
limit: 500,
|
|
724
|
+
});
|
|
725
|
+
if (page.nextCursor !== null && cursors.has(page.nextCursor))
|
|
726
|
+
throw new RuntimeError({
|
|
727
|
+
code: "usage_export_incomplete",
|
|
728
|
+
status: 0,
|
|
729
|
+
message: "The usage export repeated a page cursor and could not finish.",
|
|
730
|
+
hint: "Run the export again; do not treat the partial output as complete.",
|
|
731
|
+
});
|
|
732
|
+
if (out.json)
|
|
733
|
+
out.write(JSON.stringify(page));
|
|
734
|
+
else if (page.csv)
|
|
735
|
+
out.write(page.csv);
|
|
736
|
+
await out.drain?.();
|
|
737
|
+
if (page.nextCursor === null)
|
|
738
|
+
return 0;
|
|
739
|
+
cursors.add(page.nextCursor);
|
|
740
|
+
cursor = page.nextCursor;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
691
743
|
const csv = rest.includes("--csv");
|
|
692
744
|
const unknown = rest.find((arg) => arg !== "--csv");
|
|
693
745
|
if (unknown)
|
|
@@ -738,7 +790,7 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
|
|
|
738
790
|
const page = await rt.events.list({
|
|
739
791
|
...(sandbox ? { resourceId: await sandboxIdOf(rt, sandbox) } : {}),
|
|
740
792
|
...(flag(args, "type") ? { type: flag(args, "type") } : {}),
|
|
741
|
-
...(integer(args, "limit") ? { limit: integer(args, "limit") } : {}),
|
|
793
|
+
...(integer(args, "limit") !== undefined ? { limit: integer(args, "limit") } : {}),
|
|
742
794
|
});
|
|
743
795
|
print(page.data.length
|
|
744
796
|
? table(page.data.map((e) => {
|
|
@@ -783,7 +835,7 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
|
|
|
783
835
|
throw usage(`Unknown option --${name}.`);
|
|
784
836
|
const page = await (await client(env)).audit.list({
|
|
785
837
|
...(flag(args, "action") ? { action: flag(args, "action") } : {}),
|
|
786
|
-
...(integer(args, "limit") ? { limit: integer(args, "limit") } : {}),
|
|
838
|
+
...(integer(args, "limit") !== undefined ? { limit: integer(args, "limit") } : {}),
|
|
787
839
|
...(flag(args, "before") ? { before: flag(args, "before") } : {}),
|
|
788
840
|
});
|
|
789
841
|
print(page.events.length
|
|
@@ -809,17 +861,26 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
|
|
|
809
861
|
const origins = connectionOrigins(env);
|
|
810
862
|
const { claimWalletAccount, openWalletAccount } = await import("./wallet.js");
|
|
811
863
|
return billingCommand(rest, parse(rest, ["wait", "new-account", "accept-terms"], []), out, () => client(env), undefined, {
|
|
812
|
-
open: (input) =>
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
864
|
+
open: (input) => {
|
|
865
|
+
checkBrowserOrigins(origins);
|
|
866
|
+
return openWalletAccount(input, { authUrl: origins.auth });
|
|
867
|
+
},
|
|
868
|
+
claim: (code) => {
|
|
869
|
+
checkBrowserOrigins(origins);
|
|
870
|
+
return claimWalletAccount(code, { authUrl: origins.auth });
|
|
871
|
+
},
|
|
872
|
+
save: (key, orgId) => {
|
|
873
|
+
checkBrowserOrigins(origins);
|
|
874
|
+
return connectionStore(env).save({
|
|
875
|
+
version: 1,
|
|
876
|
+
apiOrigin: origins.api,
|
|
877
|
+
authOrigin: origins.auth,
|
|
878
|
+
key,
|
|
879
|
+
connectionId: "pasted",
|
|
880
|
+
orgId,
|
|
881
|
+
agentName: "Wallet agent",
|
|
882
|
+
});
|
|
883
|
+
},
|
|
823
884
|
});
|
|
824
885
|
}
|
|
825
886
|
if (command === "referrals") {
|
|
@@ -1442,6 +1503,21 @@ async function sandbox(argv, env, out) {
|
|
|
1442
1503
|
print(describe(sbx.info), sbx.info);
|
|
1443
1504
|
return 0;
|
|
1444
1505
|
}
|
|
1506
|
+
case "resize": {
|
|
1507
|
+
const args = parse(rest, ["restart"], ["vcpu", "memory"]);
|
|
1508
|
+
const sbx = await use(need(args.positional[0], `a sandbox, e.g. ${me} sandbox resize <id> --memory 4096 --restart`));
|
|
1509
|
+
const size = {
|
|
1510
|
+
...(integer(args, "vcpu") !== undefined ? { vcpu: integer(args, "vcpu") } : {}),
|
|
1511
|
+
...(integer(args, "memory") !== undefined ? { memoryMiB: integer(args, "memory") } : {}),
|
|
1512
|
+
};
|
|
1513
|
+
if (size.vcpu === undefined && size.memoryMiB === undefined)
|
|
1514
|
+
throw usage(`Name a new size, e.g. ${me} sandbox resize ${sbx.id} --memory 4096 --restart`);
|
|
1515
|
+
if (!args.flags.has("restart"))
|
|
1516
|
+
throw usage(`A resize restarts it: its whole disk stays and its programs stop. Add --restart to say so, or snapshot it first (${me} sandbox snapshot ${sbx.id}) to keep its memory too.`);
|
|
1517
|
+
await sbx.resize(size, { restart: true });
|
|
1518
|
+
print(describe(sbx.info), sbx.info);
|
|
1519
|
+
return 0;
|
|
1520
|
+
}
|
|
1445
1521
|
case "rm":
|
|
1446
1522
|
case "delete": {
|
|
1447
1523
|
const sbx = await use(need(rest[0], `a sandbox, e.g. ${me} sandbox rm <id>`));
|
|
@@ -1453,6 +1529,23 @@ async function sandbox(argv, env, out) {
|
|
|
1453
1529
|
case "pause":
|
|
1454
1530
|
case "wake":
|
|
1455
1531
|
case "restart": {
|
|
1532
|
+
if (verb === "stop" && rest.some((word) => /^--label(=|$)/.test(word))) {
|
|
1533
|
+
const args = parse(rest, [], ["label"]);
|
|
1534
|
+
if (args.positional.length)
|
|
1535
|
+
throw usage("Give a sandbox id or --label, not both.");
|
|
1536
|
+
const { stopped, failed } = await runtime.sandboxes.stopAll({
|
|
1537
|
+
labels: pairs(args, "label"),
|
|
1538
|
+
});
|
|
1539
|
+
const said = (error) => (error instanceof Error ? error.message : String(error));
|
|
1540
|
+
print([
|
|
1541
|
+
stopped.length || failed.length
|
|
1542
|
+
? `${stopped.length} stopped${failed.length ? `, ${failed.length} failed` : ""}.`
|
|
1543
|
+
: "No live sandbox has those labels.",
|
|
1544
|
+
...stopped.map((id) => `${id} stopped`),
|
|
1545
|
+
...failed.map((f) => `${f.id} failed: ${said(f.error)}`),
|
|
1546
|
+
].join("\n"), { stopped, failed: failed.map((f) => ({ id: f.id, error: said(f.error) })) });
|
|
1547
|
+
return failed.length ? 1 : 0;
|
|
1548
|
+
}
|
|
1456
1549
|
const sbx = await get(rest[0]);
|
|
1457
1550
|
await sbx[verb]();
|
|
1458
1551
|
print(`${sbx.id} ${sbx.state}`, sbx.info);
|
|
@@ -1536,7 +1629,9 @@ async function sandbox(argv, env, out) {
|
|
|
1536
1629
|
const sbx = await get(args.positional[0]);
|
|
1537
1630
|
const snapshot = await sbx.snapshot({
|
|
1538
1631
|
...(flag(args, "name") ? { name: flag(args, "name") } : {}),
|
|
1539
|
-
...(integer(args, "retention")
|
|
1632
|
+
...(integer(args, "retention") !== undefined
|
|
1633
|
+
? { retentionDays: integer(args, "retention") }
|
|
1634
|
+
: {}),
|
|
1540
1635
|
});
|
|
1541
1636
|
print(snapshot.id, snapshot);
|
|
1542
1637
|
return 0;
|
|
@@ -1596,7 +1691,7 @@ async function sandbox(argv, env, out) {
|
|
|
1596
1691
|
...(cost === undefined
|
|
1597
1692
|
? {}
|
|
1598
1693
|
: {
|
|
1599
|
-
maxTotalCostMicros: cost === "none" ? null :
|
|
1694
|
+
maxTotalCostMicros: cost === "none" ? null : usdMicros(args, "max-total-cost"),
|
|
1600
1695
|
}),
|
|
1601
1696
|
};
|
|
1602
1697
|
if (!Object.keys(settings).length)
|
|
@@ -1709,7 +1804,7 @@ async function sandbox(argv, env, out) {
|
|
|
1709
1804
|
const args = parse(rest, [], ["depth", "glob"]);
|
|
1710
1805
|
const sbx = await use(args.positional[0]);
|
|
1711
1806
|
const entries = await sbx.files.list(args.positional[1] ?? "/workspace", {
|
|
1712
|
-
...(integer(args, "depth") ? { depth: integer(args, "depth") } : {}),
|
|
1807
|
+
...(integer(args, "depth") !== undefined ? { depth: integer(args, "depth") } : {}),
|
|
1713
1808
|
...(flag(args, "glob") ? { glob: flag(args, "glob") } : {}),
|
|
1714
1809
|
});
|
|
1715
1810
|
print(entries
|
|
@@ -1752,7 +1847,7 @@ async function sandbox(argv, env, out) {
|
|
|
1752
1847
|
const sbx = await use(args.positional[0]);
|
|
1753
1848
|
const preview = await sbx.previews.create(port, {
|
|
1754
1849
|
...(has(args, "public") ? { visibility: "public" } : {}),
|
|
1755
|
-
...(integer(args, "ttl") ? { ttlSeconds: integer(args, "ttl") } : {}),
|
|
1850
|
+
...(integer(args, "ttl") !== undefined ? { ttlSeconds: integer(args, "ttl") } : {}),
|
|
1756
1851
|
});
|
|
1757
1852
|
// The link and how to use it, nothing more: the API's `hint` (which
|
|
1758
1853
|
// carries a referral line) stays in --json, not in the command's words.
|
|
@@ -1838,6 +1933,8 @@ async function sandbox(argv, env, out) {
|
|
|
1838
1933
|
}
|
|
1839
1934
|
case "desktop":
|
|
1840
1935
|
return desktop(await use(rest[0]), rest.slice(1), out);
|
|
1936
|
+
case "browser":
|
|
1937
|
+
return browser(await use(rest[0]), rest.slice(1), out);
|
|
1841
1938
|
case "watch":
|
|
1842
1939
|
return (await import("./cli-extras.js")).watchCommand(await use(rest[0]), rest.slice(1), out, kit);
|
|
1843
1940
|
case "mcp":
|
|
@@ -2084,7 +2181,7 @@ async function runCode(sbx, argv, out) {
|
|
|
2084
2181
|
const execution = await sbx.interpreter.run(code, {
|
|
2085
2182
|
language,
|
|
2086
2183
|
...(flag(args, "context") ? { context: flag(args, "context") } : {}),
|
|
2087
|
-
...(timeout ? { timeoutMs: timeout * 1000 } : {}),
|
|
2184
|
+
...(timeout !== undefined ? { timeoutMs: timeout * 1000 } : {}),
|
|
2088
2185
|
...(out.json
|
|
2089
2186
|
? {}
|
|
2090
2187
|
: {
|
|
@@ -2133,8 +2230,8 @@ async function desktop(sbx, argv, out) {
|
|
|
2133
2230
|
case "start": {
|
|
2134
2231
|
const args = parse(rest, [], ["width", "height"]);
|
|
2135
2232
|
const started = await d.start({
|
|
2136
|
-
...(integer(args, "width") ? { width: integer(args, "width") } : {}),
|
|
2137
|
-
...(integer(args, "height") ? { height: integer(args, "height") } : {}),
|
|
2233
|
+
...(integer(args, "width") !== undefined ? { width: integer(args, "width") } : {}),
|
|
2234
|
+
...(integer(args, "height") !== undefined ? { height: integer(args, "height") } : {}),
|
|
2138
2235
|
});
|
|
2139
2236
|
print(`Desktop ${started.size} on ${started.display}. Watch it: ${started.streamUrl}`, started);
|
|
2140
2237
|
return 0;
|
|
@@ -2172,6 +2269,35 @@ async function desktop(sbx, argv, out) {
|
|
|
2172
2269
|
}
|
|
2173
2270
|
throw usage(`${me} sandbox desktop <id> start|stop|screenshot|open|click|type|press|windows|record`);
|
|
2174
2271
|
}
|
|
2272
|
+
async function browser(sbx, argv, out) {
|
|
2273
|
+
const [action, ...rest] = argv;
|
|
2274
|
+
const print = (human, value) => out.write(out.json ? JSON.stringify(value) : human);
|
|
2275
|
+
switch (action) {
|
|
2276
|
+
case "start": {
|
|
2277
|
+
const args = parse(rest, ["headed"], ["width", "height"]);
|
|
2278
|
+
const started = await sbx.browser.start({
|
|
2279
|
+
...(args.flags.has("headed") ? { headless: false } : {}),
|
|
2280
|
+
...(integer(args, "width") !== undefined ? { width: integer(args, "width") } : {}),
|
|
2281
|
+
...(integer(args, "height") !== undefined ? { height: integer(args, "height") } : {}),
|
|
2282
|
+
});
|
|
2283
|
+
print(`${started.version}${started.headless ? "" : `, on the desktop: ${started.streamUrl}`}\n` +
|
|
2284
|
+
`CDP: ${started.cdpUrl}\n(private; it expires ${started.expiresAt ?? "never"})`, started);
|
|
2285
|
+
return 0;
|
|
2286
|
+
}
|
|
2287
|
+
case "get":
|
|
2288
|
+
case undefined: {
|
|
2289
|
+
const state = await sbx.browser.get();
|
|
2290
|
+
print(state.running ? `${state.version}\nCDP: ${state.cdpUrl}` : "No browser running.", state);
|
|
2291
|
+
return 0;
|
|
2292
|
+
}
|
|
2293
|
+
case "stop": {
|
|
2294
|
+
const stopped = await sbx.browser.stop();
|
|
2295
|
+
print(stopped.stopped ? "Browser stopped." : "No browser was running.", stopped);
|
|
2296
|
+
return 0;
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
throw usage(`${me} sandbox browser <id> start [--headed]|get|stop`);
|
|
2300
|
+
}
|
|
2175
2301
|
const KEYS_HELP = `runtime keys <command>
|
|
2176
2302
|
|
|
2177
2303
|
create [--name <name>] [--read-only] [--daily-limit <usd>] [--account-wide]
|
|
@@ -2473,9 +2599,13 @@ read = readSecret) {
|
|
|
2473
2599
|
if (sources.filter((s) => s !== undefined).length !== 1)
|
|
2474
2600
|
throw usage("Give exactly one of a folder with a Dockerfile (or -f <Dockerfile>), --from <image>, or --pip/--apt/--npm.");
|
|
2475
2601
|
const build = {
|
|
2476
|
-
...(integer(args, "max-image-mib")
|
|
2477
|
-
|
|
2478
|
-
|
|
2602
|
+
...(integer(args, "max-image-mib") !== undefined
|
|
2603
|
+
? { maxImageMiB: integer(args, "max-image-mib") }
|
|
2604
|
+
: {}),
|
|
2605
|
+
...(integer(args, "disk-mib") !== undefined ? { diskMiB: integer(args, "disk-mib") } : {}),
|
|
2606
|
+
...(integer(args, "timeout") !== undefined
|
|
2607
|
+
? { timeoutSeconds: integer(args, "timeout") }
|
|
2608
|
+
: {}),
|
|
2479
2609
|
};
|
|
2480
2610
|
// -t app, -t app:v2 -t app:latest: one name, its tags.
|
|
2481
2611
|
let name = flag(args, "name");
|
|
@@ -2492,9 +2622,11 @@ read = readSecret) {
|
|
|
2492
2622
|
}
|
|
2493
2623
|
const start = {
|
|
2494
2624
|
...(flag(args, "start") ? { command: flag(args, "start") } : {}),
|
|
2495
|
-
...(integer(args, "ready-port")
|
|
2625
|
+
...(integer(args, "ready-port") !== undefined
|
|
2626
|
+
? { readyPort: integer(args, "ready-port") }
|
|
2627
|
+
: {}),
|
|
2496
2628
|
...(flag(args, "ready-command") ? { readyCommand: flag(args, "ready-command") } : {}),
|
|
2497
|
-
...(integer(args, "ready-timeout")
|
|
2629
|
+
...(integer(args, "ready-timeout") !== undefined
|
|
2498
2630
|
? { readyTimeoutSeconds: integer(args, "ready-timeout") }
|
|
2499
2631
|
: {}),
|
|
2500
2632
|
};
|
|
@@ -2758,23 +2890,29 @@ async function jobCommand(argv, env, out) {
|
|
|
2758
2890
|
? { cron, ...(flag(args, "timezone") ? { timezone: flag(args, "timezone") } : {}) }
|
|
2759
2891
|
: { at: atTime(at) },
|
|
2760
2892
|
command: cwd ? { argv, cwd } : argv,
|
|
2761
|
-
...(integer(args, "timeout")
|
|
2893
|
+
...(integer(args, "timeout") !== undefined
|
|
2894
|
+
? { timeoutSeconds: integer(args, "timeout") }
|
|
2895
|
+
: {}),
|
|
2762
2896
|
compute: {
|
|
2763
|
-
...(integer(args, "vcpu") ? { vcpu: integer(args, "vcpu") } : {}),
|
|
2764
|
-
...(integer(args, "memory") ? { memoryMiB: integer(args, "memory") } : {}),
|
|
2765
|
-
...(integer(args, "disk") ? { diskMiB: integer(args, "disk") } : {}),
|
|
2897
|
+
...(integer(args, "vcpu") !== undefined ? { vcpu: integer(args, "vcpu") } : {}),
|
|
2898
|
+
...(integer(args, "memory") !== undefined ? { memoryMiB: integer(args, "memory") } : {}),
|
|
2899
|
+
...(integer(args, "disk") !== undefined ? { diskMiB: integer(args, "disk") } : {}),
|
|
2766
2900
|
...(flag(args, "cpu")
|
|
2767
2901
|
? { cpuMode: oneOf(args, "cpu", ["shared", "reserved"]) }
|
|
2768
2902
|
: {}),
|
|
2769
|
-
...(integer(args, "cpu-floor")
|
|
2903
|
+
...(integer(args, "cpu-floor") !== undefined
|
|
2904
|
+
? { cpuFloorMillis: integer(args, "cpu-floor") }
|
|
2905
|
+
: {}),
|
|
2770
2906
|
...(flag(args, "region") ? { region: flag(args, "region") } : {}),
|
|
2771
|
-
...(usdMicros(args, "max-cost")
|
|
2907
|
+
...(usdMicros(args, "max-cost") !== undefined
|
|
2908
|
+
? { maxCostMicros: usdMicros(args, "max-cost") }
|
|
2909
|
+
: {}),
|
|
2772
2910
|
},
|
|
2773
2911
|
...(attempts !== undefined
|
|
2774
2912
|
? { retry: { maxAttempts: attempts, backoffSeconds: backoff ?? 30 } }
|
|
2775
2913
|
: {}),
|
|
2776
2914
|
...(secrets ? { secrets } : {}),
|
|
2777
|
-
...(usdMicros(args, "max-total-cost")
|
|
2915
|
+
...(usdMicros(args, "max-total-cost") !== undefined
|
|
2778
2916
|
? { maxTotalCostMicros: usdMicros(args, "max-total-cost") }
|
|
2779
2917
|
: {}),
|
|
2780
2918
|
});
|
|
@@ -2903,9 +3041,13 @@ async function jobCommand(argv, env, out) {
|
|
|
2903
3041
|
}
|
|
2904
3042
|
const VOLUME_HELP = `runtime volume <command>
|
|
2905
3043
|
|
|
2906
|
-
create --size-mib <N> [--name n]
|
|
3044
|
+
create --size-mib <N> [--name n] [--shared] A persistent disk; prints its id
|
|
2907
3045
|
ls Your volumes
|
|
2908
3046
|
get <id> One volume, its backups and where it is attached
|
|
3047
|
+
resize <id> --size-mib <N> Grow a detached volume on its current host
|
|
3048
|
+
attach <id> <sandboxId> --path /data Mount a shared disk in a running sandbox
|
|
3049
|
+
detach <id> <attachmentId> Flush and unmount its shared attachment
|
|
3050
|
+
attachment <id> <attachmentId> Read the attachment's state and error
|
|
2909
3051
|
rm <id> Delete it and everything on it (its backups stay)
|
|
2910
3052
|
backup <id> [--retention-days N] [--no-wait]
|
|
2911
3053
|
Back it up now, off its host; prints the backup id
|
|
@@ -2916,6 +3058,7 @@ const VOLUME_HELP = `runtime volume <command>
|
|
|
2916
3058
|
Daily backups on or off, and how long each is kept
|
|
2917
3059
|
Attach one: runtime sandbox create --volume <id>:/data
|
|
2918
3060
|
Volumes are backed up off their host daily unless you turn it off.
|
|
3061
|
+
Shared commands work where shared disks are enabled.
|
|
2919
3062
|
`;
|
|
2920
3063
|
async function volume(argv, env, out) {
|
|
2921
3064
|
const [verb, ...rest] = argv;
|
|
@@ -2927,10 +3070,11 @@ async function volume(argv, env, out) {
|
|
|
2927
3070
|
const api = (await client(env)).volumes;
|
|
2928
3071
|
switch (verb) {
|
|
2929
3072
|
case "create": {
|
|
2930
|
-
const args = parse(rest, [], ["size-mib", "name"]);
|
|
3073
|
+
const args = parse(rest, ["shared"], ["size-mib", "name"]);
|
|
2931
3074
|
const created = await api.create({
|
|
2932
3075
|
sizeMiB: integer(args, "size-mib") ?? Number(need(undefined, "--size-mib <N>")),
|
|
2933
3076
|
...(flag(args, "name") ? { name: flag(args, "name") } : {}),
|
|
3077
|
+
...(has(args, "shared") ? { shared: true } : {}),
|
|
2934
3078
|
});
|
|
2935
3079
|
print(created.id, created);
|
|
2936
3080
|
return 0;
|
|
@@ -2958,6 +3102,40 @@ async function volume(argv, env, out) {
|
|
|
2958
3102
|
print(describeRecord(found, table), found);
|
|
2959
3103
|
return 0;
|
|
2960
3104
|
}
|
|
3105
|
+
case "resize": {
|
|
3106
|
+
const args = parse(rest, [], ["size-mib"]);
|
|
3107
|
+
const id = need(args.positional[0], "a volume id");
|
|
3108
|
+
if (args.positional.length !== 1 || args.rest)
|
|
3109
|
+
throw usage(`Use ${me} volume resize <id> --size-mib <N>.`);
|
|
3110
|
+
const sizeMiB = integer(args, "size-mib") ?? Number(need(undefined, "--size-mib <N>"));
|
|
3111
|
+
const resized = await api.resize(id, { sizeMiB });
|
|
3112
|
+
print(describeRecord(resized, table), resized);
|
|
3113
|
+
return resized.resize?.state === "failed" ? 1 : 0;
|
|
3114
|
+
}
|
|
3115
|
+
case "attach": {
|
|
3116
|
+
const args = parse(rest, [], ["path"]);
|
|
3117
|
+
const id = need(args.positional[0], "a volume id");
|
|
3118
|
+
const sandboxId = need(args.positional[1], "a sandbox id");
|
|
3119
|
+
const path = need(flag(args, "path"), "--path /data");
|
|
3120
|
+
if (args.positional.length !== 2 || args.rest)
|
|
3121
|
+
throw usage(`Use ${me} volume attach <id> <sandboxId> --path /data.`);
|
|
3122
|
+
const attachment = await api.attach(id, { sandboxId, path });
|
|
3123
|
+
print(describeRecord(attachment, table), attachment);
|
|
3124
|
+
return attachment.error ? 1 : 0;
|
|
3125
|
+
}
|
|
3126
|
+
case "detach":
|
|
3127
|
+
case "attachment": {
|
|
3128
|
+
const args = parse(rest, [], []);
|
|
3129
|
+
const id = need(args.positional[0], "a volume id");
|
|
3130
|
+
const attachmentId = need(args.positional[1], "an attachment id");
|
|
3131
|
+
if (args.positional.length !== 2 || args.rest)
|
|
3132
|
+
throw usage(`Use ${me} volume ${verb} <id> <attachmentId>.`);
|
|
3133
|
+
const attachment = verb === "detach"
|
|
3134
|
+
? await api.detach(id, attachmentId)
|
|
3135
|
+
: await api.getAttachment(id, attachmentId);
|
|
3136
|
+
print(describeRecord(attachment, table), attachment);
|
|
3137
|
+
return verb === "detach" && attachment.error ? 1 : 0;
|
|
3138
|
+
}
|
|
2961
3139
|
case "rm":
|
|
2962
3140
|
case "delete": {
|
|
2963
3141
|
const found = await api.delete(need(rest[0], "a volume id"));
|
|
@@ -3029,6 +3207,10 @@ async function volume(argv, env, out) {
|
|
|
3029
3207
|
"create",
|
|
3030
3208
|
"ls",
|
|
3031
3209
|
"get",
|
|
3210
|
+
"resize",
|
|
3211
|
+
"attach",
|
|
3212
|
+
"detach",
|
|
3213
|
+
"attachment",
|
|
3032
3214
|
"rm",
|
|
3033
3215
|
"backup",
|
|
3034
3216
|
"backups",
|
|
@@ -3089,7 +3271,13 @@ function usdMicros(args, name) {
|
|
|
3089
3271
|
return undefined;
|
|
3090
3272
|
if (!/^\d+(\.\d{1,6})?$/.test(value))
|
|
3091
3273
|
throw usage(`--${name} takes dollars, such as 25 or 2.50.`);
|
|
3092
|
-
|
|
3274
|
+
// ARCHITECTURE.md section 10: integer microdollars throughout, with no
|
|
3275
|
+
// floating-point money path or rounding drift.
|
|
3276
|
+
const [whole, fraction = ""] = value.split(".");
|
|
3277
|
+
const micros = BigInt(whole) * 1000000n + BigInt(fraction.padEnd(6, "0"));
|
|
3278
|
+
if (micros > BigInt(Number.MAX_SAFE_INTEGER))
|
|
3279
|
+
throw usage(`--${name} is too large to represent exactly in microdollars.`);
|
|
3280
|
+
return Number(micros);
|
|
3093
3281
|
}
|
|
3094
3282
|
/** A create body from the command line's flags. */
|
|
3095
3283
|
function createInput(args) {
|
|
@@ -3109,17 +3297,23 @@ function createInput(args) {
|
|
|
3109
3297
|
...(flag(args, "name") ? { name: flag(args, "name") } : {}),
|
|
3110
3298
|
...(args.flags.has("label") ? { labels: pairs(args, "label") } : {}),
|
|
3111
3299
|
...(args.flags.has("env") ? { env: pairs(args, "env") } : {}),
|
|
3112
|
-
...(integer(args, "vcpu") ? { vcpu: integer(args, "vcpu") } : {}),
|
|
3113
|
-
...(integer(args, "memory") ? { memoryMiB: integer(args, "memory") } : {}),
|
|
3114
|
-
...(integer(args, "disk") ? { diskMiB: integer(args, "disk") } : {}),
|
|
3300
|
+
...(integer(args, "vcpu") !== undefined ? { vcpu: integer(args, "vcpu") } : {}),
|
|
3301
|
+
...(integer(args, "memory") !== undefined ? { memoryMiB: integer(args, "memory") } : {}),
|
|
3302
|
+
...(integer(args, "disk") !== undefined ? { diskMiB: integer(args, "disk") } : {}),
|
|
3115
3303
|
...(flag(args, "cpu") ? { cpu: oneOf(args, "cpu", ["shared", "reserved"]) } : {}),
|
|
3116
|
-
...(integer(args, "cpu-floor")
|
|
3304
|
+
...(integer(args, "cpu-floor") !== undefined
|
|
3305
|
+
? { cpuFloorMillis: integer(args, "cpu-floor") }
|
|
3306
|
+
: {}),
|
|
3117
3307
|
...(flag(args, "region") ? { region: flag(args, "region") } : {}),
|
|
3118
|
-
...(usdMicros(args, "max-cost")
|
|
3119
|
-
|
|
3308
|
+
...(usdMicros(args, "max-cost") !== undefined
|
|
3309
|
+
? { maxCostMicros: usdMicros(args, "max-cost") }
|
|
3310
|
+
: {}),
|
|
3311
|
+
...(usdMicros(args, "max-total-cost") !== undefined
|
|
3120
3312
|
? { maxTotalCostMicros: usdMicros(args, "max-total-cost") }
|
|
3121
3313
|
: {}),
|
|
3122
|
-
...(integer(args, "timeout")
|
|
3314
|
+
...(integer(args, "timeout") !== undefined
|
|
3315
|
+
? { timeoutSeconds: integer(args, "timeout") }
|
|
3316
|
+
: {}),
|
|
3123
3317
|
...(flag(args, "on-timeout")
|
|
3124
3318
|
? { onLeaseEnd: oneOf(args, "on-timeout", ["pause", "stop"]) }
|
|
3125
3319
|
: {}),
|
|
@@ -3149,7 +3343,7 @@ ownInterrupt = true) {
|
|
|
3149
3343
|
...(stdin ? { stdin } : {}),
|
|
3150
3344
|
...(flag(args, "cwd") ? { cwd: flag(args, "cwd") } : {}),
|
|
3151
3345
|
...(args.flags.has("env") ? { env: pairs(args, "env") } : {}),
|
|
3152
|
-
...(timeout ? { timeoutMs: timeout * 1000 } : {}),
|
|
3346
|
+
...(timeout !== undefined ? { timeoutMs: timeout * 1000 } : {}),
|
|
3153
3347
|
};
|
|
3154
3348
|
// Ctrl-C stops the command in the sandbox too, and says so; without this
|
|
3155
3349
|
// the CLI exited and the command ran on unseen.
|
|
@@ -3222,6 +3416,10 @@ function defaultOut(argv) {
|
|
|
3222
3416
|
return {
|
|
3223
3417
|
json,
|
|
3224
3418
|
write: (text) => process.stdout.write(text.endsWith("\n") ? text : `${text}\n`),
|
|
3419
|
+
drain: async () => {
|
|
3420
|
+
if (process.stdout.writableNeedDrain)
|
|
3421
|
+
await once(process.stdout, "drain");
|
|
3422
|
+
},
|
|
3225
3423
|
error: (text) => process.stderr.write(`${text}\n`),
|
|
3226
3424
|
};
|
|
3227
3425
|
}
|
|
@@ -3279,6 +3477,10 @@ function spark(values) {
|
|
|
3279
3477
|
}
|
|
3280
3478
|
const mib = (bytes) => `${Math.round(bytes / 1_048_576).toLocaleString("en-US")} MiB`;
|
|
3281
3479
|
function metricsText(m) {
|
|
3480
|
+
const waiting = (value) => value == null ? "not measured" : `${value}%`;
|
|
3481
|
+
const waitingHistory = (field) => m.points.some((point) => point[field] != null)
|
|
3482
|
+
? spark(m.points.map((point) => point[field] ?? null))
|
|
3483
|
+
: "not measured";
|
|
3282
3484
|
const cpu = m.points.map((p) => p.cpuPercent);
|
|
3283
3485
|
const memory = m.points.map((p) => p.memoryBytes);
|
|
3284
3486
|
const peak = Math.max(0, ...m.points.map((p) => p.cpuPeakPercent ?? p.cpuPercent ?? 0));
|
|
@@ -3302,7 +3504,11 @@ function metricsText(m) {
|
|
|
3302
3504
|
"memory now",
|
|
3303
3505
|
still ?? (latest ? `${mib(latest.memoryBytes)} of ${mib(m.memoryLimitBytes)}` : "-"),
|
|
3304
3506
|
],
|
|
3507
|
+
["cpu wait now", still ?? waiting(latest?.cpuWaitPercent)],
|
|
3508
|
+
["memory stalls now", still ?? waiting(latest?.memoryStallPercent)],
|
|
3305
3509
|
[`cpu, ${m.range}`, m.points.length ? `${spark(cpu)} peak ${peak}%` : "no readings"],
|
|
3510
|
+
[`cpu wait, ${m.range}`, waitingHistory("cpuWaitPercent")],
|
|
3511
|
+
[`memory stalls, ${m.range}`, waitingHistory("memoryStallPercent")],
|
|
3306
3512
|
[
|
|
3307
3513
|
`memory, ${m.range}`,
|
|
3308
3514
|
m.points.length
|
|
@@ -3397,7 +3603,7 @@ async function webhooksCommand(argv, env, out) {
|
|
|
3397
3603
|
}
|
|
3398
3604
|
case "deliveries": {
|
|
3399
3605
|
const page = await hooks.deliveries(id(), {
|
|
3400
|
-
...(integer(args, "limit") ? { limit: integer(args, "limit") } : {}),
|
|
3606
|
+
...(integer(args, "limit") !== undefined ? { limit: integer(args, "limit") } : {}),
|
|
3401
3607
|
});
|
|
3402
3608
|
print(page.data.length
|
|
3403
3609
|
? table(page.data.map((d) => [
|