withruntime 0.18.0 → 0.23.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/COMPATIBILITY.md +32 -1
- package/README.md +19 -0
- package/README.src.md +19 -0
- package/dist/agents/index.d.ts +11 -0
- package/dist/agents/index.js +12 -0
- package/dist/agents-cli.d.ts +10 -0
- package/dist/agents-cli.js +362 -0
- package/dist/apps-cli.d.ts +2 -0
- package/dist/apps-cli.js +173 -0
- package/dist/builds/index.d.ts +10 -0
- package/dist/builds/index.js +11 -0
- package/dist/cli-words.js +1 -0
- package/dist/cli.js +176 -21
- package/dist/client.d.ts +19 -0
- package/dist/client.js +20 -0
- package/dist/cloudflare/errors.d.ts +23 -0
- package/dist/cloudflare/errors.js +16 -0
- package/dist/cloudflare/index.d.ts +82 -9
- package/dist/cloudflare/index.js +162 -38
- package/dist/cloudflare/interpreter.js +14 -5
- package/dist/cloudflare/mime.d.ts +1 -0
- package/dist/cloudflare/mime.js +66 -0
- package/dist/cloudflare/streams.d.ts +8 -0
- package/dist/cloudflare/streams.js +30 -0
- package/dist/cloudflare/types.d.ts +338 -0
- package/dist/cloudflare/types.js +80 -0
- package/dist/cloudflare/worker.d.ts +297 -10
- package/dist/cloudflare/worker.js +203 -17
- package/dist/containers/index.d.ts +10 -0
- package/dist/containers/index.js +11 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/keys.d.ts +6 -0
- package/dist/keys.js +10 -1
- package/dist/product-names.js +10 -0
- package/dist/products/agents.d.ts +272 -0
- package/dist/products/agents.js +251 -0
- package/dist/products/containers.d.ts +71 -0
- package/dist/products/containers.js +6 -0
- package/dist/products/index.d.ts +6 -0
- package/dist/products/index.js +7 -0
- package/dist/products/secrets.d.ts +19 -0
- package/dist/products/secrets.js +30 -0
- package/dist/sandbox.d.ts +4 -0
- package/dist/transport.d.ts +2 -2
- package/dist/transport.js +1 -1
- package/dist/tunnel.js +1 -1
- package/dist/types.d.ts +11 -0
- package/package.json +16 -1
package/dist/apps-cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import { RuntimeError } from "./errors.js";
|
|
|
3
3
|
import { logStream } from "./terminal.js";
|
|
4
4
|
import { apps, } from "./products/apps.js";
|
|
5
5
|
import { builds, } from "./products/builds.js";
|
|
6
|
+
import { containers, } from "./products/containers.js";
|
|
6
7
|
export const APP_HELP = `runtime apps <command>
|
|
7
8
|
|
|
8
9
|
An App serves your web server from an image or a build. Copies start when
|
|
@@ -73,6 +74,34 @@ Examples
|
|
|
73
74
|
runtime builds run <repoId> --ref main
|
|
74
75
|
runtime builds ls --commit $(git rev-parse HEAD)
|
|
75
76
|
`;
|
|
77
|
+
export const CONTAINER_HELP = `runtime containers <command>
|
|
78
|
+
|
|
79
|
+
A container runs one OCI image's process on its own microVM, billed at the
|
|
80
|
+
sandbox rates. It never sleeps unless --sleep; on exit it restarts when it
|
|
81
|
+
serves a port, else it stops (--on-exit sets either).
|
|
82
|
+
In a private beta: outside it, each command says how to ask to join.
|
|
83
|
+
|
|
84
|
+
create <image> --vcpu <1-16> --memory <128-65536> [--name <name>] [--label key=value]...
|
|
85
|
+
[--env KEY[=VALUE]]... [--entrypoint <program>] [--workdir /app] [--user <user>]
|
|
86
|
+
[--port <1-65535>] [--on-exit restart|stop] [--sleep] [-- <command...>]
|
|
87
|
+
Run it; prints its id. The image's digest is pinned.
|
|
88
|
+
--env KEY alone sends KEY's value from your environment.
|
|
89
|
+
--entrypoint drops the image's command; after --, the
|
|
90
|
+
command replaces the image's
|
|
91
|
+
ls [--all] [--state <state>]... [--label key=value]...
|
|
92
|
+
Your containers: all but deleting and deleted, unless
|
|
93
|
+
--all or --state
|
|
94
|
+
get <container> One container, in full (an id or its name): its
|
|
95
|
+
environment's names, never their values
|
|
96
|
+
update <container> [--name <name>] [--label key=value]...
|
|
97
|
+
Rename it or replace its labels
|
|
98
|
+
rm <container> [--wait] Delete it and stop its process; --wait: until deleted
|
|
99
|
+
|
|
100
|
+
Examples
|
|
101
|
+
runtime containers create nginx:1.27 --vcpu 1 --memory 512 --name web
|
|
102
|
+
runtime containers create python:3.12 --vcpu 1 --memory 1024 -- python3 -m http.server 8000
|
|
103
|
+
runtime containers rm web --wait
|
|
104
|
+
`;
|
|
76
105
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
77
106
|
const when = (ms) => (ms == null ? "-" : new Date(ms).toISOString());
|
|
78
107
|
const ENDED = ["succeeded", "failed", "cancelled", "superseded"];
|
|
@@ -562,3 +591,147 @@ export async function buildsCommand(argv, out, client, kit) {
|
|
|
562
591
|
"logs",
|
|
563
592
|
]);
|
|
564
593
|
}
|
|
594
|
+
const CONTAINER_STATES = [
|
|
595
|
+
"waiting",
|
|
596
|
+
"running",
|
|
597
|
+
"sleeping",
|
|
598
|
+
"stopped",
|
|
599
|
+
"failed",
|
|
600
|
+
"deleting",
|
|
601
|
+
"deleted",
|
|
602
|
+
];
|
|
603
|
+
const CONTAINER_VALUES = [
|
|
604
|
+
"vcpu",
|
|
605
|
+
"memory",
|
|
606
|
+
"name",
|
|
607
|
+
"label",
|
|
608
|
+
"env",
|
|
609
|
+
"entrypoint",
|
|
610
|
+
"workdir",
|
|
611
|
+
"user",
|
|
612
|
+
"port",
|
|
613
|
+
"on-exit",
|
|
614
|
+
];
|
|
615
|
+
export async function containersCommand(argv, env, out, client, kit) {
|
|
616
|
+
const { parse, flag, has, integer, need, pairs, envPairs, table, usage } = kit;
|
|
617
|
+
const [verb, ...rest] = argv;
|
|
618
|
+
const print = (human, value) => out.write(out.json ? JSON.stringify(value) : human);
|
|
619
|
+
if (!verb || verb === "help" || verb === "--help") {
|
|
620
|
+
print(named(CONTAINER_HELP), { usage: named(CONTAINER_HELP) });
|
|
621
|
+
return 0;
|
|
622
|
+
}
|
|
623
|
+
const api = containers((await client()).transport);
|
|
624
|
+
/** A container's id from its id or its name: a name is looked up among the
|
|
625
|
+
* containers not deleted, and must be one container's alone. */
|
|
626
|
+
const idOf = async (target) => {
|
|
627
|
+
const given = need(target, "a container's id or name");
|
|
628
|
+
if (UUID.test(given))
|
|
629
|
+
return given;
|
|
630
|
+
const found = (await api.list({ name: given })).data;
|
|
631
|
+
if (found.length === 1)
|
|
632
|
+
return found[0].id;
|
|
633
|
+
throw usage(found.length
|
|
634
|
+
? `${found.length} containers are named ${given}; give one's id (${me} containers ls).`
|
|
635
|
+
: `No container is named ${given}. Your containers: ${me} containers ls`);
|
|
636
|
+
};
|
|
637
|
+
switch (verb) {
|
|
638
|
+
case "create": {
|
|
639
|
+
const args = parse(rest, ["sleep"], CONTAINER_VALUES);
|
|
640
|
+
if (args.positional.length !== 1)
|
|
641
|
+
throw usage(`Give one image: ${me} containers create nginx:1.27 --vcpu 1 --memory 512 [-- <command...>]`);
|
|
642
|
+
const image = args.positional[0];
|
|
643
|
+
const vcpu = integer(args, "vcpu");
|
|
644
|
+
const memoryMiB = integer(args, "memory");
|
|
645
|
+
if (vcpu === undefined || memoryMiB === undefined)
|
|
646
|
+
throw usage(`Give --vcpu and --memory: ${me} containers create ${image} --vcpu 1 --memory 512`);
|
|
647
|
+
const onExit = flag(args, "on-exit");
|
|
648
|
+
if (onExit !== undefined && onExit !== "restart" && onExit !== "stop")
|
|
649
|
+
throw usage("--on-exit takes restart or stop.");
|
|
650
|
+
const port = integer(args, "port");
|
|
651
|
+
const entrypoint = flag(args, "entrypoint");
|
|
652
|
+
const workingDir = flag(args, "workdir");
|
|
653
|
+
const user = flag(args, "user");
|
|
654
|
+
const name = flag(args, "name");
|
|
655
|
+
const input = {
|
|
656
|
+
image,
|
|
657
|
+
vcpu,
|
|
658
|
+
memoryMiB,
|
|
659
|
+
...(name === undefined ? {} : { name }),
|
|
660
|
+
...(args.flags.has("label") ? { labels: pairs(args, "label") } : {}),
|
|
661
|
+
...(args.flags.has("env") ? { env: envPairs(args, env) } : {}),
|
|
662
|
+
...(entrypoint === undefined ? {} : { entrypoint: [entrypoint] }),
|
|
663
|
+
...(args.rest?.length ? { command: args.rest } : {}),
|
|
664
|
+
...(workingDir === undefined ? {} : { workingDir }),
|
|
665
|
+
...(user === undefined ? {} : { user }),
|
|
666
|
+
...(port === undefined ? {} : { port }),
|
|
667
|
+
...(onExit === undefined ? {} : { onExit }),
|
|
668
|
+
...(has(args, "sleep") ? { sleep: true } : {}),
|
|
669
|
+
};
|
|
670
|
+
const made = await api.create(input);
|
|
671
|
+
if (!out.json)
|
|
672
|
+
out.error(`${made.name ?? made.id} is ${made.state}, running ${made.image}. Read it: ${me} containers get ${made.name ?? made.id}`);
|
|
673
|
+
print(made.id, made);
|
|
674
|
+
return 0;
|
|
675
|
+
}
|
|
676
|
+
case "ls":
|
|
677
|
+
case "list": {
|
|
678
|
+
const args = parse(rest, ["all"], ["state", "label"]);
|
|
679
|
+
const states = args.flags.get("state");
|
|
680
|
+
for (const state of states ?? [])
|
|
681
|
+
if (!CONTAINER_STATES.includes(state))
|
|
682
|
+
throw usage(`--state takes ${CONTAINER_STATES.join(", ")}.`);
|
|
683
|
+
// --label key=value, as the list's key:value filters.
|
|
684
|
+
const labels = Object.entries(pairs(args, "label")).map(([k, v]) => `${k}:${v}`);
|
|
685
|
+
const all = await (await api.list({
|
|
686
|
+
limit: 100,
|
|
687
|
+
...(states
|
|
688
|
+
? { state: states }
|
|
689
|
+
: has(args, "all")
|
|
690
|
+
? { state: CONTAINER_STATES }
|
|
691
|
+
: {}),
|
|
692
|
+
...(labels.length ? { label: labels } : {}),
|
|
693
|
+
})).toArray(Infinity);
|
|
694
|
+
print(all.length
|
|
695
|
+
? table([
|
|
696
|
+
["ID", "NAME", "STATE", "IMAGE", "PORT", "SIZE", "CREATED"],
|
|
697
|
+
...all.map((c) => [
|
|
698
|
+
c.id,
|
|
699
|
+
c.name ?? "-",
|
|
700
|
+
c.state,
|
|
701
|
+
c.image,
|
|
702
|
+
c.port === null ? "-" : String(c.port),
|
|
703
|
+
`${c.vcpu} vCPU, ${c.memoryMiB} MiB`,
|
|
704
|
+
when(c.createdAt),
|
|
705
|
+
]),
|
|
706
|
+
])
|
|
707
|
+
: `No containers. Run one: ${me} containers create nginx:1.27 --vcpu 1 --memory 512`, all);
|
|
708
|
+
return 0;
|
|
709
|
+
}
|
|
710
|
+
case "get": {
|
|
711
|
+
const found = await api.get(await idOf(rest[0]));
|
|
712
|
+
print(JSON.stringify(found, null, 2), found);
|
|
713
|
+
return 0;
|
|
714
|
+
}
|
|
715
|
+
case "update": {
|
|
716
|
+
const args = parse(rest, [], ["name", "label"]);
|
|
717
|
+
const id = await idOf(args.positional[0]);
|
|
718
|
+
const change = {
|
|
719
|
+
...(flag(args, "name") === undefined ? {} : { name: flag(args, "name") }),
|
|
720
|
+
...(args.flags.has("label") ? { labels: pairs(args, "label") } : {}),
|
|
721
|
+
};
|
|
722
|
+
if (!Object.keys(change).length)
|
|
723
|
+
throw usage(`Give --name or --label: ${me} containers update ${args.positional[0]} --name web`);
|
|
724
|
+
const updated = await api.update(id, change);
|
|
725
|
+
print(`${updated.name ?? updated.id} updated.`, updated);
|
|
726
|
+
return 0;
|
|
727
|
+
}
|
|
728
|
+
case "rm":
|
|
729
|
+
case "delete": {
|
|
730
|
+
const args = parse(rest, ["wait"], []);
|
|
731
|
+
const gone = await api.delete(await idOf(args.positional[0]), { wait: has(args, "wait") });
|
|
732
|
+
print(`${gone.name ?? gone.id} ${gone.state}.`, gone);
|
|
733
|
+
return 0;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
throw kit.unknown("containers command", verb, ["create", "ls", "get", "update", "rm"]);
|
|
737
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** `withruntime/builds`: Builds alone. An image built from a repository's
|
|
2
|
+
* commit, at the sandbox rates. In a private beta.
|
|
3
|
+
*
|
|
4
|
+
* import { builds } from "withruntime/builds";
|
|
5
|
+
* const client = builds(); // RUNTIME_API_KEY, or this machine's saved connection
|
|
6
|
+
*/
|
|
7
|
+
import { Runtime, type RuntimeOptions } from "../client.js";
|
|
8
|
+
export type * from "../products/builds.js";
|
|
9
|
+
/** `runtime.builds` of a client made with `options`. */
|
|
10
|
+
export declare function builds(options?: RuntimeOptions): Runtime["builds"];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** `withruntime/builds`: Builds alone. An image built from a repository's
|
|
2
|
+
* commit, at the sandbox rates. In a private beta.
|
|
3
|
+
*
|
|
4
|
+
* import { builds } from "withruntime/builds";
|
|
5
|
+
* const client = builds(); // RUNTIME_API_KEY, or this machine's saved connection
|
|
6
|
+
*/
|
|
7
|
+
import { Runtime } from "../client.js";
|
|
8
|
+
/** `runtime.builds` of a client made with `options`. */
|
|
9
|
+
export function builds(options) {
|
|
10
|
+
return new Runtime(options).builds;
|
|
11
|
+
}
|
package/dist/cli-words.js
CHANGED
|
@@ -26,6 +26,7 @@ const OPTIONS = {
|
|
|
26
26
|
image: { flag: "--image" },
|
|
27
27
|
snapshot: { flag: "--snapshot" },
|
|
28
28
|
spreadFrom: { flag: "--spread-from" },
|
|
29
|
+
secrets: { flag: "--secret / --no-secrets" },
|
|
29
30
|
volumes: { flag: "--volume" },
|
|
30
31
|
network: { flag: "--allow / --deny / --connect / --no-internet" },
|
|
31
32
|
maxCostMicros: { flag: "--max-cost", micros: true },
|
package/dist/cli.js
CHANGED
|
@@ -38,6 +38,7 @@ Account
|
|
|
38
38
|
account switch <name|id> Use another of them for every command after
|
|
39
39
|
account add Connect another account (choose it in the browser)
|
|
40
40
|
account close --confirm "<account name>" Close the account for good (owner, key for every product)
|
|
41
|
+
account emails [<category> on|off] Your optional emails from Runtime: own-deletions, billing, usage, tips
|
|
41
42
|
audit [--action member.] [--limit 50] [--before <next>]
|
|
42
43
|
The account's audit log (owner or admin keys)
|
|
43
44
|
sso Single sign-on and SCIM directory sync (owner or
|
|
@@ -54,6 +55,10 @@ Account
|
|
|
54
55
|
flag one selector, any may match); --only-sandbox:
|
|
55
56
|
only that sandbox. A new value keeps the scope;
|
|
56
57
|
--all-sandboxes gives it to every sandbox again
|
|
58
|
+
secrets set <NAME> --keep-value [--host <host>]... [--allow '[GET] /path']...
|
|
59
|
+
Narrow where it goes without its value: a subset of
|
|
60
|
+
its hosts, only requests its rules allow now.
|
|
61
|
+
Anything wider needs the value
|
|
57
62
|
secrets set <NAME> --jobs [--host ...] A copy scheduled jobs put in their environment
|
|
58
63
|
(job create --secret NAME); with --host, both
|
|
59
64
|
secrets scope <NAME> --only-label k=v... | --only-sandbox <id>... | --all-sandboxes
|
|
@@ -62,6 +67,9 @@ Account
|
|
|
62
67
|
secrets rotate <NAME> A new version of the jobs copy, from standard input
|
|
63
68
|
secrets reveal <NAME> [--version N] Print the jobs copy's value. The sandboxes' copy is
|
|
64
69
|
never readable, by anyone
|
|
70
|
+
secrets share <NAME> --agent <id>... | --all | --none
|
|
71
|
+
Which of your keys may use the jobs copy besides the one
|
|
72
|
+
that stored it (ids: from 'me'); --all: every key
|
|
65
73
|
secrets rm <NAME> Delete a secret, every copy
|
|
66
74
|
keys create [--name <name>] [--read-only] [--daily-limit <usd>] [--account-wide]
|
|
67
75
|
A new API key for CI, approved by an owner in the browser
|
|
@@ -111,6 +119,8 @@ Products
|
|
|
111
119
|
jobs A command in a fresh sandbox, once or on a cron schedule, with its logs
|
|
112
120
|
apps Your web server from an image or a build, copies started by requests (beta)
|
|
113
121
|
builds A connected repository's commits built into what an App deploys (beta)
|
|
122
|
+
containers One OCI image's process on its own microVM, at the sandbox rates (beta)
|
|
123
|
+
agents Claude Code, Codex, Gemini CLI or your own agent in a sandbox per session (beta)
|
|
114
124
|
domains Your own hostname for a sandbox's port, with HTTPS
|
|
115
125
|
ports A public TCP port to a sandbox: databases, game servers
|
|
116
126
|
ips A dedicated outbound address, for allow-lists
|
|
@@ -136,7 +146,7 @@ const SANDBOX_HELP = `runtime sandboxes <command>
|
|
|
136
146
|
[--timeout <60-86400>] [--on-timeout pause|stop|delete] [--no-wait]
|
|
137
147
|
[--no-internet] [--allow <host>]... [--deny <host>]... [--connect <host:port>]...
|
|
138
148
|
[--image <name:tag|imageId> | --snapshot <snapshotId>] [--volume <volumeId>:/data[:snapshot]]...
|
|
139
|
-
[--spread-from <id>]...
|
|
149
|
+
[--spread-from <id>]... [--secret NAME]... [--no-secrets]
|
|
140
150
|
[--idle-pause <seconds>] [--no-auto-wake] [--persistent] [--get-or-create]
|
|
141
151
|
Create one; prints its id
|
|
142
152
|
ls [--all] [--label k=v]... [--state running] List (live ones unless --all)
|
|
@@ -183,7 +193,7 @@ const SANDBOX_HELP = `runtime sandboxes <command>
|
|
|
183
193
|
tailscale up <id> --auth-key-secret NAME [--hostname h] [--tag tag:x]...
|
|
184
194
|
Join your tailnet (paid); the key is a secret set with --jobs
|
|
185
195
|
tailscale status <id> | tailscale down <id> Its tailnet address; take it off the tailnet
|
|
186
|
-
fork <id> [--count 3] [--name n] [--keep-snapshot]
|
|
196
|
+
fork <id> [--count 3] [--name n] [--keep-snapshot] [--secret NAME]... [--no-secrets]
|
|
187
197
|
Copies of it as it is now, running; prints their ids
|
|
188
198
|
run-code <id> <file|-> [--lang python|javascript|typescript|r|java|bash|go] [--context c] [--out-dir .]
|
|
189
199
|
Run a notebook-style cell; charts saved as PNG
|
|
@@ -427,6 +437,8 @@ const COMMANDS = [
|
|
|
427
437
|
"jobs",
|
|
428
438
|
"apps",
|
|
429
439
|
"builds",
|
|
440
|
+
"containers",
|
|
441
|
+
"agents",
|
|
430
442
|
"domains",
|
|
431
443
|
"ports",
|
|
432
444
|
"ips",
|
|
@@ -447,6 +459,8 @@ const OWN_HELP = [
|
|
|
447
459
|
"jobs",
|
|
448
460
|
"apps",
|
|
449
461
|
"builds",
|
|
462
|
+
"containers",
|
|
463
|
+
"agents",
|
|
450
464
|
"keys",
|
|
451
465
|
"webhooks",
|
|
452
466
|
"webhook",
|
|
@@ -600,6 +614,20 @@ function describe(info) {
|
|
|
600
614
|
.map(([k, v]) => `${k}=${v}`)
|
|
601
615
|
.join(", ") || "-",
|
|
602
616
|
],
|
|
617
|
+
// Null is every egress secret that reaches it; [] is none. Absent from
|
|
618
|
+
// older servers, so no line then rather than a guess.
|
|
619
|
+
...(info.secrets === undefined
|
|
620
|
+
? []
|
|
621
|
+
: [
|
|
622
|
+
[
|
|
623
|
+
"secrets",
|
|
624
|
+
info.secrets === null
|
|
625
|
+
? "every one that reaches it"
|
|
626
|
+
: info.secrets.length
|
|
627
|
+
? info.secrets.join(", ")
|
|
628
|
+
: "none",
|
|
629
|
+
],
|
|
630
|
+
]),
|
|
603
631
|
["charged", dollars(BigInt(Math.round(Number(info.chargedMicros) || 0)))],
|
|
604
632
|
]);
|
|
605
633
|
}
|
|
@@ -1111,6 +1139,14 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
|
|
|
1111
1139
|
product = "builds";
|
|
1112
1140
|
return (await import("./apps-cli.js")).buildsCommand(rest, out, () => client(env), kit);
|
|
1113
1141
|
}
|
|
1142
|
+
if (command === "container" || command === "containers") {
|
|
1143
|
+
product = "containers";
|
|
1144
|
+
return (await import("./apps-cli.js")).containersCommand(rest, env, out, () => client(env), kit);
|
|
1145
|
+
}
|
|
1146
|
+
if (command === "agent" || command === "agents") {
|
|
1147
|
+
product = "agents";
|
|
1148
|
+
return (await import("./agents-cli.js")).agentsCommand(rest, env, out, () => client(env), kit);
|
|
1149
|
+
}
|
|
1114
1150
|
// WireGuard is inside networks: `runtime networks wireguard peer add`.
|
|
1115
1151
|
const wireguard = command === "networks" && rest[0] === "wireguard";
|
|
1116
1152
|
const networkProduct = wireguard ? "wireguard" : command;
|
|
@@ -1437,6 +1473,11 @@ const DROP_IN = {
|
|
|
1437
1473
|
js: 'import { SandboxInstance } from "withruntime/blaxel"; // was: from "@blaxel/core"',
|
|
1438
1474
|
py: "from withruntime.blaxel import SandboxInstance # was: from blaxel.core import SandboxInstance",
|
|
1439
1475
|
},
|
|
1476
|
+
// A Worker-only SDK: no Python. More than the import changes, so say what.
|
|
1477
|
+
cloudflare: {
|
|
1478
|
+
js: 'import { getSandbox } from "withruntime/cloudflare"; // was: from "@cloudflare/sandbox"',
|
|
1479
|
+
also: "Change the `export { Sandbox }` line the same way, delete the `containers` block from the Wrangler config and run `npx wrangler secret put RUNTIME_API_KEY`.",
|
|
1480
|
+
},
|
|
1440
1481
|
};
|
|
1441
1482
|
/** How to move a rival's code across, when one import does it. */
|
|
1442
1483
|
export function dropInLines(provider) {
|
|
@@ -1444,9 +1485,10 @@ export function dropInLines(provider) {
|
|
|
1444
1485
|
if (!drop)
|
|
1445
1486
|
return [];
|
|
1446
1487
|
return [
|
|
1447
|
-
`Your ${RIVAL_NAMES[provider]} code runs on Runtime with one import changed:`,
|
|
1488
|
+
`Your ${RIVAL_NAMES[provider]} code runs on Runtime with ${drop.also ? "this import changed" : "one import changed"}:`,
|
|
1448
1489
|
` ${drop.js}`,
|
|
1449
|
-
` ${drop.py}
|
|
1490
|
+
...(drop.py ? [` ${drop.py}`] : []),
|
|
1491
|
+
...(drop.also ? [drop.also] : []),
|
|
1450
1492
|
];
|
|
1451
1493
|
}
|
|
1452
1494
|
/** A date as the CLI writes one: 23 September 2026. */
|
|
@@ -1888,12 +1930,14 @@ async function sandbox(argv, env, out) {
|
|
|
1888
1930
|
return 0;
|
|
1889
1931
|
}
|
|
1890
1932
|
case "fork": {
|
|
1891
|
-
const args = parse(rest, ["keep-snapshot", "trial", "paid"], ["count", "name"]);
|
|
1933
|
+
const args = parse(rest, ["keep-snapshot", "trial", "paid", "no-secrets"], ["count", "name", "secret"]);
|
|
1934
|
+
const secrets = secretsInput(args);
|
|
1892
1935
|
const sbx = await get(args.positional[0]);
|
|
1893
1936
|
const forks = await sbx.fork({
|
|
1894
1937
|
count: integer(args, "count") ?? 1,
|
|
1895
1938
|
...(flag(args, "name") ? { name: flag(args, "name") } : {}),
|
|
1896
1939
|
...(has(args, "keep-snapshot") ? { keepSnapshot: true } : {}),
|
|
1940
|
+
...secrets,
|
|
1897
1941
|
...(has(args, "trial")
|
|
1898
1942
|
? { funding: "trial" }
|
|
1899
1943
|
: has(args, "paid")
|
|
@@ -2385,7 +2429,17 @@ export async function secretsCommand(argv, env, out,
|
|
|
2385
2429
|
/** Tests give the value here; the CLI reads standard input. */
|
|
2386
2430
|
read = readSecret) {
|
|
2387
2431
|
const [verb, ...rest] = argv;
|
|
2388
|
-
const args = parse(rest, ["jobs", "all-sandboxes"
|
|
2432
|
+
const args = parse(rest, ["jobs", "all-sandboxes", "keep-value", "all", "none"], [
|
|
2433
|
+
"host",
|
|
2434
|
+
"header",
|
|
2435
|
+
"format",
|
|
2436
|
+
"allow",
|
|
2437
|
+
"value",
|
|
2438
|
+
"version",
|
|
2439
|
+
"only-label",
|
|
2440
|
+
"only-sandbox",
|
|
2441
|
+
"agent",
|
|
2442
|
+
]);
|
|
2389
2443
|
const print = (human, value) => out.write(out.json ? JSON.stringify(value) : human);
|
|
2390
2444
|
const rows = (list) => list.length
|
|
2391
2445
|
? table([
|
|
@@ -2427,13 +2481,38 @@ read = readSecret) {
|
|
|
2427
2481
|
args.flags.has("only-sandbox") ||
|
|
2428
2482
|
args.flags.has("all-sandboxes")))
|
|
2429
2483
|
throw usage(`rotate replaces the copy for jobs. ${jobsOnly}; to change the sandboxes' copy, set it again with --host.`);
|
|
2484
|
+
if (verb === "set" && has(args, "keep-value")) {
|
|
2485
|
+
// Narrowing needs no value: it only takes places away (secrets.narrow).
|
|
2486
|
+
if (jobs ||
|
|
2487
|
+
args.flags.has("value") ||
|
|
2488
|
+
args.flags.has("header") ||
|
|
2489
|
+
args.flags.has("format") ||
|
|
2490
|
+
args.flags.has("only-label") ||
|
|
2491
|
+
args.flags.has("only-sandbox") ||
|
|
2492
|
+
args.flags.has("all-sandboxes"))
|
|
2493
|
+
throw usage(`--keep-value changes only --host and --allow, and only to narrower ones. Change who gets it with \`${me} secrets scope\`; anything else needs the value.`);
|
|
2494
|
+
const allow = args.flags.get("allow");
|
|
2495
|
+
if (!hosts.length && !allow)
|
|
2496
|
+
throw usage("With --keep-value, give the narrower --host and/or --allow it keeps.");
|
|
2497
|
+
const saved = await (await client(env)).secrets.narrow(name, {
|
|
2498
|
+
...(hosts.length ? { hosts } : {}),
|
|
2499
|
+
...(allow ? { rules: parseSecretRules(allow) } : {}),
|
|
2500
|
+
});
|
|
2501
|
+
print([
|
|
2502
|
+
`Narrowed ${saved.name}; its value is unchanged. It goes only to ${saved.hosts.join(", ")}${saved.rules?.length ? `, only in these requests: ${ruleText(saved.rules)}` : ""}.`,
|
|
2503
|
+
...(saved.enforced
|
|
2504
|
+
? []
|
|
2505
|
+
: ["A host is catching up; running sandboxes have it within a minute."]),
|
|
2506
|
+
].join("\n"), saved);
|
|
2507
|
+
return 0;
|
|
2508
|
+
}
|
|
2430
2509
|
if (!hosts.length && !jobs)
|
|
2431
2510
|
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.");
|
|
2432
2511
|
if (args.flags.has("value"))
|
|
2433
2512
|
throw usage('The value is read from standard input, never an argument: printf %s "$KEY" | runtime secrets set NAME --host ...');
|
|
2434
2513
|
const value = await read();
|
|
2435
2514
|
if (!value)
|
|
2436
|
-
throw usage('The value is empty. Pipe it in: printf %s "$KEY" | runtime secrets set NAME --host
|
|
2515
|
+
throw usage('The value is empty. Pipe it in: printf %s "$KEY" | runtime secrets set NAME --host ...; or, to narrow its hosts and rules without it, add --keep-value.');
|
|
2437
2516
|
const runtime = await client(env);
|
|
2438
2517
|
if (verb === "rotate") {
|
|
2439
2518
|
const rotated = await runtime.secrets.rotate(name, value);
|
|
@@ -2487,6 +2566,17 @@ read = readSecret) {
|
|
|
2487
2566
|
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);
|
|
2488
2567
|
return 0;
|
|
2489
2568
|
}
|
|
2569
|
+
if (verb === "share") {
|
|
2570
|
+
const name = need(args.positional[0], "the secret's name");
|
|
2571
|
+
const named = args.flags.get("agent") ?? [];
|
|
2572
|
+
if (Number(named.length > 0) + Number(has(args, "all")) + Number(has(args, "none")) !== 1)
|
|
2573
|
+
throw usage("Say who may use it: --agent <id> (once per agent), --all for every key of yours, or --none for only the key that stored it.");
|
|
2574
|
+
const saved = await (await client(env)).secrets.share(name, has(args, "all") ? "all" : named);
|
|
2575
|
+
print(`${saved.name}: ${saved.usableBy === "all"
|
|
2576
|
+
? "every key of yours"
|
|
2577
|
+
: `only the key that stored it${saved.usableBy.length ? `, and ${saved.usableBy.length} named agent${saved.usableBy.length === 1 ? "" : "s"}` : ""}`}.`, saved);
|
|
2578
|
+
return 0;
|
|
2579
|
+
}
|
|
2490
2580
|
if (verb === "reveal") {
|
|
2491
2581
|
const name = need(args.positional[0], "the secret's name");
|
|
2492
2582
|
const version = flag(args, "version");
|
|
@@ -2502,7 +2592,7 @@ read = readSecret) {
|
|
|
2502
2592
|
print(`Deleted ${name}${removed.from.length > 1 ? " for sandboxes and jobs" : removed.from[0] === "jobs" ? " for jobs" : ""}.`, removed);
|
|
2503
2593
|
return 0;
|
|
2504
2594
|
}
|
|
2505
|
-
throw usage("secrets takes set, scope, ls, get, rotate, reveal or rm. Run `runtime help`.");
|
|
2595
|
+
throw usage("secrets takes set, scope, ls, get, rotate, share, reveal or rm. Run `runtime help`.");
|
|
2506
2596
|
}
|
|
2507
2597
|
async function readInput(source) {
|
|
2508
2598
|
if (source !== "-")
|
|
@@ -2664,9 +2754,11 @@ const KEYS_HELP = `runtime keys <command>
|
|
|
2664
2754
|
stdout, and lasts until revoked. --json prints it as JSON, still alone on
|
|
2665
2755
|
stdout. --account-wide makes a key that sees and uses every sandbox and
|
|
2666
2756
|
other resource in the account; without it, a key sees only what it makes.
|
|
2667
|
-
create --sandbox <id,...> --actions <action,...> [--name <name>] [--daily-limit <usd>]
|
|
2668
|
-
A key limited to those sandboxes (up to 20, by id
|
|
2669
|
-
them, made by the saved key
|
|
2757
|
+
create --sandbox <id,...> --actions <action,...> [--name <name>] [--daily-limit <usd>] [--browser]
|
|
2758
|
+
A key limited to those sandboxes (up to 20, by id, whichever key made
|
|
2759
|
+
them) and those actions on them, made by the saved key, or with no saved
|
|
2760
|
+
key (or --browser) approved by an owner or admin in the browser, as
|
|
2761
|
+
above: get reads them; create starts, wakes and
|
|
2670
2762
|
restarts them, and needs --daily-limit when the making key has one; stop
|
|
2671
2763
|
stops and pauses them; exec runs commands; read_file and write_file reach
|
|
2672
2764
|
their files. It is served nothing else: no list, no other sandbox, no
|
|
@@ -2678,6 +2770,7 @@ const KEYS_HELP = `runtime keys <command>
|
|
|
2678
2770
|
runtime keys create --name worker-1 --daily-limit 5
|
|
2679
2771
|
runtime keys create --name ci --daily-limit 25 --browser | gh secret set RUNTIME_API_KEY
|
|
2680
2772
|
runtime keys create --name watcher --sandbox sbx_id --actions get,create --daily-limit 2
|
|
2773
|
+
runtime keys create --name helper --sandbox sbx_a,sbx_b --actions get,exec,read_file,write_file --browser
|
|
2681
2774
|
|
|
2682
2775
|
Making, listing and revoking keys with a key needs one an owner or admin made.
|
|
2683
2776
|
Every key is also at https://withruntime.com/account/keys.
|
|
@@ -2745,8 +2838,28 @@ async function accountCommand(argv, env, out) {
|
|
|
2745
2838
|
].join("\n"), closed);
|
|
2746
2839
|
return 0;
|
|
2747
2840
|
}
|
|
2841
|
+
if (verb === "emails") {
|
|
2842
|
+
/* 0921: which of Runtime's optional emails this key's person gets.
|
|
2843
|
+
Bare, it lists them; `<category> on|off` changes one, for that person
|
|
2844
|
+
only. */
|
|
2845
|
+
const [category, state, ...extra] = rest;
|
|
2846
|
+
const categories = ["own-deletions", "billing", "usage", "tips"];
|
|
2847
|
+
if (extra.length || (category && !state) || (state && state !== "on" && state !== "off"))
|
|
2848
|
+
throw usage(`${me} account emails [<${categories.join("|")}> on|off]`);
|
|
2849
|
+
if (category && !categories.includes(category))
|
|
2850
|
+
throw unknown("email", category, [...categories]);
|
|
2851
|
+
const runtime = await client(env);
|
|
2852
|
+
const settings = await runtime.account.emails(category
|
|
2853
|
+
? { category: category, enabled: state === "on" }
|
|
2854
|
+
: undefined);
|
|
2855
|
+
print([
|
|
2856
|
+
...settings.map((each) => `${each.category.padEnd(14)}${each.enabled ? "on" : "off"}`),
|
|
2857
|
+
"Always sent: sign-in, invitations, revoked keys, support replies, credit expiry, service credits, and deletions Runtime decides.",
|
|
2858
|
+
].join("\n"), { settings });
|
|
2859
|
+
return 0;
|
|
2860
|
+
}
|
|
2748
2861
|
if (verb !== "ls" && verb !== "list")
|
|
2749
|
-
throw unknown("account command", verb, ["ls", "switch", "add", "close"]);
|
|
2862
|
+
throw unknown("account command", verb, ["ls", "switch", "add", "close", "emails"]);
|
|
2750
2863
|
await nameSaved(env).catch(() => undefined);
|
|
2751
2864
|
const current = await store.read().catch(() => null);
|
|
2752
2865
|
const saved = [
|
|
@@ -2915,12 +3028,14 @@ async function keys(argv, env, out) {
|
|
|
2915
3028
|
return 0;
|
|
2916
3029
|
}
|
|
2917
3030
|
/** `runtime keys create --sandbox ... --actions ...`: a key limited to
|
|
2918
|
-
* sandboxes, made by the saved key
|
|
2919
|
-
*
|
|
3031
|
+
* sandboxes, made by the saved key, or, with no key on this machine or with
|
|
3032
|
+
* --browser, asked for and approved by an owner or admin in the browser
|
|
3033
|
+
* (0921): an agent whose person reaches Runtime only through a connector
|
|
3034
|
+
* holds no key to make one with. */
|
|
2920
3035
|
async function limitedKey(args, env, out, limit) {
|
|
2921
|
-
for (const refused of ["read-only", "account-wide"
|
|
3036
|
+
for (const refused of ["read-only", "account-wide"])
|
|
2922
3037
|
if (has(args, refused))
|
|
2923
|
-
throw usage(`A key limited to sandboxes reaches only them
|
|
3038
|
+
throw usage(`A key limited to sandboxes reaches only them, so it takes no --${refused}.`);
|
|
2924
3039
|
const list = (name) => (flag(args, name) ?? "")
|
|
2925
3040
|
.split(",")
|
|
2926
3041
|
.map((each) => each.trim())
|
|
@@ -2929,15 +3044,30 @@ async function limitedKey(args, env, out, limit) {
|
|
|
2929
3044
|
const actions = list("actions");
|
|
2930
3045
|
if (!sandboxes.length || !actions.length)
|
|
2931
3046
|
throw usage(`Usage: ${named("runtime keys create --sandbox <id,...> --actions <get,create,stop,exec,read_file,write_file> [--daily-limit <usd>]")}`);
|
|
2932
|
-
const { dailyLimitMicros } = await import("./keys.js");
|
|
2933
|
-
const
|
|
3047
|
+
const { createKey, dailyLimitMicros } = await import("./keys.js");
|
|
3048
|
+
const request = {
|
|
2934
3049
|
name: flag(args, "name") ?? "Sandbox key",
|
|
2935
3050
|
sandboxes,
|
|
2936
3051
|
actions: actions,
|
|
2937
3052
|
dailyLimitMicros: limit === null ? null : dailyLimitMicros(limit),
|
|
2938
|
-
}
|
|
2939
|
-
|
|
2940
|
-
|
|
3053
|
+
};
|
|
3054
|
+
const saved = has(args, "browser")
|
|
3055
|
+
? null
|
|
3056
|
+
: env.RUNTIME_API_KEY || (await connectionStore(env).read())?.key || null;
|
|
3057
|
+
const asked = saved
|
|
3058
|
+
? null
|
|
3059
|
+
: await createKey({ ...request, access: "full" }, env, { notify: out.error });
|
|
3060
|
+
const made = asked
|
|
3061
|
+
? { ...asked, sandboxes, actions }
|
|
3062
|
+
: await new Runtime({
|
|
3063
|
+
apiKey: saved,
|
|
3064
|
+
baseUrl: env.RUNTIME_API_URL,
|
|
3065
|
+
maxRetries: 4,
|
|
3066
|
+
}).keys.create(request);
|
|
3067
|
+
const { confirm, ...shown } = made;
|
|
3068
|
+
out.write(out.json ? JSON.stringify(shown) : shown.key);
|
|
3069
|
+
out.error(`Created key "${shown.name}", limited to ${shown.sandboxes?.length === 1 ? "sandbox" : "sandboxes"} ${(shown.sandboxes ?? []).join(", ")} for ${(shown.actions ?? []).join(", ")}${shown.dailyLimitMicros === null ? "" : `, daily limit ${usdOf(shown.dailyLimitMicros)}`}, ${saved ? "made by this machine's key" : "approved in the browser"}. Store it now: it is not shown again.\nIt calls those sandboxes by id and nothing else; revoke it at https://withruntime.com/account/keys${saved ? ` or with ${named("runtime keys revoke")} ${shown.keyId}` : ""}.`);
|
|
3070
|
+
await confirm?.();
|
|
2941
3071
|
return 0;
|
|
2942
3072
|
}
|
|
2943
3073
|
const SNAPSHOT_HELP = `runtime snapshots <command>
|
|
@@ -3749,6 +3879,7 @@ const CREATE_FLAGS = [
|
|
|
3749
3879
|
"no-auto-wake",
|
|
3750
3880
|
"persistent",
|
|
3751
3881
|
"get-or-create",
|
|
3882
|
+
"no-secrets",
|
|
3752
3883
|
];
|
|
3753
3884
|
/** Says on standard error, once for each reason, why a create has not
|
|
3754
3885
|
* answered yet: it is waiting for a slot without credit or room, which can take up to
|
|
@@ -3818,6 +3949,7 @@ const CREATE_VALUES = [
|
|
|
3818
3949
|
"image",
|
|
3819
3950
|
"snapshot",
|
|
3820
3951
|
"spread-from",
|
|
3952
|
+
"secret",
|
|
3821
3953
|
"volume",
|
|
3822
3954
|
"env",
|
|
3823
3955
|
];
|
|
@@ -3836,6 +3968,28 @@ function usdMicros(args, name) {
|
|
|
3836
3968
|
throw usage(`--${name} is too large to represent exactly in microdollars.`);
|
|
3837
3969
|
return Number(micros);
|
|
3838
3970
|
}
|
|
3971
|
+
/** The egress secrets a new sandbox may use: each --secret adds a name
|
|
3972
|
+
* (commas also separate them), --no-secrets is none, and neither leaves it
|
|
3973
|
+
* out, for every secret that reaches it. Fixed for the sandbox's life. */
|
|
3974
|
+
function secretsInput(args) {
|
|
3975
|
+
const names = args.flags.get("secret");
|
|
3976
|
+
if (has(args, "no-secrets")) {
|
|
3977
|
+
if (names)
|
|
3978
|
+
throw usage("Give --secret or --no-secrets, not both.");
|
|
3979
|
+
return { secrets: [] };
|
|
3980
|
+
}
|
|
3981
|
+
if (!names)
|
|
3982
|
+
return {};
|
|
3983
|
+
return {
|
|
3984
|
+
secrets: names
|
|
3985
|
+
.flatMap((value) => value.split(","))
|
|
3986
|
+
.map((name) => {
|
|
3987
|
+
if (!name.trim())
|
|
3988
|
+
throw usage("--secret needs a non-empty value.");
|
|
3989
|
+
return name.trim();
|
|
3990
|
+
}),
|
|
3991
|
+
};
|
|
3992
|
+
}
|
|
3839
3993
|
/** A create body from the command line's flags. */
|
|
3840
3994
|
function createInput(args, env) {
|
|
3841
3995
|
for (const name of ["image", "snapshot", "volume", "name", "region", "connect"])
|
|
@@ -3857,6 +4011,7 @@ function createInput(args, env) {
|
|
|
3857
4011
|
}),
|
|
3858
4012
|
}
|
|
3859
4013
|
: {}),
|
|
4014
|
+
...secretsInput(args),
|
|
3860
4015
|
...(args.flags.has("volume") ? { volumes: args.flags.get("volume").map(attachment) } : {}),
|
|
3861
4016
|
...(has(args, "no-internet") || rules.length
|
|
3862
4017
|
? {
|
package/dist/client.d.ts
CHANGED
|
@@ -153,6 +153,16 @@ export type ClosedAccount = {
|
|
|
153
153
|
/** False while what was running is still stopping; it is gone within minutes. */
|
|
154
154
|
released: boolean;
|
|
155
155
|
};
|
|
156
|
+
/** One of Runtime's optional emails: `own-deletions` (warnings for dates
|
|
157
|
+
* the account's own retention set), `billing` (credit low or out), `usage`
|
|
158
|
+
* (the month's free usage at 80% and all) or `tips` (getting started). */
|
|
159
|
+
export type EmailCategory = "own-deletions" | "billing" | "usage" | "tips";
|
|
160
|
+
/** Whether this key's person gets one optional email, in this account. */
|
|
161
|
+
export type EmailSetting = {
|
|
162
|
+
category: EmailCategory;
|
|
163
|
+
enabled: boolean;
|
|
164
|
+
updatedAt: string | null;
|
|
165
|
+
};
|
|
156
166
|
/** The account this key belongs to: `runtime.account`. */
|
|
157
167
|
export declare class AccountApi {
|
|
158
168
|
private readonly t;
|
|
@@ -162,6 +172,15 @@ export declare class AccountApi {
|
|
|
162
172
|
* name exactly (`me().orgName`). Needs a key for every product made by an
|
|
163
173
|
* owner. Moves no money: what is unspent of a purchase made in the last 15
|
|
164
174
|
* days is refunded on request; other credit is forfeited. */
|
|
175
|
+
/** Which of Runtime's optional emails this key's person gets for this
|
|
176
|
+
* account. Without `set`, reads them; with `{ category, enabled }`, turns
|
|
177
|
+
* one on or off for that person only, and answers all of them. Sign-in,
|
|
178
|
+
* support replies, credit expiry and deletions Runtime decides are always
|
|
179
|
+
* sent and are not listed. */
|
|
180
|
+
emails(set?: {
|
|
181
|
+
category: EmailCategory;
|
|
182
|
+
enabled: boolean;
|
|
183
|
+
}, options?: RequestOptions): Promise<EmailSetting[]>;
|
|
165
184
|
close(input: {
|
|
166
185
|
confirm: string;
|
|
167
186
|
}, options?: RequestOptions): Promise<ClosedAccount>;
|