withruntime 0.12.0 → 0.14.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 (50) hide show
  1. package/README.md +3 -3
  2. package/README.src.md +2 -2
  3. package/dist/billing-cli.js +8 -4
  4. package/dist/blaxel/sandbox.d.ts +1 -1
  5. package/dist/blaxel/sandbox.js +1 -1
  6. package/dist/cli.d.ts +17 -2
  7. package/dist/cli.js +242 -69
  8. package/dist/credentials.d.ts +3 -0
  9. package/dist/credentials.js +15 -0
  10. package/dist/daytona/daytona.d.ts +1 -1
  11. package/dist/daytona/daytona.js +1 -1
  12. package/dist/e2b/sandbox.d.ts +1 -1
  13. package/dist/errors.d.ts +1 -1
  14. package/dist/errors.js +1 -1
  15. package/dist/index.d.ts +2 -1
  16. package/dist/machine.d.ts +15 -1
  17. package/dist/machine.js +26 -11
  18. package/dist/mcp.js +144 -20
  19. package/dist/products/api-keys.d.ts +73 -0
  20. package/dist/products/api-keys.js +23 -0
  21. package/dist/products/apps.d.ts +12 -31
  22. package/dist/products/apps.js +8 -113
  23. package/dist/products/balancers.d.ts +87 -0
  24. package/dist/products/balancers.js +8 -0
  25. package/dist/products/builds.d.ts +30 -0
  26. package/dist/products/builds.js +6 -0
  27. package/dist/products/index.d.ts +2 -0
  28. package/dist/products/index.js +2 -0
  29. package/dist/products/jobs.d.ts +2 -2
  30. package/dist/products/jobs.js +1 -1
  31. package/dist/products/observability.d.ts +7 -2
  32. package/dist/products/resources.d.ts +54 -0
  33. package/dist/products/resources.js +122 -0
  34. package/dist/products/secrets.d.ts +22 -0
  35. package/dist/products/secrets.js +8 -0
  36. package/dist/products/volumes.d.ts +3 -0
  37. package/dist/sandbox.d.ts +4 -0
  38. package/dist/sandbox.js +27 -2
  39. package/dist/transport.d.ts +4 -1
  40. package/dist/transport.js +25 -4
  41. package/dist/types.d.ts +9 -5
  42. package/dist/vercel/sandbox.d.ts +1 -1
  43. package/dist/vercel/sandbox.js +1 -1
  44. package/dist/wallet.d.ts +4 -1
  45. package/dist/wallet.js +2 -1
  46. package/package.json +2 -14
  47. package/dist/cli/logs.test.d.ts +0 -1
  48. package/dist/cli/logs.test.js +0 -272
  49. package/dist/products/logs.test.d.ts +0 -1
  50. package/dist/products/logs.test.js +0 -343
package/dist/cli.js CHANGED
@@ -30,6 +30,8 @@ Account
30
30
  login [--agent-name <name>] [--no-browser] [--wait]
31
31
  Connect this machine (no key to copy)
32
32
  login --with-key Or paste a key from withruntime.com/account/keys
33
+ billing topup <usd> --new-account --accept-terms [--wait]
34
+ No person? Open an account paid in USDC
33
35
  logout Revoke this machine's connection
34
36
  whoami The organization and agent you act as
35
37
  account [ls] The accounts this machine is connected to
@@ -40,16 +42,22 @@ Account
40
42
  The account's audit log (owner or admin keys)
41
43
  sso Single sign-on and SCIM directory sync (owner or
42
44
  admin keys); an owner changes it on the website
43
- usage [--csv] Balance, included usage and charges; --csv, each resource
45
+ usage [--csv] Balance, free usage and charges; --csv, each resource
44
46
  usage export --since <UTC time> --until <UTC time>
45
47
  Every settlement in the range as CSV; --json, each page
46
48
  limits Whether this key is read-only, and its daily spending limit
47
49
  secrets set <NAME> --host <host>... [--header <name> [--format '<text {value}>']]
48
50
  [--allow '[GET,HEAD] /path/*']... Store a secret sandboxes use without seeing it; the
49
- value is read from standard input, never an argument.
50
- --allow (paid): only these methods and paths get it
51
+ [--only-label key=value[,k=v]]... value is read from standard input, never an argument.
52
+ [--only-sandbox <id>]... --allow (paid): only these methods and paths get it.
53
+ [--all-sandboxes] --only-label: only sandboxes with those labels (each
54
+ flag one selector, any may match); --only-sandbox:
55
+ only that sandbox. A new value keeps the scope;
56
+ --all-sandboxes gives it to every sandbox again
51
57
  secrets set <NAME> --jobs [--host ...] A copy scheduled jobs put in their environment
52
58
  (job create --secret NAME); with --host, both
59
+ secrets scope <NAME> --only-label k=v... | --only-sandbox <id>... | --all-sandboxes
60
+ Change which sandboxes get it; no value needed
53
61
  secrets ls | get <NAME> Names, hosts, placeholders and jobs versions, never values
54
62
  secrets rotate <NAME> A new version of the jobs copy, from standard input
55
63
  secrets reveal <NAME> [--version N] Print the jobs copy's value. The sandboxes' copy is
@@ -143,6 +151,8 @@ const SANDBOX_HELP = `runtime sandboxes <command>
143
151
  stop --label k=v [--label k=v]... Stop every live sandbox with all those labels
144
152
  rm <id> Delete it for good: stops it, deletes its disk and
145
153
  paused memory, revokes its previews; snapshots stay
154
+ rm --label k=v [--label k=v]... Delete for good every sandbox with all those labels,
155
+ stopped ones too
146
156
  switch-image <id> <image> --keep-workspace Move it to another image, keeping /workspace (your home),
147
157
  volumes, env and previews; processes restart, the rest of
148
158
  its disk is lost (snapshot it first to keep everything)
@@ -242,7 +252,14 @@ commandAfter) {
242
252
  break;
243
253
  }
244
254
  if (arg.startsWith("--") || (arg.startsWith("-") && arg.length === 2 && !/^-\d/.test(arg))) {
245
- const [name, inline] = arg.replace(/^-+/, "").split(/=(.*)/s, 2);
255
+ const [typed, inline] = arg.replace(/^-+/, "").split(/=(.*)/s, 2);
256
+ // A plural of an option is the option once per comma: --hosts a,b is
257
+ // --host a --host b (13 October 2026: Marc's first try at a secret).
258
+ const plural = !known.has(typed) &&
259
+ typed.endsWith("s") &&
260
+ LISTS.has(typed.slice(0, -1)) &&
261
+ known.has(typed.slice(0, -1));
262
+ const name = plural ? typed.slice(0, -1) : typed;
246
263
  if (!known.has(name))
247
264
  throw commandAfter && !meantOption(name, known)
248
265
  ? usage(`Unknown option ${arg.split("=")[0]}. If it belongs to the command, put the command after --: ${commandAfter}`)
@@ -254,13 +271,16 @@ commandAfter) {
254
271
  throw usage(`--${name} needs a value.`);
255
272
  if (boolean && value !== "true" && value !== "false")
256
273
  throw usage(`--${name} takes true or false.`);
257
- flags.set(name, [...(flags.get(name) ?? []), value]);
274
+ const values = plural ? value.split(",").filter(Boolean) : [value];
275
+ flags.set(name, [...(flags.get(name) ?? []), ...values]);
258
276
  }
259
277
  else
260
278
  positional.push(arg);
261
279
  }
262
280
  return { positional, flags, rest };
263
281
  }
282
+ /** Options given once per item, which a plural takes as a comma list. */
283
+ const LISTS = new Set(["host", "allow", "deny", "connect"]);
264
284
  /** The declared option a mistyped one most likely meant. --memory-mib for
265
285
  * --memory, --timeout-seconds for --timeout: the SDK's spelling of an option is
266
286
  * the CLI's with its unit added. */
@@ -706,11 +726,11 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
706
726
  ...(spend.pilot ? [pilotFunding(spend.pilot)] : []),
707
727
  ...(trialLeft > 0
708
728
  ? [
709
- `included usage, ${trialLeft.toLocaleString("en-US", { maximumFractionDigits: 1 })} hours left without credit`,
729
+ `free usage, ${trialLeft.toLocaleString("en-US", { maximumFractionDigits: 1 })} hours left without credit`,
710
730
  ]
711
731
  : []),
712
732
  ...(credit > 0n ? [`${dollars(credit)} of credit`] : []),
713
- ].join("; ") || "no included usage or credit left: runtime billing";
733
+ ].join("; ") || "no free usage or credit left: runtime billing";
714
734
  print(table([
715
735
  ["organization", who.orgName ? `${who.orgName} (${who.orgId})` : who.orgId],
716
736
  ...(who.role ? [["role", who.role]] : []),
@@ -1049,7 +1069,7 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
1049
1069
  }
1050
1070
  throw unknown("command", command, COMMANDS);
1051
1071
  }
1052
- /** `runtime usage` for a person: the balance, the included usage and what each kind
1072
+ /** `runtime usage` for a person: the balance, the free usage and what each kind
1053
1073
  * of resource was charged, in dollars. --json keeps every figure, and each
1054
1074
  * resource with its rates and CPU time. */
1055
1075
  /** One row per resource, for a spreadsheet: when it ran, how big it was, how
@@ -1104,15 +1124,15 @@ export function usageCsv(u) {
1104
1124
  /** Milliseconds as hours, to a tenth: "8,731.2". */
1105
1125
  const hours = (ms) => (ms / 3_600_000).toLocaleString("en-US", { maximumFractionDigits: 1 });
1106
1126
  /** The machine time left without credit, as `runtime usage` and `runtime
1107
- * limits` say it: this month's included usage, as hours at 2 vCPU and 4 GiB,
1127
+ * limits` say it: this month's free usage, as hours at 2 vCPU and 4 GiB,
1108
1128
  * or for an account given free hours before 5 October 2026, those hours too. */
1109
1129
  function trialLeft(trial) {
1110
1130
  const renews = trial.renewsAt ? `, renews ${trial.renewsAt.slice(0, 10)}` : "";
1111
1131
  return trial.totalMs > 0
1112
- ? `${hours(trial.availableMs)} hours left (${hours(trial.totalMs)} free hours given before 5 October 2026, and the included usage${renews})`
1132
+ ? `${hours(trial.availableMs)} hours left (${hours(trial.totalMs)} free hours given before 5 October 2026, and the free usage${renews})`
1113
1133
  : `${hours(trial.availableMs)} hours at 2 vCPU and 4 GiB left this month${renews}`;
1114
1134
  }
1115
- /** This month's included usage, one row per product, as `runtime usage`
1135
+ /** This month's free usage, one row per product, as `runtime usage`
1116
1136
  * says it. */
1117
1137
  function allowanceRows(allowances) {
1118
1138
  const amount = (n) => n.toLocaleString("en-US", { maximumFractionDigits: 1 });
@@ -1598,6 +1618,28 @@ async function sandbox(argv, env, out) {
1598
1618
  const use = async (id) => SANDBOX_UUID.test(id ?? "")
1599
1619
  ? new SandboxHandle(runtime.transport, { id })
1600
1620
  : get(id);
1621
+ /* `stop --label` and `rm --label`: every sandbox with all the labels, each
1622
+ named with what happened to it; exits 1 if any failed. */
1623
+ const byLabel = (words) => words.some((word) => /^--label(=|$)/.test(word));
1624
+ const everyLabelled = async (act, words) => {
1625
+ const args = parse(words, [], ["label"]);
1626
+ if (args.positional.length)
1627
+ throw usage("Give a sandbox id or --label, not both.");
1628
+ const labels = pairs(args, "label");
1629
+ const { done, failed } = act === "stop"
1630
+ ? await runtime.sandboxes.stopAll({ labels }).then((r) => ({ ...r, done: r.stopped }))
1631
+ : await runtime.sandboxes.deleteAll({ labels }).then((r) => ({ ...r, done: r.deleted }));
1632
+ const past = act === "stop" ? "stopped" : "deleted";
1633
+ const said = (error) => (error instanceof Error ? error.message : String(error));
1634
+ print([
1635
+ done.length || failed.length
1636
+ ? `${done.length} ${past}${failed.length ? `, ${failed.length} failed` : ""}.`
1637
+ : `No ${act === "stop" ? "live " : ""}sandbox has those labels.`,
1638
+ ...done.map((id) => `${id} ${past}`),
1639
+ ...failed.map((f) => `${f.id} failed: ${said(f.error)}`),
1640
+ ].join("\n"), { [past]: done, failed: failed.map((f) => ({ id: f.id, error: said(f.error) })) });
1641
+ return failed.length ? 1 : 0;
1642
+ };
1601
1643
  switch (verb) {
1602
1644
  case "create": {
1603
1645
  const args = parse(rest, CREATE_FLAGS, CREATE_VALUES);
@@ -1681,6 +1723,8 @@ async function sandbox(argv, env, out) {
1681
1723
  }
1682
1724
  case "rm":
1683
1725
  case "delete": {
1726
+ if (byLabel(rest))
1727
+ return everyLabelled("delete", rest);
1684
1728
  const sbx = await use(need(rest[0], `a sandbox, e.g. ${me} sandboxes rm <id>`));
1685
1729
  const gone = await sbx.delete();
1686
1730
  print(`${gone.id} deleted`, gone);
@@ -1690,23 +1734,8 @@ async function sandbox(argv, env, out) {
1690
1734
  case "pause":
1691
1735
  case "wake":
1692
1736
  case "restart": {
1693
- if (verb === "stop" && rest.some((word) => /^--label(=|$)/.test(word))) {
1694
- const args = parse(rest, [], ["label"]);
1695
- if (args.positional.length)
1696
- throw usage("Give a sandbox id or --label, not both.");
1697
- const { stopped, failed } = await runtime.sandboxes.stopAll({
1698
- labels: pairs(args, "label"),
1699
- });
1700
- const said = (error) => (error instanceof Error ? error.message : String(error));
1701
- print([
1702
- stopped.length || failed.length
1703
- ? `${stopped.length} stopped${failed.length ? `, ${failed.length} failed` : ""}.`
1704
- : "No live sandbox has those labels.",
1705
- ...stopped.map((id) => `${id} stopped`),
1706
- ...failed.map((f) => `${f.id} failed: ${said(f.error)}`),
1707
- ].join("\n"), { stopped, failed: failed.map((f) => ({ id: f.id, error: said(f.error) })) });
1708
- return failed.length ? 1 : 0;
1709
- }
1737
+ if (verb === "stop" && byLabel(rest))
1738
+ return everyLabelled("stop", rest);
1710
1739
  const sbx = await get(rest[0]);
1711
1740
  await sbx[verb]();
1712
1741
  print(`${sbx.id} ${sbx.state}`, sbx.info);
@@ -2160,15 +2189,24 @@ function attachment(spec) {
2160
2189
  /** A secret's value, from standard input only, so it never sits in shell
2161
2190
  * history or a process list. Piped: everything, less one trailing newline.
2162
2191
  * A terminal: one line, not shown. */
2192
+ /** A pasted secret as the header it becomes: no line breaks (a token copied
2193
+ * from a terminal arrives wrapped), no bracketed-paste markers, no space at
2194
+ * either end. 13 October 2026: Marc's pasted Claude token was refused as
2195
+ * "not visible ASCII". A header cannot hold a line break, so none is lost. */
2196
+ export function cleanSecret(raw) {
2197
+ return raw
2198
+ .replaceAll("\u001b[200~", "")
2199
+ .replaceAll("\u001b[201~", "")
2200
+ .replace(/[\r\n]/g, "")
2201
+ .trim();
2202
+ }
2163
2203
  async function readSecret() {
2164
2204
  const stdin = process.stdin;
2165
2205
  if (!stdin.isTTY) {
2166
2206
  const chunks = [];
2167
2207
  for await (const chunk of stdin)
2168
2208
  chunks.push(chunk);
2169
- return Buffer.concat(chunks)
2170
- .toString("utf8")
2171
- .replace(/\r?\n$/, "");
2209
+ return cleanSecret(Buffer.concat(chunks).toString("utf8"));
2172
2210
  }
2173
2211
  process.stderr.write("Value (not shown), then Enter: ");
2174
2212
  stdin.setRawMode(true);
@@ -2181,7 +2219,7 @@ async function readSecret() {
2181
2219
  if (char === "\r" || char === "\n") {
2182
2220
  stdin.off("data", onData);
2183
2221
  process.stderr.write("\n");
2184
- return done(value);
2222
+ return done(cleanSecret(value));
2185
2223
  }
2186
2224
  if (char === "\u0003") {
2187
2225
  stdin.off("data", onData);
@@ -2224,6 +2262,48 @@ export function parseSecretRules(values) {
2224
2262
  }
2225
2263
  return [...byMethods.values()];
2226
2264
  }
2265
+ /** `--only-label role=hub,tier=x --only-sandbox <id>`: a sandbox whose labels
2266
+ * hold every pair of any one --only-label gets the secret, and so does each
2267
+ * --only-sandbox. `--all-sandboxes`: every sandbox again. Nothing: undefined,
2268
+ * so a replacement keeps the scope it has. */
2269
+ export function parseSecretScope(labels, sandboxes, all) {
2270
+ if (all && (labels?.length || sandboxes?.length))
2271
+ throw usage("--all-sandboxes gives the secret to every sandbox; leave out --only-label and --only-sandbox.");
2272
+ if (all)
2273
+ return {};
2274
+ if (!labels?.length && !sandboxes?.length)
2275
+ return undefined;
2276
+ const selectors = (labels ?? []).map((text) => {
2277
+ const selector = {};
2278
+ for (const pair of text.split(",").filter(Boolean)) {
2279
+ const at = pair.indexOf("=");
2280
+ if (at < 1)
2281
+ throw usage(`--only-label takes key=value pairs, such as role=hub; got "${text}".`);
2282
+ selector[pair.slice(0, at).trim()] = pair.slice(at + 1).trim();
2283
+ }
2284
+ if (!Object.keys(selector).length)
2285
+ throw usage(`--only-label takes key=value pairs, such as role=hub; got "${text}".`);
2286
+ return selector;
2287
+ });
2288
+ for (const id of sandboxes ?? [])
2289
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id))
2290
+ throw usage(`--only-sandbox takes a sandbox id; "${id}" is not one.`);
2291
+ return {
2292
+ ...(selectors.length ? { labels: selectors } : {}),
2293
+ ...(sandboxes?.length
2294
+ ? { sandboxes: [...new Set(sandboxes.map((id) => id.toLowerCase()))] }
2295
+ : {}),
2296
+ };
2297
+ }
2298
+ /** Who gets a secret, in a few words. */
2299
+ export const scopeText = (only) => only && (only.labels?.length || only.sandboxes?.length)
2300
+ ? [
2301
+ ...(only.labels ?? []).map((selector) => Object.entries(selector)
2302
+ .map(([key, value]) => `${key}=${value}`)
2303
+ .join(",")),
2304
+ ...(only.sandboxes ?? []),
2305
+ ].join(" | ")
2306
+ : "all";
2227
2307
  const ruleText = (rules) => rules?.length
2228
2308
  ? rules
2229
2309
  .map((rule) => `${rule.methods ? `${rule.methods.join(",")} ` : ""}${rule.paths.join(" ")}`)
@@ -2233,17 +2313,18 @@ export async function secretsCommand(argv, env, out,
2233
2313
  /** Tests give the value here; the CLI reads standard input. */
2234
2314
  read = readSecret) {
2235
2315
  const [verb, ...rest] = argv;
2236
- const args = parse(rest, ["jobs"], ["host", "header", "format", "allow", "value", "version"]);
2316
+ const args = parse(rest, ["jobs", "all-sandboxes"], ["host", "header", "format", "allow", "value", "version", "only-label", "only-sandbox"]);
2237
2317
  const print = (human, value) => out.write(out.json ? JSON.stringify(value) : human);
2238
2318
  const rows = (list) => list.length
2239
2319
  ? table([
2240
- ["name", "hosts", "header", "placeholder", "allow", "jobs"],
2320
+ ["name", "hosts", "header", "placeholder", "allow", "sandboxes", "jobs"],
2241
2321
  ...list.map(({ name, sandboxes: secret, jobs }) => [
2242
2322
  name,
2243
2323
  secret ? secret.hosts.join(",") : "-",
2244
2324
  secret?.header ? `${secret.header}: ${secret.format ?? "{value}"}` : "-",
2245
2325
  secret ? secret.placeholder : "-",
2246
2326
  secret ? ruleText(secret.rules) : "-",
2327
+ secret ? scopeText(secret.only) : "-",
2247
2328
  jobs ? `v${jobs.version}` : "-",
2248
2329
  ]),
2249
2330
  ])
@@ -2266,7 +2347,13 @@ read = readSecret) {
2266
2347
  const name = need(args.positional[0], "the secret's name, such as OPENAI_API_KEY");
2267
2348
  const hosts = args.flags.get("host") ?? [];
2268
2349
  const jobs = verb === "rotate" || has(args, "jobs");
2269
- if (verb === "rotate" && (hosts.length || args.flags.has("header") || args.flags.has("allow")))
2350
+ if (verb === "rotate" &&
2351
+ (hosts.length ||
2352
+ args.flags.has("header") ||
2353
+ args.flags.has("allow") ||
2354
+ args.flags.has("only-label") ||
2355
+ args.flags.has("only-sandbox") ||
2356
+ args.flags.has("all-sandboxes")))
2270
2357
  throw usage(`rotate replaces the copy for jobs. ${jobsOnly}; to change the sandboxes' copy, set it again with --host.`);
2271
2358
  if (!hosts.length && !jobs)
2272
2359
  throw usage("Give --host once for each host the value may go to, such as --host api.openai.com, and/or --jobs to let scheduled jobs use it.");
@@ -2290,16 +2377,20 @@ read = readSecret) {
2290
2377
  const format = flag(args, "format");
2291
2378
  const allow = args.flags.get("allow");
2292
2379
  const rules = allow ? parseSecretRules(allow) : undefined;
2380
+ const only = parseSecretScope(args.flags.get("only-label"), args.flags.get("only-sandbox"), has(args, "all-sandboxes"));
2293
2381
  const saved = await runtime.secrets.set(name, {
2294
2382
  value,
2295
2383
  hosts,
2296
2384
  ...(header ? { header } : {}),
2297
2385
  ...(format ? { format } : {}),
2298
2386
  ...(rules ? { rules } : {}),
2387
+ ...(only ? { only } : {}),
2299
2388
  ...(jobs ? { jobs: true } : {}),
2300
2389
  });
2301
2390
  print([
2302
- `Stored ${saved.name}. Sandboxes see ${saved.name}=${saved.placeholder}.`,
2391
+ saved.only
2392
+ ? `Stored ${saved.name}. Only these sandboxes get it and see ${saved.name}=${saved.placeholder}: ${scopeText(saved.only)}.`
2393
+ : `Stored ${saved.name}. Every sandbox sees ${saved.name}=${saved.placeholder}.`,
2303
2394
  saved.header
2304
2395
  ? `Requests to ${saved.hosts.join(", ")} get ${saved.header}: ${saved.format ?? "{value}"} over HTTPS.`
2305
2396
  : `Over HTTPS to ${saved.hosts.join(", ")}, the placeholder becomes the value in the Authorization header or another credential header (x-api-key, x-goog-api-key); in the URL it stays a placeholder.`,
@@ -2315,6 +2406,15 @@ read = readSecret) {
2315
2406
  ].join("\n"), saved);
2316
2407
  return 0;
2317
2408
  }
2409
+ if (verb === "scope") {
2410
+ const name = need(args.positional[0], "the secret's name");
2411
+ const only = parseSecretScope(args.flags.get("only-label"), args.flags.get("only-sandbox"), has(args, "all-sandboxes"));
2412
+ if (!only)
2413
+ throw usage("Say who gets it: --only-label key=value (once per selector), --only-sandbox <id>, or --all-sandboxes.");
2414
+ const saved = await (await client(env)).secrets.scope(name, only);
2415
+ print(`${saved.name}: ${saved.only ? `only ${scopeText(saved.only)}` : "every sandbox"}.${saved.enforced ? "" : " A host is catching up; running sandboxes have it within a minute."}`, saved);
2416
+ return 0;
2417
+ }
2318
2418
  if (verb === "reveal") {
2319
2419
  const name = need(args.positional[0], "the secret's name");
2320
2420
  const version = flag(args, "version");
@@ -2330,7 +2430,7 @@ read = readSecret) {
2330
2430
  print(`Deleted ${name}${removed.from.length > 1 ? " for sandboxes and jobs" : removed.from[0] === "jobs" ? " for jobs" : ""}.`, removed);
2331
2431
  return 0;
2332
2432
  }
2333
- throw usage("secrets takes set, ls, get, rotate, reveal or rm. Run `runtime help`.");
2433
+ throw usage("secrets takes set, scope, ls, get, rotate, reveal or rm. Run `runtime help`.");
2334
2434
  }
2335
2435
  async function readInput(source) {
2336
2436
  if (source !== "-")
@@ -2482,23 +2582,30 @@ async function browser(sbx, argv, out) {
2482
2582
  }
2483
2583
  const KEYS_HELP = `runtime keys <command>
2484
2584
 
2485
- create [--name <name>] [--read-only] [--daily-limit <usd>] [--account-wide]
2486
- A new API key, for a CI runner or any place with no browser. Prints a link
2487
- and a code; an account owner approves the key in the browser, seeing its
2488
- name, access and limit. The key is printed once, alone on stdout, and
2489
- lasts until revoked. --json prints it as JSON, still alone on stdout.
2490
- --account-wide makes a key that sees and uses every sandbox and other
2491
- resource in the account, whichever key made it; only an owner or admin
2492
- can approve one. Without it, a key sees only what it makes.
2585
+ create [--name <name>] [--read-only] [--daily-limit <usd>] [--account-wide] [--browser]
2586
+ A new API key. With a key saved on this machine (or RUNTIME_API_KEY), that
2587
+ key makes it at once, with no browser: a key per worker in an agent swarm.
2588
+ It never has more than the key that made it, and when that key has a daily
2589
+ limit, the keys it made share it, so each needs --daily-limit. Without a
2590
+ saved key, or with --browser, it prints a link and a code and an account
2591
+ owner approves the key in the browser. The key is printed once, alone on
2592
+ stdout, and lasts until revoked. --json prints it as JSON, still alone on
2593
+ stdout. --account-wide makes a key that sees and uses every sandbox and
2594
+ other resource in the account; without it, a key sees only what it makes.
2595
+ ls This key and the keys it made, with what each spent today
2596
+ usage <keyId> What one of them has spent and holds
2597
+ revoke <keyId> Revoke one: a worker's key when the worker is done
2493
2598
 
2494
- runtime keys create --name ci --daily-limit 25 | gh secret set RUNTIME_API_KEY
2599
+ runtime keys create --name worker-1 --daily-limit 5
2600
+ runtime keys create --name ci --daily-limit 25 --browser | gh secret set RUNTIME_API_KEY
2495
2601
 
2496
- List, limit and revoke keys at https://withruntime.com/account/keys.
2602
+ Making, listing and revoking keys with a key needs one an owner or admin made.
2603
+ Every key is also at https://withruntime.com/account/keys.
2497
2604
  `;
2498
- /* `runtime keys create`: a key made from the terminal and approved in the
2499
- browser. Marc, 23 September 2026; ARCHITECTURE.md section 10, "Keys from the
2500
- terminal". Only the options the keys page offers. No key can list, create or
2501
- revoke keys through the API, so `ls` and `revoke` send you to the page. */
2605
+ /* `runtime keys`: a key made from the terminal, by this machine's key with no
2606
+ browser (Marc, 14 October 2026, agent swarms), or approved in the browser
2607
+ (Marc, 23 September 2026; ARCHITECTURE.md section 10, "Keys from the
2608
+ terminal"). Only the options the keys page offers. */
2502
2609
  /* `runtime account`: the accounts this machine is connected to, and which one
2503
2610
  every command uses (ARCHITECTURE.md section 3.9). Each is its own saved
2504
2611
  connection with its own key; the SDK itself stays key-based. */
@@ -2604,19 +2711,63 @@ async function nameSaved(env) {
2604
2711
  await store.save({ ...again, orgName: who.orgName });
2605
2712
  }
2606
2713
  }
2714
+ /** Dollars from integer microdollars, for a person to read: "$5.00". */
2715
+ const usdOf = (micros) => `$${(Number(BigInt(micros) / 10000n) / 100).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
2607
2716
  async function keys(argv, env, out) {
2608
2717
  product = "keys";
2609
2718
  const [verb, ...rest] = argv;
2719
+ const print = (human, value) => out.write(out.json ? JSON.stringify(value) : human);
2610
2720
  if (!verb || verb === "help" || verb === "--help" || verb === "-h") {
2611
2721
  out.write(out.json ? JSON.stringify({ usage: named(KEYS_HELP) }) : named(KEYS_HELP));
2612
2722
  return 0;
2613
2723
  }
2614
- if (verb === "ls" || verb === "list" || verb === "revoke")
2615
- throw usage("List and revoke keys at https://withruntime.com/account/keys. No key can list or revoke keys.");
2724
+ if (verb === "ls" || verb === "list") {
2725
+ const args = parse(rest, [], []);
2726
+ if (args.flags.size || args.positional.length)
2727
+ throw usage(`Usage: ${named("runtime keys ls")}`);
2728
+ const all = await (await client(env)).keys.list();
2729
+ print(all.length
2730
+ ? table([
2731
+ ["KEY", "NAME", "ACCESS", "REACH", "DAILY LIMIT", "LAST 24H", "MADE BY", "STATE"],
2732
+ ...all.map((k) => [
2733
+ k.keyId,
2734
+ k.name,
2735
+ k.access,
2736
+ k.reach,
2737
+ k.dailyLimitMicros === null ? "none" : usdOf(k.dailyLimitMicros),
2738
+ usdOf(k.dailyUsedMicros),
2739
+ k.madeBy ?? "website",
2740
+ k.revokedAt ? "revoked" : "live",
2741
+ ]),
2742
+ ])
2743
+ : "No keys.", all);
2744
+ return 0;
2745
+ }
2746
+ if (verb === "revoke" || verb === "rm" || verb === "usage") {
2747
+ const args = parse(rest, [], []);
2748
+ const id = args.positional[0];
2749
+ if (!id || args.positional.length > 1 || args.flags.size)
2750
+ throw usage(`Usage: ${named(`runtime keys ${verb} <keyId>`)}`);
2751
+ const api = (await client(env)).keys;
2752
+ if (verb === "usage") {
2753
+ const used = await api.usage(id);
2754
+ print([
2755
+ `${used.name} (${used.keyId})${used.revokedAt ? ", revoked" : ""}`,
2756
+ ` last 24 hours ${usdOf(used.dailyUsedMicros)}${used.dailyLimitMicros === null ? "" : ` of ${usdOf(used.dailyLimitMicros)}`}`,
2757
+ ` spent ${usdOf(used.spentMicros)}`,
2758
+ ` held now ${usdOf(used.heldMicros)}`,
2759
+ ` sandboxes ${used.runningSandboxes} running, ${used.resources} kept in all`,
2760
+ ].join("\n"), used);
2761
+ return 0;
2762
+ }
2763
+ const revoked = await api.revoke(id);
2764
+ print(`Revoked ${revoked.keyId}. It stops working within ten seconds.`, revoked);
2765
+ return 0;
2766
+ }
2616
2767
  if (verb !== "create")
2617
- throw unknown("keys command", verb, ["create"]);
2618
- const args = parse(rest, ["read-only", "account-wide"], ["name", "daily-limit"]);
2619
- const known = new Set(["name", "read-only", "daily-limit", "account-wide"]);
2768
+ throw unknown("keys command", verb, ["create", "ls", "usage", "revoke"]);
2769
+ const args = parse(rest, ["read-only", "account-wide", "browser"], ["name", "daily-limit"]);
2770
+ const known = new Set(["name", "read-only", "daily-limit", "account-wide", "browser"]);
2620
2771
  for (const name of args.flags.keys())
2621
2772
  if (!known.has(name))
2622
2773
  throw usage(`Unknown option --${name}.`);
@@ -2629,26 +2780,48 @@ async function keys(argv, env, out) {
2629
2780
  if (readOnly && has(args, "account-wide"))
2630
2781
  throw usage("A read-only key already sees the whole account, so it takes no --account-wide.");
2631
2782
  const { createKey, dailyLimitMicros } = await import("./keys.js");
2632
- const created = await createKey({
2783
+ const request = {
2633
2784
  name: flag(args, "name") ?? (readOnly ? "Read-only key" : "My API key"),
2634
2785
  access: readOnly ? "read" : "full",
2635
2786
  ...(has(args, "account-wide") ? { reach: "account" } : {}),
2636
2787
  dailyLimitMicros: limit === undefined ? null : dailyLimitMicros(limit),
2637
- }, env, { notify: out.error });
2638
- const { confirm, ...key } = created;
2639
- out.write(out.json ? JSON.stringify(key) : key.key);
2788
+ };
2789
+ /* A key on this machine makes the new one itself, with no browser: an
2790
+ agent with no person, a swarm's coordinator (Marc, 14 October 2026). */
2791
+ const saved = has(args, "browser")
2792
+ ? null
2793
+ : env.RUNTIME_API_KEY || (await connectionStore(env).read())?.key || null;
2794
+ let key;
2795
+ if (saved) {
2796
+ key = await new Runtime({
2797
+ apiKey: saved,
2798
+ baseUrl: env.RUNTIME_API_URL,
2799
+ maxRetries: 4,
2800
+ }).keys.create({
2801
+ name: request.name,
2802
+ access: request.access,
2803
+ reach: request.reach ?? "agent",
2804
+ dailyLimitMicros: request.dailyLimitMicros,
2805
+ });
2806
+ }
2807
+ else
2808
+ key = await createKey(request, env, { notify: out.error });
2809
+ const { confirm, ...made } = key;
2810
+ out.write(out.json ? JSON.stringify(made) : made.key);
2640
2811
  const terms = [
2641
- key.access === "read" ? "read only" : "full access",
2642
- key.reach === "account" ? "account-wide" : undefined,
2643
- key.access === "read"
2812
+ made.access === "read" ? "read only" : "full access",
2813
+ made.reach === "account" ? "account-wide" : undefined,
2814
+ made.access === "read"
2644
2815
  ? undefined
2645
- : key.dailyLimitMicros === null
2816
+ : made.dailyLimitMicros === null
2646
2817
  ? "no daily limit"
2647
- : `daily limit $${(Number(BigInt(key.dailyLimitMicros) / 10000n) / 100).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`,
2648
- "until revoked",
2818
+ : `daily limit ${usdOf(made.dailyLimitMicros)}`,
2819
+ made.expires ? `until ${made.expires}` : "until revoked",
2649
2820
  ].filter(Boolean);
2650
- out.error(`Created key "${key.name}" (${terms.join(", ")}). Store it now: it is not shown again, and Runtime keeps only a hash of it.\nIn CI, give it to your jobs as RUNTIME_API_KEY from the secret store.`);
2651
- await confirm();
2821
+ out.error(saved
2822
+ ? `Created key "${made.name}" (${terms.join(", ")}), made by this machine's key. Store it now: it is not shown again, and Runtime keeps only a hash of it.\nGive it to one worker as RUNTIME_API_KEY; revoke it with ${named("runtime keys revoke")} ${made.keyId} when that worker is done.`
2823
+ : `Created key "${made.name}" (${terms.join(", ")}). Store it now: it is not shown again, and Runtime keeps only a hash of it.\nIn CI, give it to your jobs as RUNTIME_API_KEY from the secret store.`);
2824
+ await confirm?.();
2652
2825
  return 0;
2653
2826
  }
2654
2827
  const SNAPSHOT_HELP = `runtime snapshots <command>
@@ -2976,7 +3149,7 @@ read = readSecret) {
2976
3149
  const JOB_HELP = `runtime jobs <command>
2977
3150
 
2978
3151
  A job runs a command in a fresh sandbox, once or on a schedule. Each run spends
2979
- the included usage first, then credit at the sandbox rates.
3152
+ the free usage first, then credit at the sandbox rates.
2980
3153
 
2981
3154
  create <name> (--cron "<min hour day month weekday>" [--timezone Europe/Berlin] | --at <time|now>)
2982
3155
  [--vcpu 2] [--memory 4096] [--disk 4096] [--cpu shared|reserved] [--cpu-floor <thousandths>]
@@ -26,6 +26,9 @@ export declare function connectionOrigins(env: NodeJS.ProcessEnv): {
26
26
  /** Browser-issued production credentials must only reach the production API.
27
27
  * CLI and MCP share this boundary, including resumed device requests. */
28
28
  export declare function checkBrowserOrigins(origins: ReturnType<typeof connectionOrigins>): void;
29
+ /** A wallet account's key, from its claim, saved as this machine's connection
30
+ * (the MCP bridge's runtime_wallet_claim; `billing claim` saves the same). */
31
+ export declare function saveWalletConnection(env: NodeJS.ProcessEnv, key: string, orgId: string): Promise<void>;
29
32
  /** Private user storage, outside project files. Nothing reading this module
30
33
  * returns a secret to CLI output. Store names bind credentials to both origins. */
31
34
  export declare function connectionStore(env: NodeJS.ProcessEnv, directory?: string): {
@@ -37,6 +37,21 @@ export function checkBrowserOrigins(origins) {
37
37
  (local(origins.auth) && local(origins.api))))
38
38
  throw new Error("Browser login supports Runtime's production endpoints or local test endpoints. Check RUNTIME_AUTH_URL and RUNTIME_API_URL.");
39
39
  }
40
+ /** A wallet account's key, from its claim, saved as this machine's connection
41
+ * (the MCP bridge's runtime_wallet_claim; `billing claim` saves the same). */
42
+ export function saveWalletConnection(env, key, orgId) {
43
+ const origins = connectionOrigins(env);
44
+ checkBrowserOrigins(origins);
45
+ return connectionStore(env).save({
46
+ version: 1,
47
+ apiOrigin: origins.api,
48
+ authOrigin: origins.auth,
49
+ key,
50
+ connectionId: "pasted",
51
+ orgId,
52
+ agentName: "Wallet agent",
53
+ });
54
+ }
40
55
  function defaultDirectory(env) {
41
56
  const root = env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
42
57
  if (!isAbsolute(root))
@@ -183,7 +183,7 @@ export declare class Daytona implements AsyncDisposable {
183
183
  [Symbol.asyncDispose](): Promise<void>;
184
184
  /** Creates a sandbox and waits until it runs, with Daytona's defaults: 1
185
185
  * vCPU, 1 GiB, 3 GiB disk, pausing after 15 minutes without calls. Funding
186
- * is left to Runtime: the included usage first, then prepaid credit, exactly as withruntime's own create. */
186
+ * is left to Runtime: the free usage first, then prepaid credit, exactly as withruntime's own create. */
187
187
  create(params?: CreateSandboxFromSnapshotParams | CreateSandboxFromImageParams, options?: {
188
188
  timeout?: number;
189
189
  onSnapshotCreateLogs?: (chunk: string) => void;
@@ -184,7 +184,7 @@ export class Daytona {
184
184
  async [Symbol.asyncDispose]() { }
185
185
  /** Creates a sandbox and waits until it runs, with Daytona's defaults: 1
186
186
  * vCPU, 1 GiB, 3 GiB disk, pausing after 15 minutes without calls. Funding
187
- * is left to Runtime: the included usage first, then prepaid credit, exactly as withruntime's own create. */
187
+ * is left to Runtime: the free usage first, then prepaid credit, exactly as withruntime's own create. */
188
188
  async create(params = {}, options = {}) {
189
189
  refuseCreate(params);
190
190
  const timeout = options.timeout;
@@ -133,7 +133,7 @@ export declare class Sandbox {
133
133
  protected get client(): Runtime;
134
134
  /** Creates a sandbox from `template` (default "base", Runtime's stock image)
135
135
  * with E2B's default machine, 2 vCPU and 512 MiB, and waits until it runs.
136
- * Funding is left to Runtime: the included usage first, then prepaid credit, exactly as withruntime's own create. */
136
+ * Funding is left to Runtime: the free usage first, then prepaid credit, exactly as withruntime's own create. */
137
137
  static create<S extends typeof Sandbox>(this: S, opts?: SandboxOpts): Promise<InstanceType<S>>;
138
138
  static create<S extends typeof Sandbox>(this: S, template: string, opts?: SandboxOpts): Promise<InstanceType<S>>;
139
139
  /** Connects to a sandbox by id, waking it if it is paused, as E2B does. A
package/dist/errors.d.ts CHANGED
@@ -13,7 +13,7 @@ export declare const DELIBERATE: ReadonlySet<string>;
13
13
  * minutes when it has none), however many retries it allows otherwise. */
14
14
  export declare const NOTHING_RAN: ReadonlySet<string>;
15
15
  /** Refusals of a create that clear when a sandbox stops or pauses, or a host
16
- * frees room: an account without credit's eight at once and its total, an
16
+ * frees room: an account without credit's four at once and its total, an
17
17
  * email domain's, the account's quota and the region's capacity. `sandboxes.create` waits them out, retrying
18
18
  * with the same key and input, for up to `waitForCapacityMs`. */
19
19
  export declare const WAITS_FOR_ROOM: ReadonlySet<string>;
package/dist/errors.js CHANGED
@@ -27,7 +27,7 @@ export const NOTHING_RAN = new Set(["guest_busy", "busy", "rate_limited"]);
27
27
  * account without credit stops. */
28
28
  const PASSING = new Set(["no_capacity", "no_credit_running_limit", "no_credit_total_limit"]);
29
29
  /** Refusals of a create that clear when a sandbox stops or pauses, or a host
30
- * frees room: an account without credit's eight at once and its total, an
30
+ * frees room: an account without credit's four at once and its total, an
31
31
  * email domain's, the account's quota and the region's capacity. `sandboxes.create` waits them out, retrying
32
32
  * with the same key and input, for up to `waitForCapacityMs`. */
33
33
  export const WAITS_FOR_ROOM = new Set([
package/dist/index.d.ts CHANGED
@@ -16,7 +16,7 @@ export { Page } from "./page.js";
16
16
  export type { KeyLimits } from "./products/limits.js";
17
17
  export type { AuditEvent, AuditPage } from "./products/audit.js";
18
18
  export type { UsageExportQuery, UsageExportRow, UsageExportPage, } from "./products/observability.js";
19
- export type { JobSecret, Secret, SecretRule, SecretUse, SetSecret } from "./products/secrets.js";
19
+ export type { JobSecret, Secret, SecretRule, SecretScope, SecretUse, SetSecret, } from "./products/secrets.js";
20
20
  export { SecretPartlyStoredError } from "./products/secrets.js";
21
21
  export type { McpCatalogEntry, McpGateway, McpServerRequest } from "./products/mcp.js";
22
22
  export type { FileEvent, FileEventType, WatchHandle, WatchNotice, WatchOptions, } from "./products/watch.js";
@@ -74,4 +74,5 @@ export type { RuntimeOptions as ClientOptions };
74
74
  export { identityToken, type IdentityToken } from "./identity.js";
75
75
  export { openWalletAccount, claimWalletAccount, type OpenedWalletAccount, type WalletClaim, type WalletOptions, } from "./wallet.js";
76
76
  export type { Topup } from "./products/billing.js";
77
+ export type { NewKey, MadeKey, ListedKey, KeyUsage } from "./products/api-keys.js";
77
78
  export type { SsoConnection, SsoStatus } from "./products/sso.js";