withruntime 0.33.3 → 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.
- package/E2B.md +7 -0
- package/dist/agents-cli.d.ts +1 -1
- package/dist/agents-cli.js +171 -7
- package/dist/agents-deploy.d.ts +13 -3
- package/dist/agents-deploy.js +68 -9
- package/dist/apps-cli.d.ts +1 -1
- package/dist/apps-cli.js +41 -9
- package/dist/browsers/index.d.ts +9 -0
- package/dist/browsers/index.js +10 -0
- package/dist/browsers-cli.d.ts +13 -0
- package/dist/browsers-cli.js +160 -0
- package/dist/ci-cli.d.ts +1 -1
- package/dist/ci-cli.js +16 -3
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +94 -10
- package/dist/desktops/index.d.ts +9 -0
- package/dist/desktops/index.js +10 -0
- package/dist/e2b/code-interpreter.d.ts +3 -12
- package/dist/e2b/code-interpreter.js +3 -6
- package/dist/e2b/index.d.ts +7 -1
- package/dist/e2b/index.js +9 -2
- package/dist/index.d.ts +5 -3
- package/dist/products/agents.d.ts +162 -8
- package/dist/products/agents.js +108 -3
- package/dist/products/apps.d.ts +46 -5
- package/dist/products/browsers.d.ts +26 -0
- package/dist/products/browsers.js +11 -0
- package/dist/products/ci.d.ts +10 -4
- package/dist/products/ci.js +8 -0
- package/dist/products/desktops.d.ts +71 -0
- package/dist/products/desktops.js +27 -0
- package/dist/products/index.d.ts +4 -0
- package/dist/products/index.js +5 -0
- package/dist/products/network.d.ts +4 -2
- package/dist/products/secrets.d.ts +10 -2
- package/dist/products/secrets.js +27 -7
- package/dist/products/uptime.d.ts +97 -0
- package/dist/products/uptime.js +42 -0
- package/dist/products/volumes.d.ts +41 -9
- package/dist/products/volumes.js +5 -2
- package/dist/transport.d.ts +4 -1
- package/dist/transport.js +2 -2
- package/package.json +12 -1
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. --
|
|
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
|
|
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
|
-
? {
|
|
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(
|
|
423
|
-
|
|
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 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 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";
|
|
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
|
@@ -35,6 +35,8 @@ export const CI_HELP = `runtime ci <command>
|
|
|
35
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
|
|
@@ -310,6 +312,16 @@ export async function ciCommand(argv, out, client, kit) {
|
|
|
310
312
|
print(`${check.repository} ${check.commit.slice(0, 12)} ${where(check)}: ${check.state}${check.reason ? ` (${check.reason})` : ""}`, check);
|
|
311
313
|
return 0;
|
|
312
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
|
+
}
|
|
313
325
|
case "logs":
|
|
314
326
|
case "job": {
|
|
315
327
|
const job = await api.jobs.get(need(rest[0], "a job's id (runtime ci get <checkId>)"));
|
|
@@ -440,6 +452,7 @@ export async function ciCommand(argv, out, client, kit) {
|
|
|
440
452
|
"ls",
|
|
441
453
|
"get",
|
|
442
454
|
"cancel",
|
|
455
|
+
"rerun",
|
|
443
456
|
"logs",
|
|
444
457
|
"tests",
|
|
445
458
|
"flakes",
|
|
@@ -447,15 +460,15 @@ export async function ciCommand(argv, out, client, kit) {
|
|
|
447
460
|
"releases",
|
|
448
461
|
]);
|
|
449
462
|
}
|
|
450
|
-
/**
|
|
451
|
-
* the notes, then who
|
|
463
|
+
/** An agent step's verdict in words: each finding with its place and fix,
|
|
464
|
+
* then the notes, then who judged. */
|
|
452
465
|
function reviewed(r) {
|
|
453
466
|
const at = (f) => (f.line > 0 ? `${f.file}:${f.line}` : f.file);
|
|
454
467
|
return [
|
|
455
468
|
`\n${r.verdict === "pass" ? "Passed review" : `Blocked: ${r.findings.length} finding(s)`}. ${r.summary}`,
|
|
456
469
|
...r.findings.map((f) => ` ${f.kind} at ${at(f)}: ${f.what}\n Fix: ${f.fix}`),
|
|
457
470
|
...r.notes.map((n) => ` note at ${at(n)}: ${n.what}`),
|
|
458
|
-
`
|
|
471
|
+
`Judged by agent ${r.agent}${r.model ? ` (${r.model})` : ""}${r.account ? ` on ${r.account}` : ""}${r.cut ? ", diff cut short" : ""}: session ${r.session}`,
|
|
459
472
|
"",
|
|
460
473
|
].join("\n");
|
|
461
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. */
|
package/dist/cli.js
CHANGED
|
@@ -69,7 +69,9 @@ Account
|
|
|
69
69
|
(job create --secret NAME); with --host, both
|
|
70
70
|
secrets scope <NAME> --only-label k=v... | --only-sandbox <id>... | --only-app <id>... | --all-sandboxes
|
|
71
71
|
Change which sandboxes get it; no value needed
|
|
72
|
-
secrets ls | get <NAME>
|
|
72
|
+
secrets ls [--limit N] [--after NAME] | get <NAME>
|
|
73
|
+
Names, hosts, placeholders and jobs versions, never values;
|
|
74
|
+
by name, a page of N after NAME
|
|
73
75
|
secrets rotate <NAME> A new version of the jobs copy, from standard input
|
|
74
76
|
secrets reveal <NAME> [--version N] Print the jobs copy's value. The sandboxes' copy is
|
|
75
77
|
never readable, by anyone
|
|
@@ -136,6 +138,8 @@ Products
|
|
|
136
138
|
builds A connected repository's commits built into what an App deploys (beta)
|
|
137
139
|
ci CI/CD: each push and pull request of a repository checked in sandboxes (beta)
|
|
138
140
|
containers One OCI image's process on its own microVM, at the sandbox rates (beta)
|
|
141
|
+
browsers Chromium on its own sandbox: a CDP address and screenshots (beta)
|
|
142
|
+
desktops A Linux desktop on its own sandbox: live view, computer use (beta)
|
|
139
143
|
agents Claude Code, Codex, Gemini CLI or your own agent in a sandbox per session (beta)
|
|
140
144
|
domains Your own hostname for a sandbox's port, with HTTPS
|
|
141
145
|
ports A public TCP port to a sandbox: databases, game servers
|
|
@@ -476,6 +480,8 @@ const COMMANDS = [
|
|
|
476
480
|
"builds",
|
|
477
481
|
"ci",
|
|
478
482
|
"containers",
|
|
483
|
+
"browsers",
|
|
484
|
+
"desktops",
|
|
479
485
|
"agents",
|
|
480
486
|
"domains",
|
|
481
487
|
"ports",
|
|
@@ -500,6 +506,8 @@ const OWN_HELP = [
|
|
|
500
506
|
"builds",
|
|
501
507
|
"ci",
|
|
502
508
|
"containers",
|
|
509
|
+
"browsers",
|
|
510
|
+
"desktops",
|
|
503
511
|
"agents",
|
|
504
512
|
"keys",
|
|
505
513
|
"reservations",
|
|
@@ -872,6 +880,8 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
|
|
|
872
880
|
const rt = await client(env);
|
|
873
881
|
const cursors = new Set();
|
|
874
882
|
let cursor;
|
|
883
|
+
// The export is final only as far as its earliest page's finalThrough.
|
|
884
|
+
let finalThrough;
|
|
875
885
|
for (;;) {
|
|
876
886
|
const page = await rt.usageExport({
|
|
877
887
|
since,
|
|
@@ -891,8 +901,19 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
|
|
|
891
901
|
else if (page.csv)
|
|
892
902
|
out.write(page.csv);
|
|
893
903
|
await out.drain?.();
|
|
894
|
-
|
|
904
|
+
const at = page.finalThrough;
|
|
905
|
+
finalThrough =
|
|
906
|
+
finalThrough === undefined
|
|
907
|
+
? at
|
|
908
|
+
: finalThrough === null || at === null
|
|
909
|
+
? null
|
|
910
|
+
: at < finalThrough
|
|
911
|
+
? at
|
|
912
|
+
: finalThrough;
|
|
913
|
+
if (page.nextCursor === null) {
|
|
914
|
+
out.error(finalLine({ until: page.until, finalThrough: finalThrough ?? null }));
|
|
895
915
|
return 0;
|
|
916
|
+
}
|
|
896
917
|
cursors.add(page.nextCursor);
|
|
897
918
|
cursor = page.nextCursor;
|
|
898
919
|
}
|
|
@@ -1351,6 +1372,10 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
|
|
|
1351
1372
|
product = "containers";
|
|
1352
1373
|
return (await import("./apps-cli.js")).containersCommand(rest, env, out, () => client(env), kit);
|
|
1353
1374
|
}
|
|
1375
|
+
if (["browser", "browsers", "desktop", "desktops"].includes(command)) {
|
|
1376
|
+
product = command.endsWith("s") ? command : `${command}s`;
|
|
1377
|
+
return (await import("./browsers-cli.js")).machineProductCommand(product, rest, out, () => client(env), kit);
|
|
1378
|
+
}
|
|
1354
1379
|
if (command === "agent" || command === "agents") {
|
|
1355
1380
|
product = "agents";
|
|
1356
1381
|
return (await import("./agents-cli.js")).agentsCommand(rest, env, out, () => client(env), kit);
|
|
@@ -1365,6 +1390,15 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
|
|
|
1365
1390
|
}
|
|
1366
1391
|
throw unknown("command", command, COMMANDS);
|
|
1367
1392
|
}
|
|
1393
|
+
/** `usage export`'s last word, on stderr beside the CSV: how much of the
|
|
1394
|
+
* range can still change. */
|
|
1395
|
+
export function finalLine(page) {
|
|
1396
|
+
if (page.finalThrough === null)
|
|
1397
|
+
return "Charges in this range may still settle; export it again later for a final copy.";
|
|
1398
|
+
if (Date.parse(page.finalThrough) >= Date.parse(page.until))
|
|
1399
|
+
return "Final: no more charges will settle in this range.";
|
|
1400
|
+
return `Final up to ${page.finalThrough}; charges after that may still settle. Export again later for the rest.`;
|
|
1401
|
+
}
|
|
1368
1402
|
/** `runtime usage` for a person: the balance, the free usage and what each kind
|
|
1369
1403
|
* of resource was charged, in dollars. --json keeps every figure, and each
|
|
1370
1404
|
* resource with its rates and CPU time. */
|
|
@@ -2689,6 +2723,8 @@ read = readSecret) {
|
|
|
2689
2723
|
"only-app",
|
|
2690
2724
|
"agent",
|
|
2691
2725
|
"env",
|
|
2726
|
+
"limit",
|
|
2727
|
+
"after",
|
|
2692
2728
|
]);
|
|
2693
2729
|
const print = (human, value) => out.write(out.json ? JSON.stringify(value) : human);
|
|
2694
2730
|
const rows = (list) => list.length
|
|
@@ -2706,11 +2742,19 @@ read = readSecret) {
|
|
|
2706
2742
|
])
|
|
2707
2743
|
: "No secrets.";
|
|
2708
2744
|
if (verb === "ls" || verb === "list" || verb === undefined) {
|
|
2745
|
+
const limit = integer(args, "limit");
|
|
2746
|
+
if (limit !== undefined && (limit < 1 || limit > 100))
|
|
2747
|
+
throw usage("--limit takes 1 to 100 secrets a page.");
|
|
2748
|
+
const after = flag(args, "after");
|
|
2709
2749
|
const runtime = await client(env);
|
|
2710
|
-
|
|
2750
|
+
// A page of both copies by name, as the API pages each: an organization
|
|
2751
|
+
// keeps at most 50 secrets for sandboxes, so both are read whole.
|
|
2752
|
+
const every = (await runtime.secrets.all()).filter((s) => after === undefined || s.name > after);
|
|
2753
|
+
const all = limit === undefined ? every : every.slice(0, limit);
|
|
2754
|
+
const more = all.length < every.length ? all.at(-1).name : undefined;
|
|
2711
2755
|
// --json keeps the sandboxes' list it always printed; the jobs copies
|
|
2712
2756
|
// are in `runtime secrets get <NAME> --json`.
|
|
2713
|
-
print(rows(all), all.flatMap((s) => (s.sandboxes ? [s.sandboxes] : [])));
|
|
2757
|
+
print(more ? `${rows(all)}\nMore: ${me} secrets ls --limit ${limit} --after ${more}` : rows(all), all.flatMap((s) => (s.sandboxes ? [s.sandboxes] : [])));
|
|
2714
2758
|
return 0;
|
|
2715
2759
|
}
|
|
2716
2760
|
const jobsOnly = "Only a secret's copy for jobs has versions and can be read back";
|
|
@@ -4377,10 +4421,39 @@ const VOLUME_HELP = `runtime volumes <command>
|
|
|
4377
4421
|
backup-rm <backupId> Delete a backup
|
|
4378
4422
|
backup-policy <id> [--daily on|off] [--retention-days N]
|
|
4379
4423
|
Daily backups on or off, and how long each is kept
|
|
4424
|
+
backup-policy <id> --every <minutes> | --cron "<m h dom mon dow>" [--keep hourly=24,daily=7,...]
|
|
4425
|
+
Back up on a schedule of its own (UTC), keeping
|
|
4426
|
+
last, hourly, daily, weekly, monthly, yearly counts
|
|
4427
|
+
backup-policy <id> --no-schedule Back to daily backups
|
|
4380
4428
|
Attach one: runtime sandboxes create --volume <id>:/data
|
|
4381
|
-
Volumes are backed up off their host daily unless you turn it off.
|
|
4429
|
+
Volumes are backed up off their host daily, or on their own schedule, unless you turn it off.
|
|
4382
4430
|
Shared commands work where shared disks are enabled.
|
|
4383
4431
|
`;
|
|
4432
|
+
const KEEP_TIERS = ["last", "hourly", "daily", "weekly", "monthly", "yearly"];
|
|
4433
|
+
/** `hourly=24,daily=7` as keep counts; the server checks the numbers. */
|
|
4434
|
+
function keepCounts(text) {
|
|
4435
|
+
const counts = {};
|
|
4436
|
+
for (const part of text.split(",")) {
|
|
4437
|
+
const [name, value, extra] = part.trim().split("=");
|
|
4438
|
+
const tier = KEEP_TIERS.find((t) => t === name);
|
|
4439
|
+
if (!tier || value === undefined || extra !== undefined || !/^\d+$/.test(value))
|
|
4440
|
+
throw usage(`--keep takes name=count pairs such as hourly=24,daily=7; names: ${KEEP_TIERS.join(", ")}.`);
|
|
4441
|
+
counts[tier] = Number(value);
|
|
4442
|
+
}
|
|
4443
|
+
return counts;
|
|
4444
|
+
}
|
|
4445
|
+
/** "daily backups on, kept 7 days", or the schedule, its keep counts and next run. */
|
|
4446
|
+
function backupPolicyWords(b) {
|
|
4447
|
+
if (!b.schedule)
|
|
4448
|
+
return `daily backups ${b.daily ? "on" : "off"}, kept ${b.retentionDays} days`;
|
|
4449
|
+
const when = "everyMinutes" in b.schedule
|
|
4450
|
+
? `every ${b.schedule.everyMinutes} minutes`
|
|
4451
|
+
: `on cron "${b.schedule.cron.trim()}" (UTC)`;
|
|
4452
|
+
const kept = KEEP_TIERS.filter((t) => (b.keep?.[t] ?? 0) > 0)
|
|
4453
|
+
.map((t) => `${t} ${b.keep[t]}`)
|
|
4454
|
+
.join(", ");
|
|
4455
|
+
return `backups ${when}${b.daily ? "" : " (off)"}, keeping ${kept}${b.daily && b.nextAt ? `; next ${b.nextAt}` : ""}`;
|
|
4456
|
+
}
|
|
4384
4457
|
async function volume(argv, env, out) {
|
|
4385
4458
|
const [verb, ...rest] = argv;
|
|
4386
4459
|
const print = (human, value) => out.write(out.json ? JSON.stringify(value) : human);
|
|
@@ -4489,7 +4562,7 @@ async function volume(argv, env, out) {
|
|
|
4489
4562
|
String(b.sizeMiB),
|
|
4490
4563
|
b.storedBytes === null ? "-" : `${Math.ceil(b.storedBytes / 1048576)} MiB`,
|
|
4491
4564
|
b.createdAt,
|
|
4492
|
-
b.expiresAt,
|
|
4565
|
+
b.expiresAt ?? "by keep counts",
|
|
4493
4566
|
]),
|
|
4494
4567
|
])
|
|
4495
4568
|
: "No backups yet.", all);
|
|
@@ -4510,19 +4583,30 @@ async function volume(argv, env, out) {
|
|
|
4510
4583
|
return 0;
|
|
4511
4584
|
}
|
|
4512
4585
|
case "backup-policy": {
|
|
4513
|
-
const args = parse(rest, [], ["daily", "retention-days"]);
|
|
4586
|
+
const args = parse(rest, ["no-schedule"], ["daily", "retention-days", "every", "cron", "keep"]);
|
|
4514
4587
|
const id = need(args.positional[0], "a volume id");
|
|
4515
4588
|
const daily = flag(args, "daily");
|
|
4516
4589
|
if (daily !== undefined && !["on", "off", "true", "false"].includes(daily))
|
|
4517
4590
|
throw usage("--daily takes on or off.");
|
|
4518
4591
|
const days = integer(args, "retention-days");
|
|
4519
|
-
|
|
4520
|
-
|
|
4592
|
+
const every = integer(args, "every");
|
|
4593
|
+
const cron = flag(args, "cron");
|
|
4594
|
+
const off = has(args, "no-schedule");
|
|
4595
|
+
if ([every !== undefined, cron !== undefined, off].filter(Boolean).length > 1)
|
|
4596
|
+
throw usage("Give one of --every, --cron and --no-schedule.");
|
|
4597
|
+
const keepText = flag(args, "keep");
|
|
4598
|
+
const keep = keepText === undefined ? undefined : keepCounts(keepText);
|
|
4599
|
+
if ([daily, days, every, cron, keep].every((x) => x === undefined) && !off)
|
|
4600
|
+
throw usage("Give --daily on|off, --retention-days N, --every, --cron, --keep or --no-schedule.");
|
|
4521
4601
|
const updated = await api.setBackupPolicy(id, {
|
|
4522
4602
|
...(daily === undefined ? {} : { daily: daily === "on" || daily === "true" }),
|
|
4523
4603
|
...(days === undefined ? {} : { retentionDays: days }),
|
|
4604
|
+
...(every === undefined ? {} : { schedule: { everyMinutes: every } }),
|
|
4605
|
+
...(cron === undefined ? {} : { schedule: { cron } }),
|
|
4606
|
+
...(off ? { schedule: null } : {}),
|
|
4607
|
+
...(keep === undefined ? {} : { keep }),
|
|
4524
4608
|
});
|
|
4525
|
-
print(`${updated.id}
|
|
4609
|
+
print(`${updated.id} ${backupPolicyWords(updated.backups)}`, updated);
|
|
4526
4610
|
return 0;
|
|
4527
4611
|
}
|
|
4528
4612
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** `withruntime/desktops`: Desktops alone. A Linux desktop on its own sandbox, with a live view and computer use, at the sandbox rates. In a private beta.
|
|
2
|
+
*
|
|
3
|
+
* import { desktops } from "withruntime/desktops";
|
|
4
|
+
* const client = desktops(); // RUNTIME_API_KEY, or this machine's saved connection
|
|
5
|
+
*/
|
|
6
|
+
import { Runtime, type RuntimeOptions } from "../client.js";
|
|
7
|
+
export type * from "../products/desktops.js";
|
|
8
|
+
/** `runtime.desktops` of a client made with `options`. */
|
|
9
|
+
export declare function desktops(options?: RuntimeOptions): Runtime["desktops"];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** `withruntime/desktops`: Desktops alone. A Linux desktop on its own sandbox, with a live view and computer use, at the sandbox rates. In a private beta.
|
|
2
|
+
*
|
|
3
|
+
* import { desktops } from "withruntime/desktops";
|
|
4
|
+
* const client = desktops(); // RUNTIME_API_KEY, or this machine's saved connection
|
|
5
|
+
*/
|
|
6
|
+
import { Runtime } from "../client.js";
|
|
7
|
+
/** `runtime.desktops` of a client made with `options`. */
|
|
8
|
+
export function desktops(options) {
|
|
9
|
+
return new Runtime(options).desktops;
|
|
10
|
+
}
|