withruntime 0.17.2 → 0.21.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 +14 -0
- package/dist/apps-cli.js +737 -0
- package/dist/builds/index.d.ts +10 -0
- package/dist/builds/index.js +11 -0
- package/dist/cli-words.js +11 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +104 -6
- 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/daytona/sandbox.d.ts +2 -1
- package/dist/daytona/sandbox.js +2 -1
- package/dist/identity.js +18 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/product-names.js +3 -0
- package/dist/products/agents.d.ts +272 -0
- package/dist/products/agents.js +251 -0
- package/dist/products/builds.d.ts +11 -1
- package/dist/products/builds.js +7 -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/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/dist/vercel/errors.d.ts +1 -1
- 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.
|
|
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
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
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 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
|
+
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";
|
|
11
|
+
export declare function appsCommand(argv: string[], env: NodeJS.ProcessEnv, out: Out, client: () => Promise<Runtime>, kit: CliKit): Promise<number>;
|
|
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>;
|
|
14
|
+
export {};
|