withruntime 0.33.2 → 0.34.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 (43) hide show
  1. package/E2B.md +7 -0
  2. package/dist/agents-cli.d.ts +1 -1
  3. package/dist/agents-cli.js +171 -7
  4. package/dist/agents-deploy.d.ts +13 -3
  5. package/dist/agents-deploy.js +68 -9
  6. package/dist/apps-cli.d.ts +1 -1
  7. package/dist/apps-cli.js +41 -9
  8. package/dist/browsers/index.d.ts +9 -0
  9. package/dist/browsers/index.js +10 -0
  10. package/dist/browsers-cli.d.ts +13 -0
  11. package/dist/browsers-cli.js +160 -0
  12. package/dist/ci-cli.d.ts +1 -1
  13. package/dist/ci-cli.js +66 -13
  14. package/dist/cli.d.ts +4 -0
  15. package/dist/cli.js +94 -10
  16. package/dist/desktops/index.d.ts +9 -0
  17. package/dist/desktops/index.js +10 -0
  18. package/dist/e2b/code-interpreter.d.ts +3 -12
  19. package/dist/e2b/code-interpreter.js +3 -6
  20. package/dist/e2b/index.d.ts +7 -1
  21. package/dist/e2b/index.js +9 -2
  22. package/dist/index.d.ts +6 -4
  23. package/dist/products/agents.d.ts +162 -8
  24. package/dist/products/agents.js +108 -3
  25. package/dist/products/apps.d.ts +46 -5
  26. package/dist/products/browsers.d.ts +26 -0
  27. package/dist/products/browsers.js +11 -0
  28. package/dist/products/ci.d.ts +49 -7
  29. package/dist/products/ci.js +15 -1
  30. package/dist/products/desktops.d.ts +71 -0
  31. package/dist/products/desktops.js +27 -0
  32. package/dist/products/index.d.ts +4 -0
  33. package/dist/products/index.js +5 -0
  34. package/dist/products/network.d.ts +4 -2
  35. package/dist/products/secrets.d.ts +10 -2
  36. package/dist/products/secrets.js +27 -7
  37. package/dist/products/uptime.d.ts +97 -0
  38. package/dist/products/uptime.js +42 -0
  39. package/dist/products/volumes.d.ts +41 -9
  40. package/dist/products/volumes.js +5 -2
  41. package/dist/transport.d.ts +4 -1
  42. package/dist/transport.js +2 -2
  43. package/package.json +13 -2
package/dist/apps-cli.js CHANGED
@@ -13,11 +13,17 @@ export const APP_HELP = `runtime apps <command>
13
13
  create [<name>] --vcpu <1-16> --memory <128-65536> --max-copies <1-1000>
14
14
  [--min-copies 0] [--concurrency 100] [--spares 2] [--port 8080]
15
15
  [--health-path /healthz] [--health-fail 60] [--stop-grace 30] [--cdn on|off]
16
- [--volume <volumeId>:<path>] [--label key=value]...
16
+ [--release at-once|staged] [--soak 300] [--volume <volumeId>:<path>] [--label key=value]...
17
17
  Make an App, waiting for its first deploy; prints its id.
18
18
  --health-fail: seconds a live copy may fail its health
19
19
  check before it is replaced. --stop-grace: seconds a
20
- stopped copy has after SIGTERM. --volume mounts one
20
+ stopped copy has after SIGTERM. --release staged: a new
21
+ version goes live on one copy, then about 10%, 50% and
22
+ all of them, each stage running --soak seconds (60 to
23
+ 3600) and judged on its visitors' errors and speed
24
+ before the next; a stage that does worse goes back and
25
+ the deploy fails, the live version never stopping.
26
+ --volume mounts one
21
27
  volume of yours at a path under /data, /mnt, /state,
22
28
  /storage or /volumes, in the App's one copy: it needs
23
29
  --max-copies 1 (--min-copies is then 1, --spares 0), and
@@ -27,7 +33,7 @@ export const APP_HELP = `runtime apps <command>
27
33
  update <app> [--name <name>] [--vcpu n] [--memory n] [--min-copies n] [--max-copies n]
28
34
  [--concurrency n] [--spares n] [--port n] [--health-path /path] [--health-fail n]
29
35
  [--stop-grace n] [--volume <volumeId>:<path> | none] [--cdn on|off]
30
- [--label key=value]...
36
+ [--release at-once|staged] [--soak n] [--label key=value]...
31
37
  Change its settings; --volume none takes the volume
32
38
  away, only before its first deploy;
33
39
  --cdn off sends visitors straight to Runtime's routers,
@@ -224,6 +230,19 @@ function onOff(value, kit) {
224
230
  return false;
225
231
  throw kit.usage(`--cdn takes on or off, not ${value}.`);
226
232
  }
233
+ /** --release at-once|staged and --soak <seconds>: how an App's new version
234
+ * goes live. */
235
+ function releases(args, kit) {
236
+ const given = kit.flag(args, "release");
237
+ const soak = kit.integer(args, "soak");
238
+ const stages = given?.replace("-", "_");
239
+ if (stages !== undefined && stages !== "at_once" && stages !== "staged")
240
+ throw kit.usage(`--release takes at-once or staged, not ${given}.`);
241
+ return {
242
+ ...(stages === undefined ? {} : { releaseStages: stages }),
243
+ ...(soak === undefined ? {} : { releaseSoakSeconds: soak }),
244
+ };
245
+ }
227
246
  /** --volume <volumeId>:<path>, or none (an update's: no volume). */
228
247
  function volume(args, kit) {
229
248
  const given = kit.flag(args, "volume");
@@ -239,8 +258,8 @@ function volume(args, kit) {
239
258
  return { volume: { volumeId, path } };
240
259
  }
241
260
  /** The flags an App's settings are given with, as the API names them. A
242
- * Function's leave out --min-copies, --stop-grace and --volume, which its
243
- * parse does not take. */
261
+ * Function's leave out --min-copies, --stop-grace, --volume, --release and
262
+ * --soak, which its parse does not take. */
244
263
  function settings(args, kit, kind) {
245
264
  const { integer, flag } = kit;
246
265
  const number = (name, field) => integer(args, name) === undefined ? {} : { [field]: integer(args, name) };
@@ -256,7 +275,11 @@ function settings(args, kit, kind) {
256
275
  ...(flag(args, "cdn") === undefined ? {} : { cdn: onOff(flag(args, "cdn"), kit) }),
257
276
  ...number("health-fail", "healthFailSeconds"),
258
277
  ...(kind.minCopies
259
- ? { ...number("stop-grace", "stopGraceSeconds"), ...volume(args, kit) }
278
+ ? {
279
+ ...number("stop-grace", "stopGraceSeconds"),
280
+ ...volume(args, kit),
281
+ ...releases(args, kit),
282
+ }
260
283
  : {}),
261
284
  };
262
285
  }
@@ -273,11 +296,13 @@ const SETTINGS = [
273
296
  "health-fail",
274
297
  "stop-grace",
275
298
  "volume",
299
+ "release",
300
+ "soak",
276
301
  "label",
277
302
  ];
278
303
  const settingsOf = (kind) => kind.minCopies
279
304
  ? SETTINGS
280
- : SETTINGS.filter((name) => !["min-copies", "stop-grace", "volume"].includes(name));
305
+ : SETTINGS.filter((name) => !["min-copies", "stop-grace", "volume", "release", "soak"].includes(name));
281
306
  /** A version's copies replaced while live: how many, and why. */
282
307
  function replaced(list) {
283
308
  if (!list?.length)
@@ -418,9 +443,16 @@ async function copiesCommand(kind, argv, env, out, client, kit) {
418
443
  ...(healthPath === undefined ? {} : { healthPath }),
419
444
  wait,
420
445
  };
446
+ // A staged App's deploy walks up to four stages, each soaking.
447
+ const app = wait && kind.minCopies ? await api.get(id) : undefined;
448
+ const staged = app && "releaseStages" in app && app.releaseStages === "staged" ? app : undefined;
421
449
  if (wait && !out.json)
422
- out.error(`Deploying ${args.positional[0]}...`);
423
- const deploy = await api.deploy(id, input, { timeoutMs: 15 * 60_000 });
450
+ out.error(staged
451
+ ? `Deploying ${args.positional[0]} in stages, each soaking ${staged.releaseSoakSeconds} s...`
452
+ : `Deploying ${args.positional[0]}...`);
453
+ const deploy = await api.deploy(id, input, {
454
+ timeoutMs: 15 * 60_000 + (staged ? 4 * staged.releaseSoakSeconds * 1000 : 0),
455
+ });
424
456
  const failed = deployFailure(deploy);
425
457
  if (failed && !out.json)
426
458
  throw failed;
@@ -0,0 +1,9 @@
1
+ /** `withruntime/browsers`: Browsers alone. Chromium on its own sandbox, with a private CDP address and screenshots, at the sandbox rates. In a private beta.
2
+ *
3
+ * import { browsers } from "withruntime/browsers";
4
+ * const client = browsers(); // RUNTIME_API_KEY, or this machine's saved connection
5
+ */
6
+ import { Runtime, type RuntimeOptions } from "../client.js";
7
+ export type * from "../products/browsers.js";
8
+ /** `runtime.browsers` of a client made with `options`. */
9
+ export declare function browsers(options?: RuntimeOptions): Runtime["browsers"];
@@ -0,0 +1,10 @@
1
+ /** `withruntime/browsers`: Browsers alone. Chromium on its own sandbox, with a private CDP address and screenshots, at the sandbox rates. In a private beta.
2
+ *
3
+ * import { browsers } from "withruntime/browsers";
4
+ * const client = browsers(); // RUNTIME_API_KEY, or this machine's saved connection
5
+ */
6
+ import { Runtime } from "../client.js";
7
+ /** `runtime.browsers` of a client made with `options`. */
8
+ export function browsers(options) {
9
+ return new Runtime(options).browsers;
10
+ }
@@ -0,0 +1,13 @@
1
+ import type { CliKit } from "./cli.js";
2
+ import type { Runtime } from "./client.js";
3
+ type Out = {
4
+ json: boolean;
5
+ write: (text: string) => void;
6
+ error: (text: string) => void;
7
+ };
8
+ export declare const BROWSER_HELP = "runtime browsers <command>\n\n A browser is Chromium on its own sandbox, billed at the sandbox rates, with a\n private CDP address for Playwright, Puppeteer, browser-use or Stagehand.\n In a private beta: outside it, each command says how to ask to join.\n\n create [--memory <1024-16384>] [--vcpu <1-16>] [--visible] [--width <px>] [--height <px>]\n [--timeout <seconds>] [--name <name>] [--label key=value]...\n Start one; prints its id, then its cdpUrl\n ls Your browsers\n get <browser> One browser, with a fresh cdpUrl (an id or its name)\n screenshot <browser> [<url>] [--out <file.png>] [--full] [--jpeg] [--wait <ms>]\n Open url in a new tab and save its screenshot\n (without url: what it shows now)\n rm <browser> Delete it and its sandbox\n\nExamples\n runtime browsers create --name scraper\n runtime browsers screenshot scraper https://withruntime.com --out home.png\n runtime browsers rm scraper\n";
9
+ export declare const DESKTOP_HELP = "runtime desktops <command>\n\n A desktop is a Linux desktop on its own sandbox, billed at the sandbox rates,\n with a private live view and computer use: screenshot, click, type, open.\n In a private beta: outside it, each command says how to ask to join.\n\n create [--memory <1024-16384>] [--vcpu <1-16>] [--width <px>] [--height <px>]\n [--timeout <seconds>] [--name <name>] [--label key=value]...\n Start one; prints its id, then its live view\n ls Your desktops\n get <desktop> One desktop, with a fresh live view (an id or its name)\n screenshot <desktop> [--out <file.png>] Save its screen\n open <desktop> <url> Open url in its Chromium\n rm <desktop> Delete it and its sandbox\n\nExamples\n runtime desktops create --name qa\n runtime desktops open qa https://withruntime.com\n runtime desktops screenshot qa --out screen.png\n";
10
+ /** `runtime browsers` and `runtime desktops`: the five verbs, a screenshot
11
+ * saved to a file, and a desktop's open. */
12
+ export declare function machineProductCommand(which: "browsers" | "desktops", argv: string[], out: Out, client: () => Promise<Runtime>, kit: CliKit): Promise<number>;
13
+ export {};
@@ -0,0 +1,160 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { me, named } from "./cli-name.js";
3
+ import { browsers } from "./products/browsers.js";
4
+ import { desktops } from "./products/desktops.js";
5
+ export const BROWSER_HELP = `runtime browsers <command>
6
+
7
+ A browser is Chromium on its own sandbox, billed at the sandbox rates, with a
8
+ private CDP address for Playwright, Puppeteer, browser-use or Stagehand.
9
+ In a private beta: outside it, each command says how to ask to join.
10
+
11
+ create [--memory <1024-16384>] [--vcpu <1-16>] [--visible] [--width <px>] [--height <px>]
12
+ [--timeout <seconds>] [--name <name>] [--label key=value]...
13
+ Start one; prints its id, then its cdpUrl
14
+ ls Your browsers
15
+ get <browser> One browser, with a fresh cdpUrl (an id or its name)
16
+ screenshot <browser> [<url>] [--out <file.png>] [--full] [--jpeg] [--wait <ms>]
17
+ Open url in a new tab and save its screenshot
18
+ (without url: what it shows now)
19
+ rm <browser> Delete it and its sandbox
20
+
21
+ Examples
22
+ runtime browsers create --name scraper
23
+ runtime browsers screenshot scraper https://withruntime.com --out home.png
24
+ runtime browsers rm scraper
25
+ `;
26
+ export const DESKTOP_HELP = `runtime desktops <command>
27
+
28
+ A desktop is a Linux desktop on its own sandbox, billed at the sandbox rates,
29
+ with a private live view and computer use: screenshot, click, type, open.
30
+ In a private beta: outside it, each command says how to ask to join.
31
+
32
+ create [--memory <1024-16384>] [--vcpu <1-16>] [--width <px>] [--height <px>]
33
+ [--timeout <seconds>] [--name <name>] [--label key=value]...
34
+ Start one; prints its id, then its live view
35
+ ls Your desktops
36
+ get <desktop> One desktop, with a fresh live view (an id or its name)
37
+ screenshot <desktop> [--out <file.png>] Save its screen
38
+ open <desktop> <url> Open url in its Chromium
39
+ rm <desktop> Delete it and its sandbox
40
+
41
+ Examples
42
+ runtime desktops create --name qa
43
+ runtime desktops open qa https://withruntime.com
44
+ runtime desktops screenshot qa --out screen.png
45
+ `;
46
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
47
+ /** `runtime browsers` and `runtime desktops`: the five verbs, a screenshot
48
+ * saved to a file, and a desktop's open. */
49
+ export async function machineProductCommand(which, argv, out, client, kit) {
50
+ const { parse, flag, has, integer, need, pairs, table, usage } = kit;
51
+ const noun = which === "browsers" ? "browser" : "desktop";
52
+ const help = which === "browsers" ? BROWSER_HELP : DESKTOP_HELP;
53
+ const [verb, ...rest] = argv;
54
+ const print = (human, value) => out.write(out.json ? JSON.stringify(value) : human);
55
+ if (!verb || verb === "help" || verb === "--help") {
56
+ print(named(help), { usage: named(help) });
57
+ return 0;
58
+ }
59
+ const transport = (await client()).transport;
60
+ const api = which === "browsers" ? browsers(transport) : desktops(transport);
61
+ const idOf = async (target) => {
62
+ const given = need(target, `a ${noun}'s id or name`);
63
+ if (UUID.test(given))
64
+ return given;
65
+ const found = (await api.list({ name: given })).data;
66
+ if (found.length === 1)
67
+ return found[0].id;
68
+ throw usage(found.length
69
+ ? `${found.length} ${which} are named ${given}; give one's id (${me} ${which} ls).`
70
+ : `No ${noun} is named ${given}. Your ${which}: ${me} ${which} ls`);
71
+ };
72
+ const save = (file, bytes, value) => {
73
+ writeFileSync(file, bytes);
74
+ print(`Saved ${file} (${bytes.length} bytes).`, { ...value, file, base64: undefined });
75
+ };
76
+ switch (verb) {
77
+ case "create": {
78
+ const args = parse(rest, ["visible"], ["memory", "vcpu", "width", "height", "timeout", "name", "label"]);
79
+ const input = {
80
+ ...(integer(args, "memory") === undefined ? {} : { memoryMiB: integer(args, "memory") }),
81
+ ...(integer(args, "vcpu") === undefined ? {} : { vcpu: integer(args, "vcpu") }),
82
+ ...(integer(args, "width") === undefined ? {} : { width: integer(args, "width") }),
83
+ ...(integer(args, "height") === undefined ? {} : { height: integer(args, "height") }),
84
+ ...(integer(args, "timeout") === undefined
85
+ ? {}
86
+ : { timeoutSeconds: integer(args, "timeout") }),
87
+ ...(flag(args, "name") === undefined ? {} : { name: flag(args, "name") }),
88
+ ...(args.flags.has("label") ? { labels: pairs(args, "label") } : {}),
89
+ ...(which === "browsers" && has(args, "visible") ? { headless: false } : {}),
90
+ };
91
+ const made = (await api.create(input));
92
+ const reach = which === "browsers" ? made.cdpUrl : made.liveViewUrl;
93
+ print(`${String(made.id)}\n${typeof reach === "string" ? reach : ""}`, made);
94
+ return 0;
95
+ }
96
+ case "ls":
97
+ case "list": {
98
+ const all = (await (await api.list({ limit: 100 })).toArray(Infinity));
99
+ print(all.length
100
+ ? table([
101
+ ["ID", "NAME", "STATE", "SIZE", "SANDBOX"],
102
+ ...all.map((item) => [
103
+ String(item.id),
104
+ typeof item.name === "string" ? item.name : "-",
105
+ String(item.state),
106
+ `${String(item.vcpu)} vCPU, ${String(item.memoryMiB)} MiB`,
107
+ String(item.sandboxId),
108
+ ]),
109
+ ])
110
+ : `No ${which}. Start one: ${me} ${which} create`, all);
111
+ return 0;
112
+ }
113
+ case "get": {
114
+ const found = await api.get(await idOf(rest[0]));
115
+ print(JSON.stringify(found, null, 2), found);
116
+ return 0;
117
+ }
118
+ case "screenshot":
119
+ case "shot": {
120
+ const args = parse(rest, ["full", "jpeg"], ["out", "wait"]);
121
+ const id = await idOf(args.positional[0]);
122
+ const jpeg = has(args, "jpeg");
123
+ const file = flag(args, "out") ?? `${noun}-${id.slice(0, 8)}.${jpeg ? "jpg" : "png"}`;
124
+ if (which === "desktops") {
125
+ const bytes = await desktops(transport).screenshot(id, jpeg ? { format: "jpeg" } : {});
126
+ save(file, bytes, { id });
127
+ return 0;
128
+ }
129
+ const url = args.positional[1];
130
+ const shot = await browsers(transport).screenshot(id, {
131
+ ...(url ? { url } : {}),
132
+ ...(has(args, "full") ? { fullPage: true } : {}),
133
+ ...(jpeg ? { format: "jpeg" } : {}),
134
+ ...(integer(args, "wait") === undefined ? {} : { waitMs: integer(args, "wait") }),
135
+ });
136
+ if (!out.json)
137
+ out.error(`${shot.url} (${shot.status ?? "-"}): ${shot.title}`);
138
+ save(file, Buffer.from(shot.base64, "base64"), shot);
139
+ return 0;
140
+ }
141
+ case "open": {
142
+ if (which !== "desktops")
143
+ break;
144
+ const id = await idOf(rest[0]);
145
+ const url = need(rest[1], "a URL to open");
146
+ const done = await desktops(transport).act(id, { action: "open", url });
147
+ print(`Opened ${url}.`, done);
148
+ return 0;
149
+ }
150
+ case "rm":
151
+ case "delete": {
152
+ const gone = await api.delete(await idOf(rest[0]));
153
+ print(`${String(rest[0])} deleted.`, gone ?? { deleted: true });
154
+ return 0;
155
+ }
156
+ }
157
+ throw kit.unknown(`${which} command`, verb, which === "browsers"
158
+ ? ["create", "ls", "get", "screenshot", "rm"]
159
+ : ["create", "ls", "get", "screenshot", "open", "rm"]);
160
+ }
package/dist/ci-cli.d.ts CHANGED
@@ -5,6 +5,6 @@ type Out = {
5
5
  write: (text: string) => void;
6
6
  error: (text: string) => void;
7
7
  };
8
- export declare const CI_HELP = "runtime ci <command>\n\n CI/CD checks each push and pull request of a connected GitHub repository in\n sandboxes, as the steps its config file names. A fork's pull request waits\n until a writer re-runs it on GitHub. In a private beta: outside it, each\n command says how to ask to join.\n\n import [dir] [--write] [--force] This folder's GitHub Actions workflows and CircleCI\n config as a runtime.yml: printed, or written with\n --write; what isn't carried over is noted at its top.\n Needs no key and sends nothing\n connect <owner/name> [--branch main] [--config runtime.yml] [--installation <id>]\n [--trust <app-slug>[bot]]... [--name <name>]\n Connect a repository; prints its id. Takes a full key.\n --trust runs that App's pushes as its own from the start\n repos [ls] [--all] Connected repositories, with the Apps each trusts;\n --all: disconnected ones too\n repos trust <repoId | owner/name> <app-slug>[bot]...\n Run that App's pushes as the repository's own code,\n keeping the Apps it trusts already\n repos untrust <repoId | owner/name> <app-slug>[bot]...\n Stop trusting those Apps, keeping the rest\n repos undo <repoId | owner/name> on|off When main's check fails, find the first commit that\n fails it and open a pull request reverting it, which\n lands once its check passes. Off until turned on;\n takes an owner's or admin's full key\n disconnect <repoId | owner/name> Stop checking it; its checks still going are cancelled\n ls [--repository <repoId>] [--branch <name>] [--commit <sha>] [--pr <number>]\n [--state <state>]... [--limit 20] Checks, newest first, with their jobs by state\n get <checkId> One check and its jobs in plan order\n cancel <checkId> Cancel a queued or running check: its jobs end,\n its machines are let go, GitHub is told\n logs <jobId> A job's output, its last 256 KiB, and its sandboxes\n tests <repoId> [--quarantined] Its test files: time, recent outcomes, quarantine\n flakes [--repository <repoId>] [--all] Flaky tests: each quarantined (or still gating), its\n owner and 48-hour deadline; --all: ended ones too\n flake <flakeId> One flaky test: its proof, owner, and recent runs\n flake prove <flakeId> Its 100 proof runs again at main's next commit\n flake hand <flakeId> <login | org/team> Hand it to someone else\n flake delete <flakeId> --reason <why> Say why its test is deleted; it ends once main\n no longer holds the file\n\n A failed job's sandbox may be kept paused: `runtime sandboxes wake <id>`, then\n `runtime sandboxes shell <id>` to look around where it failed.\n\nExamples\n runtime ci import --write\n runtime ci connect acme/web --config .runtime/ci.yml\n runtime ci repos trust acme/web acme-bot[bot]\n runtime ci repos undo acme/web on\n runtime ci ls --commit $(git rev-parse HEAD)\n runtime ci logs <jobId>\n";
8
+ export declare const CI_HELP = "runtime ci <command>\n\n CI/CD checks each push and pull request of a connected GitHub repository in\n sandboxes, as the steps its config file names. A fork's pull request runs\n nothing; a writer can open it again from a branch of the repository. In a\n private beta: outside it, each command says how to ask to join.\n\n import [dir] [--write] [--force] This folder's GitHub Actions workflows and CircleCI\n config as a runtime.yml: printed, or written with\n --write; what isn't carried over is noted at its top.\n Needs no key and sends nothing\n connect <owner/name> [--branch main] [--config runtime.yml] [--installation <id>]\n [--trust <app-slug>[bot]]... [--name <name>]\n Connect a repository; prints its id. Takes a full key.\n --trust runs that App's pushes as its own from the start\n repos [ls] [--all] Connected repositories, with the Apps each trusts;\n --all: disconnected ones too\n repos trust <repoId | owner/name> <app-slug>[bot]...\n Run that App's pushes as the repository's own code,\n keeping the Apps it trusts already\n repos untrust <repoId | owner/name> <app-slug>[bot]...\n Stop trusting those Apps, keeping the rest\n repos undo <repoId | owner/name> on|off When main's check fails, find the first commit that\n fails it and open a pull request reverting it, which\n lands once its check passes. Off until turned on;\n takes an owner's or admin's full key\n disconnect <repoId | owner/name> Stop checking it; its checks still going are cancelled\n checks [--repository <repoId>] [--branch <name>] [--commit <sha>] [--pr <number>]\n [--state <state>]... [--limit 20] Checks, newest first, with their jobs by state (or ls)\n check <checkId> One check and its jobs in plan order (or get)\n cancel <checkId> Cancel a queued or running check: its jobs end,\n its machines are let go, GitHub is told\n rerun <checkId> Run a check again: a new check of its commit, a\n pull request's as it would land now\n logs <jobId> A job's output, its last 256 KiB, and its sandboxes\n tests <repoId> [--quarantined] Its test files: time, recent outcomes, quarantine\n flakes [--repository <repoId>] [--all] Flaky tests: each quarantined (or still gating), its\n owner and 48-hour deadline; --all: ended ones too\n flake <flakeId> One flaky test: its proof, owner, and recent runs\n flake prove <flakeId> Its 100 proof runs again at main's next commit\n flake hand <flakeId> <login | org/team> Hand it to someone else\n flake delete <flakeId> --reason <why> Say why its test is deleted; it ends once main\n no longer holds the file\n releases [--repository <repoId>] [--commit <sha>] [--limit 50]\n A release's steps as they were taken, oldest\n first: migrations, deploys, servers, waits\n\n A failed job's sandbox may be kept paused: `runtime sandboxes wake <id>`, then\n `runtime sandboxes shell <id>` to look around where it failed. A key reads the\n checks of repositories its own agent connected; an account-wide key reads and\n cancels every check of the account (switch one at withruntime.com/account/keys).\n\nExamples\n runtime ci import --write\n runtime ci connect acme/web --config .runtime/ci.yml\n runtime ci repos trust acme/web acme-bot[bot]\n runtime ci repos undo acme/web on\n runtime ci checks --commit $(git rev-parse HEAD)\n runtime ci cancel <checkId>\n runtime ci logs <jobId>\n";
9
9
  export declare function ciCommand(argv: string[], out: Out, client: () => Promise<Runtime>, kit: CliKit): Promise<number>;
10
10
  export {};
package/dist/ci-cli.js CHANGED
@@ -6,9 +6,9 @@ import { ci, } from "./products/ci.js";
6
6
  export const CI_HELP = `runtime ci <command>
7
7
 
8
8
  CI/CD checks each push and pull request of a connected GitHub repository in
9
- sandboxes, as the steps its config file names. A fork's pull request waits
10
- until a writer re-runs it on GitHub. In a private beta: outside it, each
11
- command says how to ask to join.
9
+ sandboxes, as the steps its config file names. A fork's pull request runs
10
+ nothing; a writer can open it again from a branch of the repository. In a
11
+ private beta: outside it, each command says how to ask to join.
12
12
 
13
13
  import [dir] [--write] [--force] This folder's GitHub Actions workflows and CircleCI
14
14
  config as a runtime.yml: printed, or written with
@@ -30,11 +30,13 @@ export const CI_HELP = `runtime ci <command>
30
30
  lands once its check passes. Off until turned on;
31
31
  takes an owner's or admin's full key
32
32
  disconnect <repoId | owner/name> Stop checking it; its checks still going are cancelled
33
- ls [--repository <repoId>] [--branch <name>] [--commit <sha>] [--pr <number>]
34
- [--state <state>]... [--limit 20] Checks, newest first, with their jobs by state
35
- get <checkId> One check and its jobs in plan order
33
+ checks [--repository <repoId>] [--branch <name>] [--commit <sha>] [--pr <number>]
34
+ [--state <state>]... [--limit 20] Checks, newest first, with their jobs by state (or ls)
35
+ check <checkId> One check and its jobs in plan order (or get)
36
36
  cancel <checkId> Cancel a queued or running check: its jobs end,
37
37
  its machines are let go, GitHub is told
38
+ rerun <checkId> Run a check again: a new check of its commit, a
39
+ pull request's as it would land now
38
40
  logs <jobId> A job's output, its last 256 KiB, and its sandboxes
39
41
  tests <repoId> [--quarantined] Its test files: time, recent outcomes, quarantine
40
42
  flakes [--repository <repoId>] [--all] Flaky tests: each quarantined (or still gating), its
@@ -44,16 +46,22 @@ export const CI_HELP = `runtime ci <command>
44
46
  flake hand <flakeId> <login | org/team> Hand it to someone else
45
47
  flake delete <flakeId> --reason <why> Say why its test is deleted; it ends once main
46
48
  no longer holds the file
49
+ releases [--repository <repoId>] [--commit <sha>] [--limit 50]
50
+ A release's steps as they were taken, oldest
51
+ first: migrations, deploys, servers, waits
47
52
 
48
53
  A failed job's sandbox may be kept paused: \`runtime sandboxes wake <id>\`, then
49
- \`runtime sandboxes shell <id>\` to look around where it failed.
54
+ \`runtime sandboxes shell <id>\` to look around where it failed. A key reads the
55
+ checks of repositories its own agent connected; an account-wide key reads and
56
+ cancels every check of the account (switch one at withruntime.com/account/keys).
50
57
 
51
58
  Examples
52
59
  runtime ci import --write
53
60
  runtime ci connect acme/web --config .runtime/ci.yml
54
61
  runtime ci repos trust acme/web acme-bot[bot]
55
62
  runtime ci repos undo acme/web on
56
- runtime ci ls --commit $(git rev-parse HEAD)
63
+ runtime ci checks --commit $(git rev-parse HEAD)
64
+ runtime ci cancel <checkId>
57
65
  runtime ci logs <jobId>
58
66
  `;
59
67
  const when = (ms) => (ms === null ? "-" : new Date(ms).toISOString());
@@ -236,7 +244,8 @@ export async function ciCommand(argv, out, client, kit) {
236
244
  case "disconnect":
237
245
  return disconnect(need(rest[0], "the repository's id or owner/name"));
238
246
  case "ls":
239
- case "list": {
247
+ case "list":
248
+ case "checks": {
240
249
  const args = parse(rest, [], ["repository", "branch", "commit", "pr", "state", "limit"]);
241
250
  const commit = flag(args, "commit");
242
251
  if (commit !== undefined && !/^([0-9a-f]{40}|[0-9a-f]{64})$/.test(commit))
@@ -273,7 +282,8 @@ export async function ciCommand(argv, out, client, kit) {
273
282
  : "No checks.", all);
274
283
  return 0;
275
284
  }
276
- case "get": {
285
+ case "get":
286
+ case "check": {
277
287
  const check = await api.checks.get(need(rest[0], "a check's id"));
278
288
  const job = (j) => [
279
289
  j.id,
@@ -294,9 +304,24 @@ export async function ciCommand(argv, out, client, kit) {
294
304
  }
295
305
  case "cancel": {
296
306
  const check = await api.checks.cancel(need(rest[0], "a check's id (runtime ci ls)"));
307
+ // A key that may cancel the check but not read it is told its state alone.
308
+ if (!("commit" in check)) {
309
+ print(`${check.id}: ${check.state}`, check);
310
+ return 0;
311
+ }
297
312
  print(`${check.repository} ${check.commit.slice(0, 12)} ${where(check)}: ${check.state}${check.reason ? ` (${check.reason})` : ""}`, check);
298
313
  return 0;
299
314
  }
315
+ case "rerun": {
316
+ const check = await api.checks.rerun(need(rest[0], "a check's id (runtime ci ls)"));
317
+ // A key that may run the check again but not read it is told the new one's id alone.
318
+ if (!("commit" in check)) {
319
+ print(`${check.id}: ${check.state}`, check);
320
+ return 0;
321
+ }
322
+ print(`${check.id} ${check.repository} ${check.commit.slice(0, 12)} ${where(check)}: ${check.state}${check.reason ? ` (${check.reason})` : ""}`, check);
323
+ return 0;
324
+ }
300
325
  case "logs":
301
326
  case "job": {
302
327
  const job = await api.jobs.get(need(rest[0], "a job's id (runtime ci get <checkId>)"));
@@ -370,6 +395,30 @@ export async function ciCommand(argv, out, client, kit) {
370
395
  ].join("\n"), f);
371
396
  return 0;
372
397
  }
398
+ case "releases": {
399
+ const args = parse(rest, [], ["repository", "commit", "limit"]);
400
+ const repositoryId = flag(args, "repository");
401
+ const commit = flag(args, "commit");
402
+ const limit = flag(args, "limit");
403
+ const steps = (await api.releases.list({
404
+ limit: limit ? whole(limit, "--limit") : 50,
405
+ ...(repositoryId ? { repositoryId } : {}),
406
+ ...(commit ? { commit } : {}),
407
+ })).data.reverse();
408
+ print(steps.length
409
+ ? table([
410
+ ["AT", "RELEASE", "BY", "SERVER", "STEP"],
411
+ ...steps.map((s) => [
412
+ when(s.at),
413
+ s.commit.slice(0, 9),
414
+ s.source,
415
+ s.host ?? "-",
416
+ s.line,
417
+ ]),
418
+ ])
419
+ : "No release steps.", steps);
420
+ return 0;
421
+ }
373
422
  case "tests": {
374
423
  const args = parse(rest, ["quarantined"], []);
375
424
  const repositoryId = need(args.positional[0], "a repository's id (runtime ci repos)");
@@ -398,24 +447,28 @@ export async function ciCommand(argv, out, client, kit) {
398
447
  "connect",
399
448
  "repos",
400
449
  "disconnect",
450
+ "checks",
451
+ "check",
401
452
  "ls",
402
453
  "get",
403
454
  "cancel",
455
+ "rerun",
404
456
  "logs",
405
457
  "tests",
406
458
  "flakes",
407
459
  "flake",
460
+ "releases",
408
461
  ]);
409
462
  }
410
- /** A review's verdict in words: each finding with its place and fix, then
411
- * the notes, then who reviewed. */
463
+ /** An agent step's verdict in words: each finding with its place and fix,
464
+ * then the notes, then who judged. */
412
465
  function reviewed(r) {
413
466
  const at = (f) => (f.line > 0 ? `${f.file}:${f.line}` : f.file);
414
467
  return [
415
468
  `\n${r.verdict === "pass" ? "Passed review" : `Blocked: ${r.findings.length} finding(s)`}. ${r.summary}`,
416
469
  ...r.findings.map((f) => ` ${f.kind} at ${at(f)}: ${f.what}\n Fix: ${f.fix}`),
417
470
  ...r.notes.map((n) => ` note at ${at(n)}: ${n.what}`),
418
- `Reviewed by agent ${r.agent} (${r.model}) on ${r.account}${r.cut ? ", diff cut short" : ""}: session ${r.session}`,
471
+ `Judged by agent ${r.agent}${r.model ? ` (${r.model})` : ""}${r.account ? ` on ${r.account}` : ""}${r.cut ? ", diff cut short" : ""}: session ${r.session}`,
419
472
  "",
420
473
  ].join("\n");
421
474
  }
package/dist/cli.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  import { type Sandbox } from "./sandbox.js";
3
3
  import { RuntimeError } from "./errors.js";
4
4
  import type { OutOfMemory, Usage } from "./types.js";
5
+ import type { UsageExportPage } from "./products/observability.js";
5
6
  import type { SwitchProvider } from "./products/switching.js";
6
7
  import type { SecretRule, SecretScope } from "./products/secrets.js";
7
8
  type Out = {
@@ -38,6 +39,9 @@ declare const kit: {
38
39
  };
39
40
  export type CliKit = typeof kit;
40
41
  export declare function run(argv: string[], env?: NodeJS.ProcessEnv, out?: Out): Promise<number>;
42
+ /** `usage export`'s last word, on stderr beside the CSV: how much of the
43
+ * range can still change. */
44
+ export declare function finalLine(page: Pick<UsageExportPage, "until" | "finalThrough">): string;
41
45
  /** `runtime usage` for a person: the balance, the free usage and what each kind
42
46
  * of resource was charged, in dollars. --json keeps every figure, and each
43
47
  * resource with its rates and CPU time. */