withruntime 0.9.0 → 0.10.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.
Files changed (90) hide show
  1. package/BLAXEL.md +4 -4
  2. package/DAYTONA.md +13 -7
  3. package/E2B.md +12 -8
  4. package/README.md +15 -7
  5. package/README.src.md +15 -7
  6. package/VERCEL.md +13 -3
  7. package/compatibility-lock.json +84 -8
  8. package/dist/blaxel/errors.js +1 -1
  9. package/dist/blaxel/lookup.d.ts +3 -2
  10. package/dist/blaxel/lookup.js +3 -2
  11. package/dist/blaxel/pagination.d.ts +2 -2
  12. package/dist/blaxel/pagination.js +45 -17
  13. package/dist/blaxel/preview.d.ts +2 -1
  14. package/dist/blaxel/preview.js +15 -13
  15. package/dist/blaxel/process.js +56 -10
  16. package/dist/blaxel/sandbox.d.ts +2 -0
  17. package/dist/blaxel/sandbox.js +2 -0
  18. package/dist/blaxel/snapshot.js +3 -5
  19. package/dist/cli.d.ts +2 -0
  20. package/dist/cli.js +189 -54
  21. package/dist/client.d.ts +29 -3
  22. package/dist/client.js +40 -11
  23. package/dist/cloudflare/identity.d.ts +7 -0
  24. package/dist/cloudflare/identity.js +13 -0
  25. package/dist/cloudflare/index.d.ts +9 -0
  26. package/dist/cloudflare/index.js +47 -9
  27. package/dist/cloudflare/worker.d.ts +4 -0
  28. package/dist/cloudflare/worker.js +166 -16
  29. package/dist/codesandbox/guest-service.js +57 -20
  30. package/dist/codesandbox/index.js +42 -8
  31. package/dist/compat/core.d.ts +9 -8
  32. package/dist/compat/core.js +60 -21
  33. package/dist/compat/files.js +22 -4
  34. package/dist/credentials.d.ts +6 -1
  35. package/dist/credentials.js +107 -38
  36. package/dist/daytona/daytona.js +11 -4
  37. package/dist/daytona/filesystem.d.ts +17 -4
  38. package/dist/daytona/filesystem.js +121 -38
  39. package/dist/daytona/index.d.ts +1 -1
  40. package/dist/daytona/sandbox.d.ts +1 -1
  41. package/dist/daytona/sandbox.js +16 -7
  42. package/dist/e2b/client.js +1 -1
  43. package/dist/e2b/commands.d.ts +30 -8
  44. package/dist/e2b/commands.js +142 -54
  45. package/dist/e2b/index.d.ts +1 -0
  46. package/dist/e2b/index.js +1 -0
  47. package/dist/e2b/pty.d.ts +24 -0
  48. package/dist/e2b/pty.js +80 -0
  49. package/dist/e2b/sandbox.d.ts +2 -1
  50. package/dist/e2b/sandbox.js +49 -15
  51. package/dist/file-source.d.ts +16 -0
  52. package/dist/file-source.js +162 -0
  53. package/dist/freestyle/index.d.ts +9 -1
  54. package/dist/freestyle/index.js +38 -5
  55. package/dist/index.d.ts +2 -1
  56. package/dist/login.js +16 -10
  57. package/dist/modal/index.js +2 -1
  58. package/dist/network-cli.js +111 -43
  59. package/dist/openai-agents/index.js +9 -45
  60. package/dist/page.d.ts +1 -1
  61. package/dist/page.js +2 -2
  62. package/dist/products/index.d.ts +12 -0
  63. package/dist/products/index.js +10 -0
  64. package/dist/products/observability.d.ts +44 -0
  65. package/dist/products/observability.js +6 -0
  66. package/dist/products/previews.d.ts +8 -2
  67. package/dist/products/previews.js +11 -3
  68. package/dist/products/volumes.d.ts +42 -0
  69. package/dist/products/volumes.js +36 -0
  70. package/dist/products/watch.js +61 -8
  71. package/dist/proxy.js +3 -1
  72. package/dist/runloop/index.d.ts +13 -27
  73. package/dist/runloop/index.js +149 -52
  74. package/dist/sandbox.d.ts +37 -8
  75. package/dist/sandbox.js +432 -68
  76. package/dist/snapshots.d.ts +9 -4
  77. package/dist/sprites/index.js +69 -33
  78. package/dist/tar.d.ts +3 -1
  79. package/dist/tar.js +140 -14
  80. package/dist/transport.d.ts +4 -7
  81. package/dist/transport.js +205 -122
  82. package/dist/vercel/command.d.ts +1 -1
  83. package/dist/vercel/command.js +3 -2
  84. package/dist/vercel/errors.d.ts +1 -1
  85. package/dist/vercel/errors.js +7 -1
  86. package/dist/vercel/sandbox.d.ts +8 -8
  87. package/dist/vercel/sandbox.js +154 -59
  88. package/dist/vercel/snapshot.d.ts +3 -2
  89. package/dist/vercel/snapshot.js +9 -5
  90. 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
@@ -180,8 +183,8 @@ const SANDBOX_HELP = `runtime sandbox <command>
180
183
 
181
184
  Examples
182
185
  id=$(runtime sandbox create)
183
- runtime sandbox exec $id -- python3 -c 'print(6*7)'
184
- runtime sandbox cp ./project $id:/workspace/project
186
+ runtime sandbox cp ./invoice.py $id:/workspace/invoice.py
187
+ runtime sandbox exec $id -- python3 /workspace/invoice.py
185
188
  runtime sandbox stop $id
186
189
  `;
187
190
  /** A command's options. Reading one its parse() did not declare is a bug in
@@ -229,9 +232,13 @@ commandAfter) {
229
232
  throw commandAfter && !meantOption(name, known)
230
233
  ? usage(`Unknown option ${arg.split("=")[0]}. If it belongs to the command, put the command after --: ${commandAfter}`)
231
234
  : unknownOption(name, known);
232
- const value = inline ?? (booleans.includes(name) ? "true" : argv[++i]);
233
- if (value === undefined)
235
+ const boolean = booleans.includes(name);
236
+ const value = inline ?? (boolean ? "true" : argv[++i]);
237
+ if (value === undefined ||
238
+ (!boolean && inline === undefined && (value === "--" || /^--?[^\d]/.test(value))))
234
239
  throw usage(`--${name} needs a value.`);
240
+ if (boolean && value !== "true" && value !== "false")
241
+ throw usage(`--${name} takes true or false.`);
235
242
  flags.set(name, [...(flags.get(name) ?? []), value]);
236
243
  }
237
244
  else
@@ -263,7 +270,10 @@ const integer = (args, name) => {
263
270
  return undefined;
264
271
  if (!/^\d+$/.test(value))
265
272
  throw usage(`--${name} must be a whole number.`);
266
- return Number(value);
273
+ const parsed = Number(value);
274
+ if (!Number.isSafeInteger(parsed))
275
+ throw usage(`--${name} is too large to represent exactly.`);
276
+ return parsed;
267
277
  };
268
278
  /** A flag that takes one of a few words, checked before anything is sent. */
269
279
  function oneOf(args, name, choices) {
@@ -575,8 +585,8 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
575
585
  ? JSON.stringify({ connected, usage: HELP })
576
586
  : `Runtime Cloud ${VERSION}. ${start}
577
587
 
578
- ${me} sandbox run -- python3 -c 'print(6*7)'
579
- A fresh sandbox runs one command, then stops
588
+ ${me} sandbox run -- python3 < invoice.py
589
+ Run a local invoice script, then stop the sandbox
580
590
  ${me} sandbox create A sandbox that stays; prints its id
581
591
  ${me} sandbox exec <id> -- <command> Run in it
582
592
  ${me} help Everything else
@@ -588,10 +598,10 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
588
598
  return 0;
589
599
  }
590
600
  if (command === "run") {
591
- const args = parse(rest, [...CREATE_FLAGS, "keep"], [...CREATE_VALUES, "cwd", "env"], `${me} sandbox run -- python3 -c 'print(6*7)'`);
601
+ const args = parse(rest, [...CREATE_FLAGS, "keep"], [...CREATE_VALUES, "cwd", "env"], `${me} sandbox run -- python3 < invoice.py`);
592
602
  const commandLine = args.rest ?? args.positional;
593
603
  if (!commandLine.length)
594
- throw usage(`Give the command after --: ${me} sandbox run -- python3 -c 'print(6*7)'`);
604
+ throw usage(`Give the command after --: ${me} sandbox run -- python3 < invoice.py`);
595
605
  const runtime = await client(env);
596
606
  const timeout = integer(args, "timeout");
597
607
  const { timeoutSeconds: _lease, ...input } = createInput(args);
@@ -688,6 +698,40 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
688
698
  return 0;
689
699
  }
690
700
  if (command === "usage") {
701
+ if (rest[0] === "export") {
702
+ const args = parse(rest.slice(1), [], ["since", "until"]);
703
+ if (args.positional.length || args.rest)
704
+ throw usage(`Use ${me} usage export --since <UTC time> --until <UTC time>.`);
705
+ const since = need(flag(args, "since"), "--since <UTC time>, such as 2026-09-01T00:00:00Z");
706
+ const until = need(flag(args, "until"), "--until <UTC time>, such as 2026-10-01T00:00:00Z");
707
+ const rt = await client(env);
708
+ const cursors = new Set();
709
+ let cursor;
710
+ for (;;) {
711
+ const page = await rt.usageExport({
712
+ since,
713
+ until,
714
+ ...(cursor ? { cursor } : {}),
715
+ limit: 500,
716
+ });
717
+ if (page.nextCursor !== null && cursors.has(page.nextCursor))
718
+ throw new RuntimeError({
719
+ code: "usage_export_incomplete",
720
+ status: 0,
721
+ message: "The usage export repeated a page cursor and could not finish.",
722
+ hint: "Run the export again; do not treat the partial output as complete.",
723
+ });
724
+ if (out.json)
725
+ out.write(JSON.stringify(page));
726
+ else if (page.csv)
727
+ out.write(page.csv);
728
+ await out.drain?.();
729
+ if (page.nextCursor === null)
730
+ return 0;
731
+ cursors.add(page.nextCursor);
732
+ cursor = page.nextCursor;
733
+ }
734
+ }
691
735
  const csv = rest.includes("--csv");
692
736
  const unknown = rest.find((arg) => arg !== "--csv");
693
737
  if (unknown)
@@ -738,7 +782,7 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
738
782
  const page = await rt.events.list({
739
783
  ...(sandbox ? { resourceId: await sandboxIdOf(rt, sandbox) } : {}),
740
784
  ...(flag(args, "type") ? { type: flag(args, "type") } : {}),
741
- ...(integer(args, "limit") ? { limit: integer(args, "limit") } : {}),
785
+ ...(integer(args, "limit") !== undefined ? { limit: integer(args, "limit") } : {}),
742
786
  });
743
787
  print(page.data.length
744
788
  ? table(page.data.map((e) => {
@@ -783,7 +827,7 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
783
827
  throw usage(`Unknown option --${name}.`);
784
828
  const page = await (await client(env)).audit.list({
785
829
  ...(flag(args, "action") ? { action: flag(args, "action") } : {}),
786
- ...(integer(args, "limit") ? { limit: integer(args, "limit") } : {}),
830
+ ...(integer(args, "limit") !== undefined ? { limit: integer(args, "limit") } : {}),
787
831
  ...(flag(args, "before") ? { before: flag(args, "before") } : {}),
788
832
  });
789
833
  print(page.events.length
@@ -809,17 +853,26 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
809
853
  const origins = connectionOrigins(env);
810
854
  const { claimWalletAccount, openWalletAccount } = await import("./wallet.js");
811
855
  return billingCommand(rest, parse(rest, ["wait", "new-account", "accept-terms"], []), out, () => client(env), undefined, {
812
- open: (input) => openWalletAccount(input, { authUrl: origins.auth }),
813
- claim: (code) => claimWalletAccount(code, { authUrl: origins.auth }),
814
- save: (key, orgId) => connectionStore(env).save({
815
- version: 1,
816
- apiOrigin: origins.api,
817
- authOrigin: origins.auth,
818
- key,
819
- connectionId: "pasted",
820
- orgId,
821
- agentName: "Wallet agent",
822
- }),
856
+ open: (input) => {
857
+ checkBrowserOrigins(origins);
858
+ return openWalletAccount(input, { authUrl: origins.auth });
859
+ },
860
+ claim: (code) => {
861
+ checkBrowserOrigins(origins);
862
+ return claimWalletAccount(code, { authUrl: origins.auth });
863
+ },
864
+ save: (key, orgId) => {
865
+ checkBrowserOrigins(origins);
866
+ return connectionStore(env).save({
867
+ version: 1,
868
+ apiOrigin: origins.api,
869
+ authOrigin: origins.auth,
870
+ key,
871
+ connectionId: "pasted",
872
+ orgId,
873
+ agentName: "Wallet agent",
874
+ });
875
+ },
823
876
  });
824
877
  }
825
878
  if (command === "referrals") {
@@ -1536,7 +1589,9 @@ async function sandbox(argv, env, out) {
1536
1589
  const sbx = await get(args.positional[0]);
1537
1590
  const snapshot = await sbx.snapshot({
1538
1591
  ...(flag(args, "name") ? { name: flag(args, "name") } : {}),
1539
- ...(integer(args, "retention") ? { retentionDays: integer(args, "retention") } : {}),
1592
+ ...(integer(args, "retention") !== undefined
1593
+ ? { retentionDays: integer(args, "retention") }
1594
+ : {}),
1540
1595
  });
1541
1596
  print(snapshot.id, snapshot);
1542
1597
  return 0;
@@ -1596,7 +1651,7 @@ async function sandbox(argv, env, out) {
1596
1651
  ...(cost === undefined
1597
1652
  ? {}
1598
1653
  : {
1599
- maxTotalCostMicros: cost === "none" ? null : Math.round(Number(cost) * 1e6),
1654
+ maxTotalCostMicros: cost === "none" ? null : usdMicros(args, "max-total-cost"),
1600
1655
  }),
1601
1656
  };
1602
1657
  if (!Object.keys(settings).length)
@@ -1709,7 +1764,7 @@ async function sandbox(argv, env, out) {
1709
1764
  const args = parse(rest, [], ["depth", "glob"]);
1710
1765
  const sbx = await use(args.positional[0]);
1711
1766
  const entries = await sbx.files.list(args.positional[1] ?? "/workspace", {
1712
- ...(integer(args, "depth") ? { depth: integer(args, "depth") } : {}),
1767
+ ...(integer(args, "depth") !== undefined ? { depth: integer(args, "depth") } : {}),
1713
1768
  ...(flag(args, "glob") ? { glob: flag(args, "glob") } : {}),
1714
1769
  });
1715
1770
  print(entries
@@ -1752,7 +1807,7 @@ async function sandbox(argv, env, out) {
1752
1807
  const sbx = await use(args.positional[0]);
1753
1808
  const preview = await sbx.previews.create(port, {
1754
1809
  ...(has(args, "public") ? { visibility: "public" } : {}),
1755
- ...(integer(args, "ttl") ? { ttlSeconds: integer(args, "ttl") } : {}),
1810
+ ...(integer(args, "ttl") !== undefined ? { ttlSeconds: integer(args, "ttl") } : {}),
1756
1811
  });
1757
1812
  // The link and how to use it, nothing more: the API's `hint` (which
1758
1813
  // carries a referral line) stays in --json, not in the command's words.
@@ -2084,7 +2139,7 @@ async function runCode(sbx, argv, out) {
2084
2139
  const execution = await sbx.interpreter.run(code, {
2085
2140
  language,
2086
2141
  ...(flag(args, "context") ? { context: flag(args, "context") } : {}),
2087
- ...(timeout ? { timeoutMs: timeout * 1000 } : {}),
2142
+ ...(timeout !== undefined ? { timeoutMs: timeout * 1000 } : {}),
2088
2143
  ...(out.json
2089
2144
  ? {}
2090
2145
  : {
@@ -2133,8 +2188,8 @@ async function desktop(sbx, argv, out) {
2133
2188
  case "start": {
2134
2189
  const args = parse(rest, [], ["width", "height"]);
2135
2190
  const started = await d.start({
2136
- ...(integer(args, "width") ? { width: integer(args, "width") } : {}),
2137
- ...(integer(args, "height") ? { height: integer(args, "height") } : {}),
2191
+ ...(integer(args, "width") !== undefined ? { width: integer(args, "width") } : {}),
2192
+ ...(integer(args, "height") !== undefined ? { height: integer(args, "height") } : {}),
2138
2193
  });
2139
2194
  print(`Desktop ${started.size} on ${started.display}. Watch it: ${started.streamUrl}`, started);
2140
2195
  return 0;
@@ -2473,9 +2528,13 @@ read = readSecret) {
2473
2528
  if (sources.filter((s) => s !== undefined).length !== 1)
2474
2529
  throw usage("Give exactly one of a folder with a Dockerfile (or -f <Dockerfile>), --from <image>, or --pip/--apt/--npm.");
2475
2530
  const build = {
2476
- ...(integer(args, "max-image-mib") ? { maxImageMiB: integer(args, "max-image-mib") } : {}),
2477
- ...(integer(args, "disk-mib") ? { diskMiB: integer(args, "disk-mib") } : {}),
2478
- ...(integer(args, "timeout") ? { timeoutSeconds: integer(args, "timeout") } : {}),
2531
+ ...(integer(args, "max-image-mib") !== undefined
2532
+ ? { maxImageMiB: integer(args, "max-image-mib") }
2533
+ : {}),
2534
+ ...(integer(args, "disk-mib") !== undefined ? { diskMiB: integer(args, "disk-mib") } : {}),
2535
+ ...(integer(args, "timeout") !== undefined
2536
+ ? { timeoutSeconds: integer(args, "timeout") }
2537
+ : {}),
2479
2538
  };
2480
2539
  // -t app, -t app:v2 -t app:latest: one name, its tags.
2481
2540
  let name = flag(args, "name");
@@ -2492,9 +2551,11 @@ read = readSecret) {
2492
2551
  }
2493
2552
  const start = {
2494
2553
  ...(flag(args, "start") ? { command: flag(args, "start") } : {}),
2495
- ...(integer(args, "ready-port") ? { readyPort: integer(args, "ready-port") } : {}),
2554
+ ...(integer(args, "ready-port") !== undefined
2555
+ ? { readyPort: integer(args, "ready-port") }
2556
+ : {}),
2496
2557
  ...(flag(args, "ready-command") ? { readyCommand: flag(args, "ready-command") } : {}),
2497
- ...(integer(args, "ready-timeout")
2558
+ ...(integer(args, "ready-timeout") !== undefined
2498
2559
  ? { readyTimeoutSeconds: integer(args, "ready-timeout") }
2499
2560
  : {}),
2500
2561
  };
@@ -2758,23 +2819,29 @@ async function jobCommand(argv, env, out) {
2758
2819
  ? { cron, ...(flag(args, "timezone") ? { timezone: flag(args, "timezone") } : {}) }
2759
2820
  : { at: atTime(at) },
2760
2821
  command: cwd ? { argv, cwd } : argv,
2761
- ...(integer(args, "timeout") ? { timeoutSeconds: integer(args, "timeout") } : {}),
2822
+ ...(integer(args, "timeout") !== undefined
2823
+ ? { timeoutSeconds: integer(args, "timeout") }
2824
+ : {}),
2762
2825
  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") } : {}),
2826
+ ...(integer(args, "vcpu") !== undefined ? { vcpu: integer(args, "vcpu") } : {}),
2827
+ ...(integer(args, "memory") !== undefined ? { memoryMiB: integer(args, "memory") } : {}),
2828
+ ...(integer(args, "disk") !== undefined ? { diskMiB: integer(args, "disk") } : {}),
2766
2829
  ...(flag(args, "cpu")
2767
2830
  ? { cpuMode: oneOf(args, "cpu", ["shared", "reserved"]) }
2768
2831
  : {}),
2769
- ...(integer(args, "cpu-floor") ? { cpuFloorMillis: integer(args, "cpu-floor") } : {}),
2832
+ ...(integer(args, "cpu-floor") !== undefined
2833
+ ? { cpuFloorMillis: integer(args, "cpu-floor") }
2834
+ : {}),
2770
2835
  ...(flag(args, "region") ? { region: flag(args, "region") } : {}),
2771
- ...(usdMicros(args, "max-cost") ? { maxCostMicros: usdMicros(args, "max-cost") } : {}),
2836
+ ...(usdMicros(args, "max-cost") !== undefined
2837
+ ? { maxCostMicros: usdMicros(args, "max-cost") }
2838
+ : {}),
2772
2839
  },
2773
2840
  ...(attempts !== undefined
2774
2841
  ? { retry: { maxAttempts: attempts, backoffSeconds: backoff ?? 30 } }
2775
2842
  : {}),
2776
2843
  ...(secrets ? { secrets } : {}),
2777
- ...(usdMicros(args, "max-total-cost")
2844
+ ...(usdMicros(args, "max-total-cost") !== undefined
2778
2845
  ? { maxTotalCostMicros: usdMicros(args, "max-total-cost") }
2779
2846
  : {}),
2780
2847
  });
@@ -2903,9 +2970,13 @@ async function jobCommand(argv, env, out) {
2903
2970
  }
2904
2971
  const VOLUME_HELP = `runtime volume <command>
2905
2972
 
2906
- create --size-mib <N> [--name n] A persistent ext4 disk; prints its id
2973
+ create --size-mib <N> [--name n] [--shared] A persistent disk; prints its id
2907
2974
  ls Your volumes
2908
2975
  get <id> One volume, its backups and where it is attached
2976
+ resize <id> --size-mib <N> Grow a detached volume on its current host
2977
+ attach <id> <sandboxId> --path /data Mount a shared disk in a running sandbox
2978
+ detach <id> <attachmentId> Flush and unmount its shared attachment
2979
+ attachment <id> <attachmentId> Read the attachment's state and error
2909
2980
  rm <id> Delete it and everything on it (its backups stay)
2910
2981
  backup <id> [--retention-days N] [--no-wait]
2911
2982
  Back it up now, off its host; prints the backup id
@@ -2916,6 +2987,7 @@ const VOLUME_HELP = `runtime volume <command>
2916
2987
  Daily backups on or off, and how long each is kept
2917
2988
  Attach one: runtime sandbox create --volume <id>:/data
2918
2989
  Volumes are backed up off their host daily unless you turn it off.
2990
+ Shared commands work where shared disks are enabled.
2919
2991
  `;
2920
2992
  async function volume(argv, env, out) {
2921
2993
  const [verb, ...rest] = argv;
@@ -2927,10 +2999,11 @@ async function volume(argv, env, out) {
2927
2999
  const api = (await client(env)).volumes;
2928
3000
  switch (verb) {
2929
3001
  case "create": {
2930
- const args = parse(rest, [], ["size-mib", "name"]);
3002
+ const args = parse(rest, ["shared"], ["size-mib", "name"]);
2931
3003
  const created = await api.create({
2932
3004
  sizeMiB: integer(args, "size-mib") ?? Number(need(undefined, "--size-mib <N>")),
2933
3005
  ...(flag(args, "name") ? { name: flag(args, "name") } : {}),
3006
+ ...(has(args, "shared") ? { shared: true } : {}),
2934
3007
  });
2935
3008
  print(created.id, created);
2936
3009
  return 0;
@@ -2958,6 +3031,40 @@ async function volume(argv, env, out) {
2958
3031
  print(describeRecord(found, table), found);
2959
3032
  return 0;
2960
3033
  }
3034
+ case "resize": {
3035
+ const args = parse(rest, [], ["size-mib"]);
3036
+ const id = need(args.positional[0], "a volume id");
3037
+ if (args.positional.length !== 1 || args.rest)
3038
+ throw usage(`Use ${me} volume resize <id> --size-mib <N>.`);
3039
+ const sizeMiB = integer(args, "size-mib") ?? Number(need(undefined, "--size-mib <N>"));
3040
+ const resized = await api.resize(id, { sizeMiB });
3041
+ print(describeRecord(resized, table), resized);
3042
+ return resized.resize?.state === "failed" ? 1 : 0;
3043
+ }
3044
+ case "attach": {
3045
+ const args = parse(rest, [], ["path"]);
3046
+ const id = need(args.positional[0], "a volume id");
3047
+ const sandboxId = need(args.positional[1], "a sandbox id");
3048
+ const path = need(flag(args, "path"), "--path /data");
3049
+ if (args.positional.length !== 2 || args.rest)
3050
+ throw usage(`Use ${me} volume attach <id> <sandboxId> --path /data.`);
3051
+ const attachment = await api.attach(id, { sandboxId, path });
3052
+ print(describeRecord(attachment, table), attachment);
3053
+ return attachment.error ? 1 : 0;
3054
+ }
3055
+ case "detach":
3056
+ case "attachment": {
3057
+ const args = parse(rest, [], []);
3058
+ const id = need(args.positional[0], "a volume id");
3059
+ const attachmentId = need(args.positional[1], "an attachment id");
3060
+ if (args.positional.length !== 2 || args.rest)
3061
+ throw usage(`Use ${me} volume ${verb} <id> <attachmentId>.`);
3062
+ const attachment = verb === "detach"
3063
+ ? await api.detach(id, attachmentId)
3064
+ : await api.getAttachment(id, attachmentId);
3065
+ print(describeRecord(attachment, table), attachment);
3066
+ return verb === "detach" && attachment.error ? 1 : 0;
3067
+ }
2961
3068
  case "rm":
2962
3069
  case "delete": {
2963
3070
  const found = await api.delete(need(rest[0], "a volume id"));
@@ -3029,6 +3136,10 @@ async function volume(argv, env, out) {
3029
3136
  "create",
3030
3137
  "ls",
3031
3138
  "get",
3139
+ "resize",
3140
+ "attach",
3141
+ "detach",
3142
+ "attachment",
3032
3143
  "rm",
3033
3144
  "backup",
3034
3145
  "backups",
@@ -3089,7 +3200,13 @@ function usdMicros(args, name) {
3089
3200
  return undefined;
3090
3201
  if (!/^\d+(\.\d{1,6})?$/.test(value))
3091
3202
  throw usage(`--${name} takes dollars, such as 25 or 2.50.`);
3092
- return Math.round(Number(value) * 1e6);
3203
+ // ARCHITECTURE.md section 10: integer microdollars throughout, with no
3204
+ // floating-point money path or rounding drift.
3205
+ const [whole, fraction = ""] = value.split(".");
3206
+ const micros = BigInt(whole) * 1000000n + BigInt(fraction.padEnd(6, "0"));
3207
+ if (micros > BigInt(Number.MAX_SAFE_INTEGER))
3208
+ throw usage(`--${name} is too large to represent exactly in microdollars.`);
3209
+ return Number(micros);
3093
3210
  }
3094
3211
  /** A create body from the command line's flags. */
3095
3212
  function createInput(args) {
@@ -3109,17 +3226,23 @@ function createInput(args) {
3109
3226
  ...(flag(args, "name") ? { name: flag(args, "name") } : {}),
3110
3227
  ...(args.flags.has("label") ? { labels: pairs(args, "label") } : {}),
3111
3228
  ...(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") } : {}),
3229
+ ...(integer(args, "vcpu") !== undefined ? { vcpu: integer(args, "vcpu") } : {}),
3230
+ ...(integer(args, "memory") !== undefined ? { memoryMiB: integer(args, "memory") } : {}),
3231
+ ...(integer(args, "disk") !== undefined ? { diskMiB: integer(args, "disk") } : {}),
3115
3232
  ...(flag(args, "cpu") ? { cpu: oneOf(args, "cpu", ["shared", "reserved"]) } : {}),
3116
- ...(integer(args, "cpu-floor") ? { cpuFloorMillis: integer(args, "cpu-floor") } : {}),
3233
+ ...(integer(args, "cpu-floor") !== undefined
3234
+ ? { cpuFloorMillis: integer(args, "cpu-floor") }
3235
+ : {}),
3117
3236
  ...(flag(args, "region") ? { region: flag(args, "region") } : {}),
3118
- ...(usdMicros(args, "max-cost") ? { maxCostMicros: usdMicros(args, "max-cost") } : {}),
3119
- ...(usdMicros(args, "max-total-cost")
3237
+ ...(usdMicros(args, "max-cost") !== undefined
3238
+ ? { maxCostMicros: usdMicros(args, "max-cost") }
3239
+ : {}),
3240
+ ...(usdMicros(args, "max-total-cost") !== undefined
3120
3241
  ? { maxTotalCostMicros: usdMicros(args, "max-total-cost") }
3121
3242
  : {}),
3122
- ...(integer(args, "timeout") ? { timeoutSeconds: integer(args, "timeout") } : {}),
3243
+ ...(integer(args, "timeout") !== undefined
3244
+ ? { timeoutSeconds: integer(args, "timeout") }
3245
+ : {}),
3123
3246
  ...(flag(args, "on-timeout")
3124
3247
  ? { onLeaseEnd: oneOf(args, "on-timeout", ["pause", "stop"]) }
3125
3248
  : {}),
@@ -3149,7 +3272,7 @@ ownInterrupt = true) {
3149
3272
  ...(stdin ? { stdin } : {}),
3150
3273
  ...(flag(args, "cwd") ? { cwd: flag(args, "cwd") } : {}),
3151
3274
  ...(args.flags.has("env") ? { env: pairs(args, "env") } : {}),
3152
- ...(timeout ? { timeoutMs: timeout * 1000 } : {}),
3275
+ ...(timeout !== undefined ? { timeoutMs: timeout * 1000 } : {}),
3153
3276
  };
3154
3277
  // Ctrl-C stops the command in the sandbox too, and says so; without this
3155
3278
  // the CLI exited and the command ran on unseen.
@@ -3222,6 +3345,10 @@ function defaultOut(argv) {
3222
3345
  return {
3223
3346
  json,
3224
3347
  write: (text) => process.stdout.write(text.endsWith("\n") ? text : `${text}\n`),
3348
+ drain: async () => {
3349
+ if (process.stdout.writableNeedDrain)
3350
+ await once(process.stdout, "drain");
3351
+ },
3225
3352
  error: (text) => process.stderr.write(`${text}\n`),
3226
3353
  };
3227
3354
  }
@@ -3279,6 +3406,10 @@ function spark(values) {
3279
3406
  }
3280
3407
  const mib = (bytes) => `${Math.round(bytes / 1_048_576).toLocaleString("en-US")} MiB`;
3281
3408
  function metricsText(m) {
3409
+ const waiting = (value) => value == null ? "not measured" : `${value}%`;
3410
+ const waitingHistory = (field) => m.points.some((point) => point[field] != null)
3411
+ ? spark(m.points.map((point) => point[field] ?? null))
3412
+ : "not measured";
3282
3413
  const cpu = m.points.map((p) => p.cpuPercent);
3283
3414
  const memory = m.points.map((p) => p.memoryBytes);
3284
3415
  const peak = Math.max(0, ...m.points.map((p) => p.cpuPeakPercent ?? p.cpuPercent ?? 0));
@@ -3302,7 +3433,11 @@ function metricsText(m) {
3302
3433
  "memory now",
3303
3434
  still ?? (latest ? `${mib(latest.memoryBytes)} of ${mib(m.memoryLimitBytes)}` : "-"),
3304
3435
  ],
3436
+ ["cpu wait now", still ?? waiting(latest?.cpuWaitPercent)],
3437
+ ["memory stalls now", still ?? waiting(latest?.memoryStallPercent)],
3305
3438
  [`cpu, ${m.range}`, m.points.length ? `${spark(cpu)} peak ${peak}%` : "no readings"],
3439
+ [`cpu wait, ${m.range}`, waitingHistory("cpuWaitPercent")],
3440
+ [`memory stalls, ${m.range}`, waitingHistory("memoryStallPercent")],
3306
3441
  [
3307
3442
  `memory, ${m.range}`,
3308
3443
  m.points.length
@@ -3397,7 +3532,7 @@ async function webhooksCommand(argv, env, out) {
3397
3532
  }
3398
3533
  case "deliveries": {
3399
3534
  const page = await hooks.deliveries(id(), {
3400
- ...(integer(args, "limit") ? { limit: integer(args, "limit") } : {}),
3535
+ ...(integer(args, "limit") !== undefined ? { limit: integer(args, "limit") } : {}),
3401
3536
  });
3402
3537
  print(page.data.length
3403
3538
  ? table(page.data.map((d) => [
package/dist/client.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { RuntimeError } from "./errors.js";
2
2
  import type { Page } from "./page.js";
3
3
  import { type ClientExtensions } from "./products/index.js";
4
+ import { usageExport as makeUsageExport } from "./products/observability.js";
4
5
  import { Sandbox } from "./sandbox.js";
5
6
  import { Snapshots } from "./snapshots.js";
6
7
  import { Transport, type RequestOptions } from "./transport.js";
@@ -11,7 +12,7 @@ export type RuntimeOptions = {
11
12
  /** Default: RUNTIME_API_URL, then https://api.withruntime.com. */
12
13
  baseUrl?: string;
13
14
  fetch?: typeof fetch;
14
- /** Per call, retries included. Default 5 minutes. */
15
+ /** Per call, queue and retries included. Default 5 minutes; 0 disables it. */
15
16
  timeoutMs?: number;
16
17
  /** Retries of transport failures, 429, 502, 503 and 504. Default 4. */
17
18
  maxRetries?: number;
@@ -30,11 +31,16 @@ export interface Runtime extends ClientExtensions {
30
31
  }
31
32
  export declare class Runtime {
32
33
  readonly transport: Transport;
34
+ /** The sandbox product; `sandboxes` remains the same client for compatibility. */
35
+ readonly sandbox: Sandboxes;
33
36
  readonly sandboxes: Sandboxes;
37
+ readonly snapshot: Snapshots;
34
38
  readonly snapshots: Snapshots;
35
39
  readonly feedback: FeedbackApi;
36
40
  readonly support: SupportApi;
37
41
  readonly account: AccountApi;
42
+ /** Date-range settlements, paged with exact charges and the server's CSV. */
43
+ readonly usageExport: ReturnType<typeof makeUsageExport>;
38
44
  constructor(options?: RuntimeOptions);
39
45
  /** Who this key is: organization, agent and credential. */
40
46
  me(options?: RequestOptions): Promise<{
@@ -50,6 +56,25 @@ export declare class Runtime {
50
56
  /** Balance, holds, trial time and per-resource charges, in integer microdollars. */
51
57
  /** Credit, holds, trial time and per-resource charges. */
52
58
  usage(options?: RequestOptions): Promise<Usage>;
59
+ /** This account's authenticated API answers, in hourly windows. Counts are
60
+ * exact decimal strings, and an empty window has a null error percentage.
61
+ * A key needs usage permission. */
62
+ usageRequests(range?: "24h" | "7d" | "30d" | "90d", options?: RequestOptions): Promise<{
63
+ range: "24h" | "7d" | "30d" | "90d";
64
+ since: string;
65
+ until: string;
66
+ calls: string;
67
+ clientErrors: string;
68
+ serverErrors: string;
69
+ errorPercent: number | null;
70
+ operations: Array<{
71
+ operation: string;
72
+ calls: string;
73
+ clientErrors: string;
74
+ serverErrors: string;
75
+ errorPercent: number | null;
76
+ }>;
77
+ }>;
53
78
  }
54
79
  /** How `sandboxes.create` waits when every trial slot, the account's quota or
55
80
  * the region is full. */
@@ -83,7 +108,8 @@ export declare class Sandboxes {
83
108
  /** Deletes a sandbox for good, by id, without reading it first: see
84
109
  * `sandbox.delete()`. Deleting it again answers the same. */
85
110
  delete(id: string, options?: RequestOptions): Promise<DeletedSandbox>;
86
- /** Live sandboxes, oldest first. Await for a page, or `for await` over all. */
111
+ /** Live sandboxes, oldest first. Await for a page, or `for await` over all.
112
+ * Request options apply to the initial request and every subsequent page. */
87
113
  list(filter?: {
88
114
  state?: SandboxInfo["state"][];
89
115
  includeStopped?: boolean;
@@ -91,7 +117,7 @@ export declare class Sandboxes {
91
117
  labels?: Record<string, string>;
92
118
  name?: string;
93
119
  limit?: number;
94
- }): Promise<Page<Sandbox>>;
120
+ }, options?: RequestOptions): Promise<Page<Sandbox>>;
95
121
  }
96
122
  export declare class FeedbackApi {
97
123
  private readonly t;