withruntime 0.2.1 → 0.3.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 (45) hide show
  1. package/README.md +68 -4
  2. package/dist/cli.d.ts +9 -0
  3. package/dist/cli.js +1262 -0
  4. package/dist/client.d.ts +118 -0
  5. package/dist/client.js +160 -0
  6. package/dist/credentials.d.ts +44 -0
  7. package/dist/credentials.js +180 -0
  8. package/dist/errors.d.ts +58 -0
  9. package/dist/errors.js +106 -0
  10. package/dist/index.d.ts +36 -0
  11. package/dist/index.js +25 -0
  12. package/dist/login.d.ts +119 -0
  13. package/dist/login.js +367 -0
  14. package/dist/mcp.d.ts +15 -0
  15. package/dist/mcp.js +221 -0
  16. package/dist/page.d.ts +13 -0
  17. package/dist/page.js +39 -0
  18. package/dist/products/desktop.d.ts +103 -0
  19. package/dist/products/desktop.js +38 -0
  20. package/dist/products/images.d.ts +121 -0
  21. package/dist/products/images.js +64 -0
  22. package/dist/products/index.d.ts +29 -0
  23. package/dist/products/index.js +36 -0
  24. package/dist/products/interpreter.d.ts +88 -0
  25. package/dist/products/interpreter.js +81 -0
  26. package/dist/products/network.d.ts +47 -0
  27. package/dist/products/network.js +19 -0
  28. package/dist/products/previews.d.ts +51 -0
  29. package/dist/products/previews.js +30 -0
  30. package/dist/products/referrals.d.ts +36 -0
  31. package/dist/products/referrals.js +8 -0
  32. package/dist/products/volumes.d.ts +47 -0
  33. package/dist/products/volumes.js +27 -0
  34. package/dist/sandbox.d.ts +192 -0
  35. package/dist/sandbox.js +628 -0
  36. package/dist/snapshots.d.ts +50 -0
  37. package/dist/snapshots.js +55 -0
  38. package/dist/tar.d.ts +3 -0
  39. package/dist/tar.js +116 -0
  40. package/dist/transport.d.ts +65 -0
  41. package/dist/transport.js +261 -0
  42. package/dist/types.d.ts +175 -0
  43. package/dist/types.js +1 -0
  44. package/package.json +53 -8
  45. package/cli.js +0 -8
@@ -0,0 +1,55 @@
1
+ import { Page } from "./page.js";
2
+ const enc = encodeURIComponent;
3
+ /** `runtime.snapshots`. Take one with `sbx.snapshot()`, start from one with
4
+ * `runtime.sandboxes.create({ snapshot: id })`, or do both with `sbx.fork()`. */
5
+ export class Snapshots {
6
+ t;
7
+ constructor(t) {
8
+ this.t = t;
9
+ }
10
+ create(input, options = {}) {
11
+ const { sandboxId, ...body } = input;
12
+ return this.t.json({
13
+ method: "POST",
14
+ path: `/v1/sandboxes/${enc(sandboxId)}:snapshot`,
15
+ body,
16
+ ...options,
17
+ });
18
+ }
19
+ get(id, options = {}) {
20
+ return this.t.json({ method: "GET", path: `/v1/snapshots/${enc(id)}`, ...options });
21
+ }
22
+ async list(filter = {}) {
23
+ const query = {
24
+ sandboxId: filter.sandboxId,
25
+ name: filter.name,
26
+ state: filter.state,
27
+ limit: filter.limit,
28
+ };
29
+ const fetchPage = async (cursor) => {
30
+ const body = await this.t.json({
31
+ method: "GET",
32
+ path: "/v1/snapshots",
33
+ query: { ...query, cursor },
34
+ });
35
+ return new Page(body.data, body.nextCursor, (next) => fetchPage(next));
36
+ };
37
+ return fetchPage();
38
+ }
39
+ async delete(id, options = {}) {
40
+ await this.t.json({
41
+ method: "POST",
42
+ path: `/v1/snapshots/${enc(id)}:delete`,
43
+ body: {},
44
+ ...options,
45
+ });
46
+ }
47
+ extend(id, retentionDays, options = {}) {
48
+ return this.t.json({
49
+ method: "POST",
50
+ path: `/v1/snapshots/${enc(id)}:extend`,
51
+ body: { retentionDays },
52
+ ...options,
53
+ });
54
+ }
55
+ }
package/dist/tar.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare function packDirectory(root: string): Promise<Uint8Array>;
2
+ /** Unpacks into `target`, refusing any entry that would land outside it. */
3
+ export declare function unpackArchive(archive: Uint8Array, target: string): Promise<void>;
package/dist/tar.js ADDED
@@ -0,0 +1,116 @@
1
+ import { mkdir, readdir, readFile, lstat, writeFile, symlink, chmod } from "node:fs/promises";
2
+ import { join, relative, resolve, sep } from "node:path";
3
+ import { gunzipSync, gzipSync } from "node:zlib";
4
+ /* A small ustar writer and reader for directory uploads and downloads, so the
5
+ SDK needs no dependency. Regular files, directories and symbolic links;
6
+ names up to 255 bytes through the ustar prefix field. */
7
+ function header(name, size, mode, type, link = "", mtime = 0) {
8
+ const block = new Uint8Array(512);
9
+ const bytes = new TextEncoder().encode(name);
10
+ let prefix = new Uint8Array();
11
+ let short = bytes;
12
+ if (bytes.length > 100) {
13
+ const split = name.lastIndexOf("/", 155);
14
+ if (split <= 0 || new TextEncoder().encode(name.slice(split + 1)).length > 100)
15
+ throw new Error(`Path too long for a tar archive: ${name}`);
16
+ prefix = new TextEncoder().encode(name.slice(0, split));
17
+ short = new TextEncoder().encode(name.slice(split + 1));
18
+ }
19
+ const put = (offset, value, length) => {
20
+ const data = typeof value === "string" ? new TextEncoder().encode(value) : value;
21
+ block.set(data.subarray(0, length), offset);
22
+ };
23
+ const octal = (value, length) => value.toString(8).padStart(length - 1, "0") + "\0";
24
+ put(0, short, 100);
25
+ put(100, octal(mode & 0o7777, 8), 8);
26
+ put(108, octal(0, 8), 8);
27
+ put(116, octal(0, 8), 8);
28
+ put(124, octal(size, 12), 12);
29
+ put(136, octal(mtime, 12), 12);
30
+ put(148, " ", 8);
31
+ put(156, type, 1);
32
+ put(157, link, 100);
33
+ put(257, "ustar\u000000", 8);
34
+ put(345, prefix, 155);
35
+ let sum = 0;
36
+ for (const byte of block)
37
+ sum += byte;
38
+ put(148, sum.toString(8).padStart(6, "0") + "\0 ", 8);
39
+ return block;
40
+ }
41
+ export async function packDirectory(root) {
42
+ const parts = [];
43
+ const base = resolve(root);
44
+ async function walk(directory) {
45
+ for (const name of (await readdir(directory)).sort()) {
46
+ const full = join(directory, name);
47
+ const path = relative(base, full).split(sep).join("/");
48
+ const info = await lstat(full);
49
+ const mtime = Math.floor(info.mtimeMs / 1000);
50
+ if (info.isDirectory()) {
51
+ parts.push(header(`${path}/`, 0, info.mode, "5", "", mtime));
52
+ await walk(full);
53
+ }
54
+ else if (info.isSymbolicLink()) {
55
+ const { readlink } = await import("node:fs/promises");
56
+ parts.push(header(path, 0, info.mode, "2", await readlink(full), mtime));
57
+ }
58
+ else if (info.isFile()) {
59
+ const data = await readFile(full);
60
+ parts.push(header(path, data.length, info.mode, "0", "", mtime), data);
61
+ const pad = (512 - (data.length % 512)) % 512;
62
+ if (pad)
63
+ parts.push(new Uint8Array(pad));
64
+ }
65
+ }
66
+ }
67
+ await walk(base);
68
+ parts.push(new Uint8Array(1024));
69
+ return gzipSync(Buffer.concat(parts));
70
+ }
71
+ /** Unpacks into `target`, refusing any entry that would land outside it. */
72
+ export async function unpackArchive(archive, target) {
73
+ const data = gunzipSync(archive);
74
+ const root = resolve(target);
75
+ await mkdir(root, { recursive: true });
76
+ const text = (start, length) => {
77
+ const slice = data.subarray(start, start + length);
78
+ const end = slice.indexOf(0);
79
+ return new TextDecoder().decode(end < 0 ? slice : slice.subarray(0, end));
80
+ };
81
+ let longName;
82
+ for (let offset = 0; offset + 512 <= data.length;) {
83
+ if (data.subarray(offset, offset + 512).every((byte) => byte === 0))
84
+ break;
85
+ const size = parseInt(text(offset + 124, 12).trim() || "0", 8);
86
+ const type = String.fromCharCode(data[offset + 156] ?? 48);
87
+ const prefix = text(offset + 345, 155);
88
+ let name = longName ?? (prefix ? `${prefix}/${text(offset, 100)}` : text(offset, 100));
89
+ longName = undefined;
90
+ const mode = parseInt(text(offset + 100, 8).trim() || "644", 8);
91
+ const link = text(offset + 157, 100);
92
+ const body = data.subarray(offset + 512, offset + 512 + size);
93
+ offset += 512 + Math.ceil(size / 512) * 512;
94
+ if (type === "L") {
95
+ longName = new TextDecoder().decode(body).replace(/\0.*$/s, "");
96
+ continue;
97
+ }
98
+ name = name.replace(/^\.\//, "");
99
+ if (!name || name === "." || name === "./")
100
+ continue;
101
+ const destination = resolve(root, name);
102
+ if (destination !== root && !destination.startsWith(root + sep))
103
+ throw new Error(`Refusing an archive entry outside the target: ${name}`);
104
+ if (type === "5")
105
+ await mkdir(destination, { recursive: true });
106
+ else if (type === "2") {
107
+ await mkdir(resolve(destination, ".."), { recursive: true });
108
+ await symlink(link, destination).catch(() => undefined);
109
+ }
110
+ else if (type === "0" || type === "\0" || type === "7") {
111
+ await mkdir(resolve(destination, ".."), { recursive: true });
112
+ await writeFile(destination, body);
113
+ await chmod(destination, mode & 0o777);
114
+ }
115
+ }
116
+ }
@@ -0,0 +1,65 @@
1
+ import { RuntimeError } from "./errors.js";
2
+ export declare const VERSION = "0.3.0";
3
+ export declare const DEFAULT_BASE_URL = "https://api.withruntime.com";
4
+ export type RequestOptions = {
5
+ /** Sent as Idempotency-Key. Omit it: the SDK makes one per call and keeps
6
+ * it across its own retries, so a retried write never happens twice. Pass
7
+ * your own only to retry a call yourself after the process restarted. */
8
+ idempotencyKey?: string;
9
+ signal?: AbortSignal;
10
+ /** Deadline for this call, retries included. */
11
+ timeoutMs?: number;
12
+ };
13
+ export type Query = Record<string, string | number | boolean | readonly string[] | undefined>;
14
+ export type Call = RequestOptions & {
15
+ method: "GET" | "POST" | "PUT" | "DELETE";
16
+ path: string;
17
+ query?: Query;
18
+ body?: unknown;
19
+ /** Raw bytes instead of JSON. */
20
+ bytes?: Uint8Array;
21
+ /** Seconds the server may hold the answer for a long-running result. */
22
+ wait?: number;
23
+ accept?: string;
24
+ /** Whether retrying after an unknown outcome is safe. Default: true for
25
+ * reads and for writes (the key makes them safe); false only for calls
26
+ * the server cannot deduplicate. */
27
+ retry?: boolean;
28
+ };
29
+ export declare function apiOrigin(value: string): string;
30
+ export declare function encodeQuery(query: Query | undefined): string;
31
+ /** One connection pool, one retry policy, one error shape for every product.
32
+ * Node's and Bun's fetch keep connections alive, so a client reused across
33
+ * calls pays TLS once. Over HTTP/1.1 every call in flight holds its own
34
+ * connection, so a hundred calls at once would open a hundred, past what one
35
+ * address may hold at the edge (64). Answers read whole are therefore held to
36
+ * `maxConnections` at once and the rest queue here, reusing those
37
+ * connections; streams and terminals are long-lived and do not wait behind
38
+ * them. */
39
+ export declare class Transport {
40
+ #private;
41
+ readonly baseUrl: string;
42
+ constructor(options: {
43
+ /** A key, or where to find one when the first call is made. */
44
+ apiKey: string | (() => Promise<string>);
45
+ baseUrl?: string;
46
+ fetch?: typeof fetch;
47
+ timeoutMs?: number;
48
+ maxRetries?: number;
49
+ client?: string;
50
+ /** Calls read whole that may be in flight at once. Default 32. */
51
+ maxConnections?: number;
52
+ });
53
+ /** Sends a call and returns the raw Response once it succeeded. Retries
54
+ * transport failures, 429, 502, 503 and 504 with backoff and jitter, with
55
+ * the same key every time. */
56
+ send(call: Call): Promise<Response>;
57
+ json<T>(call: Call): Promise<T>;
58
+ bytes(call: Call): Promise<Uint8Array>;
59
+ /** Newline-delimited JSON events, as they arrive. */
60
+ events<T>(call: Call): AsyncGenerator<T>;
61
+ /** A WebSocket to an API path, authenticated with the same key. */
62
+ websocket(path: string, query?: Query): Promise<WebSocket>;
63
+ }
64
+ /** No key given, none in RUNTIME_API_KEY, and none saved by `runtime login`. */
65
+ export declare function missingKey(): RuntimeError;
@@ -0,0 +1,261 @@
1
+ import { ConnectionError, errorFor, RuntimeError } from "./errors.js";
2
+ export const VERSION = "0.3.0";
3
+ export const DEFAULT_BASE_URL = "https://api.withruntime.com";
4
+ export function apiOrigin(value) {
5
+ const url = new URL(value);
6
+ const local = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
7
+ if ((url.protocol !== "https:" && !(local && url.protocol === "http:")) ||
8
+ url.username ||
9
+ url.password ||
10
+ url.search ||
11
+ url.hash ||
12
+ url.pathname !== "/")
13
+ throw new Error("Use an HTTPS API origin (or http://localhost for tests).");
14
+ return url.origin;
15
+ }
16
+ export function encodeQuery(query) {
17
+ if (!query)
18
+ return "";
19
+ const params = new URLSearchParams();
20
+ for (const [key, value] of Object.entries(query)) {
21
+ if (value === undefined)
22
+ continue;
23
+ if (Array.isArray(value))
24
+ for (const item of value)
25
+ params.append(key, item);
26
+ else
27
+ params.set(key, String(value));
28
+ }
29
+ const text = params.toString();
30
+ return text ? `?${text}` : "";
31
+ }
32
+ /** 503s that are a deliberate state, not a passing one: a product switched
33
+ * off here, or paused on purpose. Retrying cannot change them, so they fail
34
+ * at once with their own words. host_unavailable and busy are still retried. */
35
+ const DELIBERATE = new Set(["fork_unavailable", "previews_unavailable", "unavailable"]);
36
+ const sleep = (ms, signal) => new Promise((resolve, reject) => {
37
+ const timer = setTimeout(done, ms);
38
+ function done() {
39
+ signal?.removeEventListener("abort", abort);
40
+ resolve();
41
+ }
42
+ function abort() {
43
+ clearTimeout(timer);
44
+ reject(signal?.reason instanceof Error ? signal.reason : new Error("Aborted"));
45
+ }
46
+ if (signal?.aborted)
47
+ abort();
48
+ else
49
+ signal?.addEventListener("abort", abort, { once: true });
50
+ });
51
+ /** At most `maximum` holders at once; the rest wait their turn, first come
52
+ * first served. */
53
+ function slots(maximum) {
54
+ let active = 0;
55
+ const waiting = [];
56
+ return {
57
+ async take() {
58
+ if (active < maximum) {
59
+ active++;
60
+ return;
61
+ }
62
+ await new Promise((resolve) => waiting.push(resolve));
63
+ },
64
+ give() {
65
+ const next = waiting.shift();
66
+ if (next)
67
+ next();
68
+ else
69
+ active--;
70
+ },
71
+ };
72
+ }
73
+ /** One connection pool, one retry policy, one error shape for every product.
74
+ * Node's and Bun's fetch keep connections alive, so a client reused across
75
+ * calls pays TLS once. Over HTTP/1.1 every call in flight holds its own
76
+ * connection, so a hundred calls at once would open a hundred, past what one
77
+ * address may hold at the edge (64). Answers read whole are therefore held to
78
+ * `maxConnections` at once and the rest queue here, reusing those
79
+ * connections; streams and terminals are long-lived and do not wait behind
80
+ * them. */
81
+ export class Transport {
82
+ baseUrl;
83
+ #apiKey;
84
+ #findKey;
85
+ #fetch;
86
+ #timeoutMs;
87
+ #maxRetries;
88
+ #client;
89
+ #slots;
90
+ constructor(options) {
91
+ if (typeof options.apiKey === "function")
92
+ this.#findKey = options.apiKey;
93
+ else if (!options.apiKey || /\s/.test(options.apiKey))
94
+ throw missingKey();
95
+ else
96
+ this.#apiKey = options.apiKey;
97
+ this.baseUrl = apiOrigin(options.baseUrl ?? DEFAULT_BASE_URL);
98
+ this.#fetch = options.fetch ?? fetch;
99
+ this.#timeoutMs = options.timeoutMs ?? 300_000;
100
+ this.#maxRetries = options.maxRetries ?? 4;
101
+ this.#client = options.client ?? `sdk-js/${VERSION}`;
102
+ this.#slots = slots(Math.max(1, options.maxConnections ?? 32));
103
+ }
104
+ /** The key, found once on first use when it was not given. */
105
+ async #key() {
106
+ if (this.#apiKey === undefined) {
107
+ const found = await this.#findKey();
108
+ if (!found || /\s/.test(found))
109
+ throw missingKey();
110
+ this.#apiKey = found;
111
+ }
112
+ return this.#apiKey;
113
+ }
114
+ #headers(call, key, apiKey) {
115
+ return {
116
+ authorization: `Bearer ${apiKey}`,
117
+ accept: call.accept ?? "application/json",
118
+ "x-runtime-client": this.#client,
119
+ ...(call.bytes
120
+ ? { "content-type": "application/octet-stream" }
121
+ : call.body === undefined
122
+ ? {}
123
+ : { "content-type": "application/json" }),
124
+ ...(key ? { "idempotency-key": key } : {}),
125
+ ...(call.wait ? { prefer: `wait=${Math.min(120, Math.ceil(call.wait))}` } : {}),
126
+ };
127
+ }
128
+ /** Sends a call and returns the raw Response once it succeeded. Retries
129
+ * transport failures, 429, 502, 503 and 504 with backoff and jitter, with
130
+ * the same key every time. */
131
+ async send(call) {
132
+ const apiKey = await this.#key();
133
+ const write = call.method !== "GET";
134
+ const key = write ? (call.idempotencyKey ?? crypto.randomUUID()) : undefined;
135
+ const deadline = AbortSignal.timeout(call.timeoutMs ?? this.#timeoutMs);
136
+ const signal = call.signal ? AbortSignal.any([call.signal, deadline]) : deadline;
137
+ const url = `${this.baseUrl}${call.path}${encodeQuery(call.query)}`;
138
+ const body = call.bytes ?? (call.body === undefined ? undefined : JSON.stringify(call.body));
139
+ const retry = call.retry ?? true;
140
+ for (let attempt = 0;; attempt++) {
141
+ let response;
142
+ try {
143
+ response = await this.#fetch(url, {
144
+ method: call.method,
145
+ headers: this.#headers(call, key, apiKey),
146
+ redirect: "error",
147
+ signal,
148
+ ...(body === undefined ? {} : { body: body }),
149
+ });
150
+ }
151
+ catch (cause) {
152
+ if (signal.aborted || !retry || attempt >= this.#maxRetries)
153
+ throw new ConnectionError({
154
+ message: signal.aborted
155
+ ? "The call was cancelled or ran past its deadline."
156
+ : write
157
+ ? `No answer from Runtime at ${this.baseUrl}. The change may have happened; retrying with the same idempotencyKey is safe.`
158
+ : `No answer from Runtime at ${this.baseUrl}.`,
159
+ hint: signal.aborted
160
+ ? "Raise timeoutMs for a long call, or retry with the same idempotencyKey."
161
+ : "Check the network, and RUNTIME_API_URL if you set it.",
162
+ code: signal.aborted ? "timeout" : "connection_error",
163
+ status: 0,
164
+ ...(key ? { idempotencyKey: key } : {}),
165
+ cause,
166
+ });
167
+ await sleep(backoff(attempt), signal);
168
+ continue;
169
+ }
170
+ if (response.ok)
171
+ return response;
172
+ const text = await response.text();
173
+ let parsed = null;
174
+ try {
175
+ parsed = JSON.parse(text);
176
+ }
177
+ catch {
178
+ parsed = { error: { message: text.slice(0, 500) || `HTTP ${response.status}` } };
179
+ }
180
+ const error = errorFor(response.status, parsed, key);
181
+ const retryable = [429, 502, 503, 504].includes(response.status) && !DELIBERATE.has(error.code);
182
+ if (!retry || !retryable || attempt >= this.#maxRetries)
183
+ throw error;
184
+ const header = Number(response.headers.get("retry-after"));
185
+ const wait = error.retryAfterMs ??
186
+ (Number.isFinite(header) && header > 0 ? header * 1000 : backoff(attempt));
187
+ await sleep(Math.min(30_000, wait) * (0.9 + Math.random() * 0.2), signal);
188
+ }
189
+ }
190
+ async json(call) {
191
+ await this.#slots.take();
192
+ try {
193
+ const text = await (await this.send(call)).text();
194
+ return (text ? JSON.parse(text) : null);
195
+ }
196
+ finally {
197
+ this.#slots.give();
198
+ }
199
+ }
200
+ async bytes(call) {
201
+ await this.#slots.take();
202
+ try {
203
+ return new Uint8Array(await (await this.send(call)).arrayBuffer());
204
+ }
205
+ finally {
206
+ this.#slots.give();
207
+ }
208
+ }
209
+ /** Newline-delimited JSON events, as they arrive. */
210
+ async *events(call) {
211
+ const response = await this.send({ ...call, accept: "application/x-ndjson" });
212
+ if (!response.body)
213
+ return;
214
+ const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
215
+ let buffer = "";
216
+ try {
217
+ for (;;) {
218
+ const { value, done } = await reader.read();
219
+ if (done)
220
+ break;
221
+ buffer += value;
222
+ let newline = buffer.indexOf("\n");
223
+ while (newline >= 0) {
224
+ const line = buffer.slice(0, newline).trim();
225
+ buffer = buffer.slice(newline + 1);
226
+ if (line)
227
+ yield JSON.parse(line);
228
+ newline = buffer.indexOf("\n");
229
+ }
230
+ }
231
+ if (buffer.trim())
232
+ yield JSON.parse(buffer);
233
+ }
234
+ finally {
235
+ reader.releaseLock();
236
+ }
237
+ }
238
+ /** A WebSocket to an API path, authenticated with the same key. */
239
+ async websocket(path, query) {
240
+ const apiKey = await this.#key();
241
+ const url = `${this.baseUrl.replace(/^http/, "ws")}${path}${encodeQuery(query)}`;
242
+ const Socket = globalThis.WebSocket;
243
+ if (!Socket)
244
+ throw new Error("This runtime has no WebSocket; use Node 22+ or Bun.");
245
+ return new Socket(url, {
246
+ headers: { authorization: `Bearer ${apiKey}`, "x-runtime-client": this.#client },
247
+ });
248
+ }
249
+ }
250
+ /** No key given, none in RUNTIME_API_KEY, and none saved by `runtime login`. */
251
+ export function missingKey() {
252
+ return new RuntimeError({
253
+ message: "No Runtime key found: RUNTIME_API_KEY is not set and this machine is not connected.",
254
+ code: "missing_api_key",
255
+ status: 0,
256
+ hint: "Run `npx -y withruntime login` (a browser approval; nothing to copy), set RUNTIME_API_KEY to a key from https://withruntime.com/account/keys, or pass { apiKey }.",
257
+ });
258
+ }
259
+ function backoff(attempt) {
260
+ return Math.min(8000, 250 * 2 ** attempt) * (0.5 + Math.random());
261
+ }
@@ -0,0 +1,175 @@
1
+ /** A sandbox as the API answers it. Times are ISO 8601 strings, money is
2
+ * integer microdollars (1,000,000 = $1). */
3
+ export type SandboxInfo = {
4
+ id: string;
5
+ kind: "sandbox";
6
+ name: string | null;
7
+ labels: Record<string, string>;
8
+ status: "pending" | "active" | "paused" | "stopping" | "stopped";
9
+ state: "starting" | "running" | "pausing" | "paused" | "resuming" | "stopping" | "stopped";
10
+ region: string;
11
+ funding: "trial" | "paid";
12
+ vcpu: number;
13
+ memoryMiB: number;
14
+ diskMiB: number;
15
+ cpu: "shared" | "reserved";
16
+ cpuFloorMillis: number;
17
+ pausable: boolean;
18
+ timeoutSeconds: number;
19
+ onLeaseEnd: "pause" | "stop";
20
+ createdAt: string;
21
+ readyAt: string | null;
22
+ expiresAt: string;
23
+ endedAt: string | null;
24
+ stopReason: string | null;
25
+ pausedAt: string | null;
26
+ pausedExpiresAt: string | null;
27
+ chargedMicros: number;
28
+ heldMicros: number;
29
+ simulated?: boolean;
30
+ replayed?: boolean;
31
+ [key: string]: unknown;
32
+ };
33
+ export type CreateSandbox = {
34
+ name?: string;
35
+ labels?: Record<string, string>;
36
+ /** Omit to use the free trial while it lasts, then prepaid credit. */
37
+ funding?: "trial" | "paid";
38
+ region?: string;
39
+ vcpu?: number;
40
+ memoryMiB?: number;
41
+ diskMiB?: number;
42
+ cpu?: "shared" | "reserved";
43
+ cpuFloorMillis?: number;
44
+ /** How long it may run before its lease ends. Default 1800. */
45
+ timeoutSeconds?: number;
46
+ pausable?: boolean;
47
+ /** What happens when timeoutSeconds runs out: "pause" (default) or "stop". */
48
+ onLeaseEnd?: "pause" | "stop";
49
+ maxCostMicros?: number;
50
+ /** Network rules from the first start; the same shape as sandbox.network.set.
51
+ * Omit for the public web on ports 80 and 443. */
52
+ network?: {
53
+ internet: boolean;
54
+ allow?: string[];
55
+ deny?: string[];
56
+ connect?: string[];
57
+ };
58
+ /** A ready image's id (runtime.images.build); the sandbox starts from it. */
59
+ image?: string;
60
+ /** A ready snapshot's id: the sandbox starts as a copy of it, with its shape. Not with image. */
61
+ snapshot?: string;
62
+ /** Up to four volumes: read-write ("rw", one sandbox at a time) or a
63
+ * read-only "snapshot" copy. */
64
+ volumes?: {
65
+ volumeId: string;
66
+ path: string;
67
+ mode?: "rw" | "snapshot";
68
+ }[];
69
+ /** Anything a newer server accepts (image, snapshotId, volumes, network...). */
70
+ [field: string]: unknown;
71
+ };
72
+ export type CommandResult = {
73
+ exitCode: number | null;
74
+ stdout: string;
75
+ stderr: string;
76
+ timedOut: boolean;
77
+ stdoutTruncated?: boolean;
78
+ stderrTruncated?: boolean;
79
+ durationMs?: number | null;
80
+ processId?: string;
81
+ replayed?: boolean;
82
+ };
83
+ export type ExecOptions = {
84
+ cwd?: string;
85
+ /** Merged over the sandbox's environment. Put secrets here, never in the command. */
86
+ env?: Record<string, string>;
87
+ /** Given to standard input, then closed. */
88
+ stdin?: string | Uint8Array;
89
+ /** Default 60 000; up to 24 hours. A timeout is a result (timedOut), not an error. */
90
+ timeoutMs?: number;
91
+ onStdout?: (text: string) => void;
92
+ onStderr?: (text: string) => void;
93
+ /** Throw CommandError when the exit code is not 0. */
94
+ check?: boolean;
95
+ signal?: AbortSignal;
96
+ idempotencyKey?: string;
97
+ };
98
+ export type ProcessInfo = {
99
+ id: string;
100
+ kind: "process";
101
+ state: "running" | "exited" | "killed" | "timed_out" | "unknown";
102
+ exitCode: number | null;
103
+ command: string;
104
+ cwd: string;
105
+ pty: boolean;
106
+ stdinOpen: boolean;
107
+ stdinOffset: number;
108
+ startedAt: string;
109
+ endedAt: string | null;
110
+ timeoutMs: number | null;
111
+ outputBytes: number;
112
+ firstOffset: number;
113
+ };
114
+ export type OutputEvent = {
115
+ type: "start";
116
+ processId: string;
117
+ replayed?: boolean;
118
+ } | {
119
+ type: "stdout" | "stderr";
120
+ data: string;
121
+ offset: number;
122
+ } | {
123
+ type: "exit";
124
+ exitCode: number | null;
125
+ state: string;
126
+ timedOut: boolean;
127
+ durationMs?: number;
128
+ } | {
129
+ type: "truncated";
130
+ droppedBytes: number;
131
+ resumeAt: number;
132
+ } | {
133
+ type: "continue";
134
+ processId: string;
135
+ cursor: number;
136
+ } | {
137
+ type: "error";
138
+ error: {
139
+ code: string;
140
+ message: string;
141
+ requestId?: string;
142
+ };
143
+ };
144
+ export type FileEntry = {
145
+ name: string;
146
+ path: string;
147
+ type: "file" | "directory" | "symlink" | "other";
148
+ size: number;
149
+ mode: string;
150
+ modifiedAt: string;
151
+ };
152
+ /** GET /v1/usage. Money is integer microdollars in strings (1,000,000 = $1),
153
+ * exact past 2^53: available = credited - spent - expired - held. */
154
+ export type Usage = {
155
+ orgId: string;
156
+ unit: "microdollars";
157
+ credited: string;
158
+ spent: string;
159
+ held: string;
160
+ /** Credit that expired, or grant credit taken back. */
161
+ expired: string;
162
+ /** What can still be spent. */
163
+ available: string;
164
+ /** The part of spent that refunds and disputes took. */
165
+ takenBack: string;
166
+ trial: {
167
+ totalMs: number;
168
+ usedMs: number;
169
+ reservedMs: number;
170
+ availableMs: number;
171
+ } | null;
172
+ resources: Array<Record<string, unknown>>;
173
+ [key: string]: unknown;
174
+ };
175
+ export type FeedbackKind = "bug" | "missing_feature" | "competitor_gap" | "migration_blocker" | "docs" | "pricing" | "praise" | "other";
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};