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.
Files changed (49) hide show
  1. package/COMPATIBILITY.md +32 -1
  2. package/README.md +19 -0
  3. package/README.src.md +19 -0
  4. package/dist/agents/index.d.ts +11 -0
  5. package/dist/agents/index.js +12 -0
  6. package/dist/agents-cli.d.ts +10 -0
  7. package/dist/agents-cli.js +362 -0
  8. package/dist/apps-cli.d.ts +2 -0
  9. package/dist/apps-cli.js +173 -0
  10. package/dist/builds/index.d.ts +10 -0
  11. package/dist/builds/index.js +11 -0
  12. package/dist/cli-words.js +1 -0
  13. package/dist/cli.js +176 -21
  14. package/dist/client.d.ts +19 -0
  15. package/dist/client.js +20 -0
  16. package/dist/cloudflare/errors.d.ts +23 -0
  17. package/dist/cloudflare/errors.js +16 -0
  18. package/dist/cloudflare/index.d.ts +82 -9
  19. package/dist/cloudflare/index.js +162 -38
  20. package/dist/cloudflare/interpreter.js +14 -5
  21. package/dist/cloudflare/mime.d.ts +1 -0
  22. package/dist/cloudflare/mime.js +66 -0
  23. package/dist/cloudflare/streams.d.ts +8 -0
  24. package/dist/cloudflare/streams.js +30 -0
  25. package/dist/cloudflare/types.d.ts +338 -0
  26. package/dist/cloudflare/types.js +80 -0
  27. package/dist/cloudflare/worker.d.ts +297 -10
  28. package/dist/cloudflare/worker.js +203 -17
  29. package/dist/containers/index.d.ts +10 -0
  30. package/dist/containers/index.js +11 -0
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.js +1 -0
  33. package/dist/keys.d.ts +6 -0
  34. package/dist/keys.js +10 -1
  35. package/dist/product-names.js +10 -0
  36. package/dist/products/agents.d.ts +272 -0
  37. package/dist/products/agents.js +251 -0
  38. package/dist/products/containers.d.ts +71 -0
  39. package/dist/products/containers.js +6 -0
  40. package/dist/products/index.d.ts +6 -0
  41. package/dist/products/index.js +7 -0
  42. package/dist/products/secrets.d.ts +19 -0
  43. package/dist/products/secrets.js +30 -0
  44. package/dist/sandbox.d.ts +4 -0
  45. package/dist/transport.d.ts +2 -2
  46. package/dist/transport.js +1 -1
  47. package/dist/tunnel.js +1 -1
  48. package/dist/types.d.ts +11 -0
  49. package/package.json +16 -1
package/COMPATIBILITY.md CHANGED
@@ -102,6 +102,37 @@ all 73 inventory items are present or refused, none missing. How it maps:
102
102
  `withruntime.morph`, inventoried as `morph-python`; see
103
103
  [the Python document](../../sdks/python/COMPATIBILITY.md).
104
104
 
105
+ ### Cloudflare 0.12.10 Worker examples, live (18 October 2026)
106
+
107
+ `tests/compat/cloudflare-examples/` holds Cloudflare's own Workers, copied
108
+ verbatim with their source URL and the date read: the `minimal` and `alpine`
109
+ examples at the `@cloudflare/sandbox@0.12.10` tag, the get-started Worker, and
110
+ the runnable 0.x snippets from the docs on exec, streaming, files, background
111
+ processes, the code interpreter, preview URLs, sessions and `gitCheckout`. The
112
+ only change in each is the package name and, in `wrangler.jsonc`, the removed
113
+ `containers` block. `fixtures.ts` lists what each must answer, and the
114
+ examples skipped with the reason (Workers AI, Artifacts, model keys, backups,
115
+ tunnels, terminals, placeholder repositories, or a call 0.12.10 does not have).
116
+
117
+ - `RUNTIME_API_KEY=... bun scripts/compatibility-cloudflare-live.ts --run`
118
+ builds the package, runs each example under `wrangler dev` (workerd) with the
119
+ key as a Worker secret, sends the requests the docs describe, and checks the
120
+ answers. A local relay caps it at 12 sandboxes, gives each a 10 minute limit
121
+ and a cost cap, labels them with the run, and deletes them after each Worker
122
+ and at the end.
123
+ - 18 October 2026: 58 of 58 checks passed, 9 sandboxes created, all deleted.
124
+ - The run found that the Worker's Durable Object lacked most of the module's
125
+ calls (`gitCheckout`, `runCode`, `streamProcessLogs` and the session calls
126
+ failed with "RPC receiver does not implement"), `waitForPort` was missing,
127
+ local preview URLs were not Cloudflare's `http://<port>-<id>-<token>.localhost`,
128
+ `readFile` had no `mimeType`, and 14 of upstream's value exports and about
129
+ 70 of its types were missing. `tests/compat/cloudflare-worker-surface.test.ts`
130
+ checks each.
131
+ - Differences that stay: a sandbox holds at most 4 code contexts (a fifth
132
+ `createCodeContext` says how to free one); a process's `pid` is not
133
+ reported; `processId` and `autoCleanup` refuse. Errors across a Durable
134
+ Object call keep their message but not `code`, as on Cloudflare.
135
+
105
136
  ### Freestyle 0.2.18, 0.2.19 and 0.2.20 review (15 October 2026)
106
137
 
107
138
  The pin advances from 0.2.17 to 0.2.20; support remains partial. The four npm
@@ -311,7 +342,7 @@ Remaining implementation work is tracked only in the repository `WORKLIST.md`.
311
342
  | Sprites | No control-mode WebSocket protocol, managed services, in-place checkpoint restore (start a new sandbox from the snapshot), checkpoint history filters, network presets (`include`), an allow rule inside a denied wildcard (Runtime's deny always wins), privilege or resource policies, `maxRunAfterDisconnect`, Fly token exchange or Sprite list streaming; each is refused by name. A checkpoint is a Runtime disk snapshot (files, not running processes); the Sprite pauses for the moment it takes. An allow rule makes an allowlist, as Sprites' DNS allowlist does. The port watcher polls `ss` once a second. Session handles use native process IDs, not tmux. The sprite URL maps port 8080. File bytes and process byte streams preserve binary data. |
312
343
  | Freestyle | Provider catalog snapshot slugs are not Runtime snapshots and are refused. Firewall support is limited to an explicit empty rule set or the exact allow-public-outbound shape; arbitrary rule selectors, TLS policies and private networks are refused. No automatic-restart guarantee, retention scheduler, placement constraints, live resize, Linux-user switching, PTY namespace, public IPv6 or account-owned egress address emulation. Native snapshot retention is positive whole days. |
313
344
  | Modal | CPU sandboxes only; different resource reservation/limit values, fractional CPU, GPU and foreign placement are refused. Named Apps group Runtime sandboxes; they do not deploy Functions. Registry authentication uses Runtime's registry configuration. No Functions/Cls, volumes/bucket mounts, GPU image steps, readiness probes, sidecars, directory mounts, filesystem-only images from snapshots, or snapshot restoration API. Entry-point completion stops the sandbox while the originating client is alive; the native lease remains the bound if that client disappears. A different client can reconnect using the saved entrypoint process ID. |
314
- | Cloudflare | The conditional Worker entry supplies a Durable Object bridge and custom-hostname preview proxy; infrastructure configuration must still remove the vendor Container declaration and provide Runtime secrets. Custom preview routing is implemented but has no live public-hostname acceptance evidence. No isolated PID namespaces, interpreter contexts, bucket mounts, backup format, terminal WebSocket protocol, watchers/change versions, custom process IDs or provider cleanup-record semantics. Foreground named sessions use resident Bash; background processes inherit exported state/cwd but do not yet inherit every local variable/function/option. |
345
+ | Cloudflare | The conditional Worker entry supplies a Durable Object bridge and custom-hostname preview proxy; infrastructure configuration must still remove the vendor Container declaration and provide Runtime secrets. Cloudflare's examples run live under `wrangler dev` (above); custom preview routing has no live public-hostname acceptance evidence. At most 4 interpreter contexts. No isolated PID namespaces, backup format, terminal WebSocket protocol, custom process IDs or provider cleanup-record semantics. Foreground named sessions use resident Bash; background processes inherit exported state/cwd but do not yet inherit every local variable/function/option. |
315
346
 
316
347
  ## Keeping compatibility current
317
348
 
package/README.md CHANGED
@@ -162,6 +162,25 @@ copies memory and processes. `stop` deletes the sandbox, as Morph's does.
162
162
  SSH, reboot and a time limit set after start throw `CompatibilityError`
163
163
  naming the alternative. `COMPATIBILITY.md` lists every mapping.
164
164
 
165
+ ## Code written for Cloudflare's Sandbox SDK
166
+
167
+ `withruntime/cloudflare` runs a Worker written for Cloudflare's 0.x
168
+ `@cloudflare/sandbox` API on Runtime sandboxes. Change the package name in the
169
+ import and in the `Sandbox` export:
170
+
171
+ ```ts no-run
172
+ import { getSandbox } from "withruntime/cloudflare"; // was: from "@cloudflare/sandbox"
173
+ export { Sandbox } from "withruntime/cloudflare"; // was: from "@cloudflare/sandbox"
174
+ ```
175
+
176
+ Then delete the `containers` block from the Wrangler config, keeping the
177
+ `Sandbox` Durable Object binding, and store a key with
178
+ `npx wrangler secret put RUNTIME_API_KEY`. Exec, streaming, files, processes,
179
+ the code interpreter, sessions, `gitCheckout` and `exposePort` work as
180
+ Cloudflare documents them; backups, tunnels, terminals and outbound
181
+ interception throw `CompatibilityError` naming the alternative.
182
+ `COMPATIBILITY.md` lists every mapping.
183
+
165
184
  ## Agent frameworks
166
185
 
167
186
  `withruntime/openai-agents` is a sandbox client for the OpenAI Agents SDK's
package/README.src.md CHANGED
@@ -164,6 +164,25 @@ copies memory and processes. `stop` deletes the sandbox, as Morph's does.
164
164
  SSH, reboot and a time limit set after start throw `CompatibilityError`
165
165
  naming the alternative. `COMPATIBILITY.md` lists every mapping.
166
166
 
167
+ ## Code written for Cloudflare's Sandbox SDK
168
+
169
+ `withruntime/cloudflare` runs a Worker written for Cloudflare's 0.x
170
+ `@cloudflare/sandbox` API on Runtime sandboxes. Change the package name in the
171
+ import and in the `Sandbox` export:
172
+
173
+ ```ts no-run
174
+ import { getSandbox } from "withruntime/cloudflare"; // was: from "@cloudflare/sandbox"
175
+ export { Sandbox } from "withruntime/cloudflare"; // was: from "@cloudflare/sandbox"
176
+ ```
177
+
178
+ Then delete the `containers` block from the Wrangler config, keeping the
179
+ `Sandbox` Durable Object binding, and store a key with
180
+ `npx wrangler secret put RUNTIME_API_KEY`. Exec, streaming, files, processes,
181
+ the code interpreter, sessions, `gitCheckout` and `exposePort` work as
182
+ Cloudflare documents them; backups, tunnels, terminals and outbound
183
+ interception throw `CompatibilityError` naming the alternative.
184
+ `COMPATIBILITY.md` lists every mapping.
185
+
167
186
  ## Agent frameworks
168
187
 
169
188
  `withruntime/openai-agents` is a sandbox client for the OpenAI Agents SDK's
@@ -0,0 +1,11 @@
1
+ /** `withruntime/agents`: Agents alone. A harness in a sandbox of its own per
2
+ * session, on your own model key. In a private beta.
3
+ *
4
+ * import { agents } from "withruntime/agents";
5
+ * const client = agents(); // RUNTIME_API_KEY, or this machine's saved connection
6
+ */
7
+ import { Runtime, type RuntimeOptions } from "../client.js";
8
+ export type * from "../products/agents.js";
9
+ export { AGENT_TEMPLATES, AGENT_USES } from "../products/agents.js";
10
+ /** `runtime.agents` of a client made with `options`. */
11
+ export declare function agents(options?: RuntimeOptions): Runtime["agents"];
@@ -0,0 +1,12 @@
1
+ /** `withruntime/agents`: Agents alone. A harness in a sandbox of its own per
2
+ * session, on your own model key. In a private beta.
3
+ *
4
+ * import { agents } from "withruntime/agents";
5
+ * const client = agents(); // RUNTIME_API_KEY, or this machine's saved connection
6
+ */
7
+ import { Runtime } from "../client.js";
8
+ export { AGENT_TEMPLATES, AGENT_USES } from "../products/agents.js";
9
+ /** `runtime.agents` of a client made with `options`. */
10
+ export function agents(options) {
11
+ return new Runtime(options).agents;
12
+ }
@@ -0,0 +1,10 @@
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 AGENT_HELP = "runtime agents <command>\n\n An agent runs a harness (Claude Code, Codex, Gemini CLI, or your own code on\n an agents SDK) in a sandbox of its own for each session, on your model key.\n In a private beta: outside it, each command says how to ask to join.\n\n templates The harnesses and uses to start from\n create <name> (--template <harness> | --command \"<cmd>\") [--use support|team]\n [--instructions <text> | --instructions-file <path>] [--secret NAME]...\n [--env KEY=VALUE]... [--image <image> | --snapshot <id>] [--vcpu n] [--memory n]\n [--max-concurrent n] [--idle-pause n] [--label key=value]...\n Make an agent; prints its id. --secret names an egress\n secret it may use: its model key, of any provider\n ls Your agents\n get <agent> One agent, in full (an id or its name)\n update <agent> [the options of create] A new version: what you give changes, the rest stays\n rm <agent> Delete it, its triggers, and stop its sessions\n start <agent> <words...> [--key <key>] [--follow]\n A session with these words, or the open one with the\n key (such as customer:42); --follow prints its log\n until it ends\n send <session> <words...> Words into a session\n events <session> [--after <seq>] [--follow] A session's log; --follow until it ends\n sessions [--agent <agent>] [--state <state>]...\n Sessions, newest first\n sessions delete <session> Delete an ended session and its log; its history is\n storage, kept until you delete it\n stop <session> Stop a session\n triggers <agent> Its triggers\n triggers add <agent> --kind <slack|github|schedule|webhook> --config '<json>'\n triggers pause <triggerId> --why <words> Refuse its events, saying why; nothing else is held\n triggers resume <triggerId>\n triggers rm <triggerId>\n\n A subscription is for your own team's agents; an agent that serves your\n customers uses an API key.\n\nExamples\n printf %s \"$ANTHROPIC_API_KEY\" | runtime secrets set ANTHROPIC_API_KEY --host api.anthropic.com\n runtime agents create helper --template claude-code --use support --instructions \"Answer briefly.\"\n runtime agents start helper \"How do I reset my password?\" --key customer:42 --follow\n";
9
+ export declare function agentsCommand(argv: string[], env: NodeJS.ProcessEnv, out: Out, client: () => Promise<Runtime>, kit: CliKit): Promise<number>;
10
+ export {};
@@ -0,0 +1,362 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { me, named } from "./cli-name.js";
3
+ import { AGENT_TEMPLATES, AGENT_USES, agents, } from "./products/agents.js";
4
+ export const AGENT_HELP = `runtime agents <command>
5
+
6
+ An agent runs a harness (Claude Code, Codex, Gemini CLI, or your own code on
7
+ an agents SDK) in a sandbox of its own for each session, on your model key.
8
+ In a private beta: outside it, each command says how to ask to join.
9
+
10
+ templates The harnesses and uses to start from
11
+ create <name> (--template <harness> | --command "<cmd>") [--use support|team]
12
+ [--instructions <text> | --instructions-file <path>] [--secret NAME]...
13
+ [--env KEY=VALUE]... [--image <image> | --snapshot <id>] [--vcpu n] [--memory n]
14
+ [--max-concurrent n] [--idle-pause n] [--label key=value]...
15
+ Make an agent; prints its id. --secret names an egress
16
+ secret it may use: its model key, of any provider
17
+ ls Your agents
18
+ get <agent> One agent, in full (an id or its name)
19
+ update <agent> [the options of create] A new version: what you give changes, the rest stays
20
+ rm <agent> Delete it, its triggers, and stop its sessions
21
+ start <agent> <words...> [--key <key>] [--follow]
22
+ A session with these words, or the open one with the
23
+ key (such as customer:42); --follow prints its log
24
+ until it ends
25
+ send <session> <words...> Words into a session
26
+ events <session> [--after <seq>] [--follow] A session's log; --follow until it ends
27
+ sessions [--agent <agent>] [--state <state>]...
28
+ Sessions, newest first
29
+ sessions delete <session> Delete an ended session and its log; its history is
30
+ storage, kept until you delete it
31
+ stop <session> Stop a session
32
+ triggers <agent> Its triggers
33
+ triggers add <agent> --kind <slack|github|schedule|webhook> --config '<json>'
34
+ triggers pause <triggerId> --why <words> Refuse its events, saying why; nothing else is held
35
+ triggers resume <triggerId>
36
+ triggers rm <triggerId>
37
+
38
+ A subscription is for your own team's agents; an agent that serves your
39
+ customers uses an API key.
40
+
41
+ Examples
42
+ printf %s "$ANTHROPIC_API_KEY" | runtime secrets set ANTHROPIC_API_KEY --host api.anthropic.com
43
+ runtime agents create helper --template claude-code --use support --instructions "Answer briefly."
44
+ runtime agents start helper "How do I reset my password?" --key customer:42 --follow
45
+ `;
46
+ const when = (iso) => iso ?? "-";
47
+ const OPTIONS = [
48
+ "template",
49
+ "use",
50
+ "command",
51
+ "instructions",
52
+ "instructions-file",
53
+ "secret",
54
+ "env",
55
+ "image",
56
+ "snapshot",
57
+ "vcpu",
58
+ "memory",
59
+ "max-concurrent",
60
+ "idle-pause",
61
+ "label",
62
+ ];
63
+ const STATES = [
64
+ "queued",
65
+ "running",
66
+ "waiting",
67
+ "done",
68
+ "failed",
69
+ "stopped",
70
+ ];
71
+ /** The definition the options give, on top of `base` (an agent's own, for
72
+ * an update): a template's command and key, a use's sizes, then each option. */
73
+ function definitionOf(args, kit, env, base) {
74
+ const { flag, integer, usage } = kit;
75
+ const template = flag(args, "template");
76
+ const use = flag(args, "use");
77
+ if (template !== undefined && !(template in AGENT_TEMPLATES))
78
+ throw usage(`--template takes ${Object.keys(AGENT_TEMPLATES).join(", ")}.`);
79
+ if (use !== undefined && use !== "support" && use !== "team")
80
+ throw usage("--use takes support or team.");
81
+ const instructionsFile = flag(args, "instructions-file");
82
+ const instructions = instructionsFile !== undefined
83
+ ? readFileSync(instructionsFile, "utf8")
84
+ : flag(args, "instructions");
85
+ const image = flag(args, "image");
86
+ const snapshot = flag(args, "snapshot");
87
+ if (image !== undefined && snapshot !== undefined)
88
+ throw usage("Give --image or --snapshot, not both.");
89
+ const secrets = args.flags.get("secret");
90
+ const definition = {
91
+ ...(base ?? {}),
92
+ ...(template ? AGENT_TEMPLATES[template].definition : {}),
93
+ ...(use ? AGENT_USES[use].definition : {}),
94
+ };
95
+ const set = (field, value) => {
96
+ if (value !== undefined)
97
+ definition[field] = value;
98
+ };
99
+ set("command", flag(args, "command"));
100
+ set("instructions", instructions);
101
+ if (secrets)
102
+ set("secrets", [...new Set([...(definition.secrets ?? []), ...secrets])]);
103
+ if (args.flags.has("env"))
104
+ set("env", {
105
+ ...(definition.env ?? {}),
106
+ ...kit.envPairs(args, env),
107
+ });
108
+ if (image !== undefined)
109
+ set("from", { image });
110
+ if (snapshot !== undefined)
111
+ set("from", { snapshot });
112
+ set("vcpu", integer(args, "vcpu"));
113
+ set("memoryMiB", integer(args, "memory"));
114
+ set("maxConcurrent", integer(args, "max-concurrent"));
115
+ set("idlePauseSeconds", integer(args, "idle-pause"));
116
+ return definition;
117
+ }
118
+ const line = (e) => `${String(e.seq).padStart(4)} ${e.kind.padEnd(6)} ${e.actor}: ${typeof e.body.text === "string" ? e.body.text : JSON.stringify(e.body)}`;
119
+ export async function agentsCommand(argv, env, out, client, kit) {
120
+ const { parse, flag, has, need, pairs, table, usage, unknown } = kit;
121
+ const [verb, ...rest] = argv;
122
+ const print = (human, value) => out.write(out.json ? JSON.stringify(value) : human);
123
+ if (!verb || verb === "help" || verb === "--help") {
124
+ print(named(AGENT_HELP), { usage: named(AGENT_HELP) });
125
+ return 0;
126
+ }
127
+ if (verb === "templates") {
128
+ const value = { templates: AGENT_TEMPLATES, uses: AGENT_USES };
129
+ print([
130
+ table([
131
+ ["TEMPLATE", "HARNESS", "MODEL KEY", "SUBSCRIPTION"],
132
+ ...Object.values(AGENT_TEMPLATES).map((t) => [
133
+ t.key,
134
+ t.title,
135
+ `${t.apiKeySecret} (${t.apiKeyHost})`,
136
+ t.subscription ? "yes, for your own team" : "no",
137
+ ]),
138
+ ]),
139
+ "",
140
+ table([
141
+ ["USE", "FOR", "SIZE", "TRIGGERS"],
142
+ ...Object.values(AGENT_USES).map((u) => [
143
+ u.key,
144
+ u.title,
145
+ `${u.definition.vcpu} vCPU, ${u.definition.memoryMiB} MiB`,
146
+ u.triggers.join(", "),
147
+ ]),
148
+ ]),
149
+ ].join("\n"), value);
150
+ return 0;
151
+ }
152
+ const transport = (await client()).transport;
153
+ const api = agents(transport);
154
+ const follow = async (sessionId, after) => {
155
+ for await (const e of (await api.session(sessionId)).events({ after }))
156
+ out.write(out.json ? JSON.stringify(e) : line(e));
157
+ };
158
+ switch (verb) {
159
+ case "create": {
160
+ const args = parse(rest, [], OPTIONS);
161
+ const name = need(args.positional[0], "the agent's name");
162
+ const definition = definitionOf(args, kit, env);
163
+ if (!definition.command)
164
+ throw usage(`Give --template (${Object.keys(AGENT_TEMPLATES).join(", ")}) or --command.`);
165
+ const agent = await api.create({
166
+ name,
167
+ definition,
168
+ ...(args.flags.has("label") ? { labels: pairs(args, "label") } : {}),
169
+ });
170
+ if (!out.json && agent.warnings.includes("subscription_for_customers"))
171
+ out.error("Warning: this agent answers your customers with a subscription's token. A subscription is for your own team's agents; an agent that serves your customers uses an API key.");
172
+ print(agent.id, agent);
173
+ return 0;
174
+ }
175
+ case "ls":
176
+ case "list": {
177
+ parse(rest, [], []);
178
+ const all = await (await api.list({ limit: 100 })).toArray(Infinity);
179
+ print(all.length
180
+ ? table([
181
+ ["ID", "NAME", "VERSION", "SIZE", "COMMAND", "CREATED"],
182
+ ...all.map((a) => [
183
+ a.id,
184
+ a.name,
185
+ `v${a.version}`,
186
+ `${a.definition.vcpu} vCPU, ${a.definition.memoryMiB} MiB`,
187
+ a.definition.command.length > 40
188
+ ? `${a.definition.command.slice(0, 39)}…`
189
+ : a.definition.command,
190
+ when(a.createdAt),
191
+ ]),
192
+ ])
193
+ : `No agents. Make one: ${me} agents create helper --template claude-code --use support`, all);
194
+ return 0;
195
+ }
196
+ case "get": {
197
+ const agent = await api.get(need(rest[0], "an agent's id or name"));
198
+ print(JSON.stringify(agent, null, 2), agent);
199
+ return 0;
200
+ }
201
+ case "update": {
202
+ const args = parse(rest, [], OPTIONS.filter((o) => o !== "label"));
203
+ const current = await api.get(need(args.positional[0], "an agent's id or name"));
204
+ const agent = await api.update(current.id, definitionOf(args, kit, env, current.definition));
205
+ print(`${agent.name} is now version ${agent.version}.`, agent);
206
+ return 0;
207
+ }
208
+ case "rm":
209
+ case "delete": {
210
+ const target = need(rest[0], "an agent's id or name");
211
+ await api.delete(target);
212
+ print(`${target} deleted; its open sessions are stopping.`, { deleted: true });
213
+ return 0;
214
+ }
215
+ case "start": {
216
+ const args = parse(rest, ["follow"], ["key"]);
217
+ const target = need(args.positional[0], "an agent's id or name");
218
+ const input = args.positional.slice(1).join(" ");
219
+ if (!input)
220
+ throw usage(`Give the words: ${me} agents start ${target} "Fix the failing test"`);
221
+ const key = flag(args, "key");
222
+ const session = await api.start(target, {
223
+ input,
224
+ ...(key === undefined ? {} : { key }),
225
+ });
226
+ if (!has(args, "follow")) {
227
+ print(session.id, { session: session.session, joined: session.joined, seq: session.seq });
228
+ return 0;
229
+ }
230
+ if (!out.json)
231
+ out.error(`Session ${session.id}${session.joined ? " (joined)" : ""}`);
232
+ await follow(session.id, 0);
233
+ return 0;
234
+ }
235
+ case "send": {
236
+ const id = need(rest[0], "a session's id");
237
+ const text = rest.slice(1).join(" ");
238
+ if (!text)
239
+ throw usage(`Give the words: ${me} agents send ${id} "And the docs too"`);
240
+ const sent = await (await api.session(id)).send(text);
241
+ print(`seq ${sent.seq}`, sent);
242
+ return 0;
243
+ }
244
+ case "events": {
245
+ const args = parse(rest, ["follow"], ["after"]);
246
+ const id = need(args.positional[0], "a session's id");
247
+ const after = kit.integer(args, "after") ?? 0;
248
+ if (has(args, "follow")) {
249
+ await follow(id, after);
250
+ return 0;
251
+ }
252
+ // One read without waiting: what is there now.
253
+ const read = await transport.json({
254
+ method: "GET",
255
+ path: `/v1/agents/sessions/${encodeURIComponent(id)}/events`,
256
+ query: { after },
257
+ });
258
+ print(read.data.map(line).join("\n") || "No events yet.", read);
259
+ return 0;
260
+ }
261
+ case "sessions": {
262
+ if (rest[0] === "delete" || rest[0] === "rm") {
263
+ const target = need(rest[1], "a session's id");
264
+ await api.sessions.delete(target);
265
+ print(`${target} deleted.`, { deleted: true });
266
+ return 0;
267
+ }
268
+ const args = parse(rest, [], ["agent", "state"]);
269
+ const states = args.flags.get("state");
270
+ for (const state of states ?? [])
271
+ if (!STATES.includes(state))
272
+ throw usage(`--state takes ${STATES.join(", ")}.`);
273
+ const agent = flag(args, "agent");
274
+ const page = await api.sessions.list({
275
+ limit: 100,
276
+ ...(agent === undefined ? {} : { agent }),
277
+ ...(states ? { state: states } : {}),
278
+ });
279
+ print(page.data.length
280
+ ? table([
281
+ ["ID", "AGENT", "STATE", "KEY", "TRIGGER", "CREATED"],
282
+ ...page.data.map((s) => [
283
+ s.id,
284
+ s.agentId,
285
+ s.state,
286
+ s.key ?? "-",
287
+ s.trigger,
288
+ when(s.createdAt),
289
+ ]),
290
+ ])
291
+ : "No sessions.", page.data);
292
+ return 0;
293
+ }
294
+ case "stop": {
295
+ const session = await (await api.session(need(rest[0], "a session's id"))).stop();
296
+ print(`${session.id} ${session.state}.`, session);
297
+ return 0;
298
+ }
299
+ case "triggers": {
300
+ const [sub, ...more] = rest;
301
+ if (sub === "add") {
302
+ const args = parse(more, [], ["kind", "config"]);
303
+ const target = need(args.positional[0], "an agent's id or name");
304
+ let config;
305
+ try {
306
+ config = JSON.parse(flag(args, "config") ?? "{}");
307
+ }
308
+ catch {
309
+ throw usage(`--config takes JSON, such as --config '{"cron":"0 9 * * 1-5","input":"Triage new issues"}'.`);
310
+ }
311
+ const trigger = await api.triggers.create(target, {
312
+ kind: need(flag(args, "kind"), "--kind"),
313
+ config,
314
+ });
315
+ // A webhook's secret is answered this once.
316
+ print(trigger.secret
317
+ ? `${trigger.id}\nsecret ${trigger.secret} (shown once; each call carries it)`
318
+ : trigger.id, trigger);
319
+ return 0;
320
+ }
321
+ if (sub === "pause") {
322
+ const args = parse(more, [], ["why"]);
323
+ const trigger = await api.triggers.pause(need(args.positional[0], "a trigger's id"), need(flag(args, "why"), "--why"));
324
+ print(`${trigger.id} paused: ${trigger.pausedWhy}`, trigger);
325
+ return 0;
326
+ }
327
+ if (sub === "resume") {
328
+ const trigger = await api.triggers.resume(need(more[0], "a trigger's id"));
329
+ print(`${trigger.id} resumed.`, trigger);
330
+ return 0;
331
+ }
332
+ if (sub === "rm" || sub === "delete") {
333
+ await api.triggers.delete(need(more[0], "a trigger's id"));
334
+ print(`${more[0]} deleted.`, { deleted: true });
335
+ return 0;
336
+ }
337
+ const all = await api.triggers.list(need(sub, "an agent's id or name"));
338
+ print(all.length
339
+ ? table([
340
+ ["ID", "KIND", "CONFIG", "PAUSED"],
341
+ ...all.map((t) => [t.id, t.kind, JSON.stringify(t.config), t.pausedWhy ?? "-"]),
342
+ ])
343
+ : "No triggers.", all);
344
+ return 0;
345
+ }
346
+ default:
347
+ throw unknown("agents command", verb, [
348
+ "templates",
349
+ "create",
350
+ "ls",
351
+ "get",
352
+ "update",
353
+ "rm",
354
+ "start",
355
+ "send",
356
+ "events",
357
+ "sessions",
358
+ "stop",
359
+ "triggers",
360
+ ]);
361
+ }
362
+ }
@@ -7,6 +7,8 @@ type Out = {
7
7
  };
8
8
  export declare const APP_HELP = "runtime apps <command>\n\n An App serves your web server from an image or a build. Copies start when\n requests come and stop when they end, between --min-copies and --max-copies.\n In a private beta: outside it, each command says how to ask to join.\n\n create [<name>] --vcpu <1-16> --memory <128-65536> --max-copies <1-1000>\n [--min-copies 0] [--concurrency 100] [--spares 2] [--port 8080]\n [--health-path /healthz] [--label key=value]...\n Make an App, waiting for its first deploy; prints its id\n ls [--all] Your Apps: state, live version, copies; --all, deleted too\n get <app> One App, in full (an id or its name)\n update <app> [--name <name>] [--vcpu n] [--memory n] [--min-copies n] [--max-copies n]\n [--concurrency n] [--spares n] [--port n] [--health-path /path] [--label key=value]...\n Change its settings\n rm <app> Delete it and stop its copies\n deploy <app> (--image <image> | --build <buildId>) [--env KEY=VALUE]... [--keep-env]\n [--port 8080] [--health-path /healthz] [--no-wait]\n A new version. Waits until it is live, or says why it\n failed; --no-wait prints the deploy's id at once.\n --image: an image of yours (id, name, name:tag);\n --build: a build that succeeded (runtime builds).\n --env KEY alone sends KEY's value from your environment.\n --keep-env keeps the last deploy's environment, sealed\n deploys <app> [<deployId>] [--wait] Each version's deploy: state and address; with an id,\n that one (--wait: until it ends)\n versions <app> Its versions; the live and previous ones keep saved copies\n copies <app> Its copies now: version, region, state\n rollback <app> [--no-wait] Put the previous version live again\n\nExamples\n runtime apps create web --vcpu 1 --memory 512 --max-copies 3\n runtime apps deploy web --image web:latest --env NODE_ENV=production\n runtime apps rollback web\n";
9
9
  export declare const BUILD_HELP = "runtime builds <command>\n\n A build turns a commit of a connected repository into a saved server an App\n deploys. A push to the production branch builds and deploys by itself.\n In a private beta: outside it, each command says how to ask to join.\n\n repos connect <owner/name | https URL> --deploy <app name> [--branch main] [--no-previews]\n [--root <folder>] [--install <command>] [--build <command>] [--start <command>]\n [--port 8080] [--health-path /healthz] [--build-env KEY=VALUE]... [--name <name>]\n Connect a repository; prints its id. --deploy takes an\n App's name, or function:<name>. --start runs once the\n build is done, with PORT set: the server the App deploys\n repos ls Connected repositories\n repos change <repoId> [the options of connect but the repository]\n Change its settings; its id and builds stay\n repos disconnect <repoId> Stop building it; its builds stay\n repos install A link to install Runtime's GitHub App, so a private\n repository builds\n create <repoId> [--ref main] [--branch <name>] [--name <name>]\n Build a branch, tag or commit now; prints the build's id\n run <repoId> [--ref main] [--branch <name>] [--name <name>] [--raw]\n The same, then follows its log; exits 0 once it succeeds\n ls [--repository <repoId>] [--commit <sha>] [--state <state>]...\n Builds, oldest first; --commit: a full sha\n get <buildId> One build: state, commit, step times, what it made\n cancel <buildId> Stop a build that has not ended\n logs <buildId> [--follow] [--raw] Its last 200 lines, or every line as it comes until it\n ends; on a terminal control characters show as \\uXXXX\n unless --raw\n\nExamples\n runtime builds repos connect acme/web --deploy web --start \"npm start\"\n runtime builds run <repoId> --ref main\n runtime builds ls --commit $(git rev-parse HEAD)\n";
10
+ export declare const CONTAINER_HELP = "runtime containers <command>\n\n A container runs one OCI image's process on its own microVM, billed at the\n sandbox rates. It never sleeps unless --sleep; on exit it restarts when it\n serves a port, else it stops (--on-exit sets either).\n In a private beta: outside it, each command says how to ask to join.\n\n create <image> --vcpu <1-16> --memory <128-65536> [--name <name>] [--label key=value]...\n [--env KEY[=VALUE]]... [--entrypoint <program>] [--workdir /app] [--user <user>]\n [--port <1-65535>] [--on-exit restart|stop] [--sleep] [-- <command...>]\n Run it; prints its id. The image's digest is pinned.\n --env KEY alone sends KEY's value from your environment.\n --entrypoint drops the image's command; after --, the\n command replaces the image's\n ls [--all] [--state <state>]... [--label key=value]...\n Your containers: all but deleting and deleted, unless\n --all or --state\n get <container> One container, in full (an id or its name): its\n environment's names, never their values\n update <container> [--name <name>] [--label key=value]...\n Rename it or replace its labels\n rm <container> [--wait] Delete it and stop its process; --wait: until deleted\n\nExamples\n runtime containers create nginx:1.27 --vcpu 1 --memory 512 --name web\n runtime containers create python:3.12 --vcpu 1 --memory 1024 -- python3 -m http.server 8000\n runtime containers rm web --wait\n";
10
11
  export declare function appsCommand(argv: string[], env: NodeJS.ProcessEnv, out: Out, client: () => Promise<Runtime>, kit: CliKit): Promise<number>;
11
12
  export declare function buildsCommand(argv: string[], out: Out, client: () => Promise<Runtime>, kit: CliKit): Promise<number>;
13
+ export declare function containersCommand(argv: string[], env: NodeJS.ProcessEnv, out: Out, client: () => Promise<Runtime>, kit: CliKit): Promise<number>;
12
14
  export {};