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.
- package/BLAXEL.md +3 -3
- package/COMPATIBILITY.md +74 -9
- package/DAYTONA.md +33 -5
- package/E2B.md +137 -48
- package/README.md +11 -9
- package/README.src.md +11 -9
- package/VERCEL.md +43 -5
- package/compatibility-lock.json +58 -49
- package/dist/billing-cli.d.ts +1 -1
- package/dist/billing-cli.js +77 -1
- package/dist/blaxel/errors.d.ts +14 -0
- package/dist/blaxel/errors.js +33 -4
- package/dist/blaxel/index.d.ts +2 -2
- package/dist/blaxel/index.js +4 -4
- package/dist/blaxel/interpreter.d.ts +2 -5
- package/dist/blaxel/sandbox.d.ts +15 -7
- package/dist/blaxel/sandbox.js +64 -9
- package/dist/blaxel/snapshot.d.ts +4 -0
- package/dist/blaxel/snapshot.js +4 -0
- package/dist/cli/logs.d.ts +21 -0
- package/dist/cli/logs.js +188 -0
- package/dist/cli/logs.test.d.ts +1 -0
- package/dist/cli/logs.test.js +272 -0
- package/dist/cli-extras.d.ts +3 -3
- package/dist/cli-extras.js +17 -12
- package/dist/cli-words.d.ts +10 -1
- package/dist/cli-words.js +18 -0
- package/dist/cli.d.ts +6 -2
- package/dist/cli.js +468 -219
- package/dist/client.d.ts +13 -63
- package/dist/client.js +35 -124
- package/dist/cloudflare/errors.d.ts +143 -0
- package/dist/cloudflare/errors.js +167 -0
- package/dist/cloudflare/index.d.ts +175 -6
- package/dist/cloudflare/index.js +416 -26
- package/dist/cloudflare/interpreter.d.ts +119 -0
- package/dist/cloudflare/interpreter.js +223 -0
- package/dist/cloudflare/streams.d.ts +38 -0
- package/dist/cloudflare/streams.js +145 -0
- package/dist/cloudflare/watch.d.ts +9 -0
- package/dist/cloudflare/watch.js +24 -0
- package/dist/codesandbox/index.d.ts +216 -28
- package/dist/codesandbox/index.js +478 -78
- package/dist/compat/core.d.ts +18 -0
- package/dist/compat/core.js +29 -0
- package/dist/compat/files.d.ts +1 -0
- package/dist/compat/files.js +25 -5
- package/dist/daytona/computer-use.d.ts +13 -4
- package/dist/daytona/computer-use.js +66 -8
- package/dist/daytona/daytona.d.ts +7 -2
- package/dist/daytona/daytona.js +31 -5
- package/dist/daytona/errors.d.ts +11 -0
- package/dist/daytona/errors.js +18 -0
- package/dist/daytona/filesystem.js +2 -1
- package/dist/daytona/image.d.ts +13 -9
- package/dist/daytona/image.js +18 -12
- package/dist/daytona/index.d.ts +1 -1
- package/dist/daytona/sandbox.d.ts +9 -3
- package/dist/daytona/sandbox.js +17 -3
- package/dist/domains/index.d.ts +9 -0
- package/dist/domains/index.js +10 -0
- package/dist/e2b/client.d.ts +15 -2
- package/dist/e2b/client.js +61 -7
- package/dist/e2b/commands.d.ts +4 -1
- package/dist/e2b/commands.js +17 -7
- package/dist/e2b/errors.d.ts +43 -2
- package/dist/e2b/errors.js +78 -6
- package/dist/e2b/filesystem.d.ts +1 -1
- package/dist/e2b/filesystem.js +82 -16
- package/dist/e2b/index.d.ts +3 -3
- package/dist/e2b/index.js +2 -2
- package/dist/e2b/sandbox.d.ts +50 -35
- package/dist/e2b/sandbox.js +228 -185
- package/dist/e2b/unsupported.d.ts +19 -0
- package/dist/e2b/unsupported.js +22 -1
- package/dist/e2b/users.d.ts +9 -0
- package/dist/e2b/users.js +22 -0
- package/dist/errors.d.ts +14 -5
- package/dist/errors.js +28 -14
- package/dist/freestyle/index.d.ts +285 -37
- package/dist/freestyle/index.js +656 -144
- package/dist/identity.d.ts +3 -0
- package/dist/identity.js +27 -0
- package/dist/images/index.d.ts +9 -0
- package/dist/images/index.js +10 -0
- package/dist/index.d.ts +1 -0
- package/dist/ips/index.d.ts +9 -0
- package/dist/ips/index.js +10 -0
- package/dist/jobs/index.d.ts +9 -0
- package/dist/jobs/index.js +10 -0
- package/dist/keys.js +2 -0
- package/dist/login.js +2 -0
- package/dist/machine.d.ts +189 -0
- package/dist/machine.js +521 -0
- package/dist/mcp.js +1 -1
- package/dist/modal/index.d.ts +339 -61
- package/dist/modal/index.js +781 -121
- package/dist/network-cli.d.ts +3 -1
- package/dist/network-cli.js +78 -36
- package/dist/networks/index.d.ts +11 -0
- package/dist/networks/index.js +10 -0
- package/dist/openai-agents/index.d.ts +1 -1
- package/dist/openai-agents/index.js +1 -1
- package/dist/ports/index.d.ts +9 -0
- package/dist/ports/index.js +10 -0
- package/dist/product-names.d.ts +52 -0
- package/dist/product-names.js +164 -0
- package/dist/product-renames.d.ts +103 -0
- package/dist/product-renames.js +189 -0
- package/dist/products/apps.d.ts +176 -0
- package/dist/products/apps.js +165 -0
- package/dist/products/billing.d.ts +41 -0
- package/dist/products/billing.js +13 -0
- package/dist/products/browser.d.ts +10 -0
- package/dist/products/browsers.d.ts +71 -0
- package/dist/products/browsers.js +102 -0
- package/dist/products/builds.d.ts +126 -0
- package/dist/products/builds.js +221 -0
- package/dist/products/index.d.ts +3 -16
- package/dist/products/index.js +3 -14
- package/dist/products/jobs.d.ts +19 -3
- package/dist/products/jobs.js +28 -12
- package/dist/products/limits.d.ts +4 -10
- package/dist/products/logs.d.ts +245 -0
- package/dist/products/logs.js +274 -0
- package/dist/products/logs.test.d.ts +1 -0
- package/dist/products/logs.test.js +343 -0
- package/dist/products/network-products.d.ts +38 -7
- package/dist/products/network-products.js +31 -18
- package/dist/products/network.d.ts +1 -1
- package/dist/products/observability.d.ts +3 -2
- package/dist/products/private-network.js +1 -1
- package/dist/products/queues.d.ts +149 -0
- package/dist/products/queues.js +213 -0
- package/dist/products/referrals.d.ts +21 -7
- package/dist/products/referrals.js +3 -3
- package/dist/products/secrets.d.ts +8 -4
- package/dist/products/secrets.js +39 -18
- package/dist/products/signed-requests.d.ts +21 -0
- package/dist/products/signed-requests.js +11 -0
- package/dist/products/streams.d.ts +73 -0
- package/dist/products/streams.js +17 -0
- package/dist/products/switching.d.ts +37 -10
- package/dist/products/switching.js +4 -3
- package/dist/products/volumes.d.ts +3 -0
- package/dist/record-cli.d.ts +23 -0
- package/dist/record-cli.js +308 -0
- package/dist/renamed-products.d.ts +38 -0
- package/dist/renamed-products.js +34 -0
- package/dist/runloop/index.d.ts +9 -2
- package/dist/runloop/index.js +29 -8
- package/dist/runloop/sdk.d.ts +131 -0
- package/dist/runloop/sdk.js +167 -0
- package/dist/sandbox.d.ts +10 -94
- package/dist/sandbox.js +22 -371
- package/dist/sandboxes/index.d.ts +11 -0
- package/dist/sandboxes/index.js +12 -0
- package/dist/snapshots/index.d.ts +9 -0
- package/dist/snapshots/index.js +11 -0
- package/dist/snapshots.d.ts +7 -4
- package/dist/sprites/index.d.ts +343 -31
- package/dist/sprites/index.js +866 -84
- package/dist/ssh.d.ts +1 -1
- package/dist/ssh.js +11 -11
- package/dist/terminal.d.ts +24 -0
- package/dist/terminal.js +32 -0
- package/dist/transport.d.ts +18 -2
- package/dist/transport.js +61 -6
- package/dist/tunnel.js +1 -1
- package/dist/types.d.ts +116 -8
- package/dist/vercel/sandbox.d.ts +1 -2
- package/dist/vercel/sandbox.js +6 -5
- package/dist/vercel/snapshot.js +10 -2
- package/dist/volumes/index.d.ts +9 -0
- package/dist/volumes/index.js +10 -0
- 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
|
+
});
|
package/dist/cli-extras.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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 {};
|
package/dist/cli-extras.js
CHANGED
|
@@ -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}
|
|
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}
|
|
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}
|
|
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
|
|
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}
|
|
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}
|
|
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}
|
|
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}
|
|
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
|
|
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
|
|
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
|
|
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
|
-
[
|
|
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)
|
package/dist/cli-words.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
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
|
|
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
|