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.
Files changed (43) hide show
  1. package/E2B.md +7 -0
  2. package/dist/agents-cli.d.ts +1 -1
  3. package/dist/agents-cli.js +171 -7
  4. package/dist/agents-deploy.d.ts +13 -3
  5. package/dist/agents-deploy.js +68 -9
  6. package/dist/apps-cli.d.ts +1 -1
  7. package/dist/apps-cli.js +41 -9
  8. package/dist/browsers/index.d.ts +9 -0
  9. package/dist/browsers/index.js +10 -0
  10. package/dist/browsers-cli.d.ts +13 -0
  11. package/dist/browsers-cli.js +160 -0
  12. package/dist/ci-cli.d.ts +1 -1
  13. package/dist/ci-cli.js +16 -3
  14. package/dist/cli.d.ts +4 -0
  15. package/dist/cli.js +94 -10
  16. package/dist/desktops/index.d.ts +9 -0
  17. package/dist/desktops/index.js +10 -0
  18. package/dist/e2b/code-interpreter.d.ts +3 -12
  19. package/dist/e2b/code-interpreter.js +3 -6
  20. package/dist/e2b/index.d.ts +7 -1
  21. package/dist/e2b/index.js +9 -2
  22. package/dist/index.d.ts +5 -3
  23. package/dist/products/agents.d.ts +162 -8
  24. package/dist/products/agents.js +108 -3
  25. package/dist/products/apps.d.ts +46 -5
  26. package/dist/products/browsers.d.ts +26 -0
  27. package/dist/products/browsers.js +11 -0
  28. package/dist/products/ci.d.ts +10 -4
  29. package/dist/products/ci.js +8 -0
  30. package/dist/products/desktops.d.ts +71 -0
  31. package/dist/products/desktops.js +27 -0
  32. package/dist/products/index.d.ts +4 -0
  33. package/dist/products/index.js +5 -0
  34. package/dist/products/network.d.ts +4 -2
  35. package/dist/products/secrets.d.ts +10 -2
  36. package/dist/products/secrets.js +27 -7
  37. package/dist/products/uptime.d.ts +97 -0
  38. package/dist/products/uptime.js +42 -0
  39. package/dist/products/volumes.d.ts +41 -9
  40. package/dist/products/volumes.js +5 -2
  41. package/dist/transport.d.ts +4 -1
  42. package/dist/transport.js +2 -2
  43. package/package.json +12 -1
package/E2B.md CHANGED
@@ -38,6 +38,12 @@ may be two accounts, a warning says so once (`RUNTIME_E2B_TWO_KEYS`; Python: a
38
38
  `RuntimeWarning`). A template with no image of its name fails naming the
39
39
  account the key belongs to and where the key came from.
40
40
 
41
+ E2B's client works the same way: `new E2B({ apiKey }).Sandbox` (Python:
42
+ `E2B(api_key=...).Sandbox` and `.AsyncSandbox`) uses that key unless a call
43
+ names its own, and the code interpreter's `E2B` gives its own `Sandbox`. The
44
+ client's `Template`, `Volume` and `Secret` (Python: and their `Async` forms)
45
+ refuse as the top-level ones do, naming the alternative under "Gaps".
46
+
41
47
  ## What a sandbox gets
42
48
 
43
49
  - **Machine:** E2B's default, 2 vCPU and 512 MiB. For another, pass
@@ -145,6 +151,7 @@ before anything happens. The error's `feature` names the gap and its
145
151
  | `git` | `commands.run("git ...")`. E2B has deprecated its git module too. |
146
152
  | File `metadata` | Keep it in a file beside the data. |
147
153
  | `runCode(code, { envs })` | A context made with them: `sandbox.runtime.interpreter.contexts.create({ env })`. |
154
+ | `new E2B(opts).Template`, `.Secret`, `.Volume` | As `Template`, `Secret` and `Volume` above: each throws `NotSupportedError` naming the alternative. |
148
155
  | `domain`, `apiUrl`, `sandboxUrl`, `headers`, `proxy`, `debug` | Remove them. `RUNTIME_API_URL` sets Runtime's origin, and `HTTPS_PROXY` sets a proxy. |
149
156
 
150
157
  Directory watches use Runtime's native watch stream. JavaScript uses
@@ -5,6 +5,6 @@ type Out = {
5
5
  write: (text: string) => void;
6
6
  error: (text: string) => void;
7
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 init [name] [--template <harness>] [--use support|team] [--file runtime.yml]\n Write a starter agents: section into runtime.yml\n deploy [--file runtime.yml] [--dry-run] [--prune [--prune-memory]]\n Make your agents match runtime.yml: create, update\n (a new version only when it changed) and set\n triggers; one line per change. --prune deletes the\n agents a deploy made that the file no longer names,\n with their triggers; one holding memory files only\n with --prune-memory too, which deletes them for good\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 memory ls <agent> [--scope <key>] [--prefix <path>]\n Its memory files, with their scope (shared or a key)\n memory search <agent> <words...> [--scope <key>] [--limit n]\n Files whose words match, best first\n memory cat <agent> <path> [--scope <key>] A file's text\n memory put <agent> <path> [<file> | - | --content <text>] [--scope <key>]\n Seed or replace a file (from a file, or - for stdin)\n memory rm <agent> <path> [--scope <key>] Delete a file\n Without --scope: the shared memory, which every\n session reads and only a shared agent's write\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 runtime agents memory put helper faq/refunds.md refunds.md\n runtime agents init --template claude-code && runtime agents deploy --dry-run\n";
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 init [name] [--template <harness>] [--use support|team] [--file runtime.yml]\n Write a starter agents: section into runtime.yml\n deploy [--file runtime.yml] [--dry-run] [--prune [--prune-memory]]\n Make your agents match runtime.yml: create, update\n (a new version only when it changed) and set\n triggers; one line per change. --prune deletes the\n agents a deploy made that the file no longer names,\n with their triggers; one holding memory files only\n with --prune-memory too, which deletes them for good\n create <name> (--template <harness> | --command \"<cmd>\") [--use support|team]\n [--instructions <text> | --instructions-file <path>] [--secret NAME]... [--pool 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 --pool names a Claude account's secret (each one\n given replaces the template's): each session takes\n the one with the most usage left\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 asks [--state <state>]... [--agent <agent>] [--label key=value]...\n The inbox: what your agents' sessions need answered,\n one row per step however many sessions ask it\n asks get <ask> One ask: its options, pick and the sessions asking\n answer <ask> [<option>] [--text <words>] Answer it with an option's label, words, or both:\n every session asking reads it next and wakes\n triggers <agent> Its triggers\n triggers add <agent> --kind <slack|github|schedule|webhook> --config '<json>' [--rules '<json>']\n triggers rules <triggerId> '<json>' Set its rules in place: priority, maxStartsPerHour,\n onEnded (start|note|ask), notify (email|webhook)\n and a webhook's key (\"{cause}\"); its secret stays\n triggers call <triggerId> <words...> [--key <key>] [--label key=value]... [--secret <secret>]\n Call a webhook as its sender would, by its secret\n (or RUNTIME_TRIGGER_SECRET): starts, joins, notes or\n holds\n triggers held <triggerId> [--state held|started|dismissed]...\n Calls it held for you; none is dropped\n triggers held start <triggerId> <callId> Start one, with every call held for its key since\n triggers held dismiss <triggerId> <callId> Dismiss one; nothing runs\n triggers pause <triggerId> --why <words> Hold it: a webhook's calls are kept for you, saying why\n triggers resume <triggerId>\n triggers rm <triggerId>\n memory ls <agent> [--scope <key>] [--prefix <path>]\n Its memory files, with their scope (shared or a key)\n memory search <agent> <words...> [--scope <key>] [--limit n]\n Files whose words match, best first\n memory cat <agent> <path> [--scope <key>] A file's text\n memory put <agent> <path> [<file> | - | --content <text>] [--scope <key>]\n Seed or replace a file (from a file, or - for stdin)\n memory rm <agent> <path> [--scope <key>] Delete a file\n Without --scope: the shared memory, which every\n session reads and only a shared agent's write\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 runtime agents memory put helper faq/refunds.md refunds.md\n runtime agents asks && runtime agents answer <ask> \"Raise to 20 TB\"\n runtime agents init --template claude-code && runtime agents deploy --dry-run\n runtime agents create ci-review --template review --pool CLAUDE_1 --pool CLAUDE_2\n";
9
9
  export declare function agentsCommand(argv: string[], env: NodeJS.ProcessEnv, out: Out, client: () => Promise<Runtime>, kit: CliKit): Promise<number>;
10
10
  export {};
@@ -19,11 +19,14 @@ export const AGENT_HELP = `runtime agents <command>
19
19
  with their triggers; one holding memory files only
20
20
  with --prune-memory too, which deletes them for good
21
21
  create <name> (--template <harness> | --command "<cmd>") [--use support|team]
22
- [--instructions <text> | --instructions-file <path>] [--secret NAME]...
22
+ [--instructions <text> | --instructions-file <path>] [--secret NAME]... [--pool NAME]...
23
23
  [--env KEY=VALUE]... [--image <image> | --snapshot <id>] [--vcpu n] [--memory n]
24
24
  [--max-concurrent n] [--idle-pause n] [--label key=value]...
25
25
  Make an agent; prints its id. --secret names an egress
26
- secret it may use: its model key, of any provider
26
+ secret it may use: its model key, of any provider.
27
+ --pool names a Claude account's secret (each one
28
+ given replaces the template's): each session takes
29
+ the one with the most usage left
27
30
  ls Your agents
28
31
  get <agent> One agent, in full (an id or its name)
29
32
  update <agent> [the options of create] A new version: what you give changes, the rest stays
@@ -39,9 +42,26 @@ export const AGENT_HELP = `runtime agents <command>
39
42
  sessions delete <session> Delete an ended session and its log; its history is
40
43
  storage, kept until you delete it
41
44
  stop <session> Stop a session
45
+ asks [--state <state>]... [--agent <agent>] [--label key=value]...
46
+ The inbox: what your agents' sessions need answered,
47
+ one row per step however many sessions ask it
48
+ asks get <ask> One ask: its options, pick and the sessions asking
49
+ answer <ask> [<option>] [--text <words>] Answer it with an option's label, words, or both:
50
+ every session asking reads it next and wakes
42
51
  triggers <agent> Its triggers
43
- triggers add <agent> --kind <slack|github|schedule|webhook> --config '<json>'
44
- triggers pause <triggerId> --why <words> Refuse its events, saying why; nothing else is held
52
+ triggers add <agent> --kind <slack|github|schedule|webhook> --config '<json>' [--rules '<json>']
53
+ triggers rules <triggerId> '<json>' Set its rules in place: priority, maxStartsPerHour,
54
+ onEnded (start|note|ask), notify (email|webhook)
55
+ and a webhook's key ("{cause}"); its secret stays
56
+ triggers call <triggerId> <words...> [--key <key>] [--label key=value]... [--secret <secret>]
57
+ Call a webhook as its sender would, by its secret
58
+ (or RUNTIME_TRIGGER_SECRET): starts, joins, notes or
59
+ holds
60
+ triggers held <triggerId> [--state held|started|dismissed]...
61
+ Calls it held for you; none is dropped
62
+ triggers held start <triggerId> <callId> Start one, with every call held for its key since
63
+ triggers held dismiss <triggerId> <callId> Dismiss one; nothing runs
64
+ triggers pause <triggerId> --why <words> Hold it: a webhook's calls are kept for you, saying why
45
65
  triggers resume <triggerId>
46
66
  triggers rm <triggerId>
47
67
  memory ls <agent> [--scope <key>] [--prefix <path>]
@@ -63,9 +83,24 @@ Examples
63
83
  runtime agents create helper --template claude-code --use support --instructions "Answer briefly."
64
84
  runtime agents start helper "How do I reset my password?" --key customer:42 --follow
65
85
  runtime agents memory put helper faq/refunds.md refunds.md
86
+ runtime agents asks && runtime agents answer <ask> "Raise to 20 TB"
66
87
  runtime agents init --template claude-code && runtime agents deploy --dry-run
88
+ runtime agents create ci-review --template review --pool CLAUDE_1 --pool CLAUDE_2
67
89
  `;
68
90
  const when = (iso) => iso ?? "-";
91
+ /** A trigger's rules as the CLI takes them: one JSON object. */
92
+ function rulesOf(text, kit) {
93
+ let rules;
94
+ try {
95
+ rules = JSON.parse(text);
96
+ }
97
+ catch {
98
+ rules = undefined;
99
+ }
100
+ if (!rules || typeof rules !== "object" || Array.isArray(rules))
101
+ throw kit.usage(`Rules are a JSON object, such as '{"key":"{cause}","maxStartsPerHour":60,"onEnded":"ask"}'.`);
102
+ return rules;
103
+ }
69
104
  const OPTIONS = [
70
105
  "template",
71
106
  "use",
@@ -73,6 +108,7 @@ const OPTIONS = [
73
108
  "instructions",
74
109
  "instructions-file",
75
110
  "secret",
111
+ "pool",
76
112
  "env",
77
113
  "image",
78
114
  "snapshot",
@@ -82,6 +118,20 @@ const OPTIONS = [
82
118
  "idle-pause",
83
119
  "label",
84
120
  ];
121
+ const ASK_STATES = ["open", "answered", "expired", "withdrawn", "closed"];
122
+ const askLine = (a) => {
123
+ const options = a.options.map((o) => (o.label === a.pick ? `${o.label} (pick)` : o.label));
124
+ return [
125
+ `${a.id} ${a.state}${a.step ? ` step ${a.step}` : ""}, ${a.sessions.length} session${a.sessions.length === 1 ? "" : "s"} asking`,
126
+ ` ${a.question}`,
127
+ ...(a.why ? [` why: ${a.why}`] : []),
128
+ ...(options.length ? [` options: ${options.join(" | ")}`] : []),
129
+ ...(a.expiresAt ? [` expires ${a.expiresAt}`] : []),
130
+ ...(a.answer
131
+ ? [` answered: ${[a.answer.option, a.answer.text].filter(Boolean).join(": ")}`]
132
+ : []),
133
+ ].join("\n");
134
+ };
85
135
  const STATES = [
86
136
  "queued",
87
137
  "running",
@@ -122,6 +172,9 @@ function definitionOf(args, kit, env, base) {
122
172
  set("instructions", instructions);
123
173
  if (secrets)
124
174
  set("secrets", [...new Set([...(definition.secrets ?? []), ...secrets])]);
175
+ const pool = args.flags.get("pool");
176
+ if (pool)
177
+ set("pool", [...new Set(pool)]);
125
178
  if (args.flags.has("env"))
126
179
  set("env", {
127
180
  ...(definition.env ?? {}),
@@ -317,6 +370,42 @@ export async function agentsCommand(argv, env, out, client, kit) {
317
370
  : "No sessions.", page.data);
318
371
  return 0;
319
372
  }
373
+ case "asks": {
374
+ if (rest[0] === "get") {
375
+ const one = await api.asks.get(need(rest[1], "an ask's id"));
376
+ print(askLine(one), one);
377
+ return 0;
378
+ }
379
+ const args = parse(rest, [], ["state", "agent", "label"]);
380
+ const states = args.flags.get("state");
381
+ for (const state of states ?? [])
382
+ if (!ASK_STATES.includes(state))
383
+ throw usage(`--state takes ${ASK_STATES.join(", ")}.`);
384
+ const agent = flag(args, "agent");
385
+ const labels = args.flags.has("label") ? pairs(args, "label") : undefined;
386
+ const page = await api.asks.list({
387
+ limit: 100,
388
+ ...(agent === undefined ? {} : { agent }),
389
+ ...(states ? { state: states } : {}),
390
+ ...(labels ? { labels } : {}),
391
+ });
392
+ print(page.data.map(askLine).join("\n\n") || "Nothing to answer.", page.data);
393
+ return 0;
394
+ }
395
+ case "answer": {
396
+ const args = parse(rest, [], ["text"]);
397
+ const id = need(args.positional[0], "an ask's id");
398
+ const option = args.positional.slice(1).join(" ") || undefined;
399
+ const text = flag(args, "text");
400
+ if (option === undefined && text === undefined)
401
+ throw usage(`Give an option's label, --text, or both: ${me} agents answer ${id} "Keep it"`);
402
+ const answered = await api.asks.answer(id, {
403
+ ...(option === undefined ? {} : { option }),
404
+ ...(text === undefined ? {} : { text }),
405
+ });
406
+ print(`${answered.id} answered; ${answered.sessions.filter((x) => x.state === "answered").length} session(s) woken.`, answered);
407
+ return 0;
408
+ }
320
409
  case "stop": {
321
410
  const session = await (await api.session(need(rest[0], "a session's id"))).stop();
322
411
  print(`${session.id} ${session.state}.`, session);
@@ -325,7 +414,7 @@ export async function agentsCommand(argv, env, out, client, kit) {
325
414
  case "triggers": {
326
415
  const [sub, ...more] = rest;
327
416
  if (sub === "add") {
328
- const args = parse(more, [], ["kind", "config"]);
417
+ const args = parse(more, [], ["kind", "config", "rules"]);
329
418
  const target = need(args.positional[0], "an agent's id or name");
330
419
  let config;
331
420
  try {
@@ -334,9 +423,11 @@ export async function agentsCommand(argv, env, out, client, kit) {
334
423
  catch {
335
424
  throw usage(`--config takes JSON, such as --config '{"cron":"0 9 * * 1-5","input":"Triage new issues"}'.`);
336
425
  }
426
+ const rules = flag(args, "rules");
337
427
  const trigger = await api.triggers.create(target, {
338
428
  kind: need(flag(args, "kind"), "--kind"),
339
429
  config,
430
+ ...(rules === undefined ? {} : { rules: rulesOf(rules, kit) }),
340
431
  });
341
432
  // A webhook's secret is answered this once.
342
433
  print(trigger.secret
@@ -344,6 +435,71 @@ export async function agentsCommand(argv, env, out, client, kit) {
344
435
  : trigger.id, trigger);
345
436
  return 0;
346
437
  }
438
+ if (sub === "rules") {
439
+ const id = need(more[0], "a trigger's id");
440
+ const trigger = await api.triggers.setRules(id, rulesOf(need(more[1], `the rules as JSON: ${me} agents triggers rules ${id} '{"priority":10}'`), kit));
441
+ print(`${trigger.id} rules ${JSON.stringify(trigger.rules)}`, trigger);
442
+ return 0;
443
+ }
444
+ if (sub === "call") {
445
+ const args = parse(more, [], ["key", "label", "secret"]);
446
+ const id = need(args.positional[0], "a trigger's id");
447
+ const text = args.positional.slice(1).join(" ");
448
+ if (!text)
449
+ throw usage(`Give the words: ${me} agents triggers call ${id} "disk full on db-1"`);
450
+ const secret = flag(args, "secret") ?? env.RUNTIME_TRIGGER_SECRET;
451
+ if (!secret)
452
+ throw usage("Give the trigger's secret: --secret rtat_… or RUNTIME_TRIGGER_SECRET, as answered when the webhook was added.");
453
+ const key = flag(args, "key");
454
+ const done = await api.triggers.call(id, secret, {
455
+ text,
456
+ ...(key === undefined ? {} : { key }),
457
+ ...(args.flags.has("label") ? { labels: pairs(args, "label") } : {}),
458
+ });
459
+ print(done.outcome === "held"
460
+ ? `held ${done.callId}: ${done.why}`
461
+ : `${done.outcome} ${done.sessionId} seq ${done.seq}`, done);
462
+ return 0;
463
+ }
464
+ if (sub === "held") {
465
+ const [action, ...after] = more;
466
+ if (action === "start" || action === "dismiss") {
467
+ const id = need(after[0], "a trigger's id");
468
+ const call = need(after[1], "a held call's id");
469
+ if (action === "start") {
470
+ const started = await api.triggers.startHeld(id, call);
471
+ print(`${started.joined ? "joined" : "started"} ${started.session.id} with ${started.calls} held call${started.calls === 1 ? "" : "s"}`, started);
472
+ }
473
+ else {
474
+ const dismissed = await api.triggers.dismissHeld(id, call);
475
+ print(`${dismissed.id} dismissed.`, dismissed);
476
+ }
477
+ return 0;
478
+ }
479
+ const args = parse(more, [], ["state"]);
480
+ const states = args.flags.get("state");
481
+ for (const state of states ?? [])
482
+ if (!["held", "started", "dismissed"].includes(state))
483
+ throw usage("--state takes held, started, dismissed.");
484
+ const page = await api.triggers.held(need(args.positional[0], "a trigger's id"), {
485
+ limit: 100,
486
+ ...(states ? { state: states } : {}),
487
+ });
488
+ print(page.data.length
489
+ ? table([
490
+ ["ID", "STATE", "WHY", "KEY", "CREATED", "WORDS"],
491
+ ...page.data.map((c) => [
492
+ c.id,
493
+ c.state,
494
+ c.why,
495
+ c.key ?? "-",
496
+ when(c.createdAt),
497
+ c.text.replace(/\s+/g, " ").slice(0, 60),
498
+ ]),
499
+ ])
500
+ : "No held calls.", page.data);
501
+ return 0;
502
+ }
347
503
  if (sub === "pause") {
348
504
  const args = parse(more, [], ["why"]);
349
505
  const trigger = await api.triggers.pause(need(args.positional[0], "a trigger's id"), need(flag(args, "why"), "--why"));
@@ -363,8 +519,14 @@ export async function agentsCommand(argv, env, out, client, kit) {
363
519
  const all = await api.triggers.list(need(sub, "an agent's id or name"));
364
520
  print(all.length
365
521
  ? table([
366
- ["ID", "KIND", "CONFIG", "PAUSED"],
367
- ...all.map((t) => [t.id, t.kind, JSON.stringify(t.config), t.pausedWhy ?? "-"]),
522
+ ["ID", "KIND", "CONFIG", "RULES", "PAUSED"],
523
+ ...all.map((t) => [
524
+ t.id,
525
+ t.kind,
526
+ JSON.stringify(t.config),
527
+ Object.keys(t.rules ?? {}).length ? JSON.stringify(t.rules) : "-",
528
+ t.pausedWhy ?? "-",
529
+ ]),
368
530
  ])
369
531
  : "No triggers.", all);
370
532
  return 0;
@@ -386,6 +548,8 @@ export async function agentsCommand(argv, env, out, client, kit) {
386
548
  "events",
387
549
  "sessions",
388
550
  "stop",
551
+ "asks",
552
+ "answer",
389
553
  "triggers",
390
554
  "memory",
391
555
  ]);
@@ -1,16 +1,18 @@
1
1
  import type { Runtime } from "./client.js";
2
- import { type Agent, type AgentDefinition, type AgentTrigger, type AgentTriggerKind, type CreateAgentTrigger } from "./products/agents.js";
3
- /** One trigger as the file writes it. `paused` holds it, saying why. */
2
+ import { type Agent, type AgentDefinition, type AgentTrigger, type AgentTriggerKind, type AgentTriggerRules, type CreateAgentTrigger } from "./products/agents.js";
3
+ /** One trigger as the file writes it. `paused` holds it, saying why;
4
+ * `rules` are changed in place, never by making the trigger again. */
4
5
  export type AgentsFileTrigger = {
5
6
  kind: AgentTriggerKind;
6
7
  config: Record<string, unknown>;
8
+ rules?: AgentTriggerRules;
7
9
  paused?: string;
8
10
  };
9
11
  /** One agent as the file writes it: a harness template and a use fill in
10
12
  * what it leaves out, then each definition field it sets. */
11
13
  export type AgentsFileAgent = Omit<Partial<AgentDefinition>, "template"> & {
12
14
  /** A harness: claude-code, codex, gemini-cli, claude-agent-sdk,
13
- * openai-agents-sdk. Fills in `command` and `secrets`. */
15
+ * openai-agents-sdk, review, judge. Fills in `command` and its model keys. */
14
16
  template?: string;
15
17
  /** support or team: a size and bounds for that use (`agents.uses`). */
16
18
  use?: "support" | "team";
@@ -53,6 +55,7 @@ export type AgentsDeployPort = {
53
55
  pauseTrigger(triggerId: string, why: string, key: string): Promise<AgentTrigger>;
54
56
  resumeTrigger(triggerId: string, key: string): Promise<AgentTrigger>;
55
57
  removeTrigger(triggerId: string, key: string): Promise<void>;
58
+ setTriggerRules(triggerId: string, rules: AgentTriggerRules, key: string): Promise<AgentTrigger>;
56
59
  /** How many memory files the agent holds: deleting it deletes them. */
57
60
  memoryFiles(agentId: string): Promise<number>;
58
61
  };
@@ -101,6 +104,13 @@ export type AgentsDeployChange = {
101
104
  triggerId: string;
102
105
  key: string;
103
106
  text: string;
107
+ } | {
108
+ action: "set-trigger-rules";
109
+ agent: string;
110
+ triggerId: string;
111
+ rules: AgentTriggerRules;
112
+ key: string;
113
+ text: string;
104
114
  } | {
105
115
  action: "remove-trigger";
106
116
  agent: string;
@@ -22,6 +22,7 @@ const DEFINITION_FIELDS = [
22
22
  "instructions",
23
23
  "env",
24
24
  "secrets",
25
+ "pool",
25
26
  "mcp",
26
27
  "volumes",
27
28
  "idlePauseSeconds",
@@ -131,9 +132,10 @@ function checkAgent(name, entry, source, say) {
131
132
  Object.assign(def, AGENT_USES[use].definition);
132
133
  const found = typeof template === "string" ? agentTemplate(template) : undefined;
133
134
  if (found) {
134
- const t = found.definition;
135
- def.command = t.command;
136
- def.secrets = [...t.secrets];
135
+ // A use's size wins over the template's, as the CLI's --use does.
136
+ for (const [k, v] of Object.entries(found.definition))
137
+ if (def[k] === undefined)
138
+ def[k] = Array.isArray(v) ? [...v] : v;
137
139
  }
138
140
  const integer = (field, min, max) => {
139
141
  const v = entry[field];
@@ -200,8 +202,11 @@ function checkAgent(name, entry, source, say) {
200
202
  if (!isObject(v) ||
201
203
  keys.length !== 1 ||
202
204
  !((typeof v.image === "string" && /^[A-Za-z0-9][A-Za-z0-9_./:@-]{0,255}$/.test(v.image)) ||
203
- (typeof v.snapshot === "string" && UUID.test(v.snapshot))))
204
- note(at("from"), "takes { image: name[:tag] } or { snapshot: <snapshot id> }.");
205
+ (typeof v.snapshot === "string" && UUID.test(v.snapshot)) ||
206
+ // The API's own pattern (packages/cloud/src/api/products/agents.ts).
207
+ (typeof v.repo === "string" &&
208
+ /^[A-Za-z0-9_.-]{1,100}\/[A-Za-z0-9_.-]{1,100}$/.test(v.repo))))
209
+ note(at("from"), "takes { image: name[:tag] }, { snapshot: <snapshot id> } or { repo: owner/name }.");
205
210
  else
206
211
  def.from = { ...v };
207
212
  }
@@ -258,6 +263,18 @@ function checkAgent(name, entry, source, say) {
258
263
  def.secrets = [...new Set(v)];
259
264
  }
260
265
  }
266
+ if (entry.pool !== undefined) {
267
+ const v = entry.pool;
268
+ if (!Array.isArray(v) || v.length < 1 || v.length > 16)
269
+ note(at("pool"), "takes a list of 1 to 16 egress secrets' names, one per Claude account.");
270
+ else {
271
+ v.forEach((s, i) => {
272
+ if (typeof s !== "string" || !SHELL_NAME.test(s))
273
+ note(at("pool", i), "is not a secret's name, such as CLAUDE_1.");
274
+ });
275
+ def.pool = [...new Set(v)];
276
+ }
277
+ }
261
278
  if (entry.mcp !== undefined) {
262
279
  const v = entry.mcp;
263
280
  if (!Array.isArray(v) || v.length > 16)
@@ -347,8 +364,8 @@ function checkTrigger(t, note) {
347
364
  note(rest, message);
348
365
  };
349
366
  for (const key of Object.keys(t))
350
- if (!["kind", "config", "paused"].includes(key))
351
- bad([key], `is not a field a trigger takes${hint(key, ["kind", "config", "paused"])}`);
367
+ if (!["kind", "config", "rules", "paused"].includes(key))
368
+ bad([key], `is not a field a trigger takes${hint(key, ["kind", "config", "rules", "paused"])}`);
352
369
  const kind = t.kind;
353
370
  if (!TRIGGER_KINDS.includes(kind)) {
354
371
  bad(["kind"], `takes ${TRIGGER_KINDS.join(", ")}.`);
@@ -391,12 +408,37 @@ function checkTrigger(t, note) {
391
408
  if (paused !== undefined &&
392
409
  (typeof paused !== "string" || paused.length < 1 || paused.length > 1024))
393
410
  bad(["paused"], "takes the words a held trigger says, 1 to 1024 characters.");
411
+ const rules = t.rules ?? {};
412
+ if (!isObject(rules))
413
+ bad(["rules"], `takes ${RULES}.`);
414
+ else
415
+ for (const [k, v] of Object.entries(rules)) {
416
+ const whole = (lo, hi) => typeof v === "number" && Number.isInteger(v) && v >= lo && v <= hi;
417
+ if (!((k === "priority" && whole(-100, 100)) ||
418
+ (k === "maxStartsPerHour" && whole(1, 100000)) ||
419
+ (k === "onEnded" && ["start", "note", "ask"].includes(v)) ||
420
+ (k === "notify" && ["email", "webhook"].includes(v)) ||
421
+ (k === "key" &&
422
+ kind === "webhook" &&
423
+ typeof v === "string" &&
424
+ v.length <= 256 &&
425
+ RULE_KEY.test(v))))
426
+ bad(["rules", k], `takes ${RULES}.`);
427
+ }
394
428
  if (ok && !within(config, TRIGGER_MAX_BYTES))
395
429
  bad(["config"], "is over 40 KiB.");
396
430
  if (!ok)
397
431
  return null;
398
- return { kind, config, ...(typeof paused === "string" ? { paused } : {}) };
432
+ return {
433
+ kind,
434
+ config,
435
+ ...(isObject(rules) && Object.keys(rules).length ? { rules: rules } : {}),
436
+ ...(typeof paused === "string" ? { paused } : {}),
437
+ };
399
438
  }
439
+ const RULES = '{ priority?: -100 to 100, maxStartsPerHour?: 1 to 100000, onEnded?: start | note | ask, notify?: email | webhook, key?: "{field}" (a webhook\'s) }';
440
+ /** A webhook's key template, as the API holds it. */
441
+ const RULE_KEY = /^([A-Za-z0-9._:/@#+=-]|\{[A-Za-z0-9_-]{1,64}(\.[A-Za-z0-9_-]{1,64}){0,7}\})+$/;
400
442
  const TRIGGER_KEYS = {
401
443
  slack: ["team", "channel"],
402
444
  github: ["installation", "repository", "events"],
@@ -479,6 +521,15 @@ export async function planAgents(port, desired, options = {}) {
479
521
  continue;
480
522
  }
481
523
  const [match] = open.splice(at, 1);
524
+ if (canonical(trigger.rules ?? {}) !== canonical(match.rules ?? {}))
525
+ changes.push({
526
+ action: "set-trigger-rules",
527
+ agent: want.name,
528
+ triggerId: match.id,
529
+ rules: trigger.rules ?? {},
530
+ key: await keyOf("set-trigger-rules", match.id, trigger.rules ?? {}, match.updatedAt),
531
+ text: `set rules of trigger ${match.id} (${triggerText(trigger)}) of ${want.name}: ${canonical(trigger.rules ?? {})}`,
532
+ });
482
533
  if (trigger.paused !== undefined && match.pausedWhy !== trigger.paused)
483
534
  changes.push({
484
535
  action: "pause-trigger",
@@ -590,7 +641,11 @@ async function apply(port, change, ids) {
590
641
  if (!change.agentId)
591
642
  change.key = await keyOf(change.key, agentId);
592
643
  const key = change.key;
593
- const made = await port.addTrigger(agentId, { kind: change.trigger.kind, config: change.trigger.config }, key);
644
+ const made = await port.addTrigger(agentId, {
645
+ kind: change.trigger.kind,
646
+ config: change.trigger.config,
647
+ ...(change.trigger.rules ? { rules: change.trigger.rules } : {}),
648
+ }, key);
594
649
  change.triggerId = made.id;
595
650
  if (made.secret)
596
651
  change.secret = made.secret;
@@ -607,6 +662,9 @@ async function apply(port, change, ids) {
607
662
  case "remove-trigger":
608
663
  await port.removeTrigger(change.triggerId, change.key);
609
664
  return;
665
+ case "set-trigger-rules":
666
+ await port.setTriggerRules(change.triggerId, change.rules, change.key);
667
+ return;
610
668
  }
611
669
  }
612
670
  /** The fields whose value differs. A field the file leaves out matches the
@@ -740,6 +798,7 @@ function clientPort(api) {
740
798
  pauseTrigger: (id, why, idempotencyKey) => api.triggers.pause(id, why, { idempotencyKey }),
741
799
  resumeTrigger: (id, idempotencyKey) => api.triggers.resume(id, { idempotencyKey }),
742
800
  removeTrigger: (id, idempotencyKey) => api.triggers.delete(id, { idempotencyKey }),
801
+ setTriggerRules: (id, rules, idempotencyKey) => api.triggers.setRules(id, rules, { idempotencyKey }),
743
802
  memoryFiles: async (agentId) => (await api.memory.list(agentId, { limit: 1 })).usage.files,
744
803
  };
745
804
  }
@@ -5,7 +5,7 @@ type Out = {
5
5
  write: (text: string) => void;
6
6
  error: (text: string) => void;
7
7
  };
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] [--health-fail 60] [--stop-grace 30] [--cdn on|off]\n [--volume <volumeId>:<path>] [--label key=value]...\n Make an App, waiting for its first deploy; prints its id.\n --health-fail: seconds a live copy may fail its health\n check before it is replaced. --stop-grace: seconds a\n stopped copy has after SIGTERM. --volume mounts one\n volume of yours at a path under /data, /mnt, /state,\n /storage or /volumes, in the App's one copy: it needs\n --max-copies 1 (--min-copies is then 1, --spares 0), and\n each deploy stops the old copy before the new one boots\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] [--health-fail n]\n [--stop-grace n] [--volume <volumeId>:<path> | none] [--cdn on|off]\n [--label key=value]...\n Change its settings; --volume none takes the volume\n away, only before its first deploy;\n --cdn off sends visitors straight to Runtime's routers,\n with nothing cached (on: Cloudflare's CDN in front)\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 logs <app> [--follow] [--since 1h] [--version n] [--copy <copyId>] [--stream stdout|stderr]\n [--limit 100] [--raw] What its copies printed: the newest lines since --since\n (a time, or 30s, 15m, 2h, 7d; kept 14 days), or with\n --follow every line as it comes, until Ctrl-C. On a\n terminal control characters show as \\uXXXX unless --raw\n exec <app> [--copy <id>] [--root] [--cwd <dir>] [--env K=V]... [--timeout <ms>] [--trace-files <file.json>] -- <command>\n Run a command in its live copy\n cp <file> <app>:/<path> [--copy <id>] Copy a file onto its live copy\n cp <app>:/<path> <file> [--copy <id>] Copy a file off it\n exec and cp go to a serving copy of the live version unless --copy names\n one. A copy holds the App's secrets, so only an owner's or admin's key\n reaches into one. A file written to a copy lasts as long as that copy; a\n deploy replaces its copies.\n rollback <app> [--no-wait] Put the previous version live again\n restart <app> [--no-wait] Start the live version afresh, as a deploy of its own;\n with a volume, the old copy stops first and the\n downtime is said\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 logs web --follow\n runtime apps rollback web\n runtime apps create db --vcpu 1 --memory 1024 --max-copies 1 --volume <volumeId>:/data\n";
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] [--health-fail 60] [--stop-grace 30] [--cdn on|off]\n [--release at-once|staged] [--soak 300] [--volume <volumeId>:<path>] [--label key=value]...\n Make an App, waiting for its first deploy; prints its id.\n --health-fail: seconds a live copy may fail its health\n check before it is replaced. --stop-grace: seconds a\n stopped copy has after SIGTERM. --release staged: a new\n version goes live on one copy, then about 10%, 50% and\n all of them, each stage running --soak seconds (60 to\n 3600) and judged on its visitors' errors and speed\n before the next; a stage that does worse goes back and\n the deploy fails, the live version never stopping.\n --volume mounts one\n volume of yours at a path under /data, /mnt, /state,\n /storage or /volumes, in the App's one copy: it needs\n --max-copies 1 (--min-copies is then 1, --spares 0), and\n each deploy stops the old copy before the new one boots\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] [--health-fail n]\n [--stop-grace n] [--volume <volumeId>:<path> | none] [--cdn on|off]\n [--release at-once|staged] [--soak n] [--label key=value]...\n Change its settings; --volume none takes the volume\n away, only before its first deploy;\n --cdn off sends visitors straight to Runtime's routers,\n with nothing cached (on: Cloudflare's CDN in front)\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 logs <app> [--follow] [--since 1h] [--version n] [--copy <copyId>] [--stream stdout|stderr]\n [--limit 100] [--raw] What its copies printed: the newest lines since --since\n (a time, or 30s, 15m, 2h, 7d; kept 14 days), or with\n --follow every line as it comes, until Ctrl-C. On a\n terminal control characters show as \\uXXXX unless --raw\n exec <app> [--copy <id>] [--root] [--cwd <dir>] [--env K=V]... [--timeout <ms>] [--trace-files <file.json>] -- <command>\n Run a command in its live copy\n cp <file> <app>:/<path> [--copy <id>] Copy a file onto its live copy\n cp <app>:/<path> <file> [--copy <id>] Copy a file off it\n exec and cp go to a serving copy of the live version unless --copy names\n one. A copy holds the App's secrets, so only an owner's or admin's key\n reaches into one. A file written to a copy lasts as long as that copy; a\n deploy replaces its copies.\n rollback <app> [--no-wait] Put the previous version live again\n restart <app> [--no-wait] Start the live version afresh, as a deploy of its own;\n with a volume, the old copy stops first and the\n downtime is said\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 logs web --follow\n runtime apps rollback web\n runtime apps create db --vcpu 1 --memory 1024 --max-copies 1 --volume <volumeId>:/data\n";
9
9
  export declare const FUNCTION_HELP = "runtime functions <command>\n\n A Function serves your HTTP handler from an image or a build, at its own\n address. Copies start when calls come, up to --max-copies, and sleep when\n they stop: an idle Function has no copy awake.\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 [--concurrency 100] [--spares 2] [--port 8080]\n [--health-path /healthz] [--health-fail 60] [--label key=value]...\n Make a Function, waiting for its first deploy; prints its id.\n --health-fail: seconds a live copy may fail its health\n check before it is replaced\n ls [--all] Your Functions: state, live version, copies; --all, deleted too\n get <function> One Function, in full (an id or its name)\n update <function> [--name <name>] [--vcpu n] [--memory n] [--max-copies n]\n [--concurrency n] [--spares n] [--port n] [--health-path /path] [--health-fail n]\n [--label key=value]...\n Change its settings\n rm <function> Delete it and stop its copies\n deploy <function> (--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 <function> [<deployId>] [--wait] Each version's deploy: state and address; with an id,\n that one (--wait: until it ends)\n versions <function> Its versions; the live and previous ones keep saved copies\n copies <function> Its copies now: version, region, state\n logs <function> [--follow] [--since 1h] [--version n] [--copy <copyId>] [--stream stdout|stderr]\n [--limit 100] [--raw] What its copies printed: the newest lines since --since\n (a time, or 30s, 15m, 2h, 7d; kept 14 days), or with\n --follow every line as it comes, until Ctrl-C. On a\n terminal control characters show as \\uXXXX unless --raw\n rollback <function> [--no-wait] Put the previous version live again\n restart <function> [--no-wait] Start the live version afresh, as a deploy of its own\n\nExamples\n runtime functions create resize --vcpu 1 --memory 512 --max-copies 10\n runtime functions deploy resize --image resize:latest\n runtime functions logs resize --follow\n runtime functions rollback resize\n";
10
10
  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";
11
11
  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";