withruntime 0.5.0 → 0.6.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 (56) hide show
  1. package/DAYTONA.md +25 -25
  2. package/E2B.md +1 -1
  3. package/LICENSE +55 -0
  4. package/README.md +3 -2
  5. package/dist/cli-extras.d.ts +24 -0
  6. package/dist/cli-extras.js +205 -0
  7. package/dist/cli.d.ts +20 -0
  8. package/dist/cli.js +430 -37
  9. package/dist/daytona/context.d.ts +5 -0
  10. package/dist/daytona/context.js +3 -0
  11. package/dist/daytona/filesystem.js +2 -2
  12. package/dist/daytona/process.js +3 -1
  13. package/dist/e2b/commands.js +6 -2
  14. package/dist/e2b/filesystem.d.ts +26 -1
  15. package/dist/e2b/filesystem.js +30 -2
  16. package/dist/e2b/index.d.ts +1 -1
  17. package/dist/e2b/index.js +1 -1
  18. package/dist/errors.js +2 -0
  19. package/dist/identity.d.ts +27 -0
  20. package/dist/identity.js +56 -0
  21. package/dist/index.d.ts +14 -0
  22. package/dist/index.js +7 -0
  23. package/dist/mcp.js +16 -3
  24. package/dist/network-cli.d.ts +15 -0
  25. package/dist/network-cli.js +323 -0
  26. package/dist/products/desktop.d.ts +41 -4
  27. package/dist/products/desktop.js +73 -3
  28. package/dist/products/index.d.ts +12 -0
  29. package/dist/products/index.js +12 -0
  30. package/dist/products/interpreter.d.ts +5 -3
  31. package/dist/products/interpreter.js +2 -1
  32. package/dist/products/mcp.d.ts +79 -0
  33. package/dist/products/mcp.js +37 -0
  34. package/dist/products/mounts.d.ts +49 -0
  35. package/dist/products/mounts.js +21 -0
  36. package/dist/products/network-products.d.ts +167 -0
  37. package/dist/products/network-products.js +82 -0
  38. package/dist/products/network.d.ts +1 -1
  39. package/dist/products/sso.d.ts +44 -0
  40. package/dist/products/sso.js +8 -0
  41. package/dist/products/switching.d.ts +7 -0
  42. package/dist/products/volumes.d.ts +68 -5
  43. package/dist/products/volumes.js +52 -4
  44. package/dist/products/watch.d.ts +91 -0
  45. package/dist/products/watch.js +135 -0
  46. package/dist/sandbox.d.ts +32 -2
  47. package/dist/sandbox.js +93 -16
  48. package/dist/snapshots.d.ts +11 -1
  49. package/dist/ssh.js +13 -1
  50. package/dist/transport.d.ts +1 -1
  51. package/dist/transport.js +4 -1
  52. package/dist/tunnel.d.ts +4 -1
  53. package/dist/tunnel.js +8 -3
  54. package/dist/wireguard.d.ts +7 -0
  55. package/dist/wireguard.js +13 -0
  56. package/package.json +7 -3
@@ -0,0 +1,323 @@
1
+ import { chmod, writeFile } from "node:fs/promises";
2
+ import { RuntimeError } from "./errors.js";
3
+ import { named } from "./login.js";
4
+ import { generateWireGuardKeyPair } from "./wireguard.js";
5
+ function fundingState(value) {
6
+ if (value.funded === false)
7
+ return "Traffic disabled. Check balance, spending limits and account permissions. Reservation kept until you release it.";
8
+ if (value.funded !== true)
9
+ return "Funding status unavailable.";
10
+ return value.fundedUntil
11
+ ? `Funded until ${value.fundedUntil}.`
12
+ : "Funding active; no deadline reported.";
13
+ }
14
+ export const NETWORK_PRODUCTS = ["domain", "port", "address", "tunnel"];
15
+ export const NETWORK_HELP = {
16
+ domain: `runtime domain <command>
17
+
18
+ add <hostname> <sandbox> <port> Serve a sandbox's port at your own hostname, with HTTPS
19
+ Prints the DNS records to set: a TXT that proves the
20
+ name is yours, and a CNAME (an A for an apex name)
21
+ verify <hostname> Check the TXT record now; the name goes live when it matches
22
+ ls Your domains
23
+ get <hostname> One domain and its DNS records
24
+ rm <hostname> Stop serving it
25
+ Paid accounts only. The first visit gets the certificate, in a few seconds.
26
+ `,
27
+ port: `runtime port <command>
28
+
29
+ open <sandbox> <port> A public TCP port to a port in the sandbox: Postgres,
30
+ Redis, a game server, anything that is not HTTP
31
+ Prints address:port for your client
32
+ ls [--sandbox <id>] Your open ports
33
+ close <portId> Close one; open connections end within seconds
34
+ Paid accounts only. TCP, carried as it is: use your service's own password and TLS.
35
+ `,
36
+ address: `runtime address <command>
37
+
38
+ reserve [--ipv6] A dedicated outbound address: every sandbox of your
39
+ account sends from it, for allow-lists
40
+ ls Your addresses
41
+ release <addressId> Give it back; sandboxes use the shared addresses again
42
+ Paid accounts only.
43
+ `,
44
+ tunnel: `runtime tunnel <command>
45
+
46
+ create [--subnet 10.250.0.0/16] A WireGuard tunnel from your network into your sandboxes
47
+ get The tunnel, its peers, and each sandbox's address in it
48
+ rm Delete the tunnel and every peer
49
+ peer add <name> [--route 10.0.0.0/16]... [--out runtime.conf] [--public-key <key>]
50
+ Add a machine or router of yours; writes its wg-quick
51
+ file. The key pair is made here and the private key
52
+ never leaves this machine. Then: sudo wg-quick up ./runtime.conf
53
+ peer rotate <peerId> [--out runtime.conf]
54
+ A new key; the old one stops working within seconds
55
+ peer rm <peerId> Remove it; its tunnel ends within seconds
56
+ --route: your own ranges behind the peer that sandboxes may reach. Paid accounts only.
57
+ `,
58
+ };
59
+ function usage(product, message) {
60
+ return new RuntimeError({
61
+ message,
62
+ code: "usage",
63
+ status: 0,
64
+ hint: `Run \`${named("runtime")} ${product} help\`.`,
65
+ });
66
+ }
67
+ function table(rows) {
68
+ const widths = rows[0].map((_, column) => Math.max(...rows.map((row) => (row[column] ?? "").length)));
69
+ return rows
70
+ .map((row) => row
71
+ .map((cell, column) => cell.padEnd(widths[column]))
72
+ .join(" ")
73
+ .trimEnd())
74
+ .join("\n");
75
+ }
76
+ function portNumber(product, text) {
77
+ const value = Number(text);
78
+ if (!text || !/^\d+$/.test(text) || value < 1 || value > 65_535)
79
+ throw usage(product, "Give a port from 1 to 65535.");
80
+ return value;
81
+ }
82
+ /** Writes a wg-quick file readable by its owner alone, or prints it. */
83
+ async function deliver(out, config, path, hint, ready = true) {
84
+ if (!config) {
85
+ out.error(hint);
86
+ return;
87
+ }
88
+ if (path) {
89
+ await writeFile(path, config, { mode: 0o600 });
90
+ await chmod(path, 0o600);
91
+ out.error(ready
92
+ ? `Wrote ${path}. Bring it up with: sudo wg-quick up ${path.startsWith("/") ? path : `./${path}`}`
93
+ : `Wrote ${path}. ${hint}`);
94
+ }
95
+ else
96
+ out.write(config);
97
+ }
98
+ export async function networkProductCommand(product, argv, args, out, client) {
99
+ const [verb] = argv;
100
+ const flag = (name) => args.flags.get(name)?.at(-1);
101
+ const has = (name) => flag(name) === "true";
102
+ const print = (human, value) => out.write(out.json ? JSON.stringify(value) : human);
103
+ const rest = args.positional.slice(1);
104
+ const need = (value, what) => {
105
+ if (!value)
106
+ throw usage(product, `Give ${what}.`);
107
+ return value;
108
+ };
109
+ if (!verb || verb === "help" || verb === "--help") {
110
+ print(named(NETWORK_HELP[product]), { usage: named(NETWORK_HELP[product]) });
111
+ return 0;
112
+ }
113
+ const runtime = await client();
114
+ if (product === "domain") {
115
+ const records = (domain) => table([
116
+ ["TYPE", "NAME", "VALUE"],
117
+ ...domain.records.map((record) => [record.type, record.name, record.value]),
118
+ ]);
119
+ switch (verb) {
120
+ case "add": {
121
+ const found = await runtime.domains.add({
122
+ hostname: need(rest[0], "a hostname, such as app.example.com"),
123
+ sandboxId: need(rest[1], "a sandbox id"),
124
+ port: portNumber(product, rest[2]),
125
+ });
126
+ print(`${found.hostname}: ${found.state}\n\n${records(found)}\n\n${found.hint}`, found);
127
+ return 0;
128
+ }
129
+ case "verify": {
130
+ const found = await runtime.domains.verify(need(rest[0], "a hostname"));
131
+ print(`${found.hostname}: ${found.state}. ${found.hint}${found.checkError ? `\n${found.checkError}` : ""}`, found);
132
+ return found.state === "active" ? 0 : 1;
133
+ }
134
+ case "ls":
135
+ case "list": {
136
+ const all = await runtime.domains.list();
137
+ print(all.length
138
+ ? table([
139
+ ["HOSTNAME", "STATE", "SANDBOX", "PORT"],
140
+ ...all.map((d) => [
141
+ d.hostname,
142
+ d.disabled ? "taken down" : d.state,
143
+ d.sandboxId,
144
+ String(d.port),
145
+ ]),
146
+ ])
147
+ : named("No domains. Add one: runtime domain add app.example.com <sandbox> 3000"), all);
148
+ return 0;
149
+ }
150
+ case "get": {
151
+ const found = await runtime.domains.get(need(rest[0], "a hostname"));
152
+ print(`${found.hostname}: ${found.state}\n\n${records(found)}`, found);
153
+ return 0;
154
+ }
155
+ case "rm":
156
+ case "remove": {
157
+ const gone = await runtime.domains.remove(need(rest[0], "a hostname"));
158
+ print(`${gone.hostname} is no longer served. Remove its DNS records too.`, gone);
159
+ return 0;
160
+ }
161
+ }
162
+ }
163
+ if (product === "port") {
164
+ switch (verb) {
165
+ case "open": {
166
+ const opened = await runtime.ports.open({
167
+ sandboxId: need(rest[0], "a sandbox id"),
168
+ port: portNumber(product, rest[1]),
169
+ });
170
+ if (!out.json)
171
+ out.error(opened.hint);
172
+ print(opened.connect, opened);
173
+ return 0;
174
+ }
175
+ case "ls":
176
+ case "list": {
177
+ const sandboxId = flag("sandbox");
178
+ const all = await runtime.ports.list(sandboxId ? { sandboxId } : {});
179
+ print(all.length
180
+ ? table([
181
+ ["ID", "CONNECT", "SANDBOX", "PORT"],
182
+ ...all.map((p) => [p.id, p.connect, p.sandboxId, String(p.port)]),
183
+ ])
184
+ : named("No open ports. Open one: runtime port open <sandbox> 5432"), all);
185
+ return 0;
186
+ }
187
+ case "close":
188
+ case "rm": {
189
+ const gone = await runtime.ports.close(need(rest[0], "a port id"));
190
+ print(`Closed ${gone.id}.`, gone);
191
+ return 0;
192
+ }
193
+ }
194
+ }
195
+ if (product === "address") {
196
+ switch (verb) {
197
+ case "reserve": {
198
+ const reserved = await runtime.addresses.reserve({ family: has("ipv6") ? 6 : 4 });
199
+ if (!out.json)
200
+ out.error(reserved.funded === true
201
+ ? `Every sandbox of this account sends from it. ${fundingState(reserved)}`
202
+ : fundingState(reserved));
203
+ print(reserved.address, reserved);
204
+ return 0;
205
+ }
206
+ case "ls":
207
+ case "list": {
208
+ const all = await runtime.addresses.list();
209
+ print(all.length
210
+ ? table([
211
+ ["ID", "ADDRESS", "FAMILY", "FUNDING"],
212
+ ...all.map((a) => [a.id, a.address, `IPv${a.family}`, fundingState(a)]),
213
+ ])
214
+ : named("No dedicated address. Reserve one: runtime address reserve"), all);
215
+ return 0;
216
+ }
217
+ case "release":
218
+ case "rm": {
219
+ const gone = await runtime.addresses.release(need(rest[0], "an address id"));
220
+ print(`Released ${gone.address}.`, gone);
221
+ return 0;
222
+ }
223
+ }
224
+ }
225
+ if (product === "tunnel") {
226
+ const view = (tunnel) => [
227
+ fundingState(tunnel),
228
+ `Endpoint ${tunnel.endpoint}, subnet ${tunnel.subnet}, gateway key ${tunnel.gatewayPublicKey ?? "(starting)"}`,
229
+ "",
230
+ tunnel.peers.length
231
+ ? table([
232
+ ["PEER", "NAME", "ADDRESS", "ROUTES"],
233
+ ...tunnel.peers.map((p) => [p.id, p.name, p.address, p.routes.join(",") || "-"]),
234
+ ])
235
+ : named("No peers. Add one: runtime tunnel peer add office --route 10.0.0.0/16"),
236
+ "",
237
+ tunnel.sandboxes.length
238
+ ? table([
239
+ ["SANDBOX", "NAME", "ADDRESS", "STATE"],
240
+ ...tunnel.sandboxes.map((s) => [s.sandboxId, s.name ?? "-", s.address, s.state]),
241
+ ])
242
+ : "No sandboxes yet.",
243
+ ].join("\n");
244
+ switch (verb) {
245
+ case "create": {
246
+ const made = await runtime.tunnel.create(flag("subnet") ? { subnet: flag("subnet") } : {});
247
+ print(view(made), made);
248
+ return 0;
249
+ }
250
+ case "get":
251
+ case "ls": {
252
+ const found = await runtime.tunnel.get();
253
+ print(view(found), found);
254
+ return 0;
255
+ }
256
+ case "rm":
257
+ case "delete": {
258
+ const gone = await runtime.tunnel.delete();
259
+ print(`Deleted the tunnel ${gone.id}.`, gone);
260
+ return 0;
261
+ }
262
+ case "peer":
263
+ case "peers": {
264
+ const action = rest[0];
265
+ if (action === "add" || action === "rotate") {
266
+ // The pair is made here unless a key is given: the private key never
267
+ // leaves this machine, and goes only into the file written below.
268
+ const given = flag("public-key");
269
+ const pair = given ? undefined : generateWireGuardKeyPair();
270
+ const publicKey = given ?? pair.publicKey;
271
+ const answer = action === "add"
272
+ ? await runtime.tunnel.addPeer({
273
+ name: need(rest[1], "the peer's name, such as office"),
274
+ publicKey,
275
+ routes: args.flags.get("route") ?? [],
276
+ })
277
+ : await runtime.tunnel.rotatePeer(need(rest[1], "a peer id"), { publicKey });
278
+ const configReady = answer.configReady ?? !!answer.config;
279
+ const draftHint = "Keep this file: it preserves your private key. Read runtime tunnel get when the gateway is ready, replace PublicKey in [Peer] with gatewayPublicKey, then run wg-quick up.";
280
+ // Older servers returned no config while the gateway was starting.
281
+ // Preserve the locally generated key in a draft rather than discard it.
282
+ const supplied = answer.config ??
283
+ [
284
+ "# DRAFT: gateway key missing. Keep this file; it preserves your private key.",
285
+ "[Interface]",
286
+ `PrivateKey = ${pair?.privateKey ?? "<your private key>"}`,
287
+ `Address = ${answer.peer.address}/32`,
288
+ "",
289
+ "[Peer]",
290
+ `PublicKey = ${answer.tunnel.gatewayPublicKey ?? "<gatewayPublicKey from runtime tunnel get>"}`,
291
+ `Endpoint = ${answer.tunnel.endpoint}`,
292
+ `AllowedIPs = ${answer.tunnel.subnet}`,
293
+ "PersistentKeepalive = 25",
294
+ "",
295
+ ].join("\n");
296
+ const config = pair
297
+ ? supplied.replace(/^PrivateKey = .*$/m, `PrivateKey = ${pair.privateKey}`)
298
+ : supplied;
299
+ if (out.json) {
300
+ print("", {
301
+ ...answer,
302
+ config,
303
+ configReady,
304
+ ...(!configReady ? { hint: draftHint } : {}),
305
+ });
306
+ return 0;
307
+ }
308
+ if (answer.tunnel.funded === false)
309
+ out.error(fundingState(answer.tunnel));
310
+ await deliver(out, config, flag("out") ?? (pair ? "runtime.conf" : undefined), configReady ? answer.hint : draftHint, configReady);
311
+ return 0;
312
+ }
313
+ if (action === "rm" || action === "remove") {
314
+ const after = await runtime.tunnel.removePeer(need(rest[1], "a peer id"));
315
+ print(`Removed. ${after.peers.length} peer(s) left.`, after);
316
+ return 0;
317
+ }
318
+ throw usage(product, "Use peer add, peer rotate or peer rm.");
319
+ }
320
+ }
321
+ }
322
+ throw usage(product, `Unknown ${product} command ${verb}.`);
323
+ }
@@ -21,6 +21,27 @@ export type DesktopStart = {
21
21
  streamUrl: string;
22
22
  preview: Preview;
23
23
  };
24
+ export type DesktopRecording = {
25
+ id: string;
26
+ state: "starting" | "installing" | "recording" | "finished" | "failed";
27
+ /** The MP4 file in the sandbox. */
28
+ path: string;
29
+ bytes: number;
30
+ seconds?: number;
31
+ /** Why it ended: stopped, max-seconds, max-bytes, disk-low, desktop-gone, error. */
32
+ reason: string | null;
33
+ startedAt?: number | null;
34
+ };
35
+ export type RecordOptions = {
36
+ /** Frames a second, 1 to 30. Default 10. */
37
+ fps?: number;
38
+ /** Stop after this much video, up to 7200. Default 1800. */
39
+ maxSeconds?: number;
40
+ /** The file never grows past this, 16 to 4096. Default 512. */
41
+ maxMiB?: number;
42
+ /** Quality, 18 (sharp, large) to 45 (small). Default 30. */
43
+ crf?: number;
44
+ };
24
45
  /** `sbx.desktop`: a Linux desktop in the sandbox, driven like a person would.
25
46
  *
26
47
  * const { streamUrl } = await sbx.desktop.start();
@@ -31,10 +52,12 @@ export type DesktopStart = {
31
52
  *
32
53
  * Coordinates are pixels from the top left of the screen. */
33
54
  export declare function sandboxDesktop(t: Transport, sandbox: Sandbox): {
34
- start: (size?: {
55
+ /** Starts the desktop. The first start in a sandbox installs it, which
56
+ * takes a minute or two; this waits for that (up to 10 minutes). */
57
+ start(size?: {
35
58
  width?: number;
36
59
  height?: number;
37
- }, options?: RequestOptions) => Promise<DesktopStart>;
60
+ }, options?: RequestOptions): Promise<DesktopStart>;
38
61
  stop: (options?: RequestOptions) => Promise<{
39
62
  ok: boolean;
40
63
  }>;
@@ -92,12 +115,26 @@ export declare function sandboxDesktop(t: Transport, sandbox: Sandbox): {
92
115
  focus: (windowId: string, options?: RequestOptions) => Promise<{
93
116
  ok: true;
94
117
  }>;
95
- /** Opens `url` in Firefox on the desktop. */
96
- open: (url: string, options?: RequestOptions) => Promise<{
118
+ /** Opens `url` in Firefox on the desktop. Right after a sandbox's first
119
+ * start, Firefox may still be installing; this waits for it. */
120
+ open(url: string, options?: RequestOptions): Promise<{
97
121
  ok: true;
98
122
  }>;
99
123
  /** Starts a program on the desktop, detached. */
100
124
  launch: (argv: string[], options?: RequestOptions) => Promise<{
101
125
  ok: true;
102
126
  }>;
127
+ /** Screen recordings to MP4: `const rec = await sbx.desktop.recordings.start();`
128
+ * ... `await sbx.desktop.recordings.stop(rec.id)`, then `download(rec.id)`. */
129
+ recordings: {
130
+ start: (input?: RecordOptions, options?: RequestOptions) => Promise<DesktopRecording>;
131
+ stop: (id: string, options?: RequestOptions) => Promise<DesktopRecording>;
132
+ get: (id: string, options?: RequestOptions) => Promise<DesktopRecording>;
133
+ list: (options?: RequestOptions) => Promise<DesktopRecording[]>;
134
+ /** The MP4 bytes. */
135
+ download: (id: string, options?: RequestOptions) => Promise<Uint8Array<ArrayBufferLike>>;
136
+ delete: (id: string, options?: RequestOptions) => Promise<{
137
+ deleted: boolean;
138
+ }>;
139
+ };
103
140
  };
@@ -11,7 +11,27 @@ export function sandboxDesktop(t, sandbox) {
11
11
  const base = () => `/v1/sandboxes/${encodeURIComponent(sandbox.id)}/desktop`;
12
12
  const act = (body, options) => t.json({ method: "POST", path: `${base()}:act`, body, ...options });
13
13
  return {
14
- start: (size = {}, options) => t.json({ method: "POST", path: `${base()}:start`, body: size, ...options }),
14
+ /** Starts the desktop. The first start in a sandbox installs it, which
15
+ * takes a minute or two; this waits for that (up to 10 minutes). */
16
+ async start(size = {}, options) {
17
+ const deadline = Date.now() + 600_000;
18
+ for (;;) {
19
+ try {
20
+ return await t.json({
21
+ method: "POST",
22
+ path: `${base()}:start`,
23
+ body: size,
24
+ ...options,
25
+ });
26
+ }
27
+ catch (error) {
28
+ const code = error.code;
29
+ if (code !== "desktop_installing" || Date.now() > deadline)
30
+ throw error;
31
+ await new Promise((resolve) => setTimeout(resolve, error.retryAfterMs ?? 10_000));
32
+ }
33
+ }
34
+ },
15
35
  stop: (options) => t.json({ method: "POST", path: `${base()}:stop`, ...options }),
16
36
  /** PNG bytes, or JPEG with `format: "jpeg"`. */
17
37
  screenshot: (input = {}, options) => t.bytes({ method: "GET", path: `${base()}/screenshot`, query: input, ...options }),
@@ -30,9 +50,59 @@ export function sandboxDesktop(t, sandbox) {
30
50
  cursor: (options) => act({ action: "cursor" }, options),
31
51
  windows: async (options) => (await act({ action: "windows" }, options)).windows,
32
52
  focus: (windowId, options) => act({ action: "focus", windowId }, options),
33
- /** Opens `url` in Firefox on the desktop. */
34
- open: (url, options) => act({ action: "open", url }, options),
53
+ /** Opens `url` in Firefox on the desktop. Right after a sandbox's first
54
+ * start, Firefox may still be installing; this waits for it. */
55
+ async open(url, options) {
56
+ const deadline = Date.now() + 600_000;
57
+ for (;;) {
58
+ try {
59
+ return await act({ action: "open", url }, options);
60
+ }
61
+ catch (error) {
62
+ const code = error.code;
63
+ if (code !== "desktop_installing" || Date.now() > deadline)
64
+ throw error;
65
+ await new Promise((resolve) => setTimeout(resolve, error.retryAfterMs ?? 10_000));
66
+ }
67
+ }
68
+ },
35
69
  /** Starts a program on the desktop, detached. */
36
70
  launch: (argv, options) => act({ action: "launch", argv }, options),
71
+ /** Screen recordings to MP4: `const rec = await sbx.desktop.recordings.start();`
72
+ * ... `await sbx.desktop.recordings.stop(rec.id)`, then `download(rec.id)`. */
73
+ recordings: {
74
+ start: (input = {}, options) => t.json({
75
+ method: "POST",
76
+ path: `${base()}/recordings`,
77
+ body: input,
78
+ ...options,
79
+ }),
80
+ stop: (id, options) => t.json({
81
+ method: "POST",
82
+ path: `${base()}/recordings/${encodeURIComponent(id)}:stop`,
83
+ ...options,
84
+ }),
85
+ get: (id, options) => t.json({
86
+ method: "GET",
87
+ path: `${base()}/recordings/${encodeURIComponent(id)}`,
88
+ ...options,
89
+ }),
90
+ list: async (options) => (await t.json({
91
+ method: "GET",
92
+ path: `${base()}/recordings`,
93
+ ...options,
94
+ })).data,
95
+ /** The MP4 bytes. */
96
+ download: (id, options) => t.bytes({
97
+ method: "GET",
98
+ path: `${base()}/recordings/${encodeURIComponent(id)}/video`,
99
+ ...options,
100
+ }),
101
+ delete: (id, options) => t.json({
102
+ method: "DELETE",
103
+ path: `${base()}/recordings/${encodeURIComponent(id)}`,
104
+ ...options,
105
+ }),
106
+ },
37
107
  };
38
108
  }
@@ -4,13 +4,17 @@ import { images } from "./images.js";
4
4
  import { sandboxDesktop } from "./desktop.js";
5
5
  import { sandboxInterpreter } from "./interpreter.js";
6
6
  import { sandboxNetwork } from "./network.js";
7
+ import { addresses, domains, ports, tunnel } from "./network-products.js";
7
8
  import { sandboxPreviews } from "./previews.js";
8
9
  import { referrals } from "./referrals.js";
9
10
  import { audit } from "./audit.js";
11
+ import { sso } from "./sso.js";
10
12
  import { switching } from "./switching.js";
11
13
  import { limits } from "./limits.js";
12
14
  import { volumes } from "./volumes.js";
15
+ import { sandboxMounts } from "./mounts.js";
13
16
  import { secrets } from "./secrets.js";
17
+ import { mcp, sandboxMcp } from "./mcp.js";
14
18
  import { events, otel, sandboxMetrics, webhooks } from "./observability.js";
15
19
  export declare const clientExtensions: {
16
20
  images: typeof images;
@@ -23,13 +27,21 @@ export declare const clientExtensions: {
23
27
  otel: typeof otel;
24
28
  events: typeof events;
25
29
  audit: typeof audit;
30
+ mcp: typeof mcp;
31
+ sso: typeof sso;
32
+ domains: typeof domains;
33
+ ports: typeof ports;
34
+ addresses: typeof addresses;
35
+ tunnel: typeof tunnel;
26
36
  };
27
37
  export declare const sandboxExtensions: {
28
38
  interpreter: typeof sandboxInterpreter;
29
39
  previews: typeof sandboxPreviews;
30
40
  network: typeof sandboxNetwork;
31
41
  desktop: typeof sandboxDesktop;
42
+ mounts: typeof sandboxMounts;
32
43
  metrics: typeof sandboxMetrics;
44
+ mcp: typeof sandboxMcp;
33
45
  };
34
46
  export type ClientExtensions = {
35
47
  readonly [K in keyof typeof clientExtensions]: ReturnType<(typeof clientExtensions)[K]>;
@@ -2,13 +2,17 @@ import { images } from "./images.js";
2
2
  import { sandboxDesktop } from "./desktop.js";
3
3
  import { sandboxInterpreter } from "./interpreter.js";
4
4
  import { sandboxNetwork } from "./network.js";
5
+ import { addresses, domains, ports, tunnel } from "./network-products.js";
5
6
  import { sandboxPreviews } from "./previews.js";
6
7
  import { referrals } from "./referrals.js";
7
8
  import { audit } from "./audit.js";
9
+ import { sso } from "./sso.js";
8
10
  import { switching } from "./switching.js";
9
11
  import { limits } from "./limits.js";
10
12
  import { volumes } from "./volumes.js";
13
+ import { sandboxMounts } from "./mounts.js";
11
14
  import { secrets } from "./secrets.js";
15
+ import { mcp, sandboxMcp } from "./mcp.js";
12
16
  import { events, otel, sandboxMetrics, webhooks } from "./observability.js";
13
17
  /* HOW A LANE ADDS A PRODUCT TO THE SDK
14
18
  *
@@ -36,13 +40,21 @@ export const clientExtensions = {
36
40
  otel,
37
41
  events,
38
42
  audit,
43
+ mcp,
44
+ sso,
45
+ domains,
46
+ ports,
47
+ addresses,
48
+ tunnel,
39
49
  };
40
50
  export const sandboxExtensions = {
41
51
  interpreter: sandboxInterpreter,
42
52
  previews: sandboxPreviews,
43
53
  network: sandboxNetwork,
44
54
  desktop: sandboxDesktop,
55
+ mounts: sandboxMounts,
45
56
  metrics: sandboxMetrics,
57
+ mcp: sandboxMcp,
46
58
  };
47
59
  /** The factories as plain pairs, for the constructors that install them. */
48
60
  export function clientFactories() {
@@ -1,6 +1,6 @@
1
1
  import type { Sandbox } from "../sandbox.js";
2
2
  import type { RequestOptions, Transport } from "../transport.js";
3
- export type InterpreterLanguage = "python" | "javascript";
3
+ export type InterpreterLanguage = "python" | "javascript" | "typescript" | "r" | "java" | "bash" | "go";
4
4
  export type ResultRef = {
5
5
  path: string;
6
6
  bytes: number;
@@ -47,7 +47,8 @@ export type InterpreterContext = {
47
47
  };
48
48
  export type RunOptions = RequestOptions & {
49
49
  language?: InterpreterLanguage;
50
- /** A context id; "python" and "javascript" are the defaults, started on first use. */
50
+ /** A context id; each language's own name ("python", "typescript", "r"...) is
51
+ * its default context, started on first use. */
51
52
  context?: string;
52
53
  timeoutMs?: number;
53
54
  onStdout?: (text: string) => void;
@@ -59,7 +60,8 @@ export type RunOptions = RequestOptions & {
59
60
  traceback: string;
60
61
  }) => void;
61
62
  };
62
- /** A stateful Python and JavaScript interpreter in the sandbox:
63
+ /** A stateful interpreter in the sandbox, in Python, JavaScript, TypeScript,
64
+ * R, Java, Bash or Go:
63
65
  * `await sbx.interpreter.run("import pandas as pd; pd.DataFrame({'a': [1]})")`.
64
66
  * Variables persist between runs of the same context, like a notebook. */
65
67
  export declare function sandboxInterpreter(t: Transport, sandbox: Sandbox): {
@@ -1,4 +1,5 @@
1
- /** A stateful Python and JavaScript interpreter in the sandbox:
1
+ /** A stateful interpreter in the sandbox, in Python, JavaScript, TypeScript,
2
+ * R, Java, Bash or Go:
2
3
  * `await sbx.interpreter.run("import pandas as pd; pd.DataFrame({'a': [1]})")`.
3
4
  * Variables persist between runs of the same context, like a notebook. */
4
5
  export function sandboxInterpreter(t, sandbox) {