withruntime 0.12.0 → 0.16.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 (94) hide show
  1. package/BLAXEL.md +97 -32
  2. package/COMPATIBILITY.md +120 -5
  3. package/DAYTONA.md +34 -3
  4. package/E2B.md +34 -5
  5. package/README.md +36 -3
  6. package/README.src.md +35 -2
  7. package/VERCEL.md +52 -5
  8. package/compatibility-lock.json +71 -111
  9. package/dist/billing-cli.js +8 -4
  10. package/dist/blaxel/client.d.ts +11 -0
  11. package/dist/blaxel/client.js +28 -0
  12. package/dist/blaxel/errors.d.ts +65 -0
  13. package/dist/blaxel/errors.js +114 -0
  14. package/dist/blaxel/index.d.ts +10 -3
  15. package/dist/blaxel/index.js +23 -4
  16. package/dist/blaxel/preview.d.ts +29 -4
  17. package/dist/blaxel/preview.js +79 -5
  18. package/dist/blaxel/sandbox.d.ts +1 -1
  19. package/dist/blaxel/sandbox.js +2 -1
  20. package/dist/blaxel/session.js +4 -2
  21. package/dist/cli-extras.js +2 -1
  22. package/dist/cli-words.js +1 -0
  23. package/dist/cli.d.ts +17 -2
  24. package/dist/cli.js +291 -85
  25. package/dist/client.d.ts +1 -1
  26. package/dist/credentials.d.ts +3 -0
  27. package/dist/credentials.js +15 -0
  28. package/dist/daytona/client.d.ts +4 -0
  29. package/dist/daytona/client.js +8 -3
  30. package/dist/daytona/daytona.d.ts +4 -1
  31. package/dist/daytona/daytona.js +3 -2
  32. package/dist/e2b/sandbox.d.ts +7 -6
  33. package/dist/e2b/sandbox.js +9 -12
  34. package/dist/errors.d.ts +1 -1
  35. package/dist/errors.js +1 -1
  36. package/dist/freestyle/index.d.ts +77 -5
  37. package/dist/freestyle/index.js +380 -48
  38. package/dist/identity.js +36 -18
  39. package/dist/index.d.ts +4 -3
  40. package/dist/machine.d.ts +39 -13
  41. package/dist/machine.js +72 -25
  42. package/dist/mcp.js +157 -20
  43. package/dist/modal/index.d.ts +15 -0
  44. package/dist/modal/index.js +35 -7
  45. package/dist/morph/index.d.ts +255 -0
  46. package/dist/morph/index.js +641 -0
  47. package/dist/network-cli.js +2 -2
  48. package/dist/product-names.d.ts +2 -2
  49. package/dist/product-names.js +2 -2
  50. package/dist/products/api-keys.d.ts +73 -0
  51. package/dist/products/api-keys.js +23 -0
  52. package/dist/products/apps.d.ts +12 -31
  53. package/dist/products/apps.js +8 -113
  54. package/dist/products/balancers.d.ts +87 -0
  55. package/dist/products/balancers.js +8 -0
  56. package/dist/products/builds.d.ts +30 -0
  57. package/dist/products/builds.js +6 -0
  58. package/dist/products/index.d.ts +2 -0
  59. package/dist/products/index.js +2 -0
  60. package/dist/products/jobs.d.ts +2 -2
  61. package/dist/products/jobs.js +1 -1
  62. package/dist/products/limits.d.ts +16 -2
  63. package/dist/products/limits.js +2 -2
  64. package/dist/products/mcp.d.ts +5 -0
  65. package/dist/products/network-products.d.ts +21 -3
  66. package/dist/products/network-products.js +3 -1
  67. package/dist/products/observability.d.ts +7 -2
  68. package/dist/products/previews.d.ts +6 -3
  69. package/dist/products/previews.js +1 -1
  70. package/dist/products/resources.d.ts +54 -0
  71. package/dist/products/resources.js +122 -0
  72. package/dist/products/secrets.d.ts +22 -0
  73. package/dist/products/secrets.js +8 -0
  74. package/dist/products/volumes.d.ts +3 -0
  75. package/dist/sandbox.d.ts +4 -0
  76. package/dist/sandbox.js +27 -2
  77. package/dist/snapshots.d.ts +4 -0
  78. package/dist/ssh.d.ts +35 -0
  79. package/dist/ssh.js +93 -1
  80. package/dist/transport.d.ts +14 -8
  81. package/dist/transport.js +70 -15
  82. package/dist/types.d.ts +33 -5
  83. package/dist/vercel/command.d.ts +48 -1
  84. package/dist/vercel/command.js +237 -6
  85. package/dist/vercel/sandbox-user.js +2 -1
  86. package/dist/vercel/sandbox.d.ts +17 -3
  87. package/dist/vercel/sandbox.js +46 -3
  88. package/dist/wallet.d.ts +4 -1
  89. package/dist/wallet.js +2 -1
  90. package/package.json +7 -14
  91. package/dist/cli/logs.test.d.ts +0 -1
  92. package/dist/cli/logs.test.js +0 -272
  93. package/dist/products/logs.test.d.ts +0 -1
  94. package/dist/products/logs.test.js +0 -343
@@ -1,5 +1,5 @@
1
1
  import { RuntimeError } from "./errors.js";
2
- export declare const VERSION = "0.12.0";
2
+ export declare const VERSION = "0.16.0";
3
3
  export declare const DEFAULT_BASE_URL = "https://api.withruntime.com";
4
4
  /** Runtime's API as code inside a Runtime sandbox reaches it: the sandbox's
5
5
  * own host sends each request on to DEFAULT_BASE_URL over HTTPS. The API runs
@@ -7,6 +7,9 @@ export declare const DEFAULT_BASE_URL = "https://api.withruntime.com";
7
7
  * because the hop never leaves the machine: it goes from the program to the
8
8
  * guest's own proxy and over the sandbox's private channel to its host. */
9
9
  export declare const SANDBOX_BASE_URL = "http://runtime.internal";
10
+ /** How a Runtime secret's placeholder starts, the value a sandbox sees in its
11
+ * place (packages/cloud-guide/docs/security.md). */
12
+ export declare const PLACEHOLDER = "rtsec_";
10
13
  /** Every Runtime sandbox has this file; guest-net.py keeps it current. Tests
11
14
  * move it, so the machine running them does not decide what they see. */
12
15
  export declare const sandboxMarker: {
@@ -51,14 +54,17 @@ export type Call = RequestOptions & {
51
54
  /** How long to keep retrying, with the same key and input, a refusal that
52
55
  * clears when a sandbox stops or a host frees room (WAITS_FOR_ROOM): a
53
56
  * full slots without credit, a full quota, a full region. Then the refusal is thrown as
54
- * it came. Only a create sets it; 0 or absent fails at once. */
57
+ * it came. A create, wake or restart sets it; 0 or absent fails at once. */
55
58
  waitForCapacityMs?: number;
56
59
  /** Called each time a call waits for room, with the refusal it is waiting
57
60
  * out and how long it will sleep before sending the call again. */
58
61
  onCapacityWait?: (refusal: RuntimeError, waitMs: number) => void;
59
- /** Internal: when the call's own deadline ends (performance.now()), set
60
- * once the call is prepared; absent when it has none. */
61
- deadlineAt?: number;
62
+ /** Internal: when the call's own deadline ends now (performance.now()),
63
+ * set once the call is prepared; absent when it has none. */
64
+ deadlineAt?: () => number;
65
+ /** Internal: moves the call's deadline on by a wait for room, so a wait
66
+ * for capacity never spends the caller's own `timeoutMs`. */
67
+ extendDeadline?: (ms: number) => void;
62
68
  };
63
69
  /** How long one attempt waits for its answer to start. The API starts every
64
70
  * answer within 120 s: Caddy answers 503 itself past that, and a longer call
@@ -66,8 +72,8 @@ export type Call = RequestOptions & {
66
72
  * with no answer by then is talking to a server that is gone, such as a
67
73
  * controller that died with the call in flight while its address moved to
68
74
  * the new leader: a client that only waits sends nothing the new leader could
69
- * refuse, so it waited out the whole deadline and failed (the leader drill on
70
- * vin-5, 4 October 2026). It is sent again under the same key. */
75
+ * refuse, so it waited out the whole deadline and failed (a controller
76
+ * failover drill, 4 October 2026). It is sent again under the same key. */
71
77
  export declare const ANSWER_START_MS = 125000;
72
78
  export declare function apiOrigin(value: string): string;
73
79
  export declare function encodeQuery(query: Query | undefined): string;
@@ -87,7 +93,7 @@ export declare const DEFAULT_MAX_CONNECTIONS = 48;
87
93
  /** AbortSignal.timeout in whole milliseconds, rounded up, never under 1.
88
94
  * Node throws ERR_OUT_OF_RANGE on a fraction (Bun accepts one), so a
89
95
  * customer's timeoutMs of 1500.5 would be a RangeError instead of a request;
90
- * the live promise probe broke this way on 9 October 2026 (d3cd4bfbd). Every
96
+ * the live promise probe broke this way on 9 October 2026. Every
91
97
  * deadline the SDK makes from a customer's or a computed value goes through
92
98
  * this. */
93
99
  export declare function timeoutSignal(ms: number): AbortSignal;
package/dist/transport.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ConnectionError, DELIBERATE, errorFor, NOTHING_RAN, RuntimeError, WAITS_FOR_ROOM, } from "./errors.js";
2
2
  import { describeRoute, envFetch, openWebSocket } from "./proxy.js";
3
3
  import { warningText } from "./product-renames.js";
4
- export const VERSION = "0.12.0";
4
+ export const VERSION = "0.16.0";
5
5
  export const DEFAULT_BASE_URL = "https://api.withruntime.com";
6
6
  /** Runtime's API as code inside a Runtime sandbox reaches it: the sandbox's
7
7
  * own host sends each request on to DEFAULT_BASE_URL over HTTPS. The API runs
@@ -9,6 +9,9 @@ export const DEFAULT_BASE_URL = "https://api.withruntime.com";
9
9
  * because the hop never leaves the machine: it goes from the program to the
10
10
  * guest's own proxy and over the sandbox's private channel to its host. */
11
11
  export const SANDBOX_BASE_URL = "http://runtime.internal";
12
+ /** How a Runtime secret's placeholder starts, the value a sandbox sees in its
13
+ * place (packages/cloud-guide/docs/security.md). */
14
+ export const PLACEHOLDER = "rtsec_";
12
15
  /** Every Runtime sandbox has this file; guest-net.py keeps it current. Tests
13
16
  * move it, so the machine running them does not decide what they see. */
14
17
  export const sandboxMarker = { path: "/run/runtime/environment.json" };
@@ -45,8 +48,8 @@ const NOTHING_RAN_MS = 300_000;
45
48
  * with no answer by then is talking to a server that is gone, such as a
46
49
  * controller that died with the call in flight while its address moved to
47
50
  * the new leader: a client that only waits sends nothing the new leader could
48
- * refuse, so it waited out the whole deadline and failed (the leader drill on
49
- * vin-5, 4 October 2026). It is sent again under the same key. */
51
+ * refuse, so it waited out the whole deadline and failed (a controller
52
+ * failover drill, 4 October 2026). It is sent again under the same key. */
50
53
  export const ANSWER_START_MS = 125_000;
51
54
  export function apiOrigin(value) {
52
55
  const url = new URL(value);
@@ -176,12 +179,36 @@ const KEPT_FOR_CALLS = 8;
176
179
  /** AbortSignal.timeout in whole milliseconds, rounded up, never under 1.
177
180
  * Node throws ERR_OUT_OF_RANGE on a fraction (Bun accepts one), so a
178
181
  * customer's timeoutMs of 1500.5 would be a RangeError instead of a request;
179
- * the live promise probe broke this way on 9 October 2026 (d3cd4bfbd). Every
182
+ * the live promise probe broke this way on 9 October 2026. Every
180
183
  * deadline the SDK makes from a customer's or a computed value goes through
181
184
  * this. */
182
185
  export function timeoutSignal(ms) {
183
186
  return AbortSignal.timeout(Math.max(1, Math.ceil(ms)));
184
187
  }
188
+ /** A deadline `ms` from now that `extend` moves later; it aborts with a
189
+ * TimeoutError, as AbortSignal.timeout does. */
190
+ function movableDeadline(ms) {
191
+ const controller = new AbortController();
192
+ let at = performance.now() + ms;
193
+ let timer;
194
+ const arm = () => {
195
+ timer = setTimeout(() => controller.abort(new DOMException("The operation timed out.", "TimeoutError")), Math.max(1, Math.ceil(at - performance.now())));
196
+ // Never keeps a process alive by itself, as AbortSignal.timeout does not.
197
+ timer.unref?.();
198
+ };
199
+ arm();
200
+ return {
201
+ signal: controller.signal,
202
+ at: () => at,
203
+ extend: (by) => {
204
+ if (controller.signal.aborted || by <= 0)
205
+ return;
206
+ at += by;
207
+ clearTimeout(timer);
208
+ arm();
209
+ },
210
+ };
211
+ }
185
212
  export class Transport {
186
213
  baseUrl;
187
214
  #apiKey;
@@ -204,7 +231,13 @@ export class Transport {
204
231
  throw missingKey();
205
232
  else
206
233
  this.#apiKey = options.apiKey;
207
- this.baseUrl = apiOrigin(options.baseUrl ? reachable(options.baseUrl) : defaultBaseUrl());
234
+ // A secret's placeholder becomes the key only on its way out through the
235
+ // sandbox's proxy; runtime.internal goes to the host without it, so the
236
+ // API sees the placeholder and answers 401 (land-lander, 11 October 2026:
237
+ // every worker's `check --remote`). Such a key's calls take the public API.
238
+ const placeholder = this.#apiKey?.startsWith(PLACEHOLDER) === true;
239
+ const origin = options.baseUrl || processEnv().RUNTIME_API_URL || DEFAULT_BASE_URL;
240
+ this.baseUrl = apiOrigin(placeholder ? origin : reachable(origin));
208
241
  // The environment's proxy (HTTPS_PROXY, NO_PROXY) on Node and Bun alike;
209
242
  // a fetch passed in is used as it is.
210
243
  this.#fetch = options.fetch ?? envFetch;
@@ -254,22 +287,29 @@ export class Transport {
254
287
  * signal through every retry and the complete response body. */
255
288
  #prepare(call) {
256
289
  const timeoutMs = call.timeoutMs ?? this.#timeoutMs;
257
- const roomMs = Math.max(0, call.waitForCapacityMs ?? 0);
258
- const deadline = timeoutMs === 0 ? undefined : timeoutSignal(timeoutMs + roomMs);
290
+ // The deadline is the caller's timeoutMs plus only the time actually
291
+ // spent waiting for room (WAITS_FOR_ROOM), which moves it on as it waits:
292
+ // a wake given timeoutMs 20 on a server that never answers still ends at
293
+ // 20 ms, not two minutes later (lifecycle-deadlines.test.ts).
294
+ const deadline = timeoutMs === 0 ? undefined : movableDeadline(timeoutMs);
259
295
  const signal = deadline
260
296
  ? call.signal
261
- ? AbortSignal.any([call.signal, deadline])
262
- : deadline
297
+ ? AbortSignal.any([call.signal, deadline.signal])
298
+ : deadline.signal
263
299
  : call.signal;
264
- return {
300
+ const prepared = {
265
301
  ...call,
266
302
  timeoutMs: 0,
267
- ...(timeoutMs === 0 ? {} : { deadlineAt: performance.now() + timeoutMs + roomMs }),
268
303
  ...(signal ? { signal } : {}),
269
304
  ...(call.method !== "GET"
270
305
  ? { idempotencyKey: call.idempotencyKey ?? crypto.randomUUID() }
271
306
  : {}),
272
307
  };
308
+ if (deadline) {
309
+ prepared.deadlineAt = deadline.at;
310
+ prepared.extendDeadline = deadline.extend;
311
+ }
312
+ return prepared;
273
313
  }
274
314
  #connectionError(call, cause) {
275
315
  const cancelled = call.signal?.aborted === true;
@@ -301,6 +341,18 @@ export class Transport {
301
341
  await sleep(ms, signal);
302
342
  await gate?.take();
303
343
  }
344
+ /** Wait before sending a refused call again. When the call's deadline
345
+ * ends the wait, the refusal stands: on a loaded machine bun can fire
346
+ * AbortSignal.timeout before a shorter setTimeout, and the call then
347
+ * failed as a timeout although the transport had judged the wait to fit
348
+ * (11 October 2026). A caller's own cancel still ends it as a cancel. */
349
+ async #retryAfter(refusal, ms, signal, gate) {
350
+ await this.#rest(ms, signal, gate).catch((cause) => {
351
+ throw signal?.reason?.name === "TimeoutError"
352
+ ? refusal
353
+ : cause;
354
+ });
355
+ }
304
356
  async #send(call, consume, gate) {
305
357
  let apiKey;
306
358
  try {
@@ -322,7 +374,9 @@ export class Transport {
322
374
  const retry = call.retry ?? true;
323
375
  let attempt = 0;
324
376
  // An answer that ran nothing is sent again until the call's deadline.
325
- const idleUntil = call.deadlineAt ?? performance.now() + NOTHING_RAN_MS;
377
+ // Read at each use: a wait for room moves the call's deadline on.
378
+ const idleFallback = performance.now() + NOTHING_RAN_MS;
379
+ const idleUntil = () => call.deadlineAt?.() ?? idleFallback;
326
380
  let idleAttempt = 0;
327
381
  // The last try never left, and was sent again at once.
328
382
  let resentAtOnce = false;
@@ -413,7 +467,8 @@ export class Transport {
413
467
  inCallback = true;
414
468
  call.onCapacityWait?.(error, pause);
415
469
  inCallback = false;
416
- await this.#rest(pause, signal, gate);
470
+ call.extendDeadline?.(pause);
471
+ await this.#retryAfter(error, pause, signal, gate);
417
472
  continue;
418
473
  }
419
474
  // Nothing ran: safe to send again for any call, past maxRetries,
@@ -427,9 +482,9 @@ export class Transport {
427
482
  const header = Number(response.headers.get("retry-after"));
428
483
  const base = error.retryAfterMs ?? (Number.isFinite(header) && header > 0 ? header * 1000 : 250);
429
484
  const pause = Math.min(30_000, base * 2 ** Math.min(idleAttempt++, 3)) * (0.9 + Math.random() * 0.2);
430
- if (pause >= idleUntil - performance.now())
485
+ if (pause >= idleUntil() - performance.now())
431
486
  throw error;
432
- await this.#rest(pause, signal, gate);
487
+ await this.#retryAfter(error, pause, signal, gate);
433
488
  continue;
434
489
  }
435
490
  const retryable = [429, 502, 503, 504].includes(status) && !DELIBERATE.has(error.code);
package/dist/types.d.ts CHANGED
@@ -62,6 +62,11 @@ export type SandboxInfo = {
62
62
  /** The names of its own environment variables (`env`), on a create, an
63
63
  * update and a read of one sandbox. Values are never shown. */
64
64
  envNames?: string[];
65
+ /** On an update with labels by a key that may read secrets, when one of
66
+ * the account's egress secrets is scoped: true once every server running its sandboxes holds the change,
67
+ * so the next request uses the new scope; false while one has not
68
+ * confirmed (it takes it within 15 seconds). */
69
+ secretsEnforced?: boolean;
65
70
  /** On a create from an image with a start command: whether it started
66
71
  * (and was still running half a second later), became ready (readyMs),
67
72
  * timed out, or exited first (exitCode, and stderr's last lines). The create's answer
@@ -78,6 +83,11 @@ export type SandboxInfo = {
78
83
  };
79
84
  /** Present and true when getOrCreate answered a sandbox that already held the name. */
80
85
  reused?: boolean;
86
+ /** An opaque tag for the server it is on, like `pl_3f9a0c1b2d4e5f60`. Your
87
+ * sandboxes and snapshots with the same tag are on the same server. It is
88
+ * never the server's name, and other accounts see unrelated tags. Null
89
+ * before it is placed. Absent from older servers. */
90
+ placement?: string | null;
81
91
  [key: string]: unknown;
82
92
  };
83
93
  export type CreateSandbox = {
@@ -92,7 +102,7 @@ export type CreateSandbox = {
92
102
  * `env`. At most 32 variables and 32 KiB. Values are never shown again;
93
103
  * answers list only `envNames`. Copies made by fork() keep them. */
94
104
  env?: Record<string, string>;
95
- /** Omit to use the included usage while it lasts, then prepaid credit. */
105
+ /** Omit to use the free usage while it lasts, then prepaid credit. */
96
106
  funding?: "trial" | "paid";
97
107
  region?: string;
98
108
  vcpu?: number;
@@ -142,6 +152,13 @@ export type CreateSandbox = {
142
152
  image?: string;
143
153
  /** A ready snapshot's id: the sandbox starts as a copy of it, with its shape. Not with image. */
144
154
  snapshot?: string;
155
+ /** One to 16 ids of your own sandboxes or snapshots. The new sandbox starts
156
+ * on a server none of them is on, and stays apart if its start is moved.
157
+ * With no other server that has room, the create answers 409 `no_capacity`
158
+ * and nothing is made or charged. A copy of a snapshot starts only where that
159
+ * snapshot is kept, so a `snapshot` with `spreadFrom` naming its server also
160
+ * answers `no_capacity`. */
161
+ spreadFrom?: string[];
145
162
  /** Up to four volumes: read-write ("rw", one sandbox at a time) or a
146
163
  * read-only "snapshot" copy. */
147
164
  volumes?: {
@@ -295,11 +312,11 @@ export type Usage = {
295
312
  /** @deprecated Read `availableMs`. Still sent for older clients. */
296
313
  reservedMs: number;
297
314
  availableMs: number;
298
- /** `monthly`: the included usage every month only. `hours`: free hours
299
- * given before 5 October 2026, kept, beside the included usage. Absent
315
+ /** `monthly`: the free usage every month only. `hours`: free hours
316
+ * given before 5 October 2026, kept, beside the free usage. Absent
300
317
  * from an API older than 5 October 2026. */
301
318
  offer?: "monthly" | "hours";
302
- /** When the included usage renews: the 1st of next month, 00:00 UTC. */
319
+ /** When the free usage renews: the 1st of next month, 00:00 UTC. */
303
320
  renewsAt?: string;
304
321
  } | null;
305
322
  /** What the account uses free this calendar month (UTC), one row per
@@ -318,9 +335,13 @@ export type Usage = {
318
335
  /** The month's first day, YYYY-MM-DD. */
319
336
  month: string;
320
337
  renewsAt: string;
338
+ /** When the free machine time ends for an account that has never paid
339
+ * (30 days after it opened); null once it has paid, when it renews
340
+ * every month. Absent from an API older than 14 October 2026. */
341
+ freeUntil?: string | null;
321
342
  }>;
322
343
  /** What Runtime for Startups includes (https://withruntime.com/startups).
323
- * Startup hours are given once, used after this month's included usage
344
+ * Startup hours are given once, used after this month's free usage
324
345
  * and before credit, never renewed, and gone after 12 months. Storage is
325
346
  * GB kept free each month for those 12 months. Empty unless the account
326
347
  * was accepted to the program or given it; absent from an older API. */
@@ -426,6 +447,13 @@ export type SandboxSettings = {
426
447
  persistent?: boolean;
427
448
  /** Lifetime cap in microdollars; null removes it. */
428
449
  maxTotalCostMicros?: number | null;
450
+ /** A new time limit counted from now, 60 to 86400 seconds, sooner or later
451
+ * than the one it had; 0 takes the limit away. A paused sandbox counts it
452
+ * from its next wake. Not for a persistent sandbox. */
453
+ timeoutSeconds?: number;
454
+ /** What the end of its time limit does: pause, stop (keep the disk) or
455
+ * delete. */
456
+ onTimeout?: "pause" | "stop" | "delete";
429
457
  };
430
458
  /** A deleted sandbox, as `delete()` answers it the first time and every time
431
459
  * after. It reads nowhere else afterwards. */
@@ -1,3 +1,4 @@
1
+ import type { Readable } from "node:stream";
1
2
  import type { Process } from "../sandbox.js";
2
3
  /** One piece of a command's output, as Vercel's `logs()` yields it. */
3
4
  export type LogOutputLine = {
@@ -21,6 +22,8 @@ type Init = {
21
22
  sandboxName: string;
22
23
  process?: Process;
23
24
  lines?: LogOutputLine[];
25
+ /** Each line as it arrives, for an attached command's stdout/stderr. */
26
+ onLine?: (line: LogOutputLine) => void;
24
27
  };
25
28
  /** A command in a sandbox, as Vercel's `Command`. A detached one runs on:
26
29
  * `wait()` for its end, `logs()` for its output as it happens. `cmdId` is
@@ -30,6 +33,8 @@ export declare class Command {
30
33
  exitCode: number | null;
31
34
  durationMs?: number;
32
35
  protected readonly init: Init;
36
+ /** Set when the command was started with a `Readable` for stdin. */
37
+ stdinPipe: StdinPipe | null;
33
38
  constructor(init: Init);
34
39
  get cmdId(): string;
35
40
  get cwd(): string;
@@ -41,7 +46,8 @@ export declare class Command {
41
46
  }): AsyncGenerator<LogOutputLine, void, void> & Disposable & {
42
47
  close(): void;
43
48
  };
44
- /** Waits for the command to end. */
49
+ /** Waits for the command to end. With a `Readable` piped to its stdin,
50
+ * rejects if piping fails, and stops piping once the command exits. */
45
51
  wait(params?: {
46
52
  signal?: AbortSignal;
47
53
  }): Promise<CommandFinished>;
@@ -60,11 +66,52 @@ export declare class Command {
60
66
  kill(signal?: Signal, opts?: {
61
67
  abortSignal?: AbortSignal;
62
68
  }): Promise<void>;
69
+ /** Writes to the stdin of a command started with `stdin: true` and
70
+ * `detached: true`. Writes reach the process in call order and resolve once
71
+ * it has accepted the data; Runtime's input offsets make every resend exact,
72
+ * so no byte is written twice. If a write fails, part of it may have been
73
+ * written, and later writes are refused; closeStdin still works. */
74
+ writeStdin(data: string | Uint8Array, opts?: {
75
+ abortSignal?: AbortSignal;
76
+ }): Promise<void>;
77
+ /** Closes the command's stdin, so it reads EOF once it has consumed what
78
+ * was written. */
79
+ closeStdin(opts?: {
80
+ abortSignal?: AbortSignal;
81
+ }): Promise<void>;
63
82
  }
83
+ /** A `Readable` being written to a command's stdin. */
84
+ export interface StdinPipe {
85
+ /** Resolves once the stream has ended and stdin is closed, or the command
86
+ * stops accepting input; rejects if a write fails or the stream errors. */
87
+ done: Promise<void>;
88
+ /** Stops reading the stream, for when the command has exited. */
89
+ stop(): void;
90
+ }
91
+ /** Writes a stream to a command's stdin in order, closing stdin when it
92
+ * ends. What is read while a write is in flight goes in the next write, and
93
+ * reading pauses while too much waits. The stream is never ended or
94
+ * destroyed, and every listener is removed once piping stops. */
95
+ export declare function pipeStdin(command: Command, stream: Readable, signal?: AbortSignal): StdinPipe;
96
+ /** Waits for a command to finish, rejecting early if piping its stdin fails,
97
+ * and stops piping once it has exited. Aborting `signal` stops waiting but
98
+ * leaves the pipe running. */
99
+ export declare function waitWithStdinPipe<T>(finished: Promise<T>, pipe: StdinPipe | null, opts?: {
100
+ signal?: AbortSignal;
101
+ onPipeError?: () => void;
102
+ }): Promise<T>;
64
103
  /** A command that has ended; `exitCode` is set. */
65
104
  export declare class CommandFinished extends Command {
66
105
  exitCode: number;
67
106
  constructor(init: Init, exitCode: number, durationMs?: number);
68
107
  wait(): Promise<CommandFinished>;
108
+ /** Always throws: the command has already exited. */
109
+ writeStdin(_data: string | Uint8Array, _opts?: {
110
+ abortSignal?: AbortSignal;
111
+ }): Promise<never>;
112
+ /** Always throws: the command has already exited. */
113
+ closeStdin(_opts?: {
114
+ abortSignal?: AbortSignal;
115
+ }): Promise<never>;
69
116
  }
70
117
  export {};