withruntime 0.11.1 → 0.12.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 (176) hide show
  1. package/BLAXEL.md +3 -3
  2. package/COMPATIBILITY.md +74 -9
  3. package/DAYTONA.md +33 -5
  4. package/E2B.md +137 -48
  5. package/README.md +11 -9
  6. package/README.src.md +11 -9
  7. package/VERCEL.md +43 -5
  8. package/compatibility-lock.json +58 -49
  9. package/dist/billing-cli.d.ts +1 -1
  10. package/dist/billing-cli.js +77 -1
  11. package/dist/blaxel/errors.d.ts +14 -0
  12. package/dist/blaxel/errors.js +33 -4
  13. package/dist/blaxel/index.d.ts +2 -2
  14. package/dist/blaxel/index.js +4 -4
  15. package/dist/blaxel/interpreter.d.ts +2 -5
  16. package/dist/blaxel/sandbox.d.ts +15 -7
  17. package/dist/blaxel/sandbox.js +64 -9
  18. package/dist/blaxel/snapshot.d.ts +4 -0
  19. package/dist/blaxel/snapshot.js +4 -0
  20. package/dist/cli/logs.d.ts +21 -0
  21. package/dist/cli/logs.js +188 -0
  22. package/dist/cli/logs.test.d.ts +1 -0
  23. package/dist/cli/logs.test.js +272 -0
  24. package/dist/cli-extras.d.ts +3 -3
  25. package/dist/cli-extras.js +17 -12
  26. package/dist/cli-words.d.ts +10 -1
  27. package/dist/cli-words.js +18 -0
  28. package/dist/cli.d.ts +6 -2
  29. package/dist/cli.js +468 -219
  30. package/dist/client.d.ts +13 -63
  31. package/dist/client.js +35 -124
  32. package/dist/cloudflare/errors.d.ts +143 -0
  33. package/dist/cloudflare/errors.js +167 -0
  34. package/dist/cloudflare/index.d.ts +175 -6
  35. package/dist/cloudflare/index.js +416 -26
  36. package/dist/cloudflare/interpreter.d.ts +119 -0
  37. package/dist/cloudflare/interpreter.js +223 -0
  38. package/dist/cloudflare/streams.d.ts +38 -0
  39. package/dist/cloudflare/streams.js +145 -0
  40. package/dist/cloudflare/watch.d.ts +9 -0
  41. package/dist/cloudflare/watch.js +24 -0
  42. package/dist/codesandbox/index.d.ts +216 -28
  43. package/dist/codesandbox/index.js +478 -78
  44. package/dist/compat/core.d.ts +18 -0
  45. package/dist/compat/core.js +29 -0
  46. package/dist/compat/files.d.ts +1 -0
  47. package/dist/compat/files.js +25 -5
  48. package/dist/daytona/computer-use.d.ts +13 -4
  49. package/dist/daytona/computer-use.js +66 -8
  50. package/dist/daytona/daytona.d.ts +7 -2
  51. package/dist/daytona/daytona.js +31 -5
  52. package/dist/daytona/errors.d.ts +11 -0
  53. package/dist/daytona/errors.js +18 -0
  54. package/dist/daytona/filesystem.js +2 -1
  55. package/dist/daytona/image.d.ts +13 -9
  56. package/dist/daytona/image.js +18 -12
  57. package/dist/daytona/index.d.ts +1 -1
  58. package/dist/daytona/sandbox.d.ts +9 -3
  59. package/dist/daytona/sandbox.js +17 -3
  60. package/dist/domains/index.d.ts +9 -0
  61. package/dist/domains/index.js +10 -0
  62. package/dist/e2b/client.d.ts +15 -2
  63. package/dist/e2b/client.js +61 -7
  64. package/dist/e2b/commands.d.ts +4 -1
  65. package/dist/e2b/commands.js +17 -7
  66. package/dist/e2b/errors.d.ts +43 -2
  67. package/dist/e2b/errors.js +78 -6
  68. package/dist/e2b/filesystem.d.ts +1 -1
  69. package/dist/e2b/filesystem.js +82 -16
  70. package/dist/e2b/index.d.ts +3 -3
  71. package/dist/e2b/index.js +2 -2
  72. package/dist/e2b/sandbox.d.ts +50 -35
  73. package/dist/e2b/sandbox.js +228 -185
  74. package/dist/e2b/unsupported.d.ts +19 -0
  75. package/dist/e2b/unsupported.js +22 -1
  76. package/dist/e2b/users.d.ts +9 -0
  77. package/dist/e2b/users.js +22 -0
  78. package/dist/errors.d.ts +14 -5
  79. package/dist/errors.js +28 -14
  80. package/dist/freestyle/index.d.ts +285 -37
  81. package/dist/freestyle/index.js +656 -144
  82. package/dist/identity.d.ts +3 -0
  83. package/dist/identity.js +27 -0
  84. package/dist/images/index.d.ts +9 -0
  85. package/dist/images/index.js +10 -0
  86. package/dist/index.d.ts +1 -0
  87. package/dist/ips/index.d.ts +9 -0
  88. package/dist/ips/index.js +10 -0
  89. package/dist/jobs/index.d.ts +9 -0
  90. package/dist/jobs/index.js +10 -0
  91. package/dist/keys.js +2 -0
  92. package/dist/login.js +2 -0
  93. package/dist/machine.d.ts +189 -0
  94. package/dist/machine.js +521 -0
  95. package/dist/mcp.js +1 -1
  96. package/dist/modal/index.d.ts +339 -61
  97. package/dist/modal/index.js +781 -121
  98. package/dist/network-cli.d.ts +3 -1
  99. package/dist/network-cli.js +78 -36
  100. package/dist/networks/index.d.ts +11 -0
  101. package/dist/networks/index.js +10 -0
  102. package/dist/openai-agents/index.d.ts +1 -1
  103. package/dist/openai-agents/index.js +1 -1
  104. package/dist/ports/index.d.ts +9 -0
  105. package/dist/ports/index.js +10 -0
  106. package/dist/product-names.d.ts +52 -0
  107. package/dist/product-names.js +164 -0
  108. package/dist/product-renames.d.ts +103 -0
  109. package/dist/product-renames.js +189 -0
  110. package/dist/products/apps.d.ts +176 -0
  111. package/dist/products/apps.js +165 -0
  112. package/dist/products/billing.d.ts +41 -0
  113. package/dist/products/billing.js +13 -0
  114. package/dist/products/browser.d.ts +10 -0
  115. package/dist/products/browsers.d.ts +71 -0
  116. package/dist/products/browsers.js +102 -0
  117. package/dist/products/builds.d.ts +126 -0
  118. package/dist/products/builds.js +221 -0
  119. package/dist/products/index.d.ts +3 -16
  120. package/dist/products/index.js +3 -14
  121. package/dist/products/jobs.d.ts +19 -3
  122. package/dist/products/jobs.js +28 -12
  123. package/dist/products/limits.d.ts +4 -10
  124. package/dist/products/logs.d.ts +245 -0
  125. package/dist/products/logs.js +274 -0
  126. package/dist/products/logs.test.d.ts +1 -0
  127. package/dist/products/logs.test.js +343 -0
  128. package/dist/products/network-products.d.ts +38 -7
  129. package/dist/products/network-products.js +31 -18
  130. package/dist/products/network.d.ts +1 -1
  131. package/dist/products/observability.d.ts +3 -2
  132. package/dist/products/private-network.js +1 -1
  133. package/dist/products/queues.d.ts +149 -0
  134. package/dist/products/queues.js +213 -0
  135. package/dist/products/referrals.d.ts +21 -7
  136. package/dist/products/referrals.js +3 -3
  137. package/dist/products/secrets.d.ts +8 -4
  138. package/dist/products/secrets.js +39 -18
  139. package/dist/products/signed-requests.d.ts +21 -0
  140. package/dist/products/signed-requests.js +11 -0
  141. package/dist/products/streams.d.ts +73 -0
  142. package/dist/products/streams.js +17 -0
  143. package/dist/products/switching.d.ts +37 -10
  144. package/dist/products/switching.js +4 -3
  145. package/dist/products/volumes.d.ts +3 -0
  146. package/dist/record-cli.d.ts +23 -0
  147. package/dist/record-cli.js +308 -0
  148. package/dist/renamed-products.d.ts +38 -0
  149. package/dist/renamed-products.js +34 -0
  150. package/dist/runloop/index.d.ts +9 -2
  151. package/dist/runloop/index.js +29 -8
  152. package/dist/runloop/sdk.d.ts +131 -0
  153. package/dist/runloop/sdk.js +167 -0
  154. package/dist/sandbox.d.ts +10 -94
  155. package/dist/sandbox.js +22 -371
  156. package/dist/sandboxes/index.d.ts +11 -0
  157. package/dist/sandboxes/index.js +12 -0
  158. package/dist/snapshots/index.d.ts +9 -0
  159. package/dist/snapshots/index.js +11 -0
  160. package/dist/snapshots.d.ts +7 -4
  161. package/dist/sprites/index.d.ts +343 -31
  162. package/dist/sprites/index.js +866 -84
  163. package/dist/ssh.d.ts +1 -1
  164. package/dist/ssh.js +11 -11
  165. package/dist/terminal.d.ts +24 -0
  166. package/dist/terminal.js +32 -0
  167. package/dist/transport.d.ts +18 -2
  168. package/dist/transport.js +61 -6
  169. package/dist/tunnel.js +1 -1
  170. package/dist/types.d.ts +116 -8
  171. package/dist/vercel/sandbox.d.ts +1 -2
  172. package/dist/vercel/sandbox.js +6 -5
  173. package/dist/vercel/snapshot.js +10 -2
  174. package/dist/volumes/index.d.ts +9 -0
  175. package/dist/volumes/index.js +10 -0
  176. package/package.json +48 -2
@@ -0,0 +1,272 @@
1
+ import { expect, test } from "bun:test";
2
+ import { logsProductCommand } from "./logs.js";
3
+ import { terminalSafe } from "../terminal.js";
4
+ import { logs, traces, alerts } from "../products/logs.js";
5
+ import { Transport } from "../transport.js";
6
+ import { InvalidRequestError } from "../errors.js";
7
+ const input = {
8
+ name: "errors",
9
+ labels: {},
10
+ query: '{app="api"}',
11
+ condition: "above 5",
12
+ for: "5m",
13
+ actions: [],
14
+ };
15
+ const cases = [
16
+ ["logs", ["query", "{}"], { from: "1", to: "2", limit: "5" }, "GET", "/v1/logs/query_range"],
17
+ ["logs", ["labels"], {}, "GET", "/v1/logs/labels"],
18
+ ["logs", ["values", "app"], {}, "GET", "/v1/logs/labels/app/values"],
19
+ ["logs", ["ingest"], { body: '{"resourceLogs":[]}' }, "POST", "/v1/logs"],
20
+ ["traces", ["ingest"], { body: '{"resourceSpans":[]}' }, "POST", "/v1/traces"],
21
+ ["traces", ["get", "id"], {}, "GET", "/v1/traces/id"],
22
+ ["traces", ["search", "{}"], {}, "GET", "/v1/traces:search"],
23
+ ["traces", ["tags"], {}, "GET", "/v1/traces/tags"],
24
+ ["alerts", ["test", "id"], {}, "POST", "/v1/alerts/id/test"],
25
+ [
26
+ "alerts",
27
+ ["silence", "id"],
28
+ { until: "2026-10-07T00:00:00Z", reason: "maintenance" },
29
+ "POST",
30
+ "/v1/alerts/id/silence",
31
+ ],
32
+ ];
33
+ for (const product of ["logs", "traces", "alerts"])
34
+ for (const verb of ["ls", "get", "create", "update", "delete"]) {
35
+ const collection = `/v1/${product}${product === "alerts" ? "" : "/views"}`;
36
+ cases.push([
37
+ product,
38
+ [
39
+ ...(product === "alerts" ? [] : ["views"]),
40
+ verb,
41
+ ...(["get", "update", "delete"].includes(verb) ? ["id"] : []),
42
+ ],
43
+ ["create", "update"].includes(verb) ? { body: JSON.stringify(input) } : {},
44
+ { ls: "GET", get: "GET", create: "POST", update: "PUT", delete: "DELETE" }[verb],
45
+ collection + (["get", "update", "delete"].includes(verb) ? "/id" : ""),
46
+ ]);
47
+ }
48
+ function setup() {
49
+ const requests = [];
50
+ const transport = new Transport({
51
+ apiKey: "fake",
52
+ baseUrl: "https://fake.test",
53
+ maxRetries: 0,
54
+ fetch: (async (url, init) => {
55
+ requests.push({ url: new URL(url instanceof Request ? url.url : url), init: init ?? {} });
56
+ return Response.json({ ...input, id: "id", data: [], nextCursor: null, marker: "ok" });
57
+ }),
58
+ });
59
+ return {
60
+ client: { logs: logs(transport), traces: traces(transport), alerts: alerts(transport) },
61
+ requests,
62
+ };
63
+ }
64
+ for (const [product, positional, flags, method, path] of cases)
65
+ for (const mode of ["tty", "pipe", "json"])
66
+ test(`${product} ${positional.join(" ")} ${mode}`, async () => {
67
+ const { client, requests } = setup(), output = [];
68
+ const result = await logsProductCommand(product, positional, {
69
+ positional,
70
+ flags: new Map(Object.entries({ ...flags, "idempotency-key": "retry-key" }).map(([key, value]) => [
71
+ key,
72
+ [value],
73
+ ])),
74
+ }, {
75
+ json: mode === "json",
76
+ tty: mode === "tty",
77
+ write: (text) => output.push(text),
78
+ error: () => {
79
+ throw new Error("unexpected stderr");
80
+ },
81
+ }, async () => client);
82
+ expect(result).toBe(0);
83
+ expect(requests).toHaveLength(1);
84
+ expect(requests[0].url.pathname).toBe(path);
85
+ expect(requests[0].init.method).toBe(method);
86
+ expect(output).toHaveLength(1);
87
+ expect(output[0]).not.toContain("\x1b");
88
+ const printed = JSON.parse(output[0]);
89
+ if (positional.includes("delete"))
90
+ expect(printed).toEqual({ deleted: "id" });
91
+ else if (positional.includes("ls"))
92
+ expect(printed).toEqual({ data: [], nextCursor: null });
93
+ else
94
+ expect(printed.marker).toBe("ok");
95
+ if (method !== "GET")
96
+ expect(new Headers(requests[0].init.headers).get("idempotency-key")).toBe("retry-key");
97
+ if (positional[0] === "query")
98
+ expect(Object.fromEntries(requests[0].url.searchParams)).toEqual({
99
+ query: "{}",
100
+ start: "1",
101
+ end: "2",
102
+ limit: "5",
103
+ });
104
+ });
105
+ for (const mode of ["tty", "pipe", "json"])
106
+ test(`tail ${mode}, selector escaping, gap and backpressure`, async () => {
107
+ const { client } = setup();
108
+ const output = [];
109
+ let drained = 0;
110
+ const events = [
111
+ {
112
+ time: "12:00",
113
+ timeUnixNano: "1",
114
+ severity: 9,
115
+ severityText: "INFO",
116
+ labels: {},
117
+ attributes: {},
118
+ traceId: null,
119
+ spanId: null,
120
+ body: "line\x1b[2J\nnext",
121
+ cursor: "one",
122
+ },
123
+ { gap: true, from: "old", to: "kept" },
124
+ ];
125
+ let received;
126
+ client.logs.tail = async function* (query, options) {
127
+ received = { query, options };
128
+ yield* events;
129
+ };
130
+ const positional = ["tail", 'app"name'];
131
+ await logsProductCommand("logs", positional, {
132
+ positional,
133
+ flags: new Map([
134
+ ["since", ["1h"]],
135
+ ["grep", ['a"b']],
136
+ ]),
137
+ }, {
138
+ json: mode === "json",
139
+ tty: mode === "tty",
140
+ write: (text) => output.push(text),
141
+ error() { },
142
+ async drain() {
143
+ drained++;
144
+ },
145
+ }, async () => client, { now: Date.parse("2026-10-06T12:00:00Z") });
146
+ expect(received).toEqual({
147
+ query: '{app="app\\"name"} |= "a\\"b"',
148
+ options: { start: "2026-10-06T11:00:00.000Z" },
149
+ });
150
+ expect(drained).toBe(2);
151
+ if (mode === "json")
152
+ expect(output.map((text) => JSON.parse(text))).toEqual(events);
153
+ else {
154
+ if (mode === "tty")
155
+ expect(output[0]).toContain("line\\u001b[2J\\u000anext");
156
+ else
157
+ expect(output[0]).toContain("line\u001b[2J\nnext");
158
+ expect(output[1]).toContain("Logs expired");
159
+ expect(output[1].includes("\x1b[2m")).toBe(mode === "tty");
160
+ }
161
+ });
162
+ test("tail survives an actual fake-fetch disconnect through the SDK", async () => {
163
+ let calls = 0;
164
+ const cursors = [], output = [];
165
+ const abort = new AbortController();
166
+ const transport = new Transport({
167
+ apiKey: "fake",
168
+ baseUrl: "https://fake.test",
169
+ fetch: (async (input) => {
170
+ cursors.push(new URL(input instanceof Request ? input.url : input).searchParams.get("cursor"));
171
+ const cursor = String(++calls);
172
+ return new Response(`id: ${cursor}\ndata: ${JSON.stringify({ time: "now", timeUnixNano: "1", labels: {}, body: "same", severityText: "INFO" })}\n\n`, { headers: { "content-type": "text/event-stream" } });
173
+ }),
174
+ });
175
+ const client = { logs: logs(transport), traces: traces(transport), alerts: alerts(transport) };
176
+ const run = logsProductCommand("logs", ["tail", "{}"], { positional: ["tail", "{}"], flags: new Map() }, {
177
+ json: true,
178
+ write(text) {
179
+ output.push(text);
180
+ if (output.length === 2)
181
+ abort.abort(new Error("done"));
182
+ },
183
+ error() { },
184
+ }, async () => client, { signal: abort.signal });
185
+ await expect(run).rejects.toThrow("done");
186
+ expect(cursors).toEqual([null, "1"]);
187
+ expect(output.map((text) => JSON.parse(text).cursor)).toEqual(["1", "2"]);
188
+ });
189
+ for (const [product, positional, flags] of [
190
+ ["logs", ["query", "{}"], { limit: "0" }],
191
+ ["logs", ["query"], {}],
192
+ ["logs", ["tail", "api"], { since: "forever" }],
193
+ ["alerts", ["create"], { body: "[1]" }],
194
+ ["alerts", ["create"], { body: "broken" }],
195
+ ["alerts", ["silence", "id"], { until: "tomorrow" }],
196
+ ["logs", ["ingest"], { body: "{}" }],
197
+ ["traces", ["ingest"], { body: "{}" }],
198
+ ["alerts", ["oops", "id"], {}],
199
+ ])
200
+ test(`refuses ${positional.join(" ")} ${JSON.stringify(flags)}`, async () => {
201
+ const { client, requests } = setup();
202
+ await expect(logsProductCommand(product, positional, { positional, flags: new Map(Object.entries(flags).map(([key, value]) => [key, [value]])) }, { json: true, write() { }, error() { } }, async () => client)).rejects.toMatchObject({ code: "usage", hint: expect.any(String) });
203
+ expect(requests).toHaveLength(0);
204
+ });
205
+ test("help needs no credentials", async () => {
206
+ const output = [];
207
+ await logsProductCommand("logs", ["help"], { positional: ["help"], flags: new Map() }, { json: false, write: (text) => output.push(text), error() { } }, async () => {
208
+ throw new Error("credentials requested");
209
+ });
210
+ expect(output[0]).toContain("\nruntime logs query");
211
+ });
212
+ test("typed failures reach the main CLI's JSON error renderer unchanged", async () => {
213
+ const { client } = setup();
214
+ const failure = new InvalidRequestError({
215
+ code: "query_limit",
216
+ message: "Too broad",
217
+ status: 400,
218
+ hint: "Narrow the time range.",
219
+ });
220
+ client.logs.queryRange = async () => {
221
+ throw failure;
222
+ };
223
+ await expect(logsProductCommand("logs", ["query", "{}"], { positional: ["query", "{}"], flags: new Map() }, {
224
+ json: true,
225
+ write() {
226
+ throw new Error("Do not print success on failure");
227
+ },
228
+ error() { },
229
+ }, async () => client)).rejects.toBe(failure);
230
+ });
231
+ test("human JSON keeps formatting but escapes C1 terminal controls", async () => {
232
+ const { client } = setup();
233
+ const output = [];
234
+ client.traces.get = async () => ({ resourceSpans: [], marker: "\u009b2J" });
235
+ await logsProductCommand("traces", ["get", "id"], { positional: ["get", "id"], flags: new Map() }, { json: false, tty: true, write: (text) => output.push(text), error() { } }, async () => client);
236
+ expect(output[0]).not.toContain("\u009b");
237
+ expect(output[0]).toContain("\\u009b");
238
+ expect(output[0]).toContain("\n");
239
+ expect(JSON.parse(output[0]).marker).toBe("\u009b2J");
240
+ });
241
+ test("a terminal shows what customers' programs wrote, escaped, and nothing acts", async () => {
242
+ // Clipboard (OSC 52), a forged link (OSC 8), screen clear, the one-byte C1
243
+ // CSI and a right-to-left override; tab stays.
244
+ const hostile = "a\tb \x1b]52;c;c2VjcmV0\x07 \x1b]8;;https://evil.test\x07ok\x1b]8;;\x07 \x1b[2J \u009b2J \u202eevil";
245
+ const shown = terminalSafe(hostile);
246
+ // eslint-disable-next-line no-control-regex -- the test looks for control characters
247
+ expect(shown).not.toMatch(/[\x00-\x08\x0a-\x1f\x7f-\x9f\u202e]/);
248
+ expect(shown).toContain("a\tb");
249
+ expect(shown).toContain("\\u001b]52;c;c2VjcmV0\\u0007");
250
+ expect(shown).toContain("\\u009b2J \\u202eevil");
251
+ const run = async (tty, raw, json) => {
252
+ const { client } = setup();
253
+ client.logs.tail = async function* () {
254
+ yield { cursor: "c", time: "t", severityText: "INFO", body: hostile, labels: {} };
255
+ };
256
+ const output = [];
257
+ const flags = new Map(raw ? [["raw", ["true"]]] : []);
258
+ await logsProductCommand("logs", ["tail", "{}"], { positional: ["tail", "{}"], flags }, { json, tty, write: (text) => output.push(text), error() { } }, async () => client);
259
+ return output.join("\n");
260
+ };
261
+ for (const json of [false, true]) {
262
+ const onTerminal = await run(true, false, json);
263
+ expect(onTerminal).not.toContain("\x1b]");
264
+ expect(onTerminal).not.toContain("\u009b");
265
+ expect(onTerminal).not.toContain("\u202e");
266
+ if (json)
267
+ expect(JSON.parse(onTerminal).body).toBe(hostile);
268
+ // A pipe, or --raw on a terminal, gets exactly what was written.
269
+ expect(await run(false, false, json)).toContain(json ? JSON.stringify(hostile) : hostile);
270
+ expect(await run(true, true, json)).toContain(json ? JSON.stringify(hostile) : hostile);
271
+ }
272
+ });
@@ -22,9 +22,9 @@ export declare function lostOutput(result: {
22
22
  stdoutTruncated?: boolean;
23
23
  stderrTruncated?: boolean;
24
24
  }, id: string): string | undefined;
25
- /** Feeds `runtime sandbox shell` from standard input. When piped input ends,
25
+ /** Feeds `runtime sandboxes shell` from standard input. When piped input ends,
26
26
  * the shell is told so the way a terminal says it, Ctrl-D, after a newline
27
- * if the last line had none: `echo ls | runtime sandbox shell <id>` runs `ls`
27
+ * if the last line had none: `echo ls | runtime sandboxes shell <id>` runs `ls`
28
28
  * and exits, as ssh does, where it used to wait forever (25 September 2026).
29
29
  * A keyboard never ends, so a terminal's input is left alone. Returns the
30
30
  * function that unhooks it. */
@@ -33,7 +33,7 @@ export declare function pipeShellInput(stdin: NodeJS.ReadableStream & {
33
33
  }, terminal: {
34
34
  write(data: Uint8Array | string): void;
35
35
  }): () => void;
36
- /** `runtime snapshot get`, as `runtime sandbox get` reads: one fact a line.
36
+ /** `runtime snapshots get`, as `runtime sandboxes get` reads: one fact a line.
37
37
  * It printed raw JSON until 25 September 2026; `--json` still does. */
38
38
  export declare function describeSnapshot(s: Snapshot, table: (rows: string[][]) => string): string;
39
39
  export {};
@@ -13,12 +13,12 @@ export async function recordCommand(sbx, argv, out, kit) {
13
13
  ...(integer(args, "max-seconds") ? { maxSeconds: integer(args, "max-seconds") } : {}),
14
14
  ...(integer(args, "max-mib") ? { maxMiB: integer(args, "max-mib") } : {}),
15
15
  });
16
- print(`Recording ${rec.id}. Stop it: ${me} sandbox desktop ${sbx.id} record stop ${rec.id}`, rec);
16
+ print(`Recording ${rec.id}. Stop it: ${me} sandboxes desktop ${sbx.id} record stop ${rec.id}`, rec);
17
17
  return 0;
18
18
  }
19
19
  case "stop": {
20
20
  const rec = await r.stop(need(rest[0], "the recording id"));
21
- print(`Stopped ${rec.id}: ${(rec.bytes / 1_048_576).toFixed(1)} MiB (${rec.reason ?? "stopped"}). Fetch it: ${me} sandbox desktop ${sbx.id} record fetch ${rec.id}`, rec);
21
+ print(`Stopped ${rec.id}: ${(rec.bytes / 1_048_576).toFixed(1)} MiB (${rec.reason ?? "stopped"}). Fetch it: ${me} sandboxes desktop ${sbx.id} record fetch ${rec.id}`, rec);
22
22
  return 0;
23
23
  }
24
24
  case "ls": {
@@ -47,7 +47,7 @@ export async function recordCommand(sbx, argv, out, kit) {
47
47
  print("Deleted.", await r.delete(need(rest[0], "the recording id")));
48
48
  return 0;
49
49
  }
50
- throw usage(`${me} sandbox desktop <id> record start|stop|ls|fetch|rm`);
50
+ throw usage(`${me} sandboxes desktop <id> record start|stop|ls|fetch|rm`);
51
51
  }
52
52
  export async function watchCommand(sbx, argv, out, kit) {
53
53
  const { parse, flag, has, need } = kit;
@@ -110,7 +110,7 @@ export async function mcpCommand(sbx, argv, out, kit) {
110
110
  case "start": {
111
111
  const args = parse(rest, ["replace"], ["secret", "env", "option", "port"]);
112
112
  if (!args.positional.length)
113
- throw usage("Name the servers: runtime sandbox mcp <id> start github fetch");
113
+ throw usage("Name the servers: runtime sandboxes mcp <id> start github fetch");
114
114
  const scoped = (name) => {
115
115
  const found = {};
116
116
  for (const [key, value] of Object.entries(pairs(args, name))) {
@@ -134,7 +134,7 @@ export async function mcpCommand(sbx, argv, out, kit) {
134
134
  ...(integer(args, "port") ? { port: integer(args, "port") } : {}),
135
135
  ...(has(args, "replace") ? { replace: true } : {}),
136
136
  });
137
- print(`${show(gw)}\nServers are installing; \`${me} sandbox mcp ${sbx.id}\` shows when they are ready.`, gw);
137
+ print(`${show(gw)}\nServers are installing; \`${me} sandboxes mcp ${sbx.id}\` shows when they are ready.`, gw);
138
138
  return 0;
139
139
  }
140
140
  case "status": {
@@ -146,7 +146,7 @@ export async function mcpCommand(sbx, argv, out, kit) {
146
146
  print("Stopped.", await sbx.mcp.stop());
147
147
  return 0;
148
148
  }
149
- throw usage(`${me} sandbox mcp <id> start|status|stop, or ${me} sandbox mcp catalog`);
149
+ throw usage(`${me} sandboxes mcp <id> start|status|stop, or ${me} sandboxes mcp catalog`);
150
150
  }
151
151
  /** Standard input when it is piped or redirected from a file, read whole, up
152
152
  * to the API's 1 MiB; undefined for a terminal. A pipe that says nothing for
@@ -184,7 +184,7 @@ export async function pipedStdin(options = {}) {
184
184
  clearTimeout(quiet);
185
185
  size += chunk.length;
186
186
  if (size > limit)
187
- return done(undefined, Object.assign(new Error(`Standard input is over ${limit / 1_048_576} MiB. Copy the file in with \`${me} sandbox cp\` and read it there.`), { code: "usage" }));
187
+ return done(undefined, Object.assign(new Error(`Standard input is over ${limit / 1_048_576} MiB. Copy the file in with \`${me} sandboxes cp\` and read it there.`), { code: "usage" }));
188
188
  chunks.push(chunk);
189
189
  };
190
190
  const onEnd = () => done(size ? new Uint8Array(Buffer.concat(chunks)) : undefined);
@@ -201,11 +201,11 @@ export async function pipedStdin(options = {}) {
201
201
  export function lostOutput(result, id) {
202
202
  if (!result.stdoutTruncated && !result.stderrTruncated)
203
203
  return undefined;
204
- return `runtime: some of the command's output was lost: it wrote faster than it was read, and the sandbox keeps the latest 1 MiB. Write it to a file and copy it out: ${me} sandbox cp ${id}:/workspace/out.txt .`;
204
+ return `runtime: some of the command's output was lost: it wrote faster than it was read, and the sandbox keeps the latest 1 MiB. Write it to a file and copy it out: ${me} sandboxes cp ${id}:/workspace/out.txt .`;
205
205
  }
206
- /** Feeds `runtime sandbox shell` from standard input. When piped input ends,
206
+ /** Feeds `runtime sandboxes shell` from standard input. When piped input ends,
207
207
  * the shell is told so the way a terminal says it, Ctrl-D, after a newline
208
- * if the last line had none: `echo ls | runtime sandbox shell <id>` runs `ls`
208
+ * if the last line had none: `echo ls | runtime sandboxes shell <id>` runs `ls`
209
209
  * and exits, as ssh does, where it used to wait forever (25 September 2026).
210
210
  * A keyboard never ends, so a terminal's input is left alone. Returns the
211
211
  * function that unhooks it. */
@@ -229,7 +229,7 @@ export function pipeShellInput(stdin, terminal) {
229
229
  stdin.off("end", onEnd);
230
230
  };
231
231
  }
232
- /** `runtime snapshot get`, as `runtime sandbox get` reads: one fact a line.
232
+ /** `runtime snapshots get`, as `runtime sandboxes get` reads: one fact a line.
233
233
  * It printed raw JSON until 25 September 2026; `--json` still does. */
234
234
  export function describeSnapshot(s, table) {
235
235
  const mib = (bytes) => bytes === null ? "-" : `${Math.ceil(bytes / 1_048_576).toLocaleString("en-US")} MiB`;
@@ -249,7 +249,12 @@ export function describeSnapshot(s, table) {
249
249
  : s.durability.state,
250
250
  ],
251
251
  ["created", s.createdAt],
252
- ["kept until", `${s.expiresAt} (${s.retentionDays} days)`],
252
+ [
253
+ "kept until",
254
+ s.expiresAt === null
255
+ ? "kept while you have credit"
256
+ : `${s.expiresAt} (${s.retentionDays} days)`,
257
+ ],
253
258
  [
254
259
  "labels",
255
260
  Object.entries(labels)
@@ -1,4 +1,5 @@
1
- import type { RuntimeError } from "./errors.js";
1
+ import { RuntimeError } from "./errors.js";
2
+ import type { ProductName } from "./product-names.js";
2
3
  import type { SandboxMetrics } from "./products/observability.js";
3
4
  /** A refusal of what a command sent, reworded for the command line: every
4
5
  * issue named by its option, since the CLI never prints details.issues, and
@@ -25,3 +26,11 @@ export declare function newestCpu(m: SandboxMetrics, now?: number): {
25
26
  * help` filed "help" as feedback, and `runtime support help` sent it to
26
27
  * support, until 30 September 2026. */
27
28
  export declare function topicHelp(help: string, command: string): string | null;
29
+ /** A command line's product words (CLOUD.md section 4): the plural, or the
30
+ * singular as a typing shortcut, reach the product silently; an old word
31
+ * reaches it with `warning` to print, and from its date throws name_retired
32
+ * naming the new command. Anything else passes through. */
33
+ export declare function productWords(argv: string[], now?: Date, table?: readonly ProductName[]): {
34
+ argv: string[];
35
+ warning?: string;
36
+ };
package/dist/cli-words.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { RuntimeError } from "./errors.js";
2
+ import { cliWords } from "./product-renames.js";
1
3
  /* What the CLI says in its own words where the API's would mislead: the
2
4
  option a refusal is about rather than the request field it was sent as,
3
5
  one fact a line for a resource read, and the newest CPU reading a
@@ -133,3 +135,19 @@ export function topicHelp(help, command) {
133
135
  }
134
136
  return out.length ? out.join("\n") : null;
135
137
  }
138
+ /** A command line's product words (CLOUD.md section 4): the plural, or the
139
+ * singular as a typing shortcut, reach the product silently; an old word
140
+ * reaches it with `warning` to print, and from its date throws name_retired
141
+ * naming the new command. Anything else passes through. */
142
+ export function productWords(argv, now = new Date(), table) {
143
+ const found = cliWords(argv, now, table);
144
+ if (!found)
145
+ return { argv };
146
+ const { use } = found;
147
+ if (use.kind === "retired")
148
+ throw new RuntimeError({ ...use.error });
149
+ return {
150
+ argv: found.args,
151
+ ...(use.kind === "renamed" ? { warning: use.warning } : {}),
152
+ };
153
+ }
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { RuntimeError } from "./errors.js";
3
- import type { Usage } from "./types.js";
3
+ import type { OutOfMemory, Usage } from "./types.js";
4
4
  import type { SwitchProvider } from "./products/switching.js";
5
5
  import type { SecretRule } from "./products/secrets.js";
6
6
  type Out = {
@@ -30,7 +30,7 @@ declare const kit: {
30
30
  };
31
31
  export type CliKit = typeof kit;
32
32
  export declare function run(argv: string[], env?: NodeJS.ProcessEnv, out?: Out): Promise<number>;
33
- /** `runtime usage` for a person: the balance, the trial and what each kind
33
+ /** `runtime usage` for a person: the balance, the included usage and what each kind
34
34
  * of resource was charged, in dollars. --json keeps every figure, and each
35
35
  * resource with its rates and CPU time. */
36
36
  /** One row per resource, for a spreadsheet: when it ran, how big it was, how
@@ -38,6 +38,10 @@ export declare function run(argv: string[], env?: NodeJS.ProcessEnv, out?: Out):
38
38
  * microdollar (the API's integer microdollars moved six places), so a column
39
39
  * sums to the ledger to the last digit. */
40
40
  export declare function usageCsv(u: Usage): string;
41
+ /** What a person reads when the sandbox's out-of-memory killer ended a
42
+ * command: the API's message and hint, on standard error. A bare exit -9 (or
43
+ * 137) said nothing of memory until 9 October 2026. */
44
+ export declare function outOfMemoryLines(memory: OutOfMemory | undefined): string | undefined;
41
45
  /** How to move a rival's code across, when one import does it. */
42
46
  export declare function dropInLines(provider: SwitchProvider | undefined): string[];
43
47
  /** `--allow` values as secret rules: each is `[METHOD[,METHOD]...] PATH`, and