withruntime 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/DAYTONA.md +25 -25
  2. package/E2B.md +1 -1
  3. package/LICENSE +55 -0
  4. package/README.md +3 -2
  5. package/dist/cli-extras.d.ts +24 -0
  6. package/dist/cli-extras.js +205 -0
  7. package/dist/cli.d.ts +20 -0
  8. package/dist/cli.js +430 -37
  9. package/dist/daytona/context.d.ts +5 -0
  10. package/dist/daytona/context.js +3 -0
  11. package/dist/daytona/filesystem.js +2 -2
  12. package/dist/daytona/process.js +3 -1
  13. package/dist/e2b/commands.js +6 -2
  14. package/dist/e2b/filesystem.d.ts +26 -1
  15. package/dist/e2b/filesystem.js +30 -2
  16. package/dist/e2b/index.d.ts +1 -1
  17. package/dist/e2b/index.js +1 -1
  18. package/dist/errors.js +2 -0
  19. package/dist/identity.d.ts +27 -0
  20. package/dist/identity.js +56 -0
  21. package/dist/index.d.ts +14 -0
  22. package/dist/index.js +7 -0
  23. package/dist/mcp.js +16 -3
  24. package/dist/network-cli.d.ts +15 -0
  25. package/dist/network-cli.js +323 -0
  26. package/dist/products/desktop.d.ts +41 -4
  27. package/dist/products/desktop.js +73 -3
  28. package/dist/products/index.d.ts +12 -0
  29. package/dist/products/index.js +12 -0
  30. package/dist/products/interpreter.d.ts +5 -3
  31. package/dist/products/interpreter.js +2 -1
  32. package/dist/products/mcp.d.ts +79 -0
  33. package/dist/products/mcp.js +37 -0
  34. package/dist/products/mounts.d.ts +49 -0
  35. package/dist/products/mounts.js +21 -0
  36. package/dist/products/network-products.d.ts +167 -0
  37. package/dist/products/network-products.js +82 -0
  38. package/dist/products/network.d.ts +1 -1
  39. package/dist/products/sso.d.ts +44 -0
  40. package/dist/products/sso.js +8 -0
  41. package/dist/products/switching.d.ts +7 -0
  42. package/dist/products/volumes.d.ts +68 -5
  43. package/dist/products/volumes.js +52 -4
  44. package/dist/products/watch.d.ts +91 -0
  45. package/dist/products/watch.js +135 -0
  46. package/dist/sandbox.d.ts +32 -2
  47. package/dist/sandbox.js +93 -16
  48. package/dist/snapshots.d.ts +11 -1
  49. package/dist/ssh.js +13 -1
  50. package/dist/transport.d.ts +1 -1
  51. package/dist/transport.js +4 -1
  52. package/dist/tunnel.d.ts +4 -1
  53. package/dist/tunnel.js +8 -3
  54. package/dist/wireguard.d.ts +7 -0
  55. package/dist/wireguard.js +13 -0
  56. package/package.json +7 -3
@@ -19,6 +19,11 @@ export declare const HOME = "/workspace";
19
19
  export declare const HOME_LINK = "[ -e /home/daytona ] || sudo ln -s /workspace /home/daytona";
20
20
  /** Commands without a timeout run until the sandbox ends: Runtime's longest. */
21
21
  export declare const LONGEST_MS = 86400000;
22
+ /** Given to exec so it streams and returns the whole output, as Daytona does:
23
+ * an exec with no output callback returns at most 64 KiB of each stream. */
24
+ export declare const WHOLE_OUTPUT: {
25
+ readonly onStdout: () => undefined;
26
+ };
22
27
  /** A Daytona path as a Runtime one: relative paths (and `~`) resolve from the
23
28
  * working directory, /workspace on Runtime. */
24
29
  export declare function resolvePath(path: string): string;
@@ -5,6 +5,9 @@ export const HOME = "/workspace";
5
5
  export const HOME_LINK = `[ -e ${DAYTONA_HOME} ] || sudo ln -s ${HOME} ${DAYTONA_HOME}`;
6
6
  /** Commands without a timeout run until the sandbox ends: Runtime's longest. */
7
7
  export const LONGEST_MS = 86_400_000;
8
+ /** Given to exec so it streams and returns the whole output, as Daytona does:
9
+ * an exec with no output callback returns at most 64 KiB of each stream. */
10
+ export const WHOLE_OUTPUT = { onStdout: () => undefined };
8
11
  /** A Daytona path as a Runtime one: relative paths (and `~`) resolve from the
9
12
  * working directory, /workspace on Runtime. */
10
13
  export function resolvePath(path) {
@@ -1,4 +1,4 @@
1
- import { resolvePath } from "./context.js";
1
+ import { resolvePath, WHOLE_OUTPUT } from "./context.js";
2
2
  import { DaytonaError, DaytonaFileNotFoundError, guard } from "./errors.js";
3
3
  const PERMISSIONS = ["---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"];
4
4
  function fileInfo(entry) {
@@ -112,7 +112,7 @@ export class FileSystem {
112
112
  async findFiles(path, pattern) {
113
113
  const target = await this.#path(path);
114
114
  const runtime = await this.#ctx.live();
115
- const result = await guard("sandbox", () => runtime.exec(["grep", "-rnI", "--", pattern, target]));
115
+ const result = await guard("sandbox", () => runtime.exec(["grep", "-rnI", "--", pattern, target], WHOLE_OUTPUT));
116
116
  if (result.exitCode !== 0 && result.exitCode !== 1)
117
117
  throw new DaytonaError(result.stderr.trim() || "grep failed", 400);
118
118
  return result.stdout
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { HOME, LONGEST_MS, quote, resolvePath } from "./context.js";
2
+ import { HOME, LONGEST_MS, quote, resolvePath, WHOLE_OUTPUT, } from "./context.js";
3
3
  import { DaytonaCommandAlreadyCompletedError, DaytonaConflictError, DaytonaNotFoundError, DaytonaProcessExecutionTimeoutError, DaytonaSessionEndedError, guard, NotSupportedError, } from "./errors.js";
4
4
  export class CodeRunParams {
5
5
  argv;
@@ -76,6 +76,7 @@ export class Process {
76
76
  cwd: cwd === undefined ? HOME : resolvePath(cwd),
77
77
  ...this.#env(env),
78
78
  timeoutMs,
79
+ ...WHOLE_OUTPUT,
79
80
  }));
80
81
  if (result.timedOut)
81
82
  throw new DaytonaProcessExecutionTimeoutError(`Command timed out after ${timeout} s. Pass a larger timeout, or 0 for none.`, 408);
@@ -97,6 +98,7 @@ export class Process {
97
98
  cwd: HOME,
98
99
  ...this.#env(params.env),
99
100
  timeoutMs,
101
+ ...WHOLE_OUTPUT,
100
102
  }));
101
103
  if (result.timedOut)
102
104
  throw new DaytonaProcessExecutionTimeoutError(`Code run timed out after ${timeout} s.`, 408);
@@ -68,12 +68,16 @@ export class Commands {
68
68
  const handle = await this.#start(cmd, opts, timeoutMs);
69
69
  return opts.background ? handle : handle.wait();
70
70
  }
71
+ /* Always streamed, so the result holds the whole output as E2B's does: an
72
+ exec with no output callback returns at most 64 KiB of each stream. */
73
+ const onStdout = opts.onStdout;
74
+ const onStderr = opts.onStderr;
71
75
  const result = await guard("sandbox", () => this.#ctx.runtime.exec(cmd, {
72
76
  ...(opts.cwd === undefined ? {} : { cwd: opts.cwd }),
73
77
  ...this.#env(opts.envs),
74
78
  timeoutMs,
75
- ...(opts.onStdout ? { onStdout: (text) => void opts.onStdout(text) } : {}),
76
- ...(opts.onStderr ? { onStderr: (text) => void opts.onStderr(text) } : {}),
79
+ onStdout: (text) => void onStdout?.(text),
80
+ ...(onStderr ? { onStderr: (text) => void onStderr(text) } : {}),
77
81
  ...(opts.signal ? { signal: opts.signal } : {}),
78
82
  }));
79
83
  return settle(result, timeoutMs);
@@ -21,6 +21,26 @@ export interface EntryInfo extends WriteInfo {
21
21
  modifiedTime?: Date;
22
22
  symlinkTarget?: string;
23
23
  }
24
+ export declare enum FilesystemEventType {
25
+ CHMOD = "chmod",
26
+ CREATE = "create",
27
+ REMOVE = "remove",
28
+ RENAME = "rename",
29
+ WRITE = "write"
30
+ }
31
+ export interface FilesystemEvent {
32
+ /** Relative to the watched directory. */
33
+ name: string;
34
+ type: FilesystemEventType;
35
+ }
36
+ export interface WatchOpts extends FilesystemRequestOpts {
37
+ /** How long the watch runs; 0 is Runtime's maximum, 24 hours. Default 60000. */
38
+ timeoutMs?: number;
39
+ recursive?: boolean;
40
+ }
41
+ export interface E2BWatchHandle {
42
+ stop(): Promise<void>;
43
+ }
24
44
  export type WriteEntry = {
25
45
  path: string;
26
46
  data: string | ArrayBuffer | Blob | ReadableStream;
@@ -78,5 +98,10 @@ export declare class Filesystem {
78
98
  remove(path: string, opts?: FilesystemRequestOpts): Promise<void>;
79
99
  exists(path: string, opts?: FilesystemRequestOpts): Promise<boolean>;
80
100
  getInfo(path: string, opts?: FilesystemRequestOpts): Promise<EntryInfo>;
81
- watchDir(): Promise<never>;
101
+ /** E2B's watchDir on Runtime's file watch: the same events (CREATE, WRITE,
102
+ * REMOVE, RENAME, CHMOD) with names relative to the directory, stopped
103
+ * with `handle.stop()`. `timeoutMs` 0 is Runtime's 24-hour maximum. */
104
+ watchDir(path: string, onEvent: (event: FilesystemEvent) => void | Promise<void>, opts?: WatchOpts & {
105
+ onExit?: (err?: Error) => void | Promise<void>;
106
+ }): Promise<E2BWatchHandle>;
82
107
  }
@@ -6,6 +6,14 @@ export var FileType;
6
6
  FileType["DIR"] = "dir";
7
7
  FileType["SYMLINK"] = "symlink";
8
8
  })(FileType || (FileType = {}));
9
+ export var FilesystemEventType;
10
+ (function (FilesystemEventType) {
11
+ FilesystemEventType["CHMOD"] = "chmod";
12
+ FilesystemEventType["CREATE"] = "create";
13
+ FilesystemEventType["REMOVE"] = "remove";
14
+ FilesystemEventType["RENAME"] = "rename";
15
+ FilesystemEventType["WRITE"] = "write";
16
+ })(FilesystemEventType || (FilesystemEventType = {}));
9
17
  /** Runtime's home, where E2B's relative paths land. */
10
18
  const HOME = "/workspace";
11
19
  function absolute(path) {
@@ -137,7 +145,27 @@ export class Filesystem {
137
145
  throw new FileNotFoundError(`${target} does not exist.`);
138
146
  return entryInfo(stat);
139
147
  }
140
- watchDir() {
141
- return Promise.reject(new NotSupportedError("Watching a directory (files.watchDir)", "Poll with files.list(path), or run your own watcher with commands.run(..., { background: true })."));
148
+ /** E2B's watchDir on Runtime's file watch: the same events (CREATE, WRITE,
149
+ * REMOVE, RENAME, CHMOD) with names relative to the directory, stopped
150
+ * with `handle.stop()`. `timeoutMs` 0 is Runtime's 24-hour maximum. */
151
+ async watchDir(path, onEvent, opts = {}) {
152
+ const target = await this.#path(path, opts);
153
+ const prefix = target.endsWith("/") ? target : `${target}/`;
154
+ const lifetime = opts.timeoutMs === 0 ? 86_400_000 : (opts.timeoutMs ?? 60_000);
155
+ const watch = await guard("file", () => this.#files.watch(target, (event) => onEvent({
156
+ name: event.path.startsWith(prefix) ? event.path.slice(prefix.length) : event.path,
157
+ type: event.type,
158
+ }), {
159
+ recursive: opts.recursive === true,
160
+ timeoutMs: Math.max(1_000, Math.min(86_400_000, lifetime)),
161
+ onExit: (reason) => {
162
+ if (reason === "stopped")
163
+ return;
164
+ void opts.onExit?.(reason === "timeout" || reason === "paused"
165
+ ? undefined
166
+ : new Error(`The watch ended: ${reason}`));
167
+ },
168
+ }));
169
+ return { stop: () => watch.stop() };
142
170
  }
143
171
  }
@@ -9,7 +9,7 @@ import { type ConnectionOpts } from "./client.js";
9
9
  import { Sandbox } from "./sandbox.js";
10
10
  export { Sandbox, SandboxPaginator, DEFAULT_SHAPE, DEFAULT_TIMEOUT_MS, type SandboxOpts, type SandboxConnectOpts, type SandboxApiOpts, type SandboxInfo, type SandboxListOpts, type SandboxState, type SandboxLifecycle, type SandboxOnTimeout, type SnapshotInfo, } from "./sandbox.js";
11
11
  export { Commands, CommandHandle, pidOf, type CommandStartOpts, type CommandConnectOpts, type CommandRequestOpts, type ProcessInfo, type Username, } from "./commands.js";
12
- export { Filesystem, FileType, type EntryInfo, type WriteInfo, type WriteEntry, type FilesystemRequestOpts, type FilesystemReadOpts, type FilesystemWriteOpts, type FilesystemListOpts, } from "./filesystem.js";
12
+ export { Filesystem, FileType, type EntryInfo, type WriteInfo, type WriteEntry, type FilesystemRequestOpts, type FilesystemReadOpts, type FilesystemWriteOpts, type FilesystemListOpts, FilesystemEventType, type FilesystemEvent, type WatchOpts, type E2BWatchHandle as WatchHandle, } from "./filesystem.js";
13
13
  export { SandboxError, TimeoutError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, FileNotFoundError, SandboxNotFoundError, TemplateError, RateLimitError, AuthenticationError, ServiceBusyError, CommandExitError, NotSupportedError, type CommandResult, } from "./errors.js";
14
14
  export type { ConnectionOpts, RuntimeOpts } from "./client.js";
15
15
  export { Template, TemplateBase, Volume, Secret, waitForPort, waitForURL, waitForProcess, waitForFile, waitForTimeout, } from "./unsupported.js";
package/dist/e2b/index.js CHANGED
@@ -2,7 +2,7 @@ import { clientFor } from "./client.js";
2
2
  import { Sandbox } from "./sandbox.js";
3
3
  export { Sandbox, SandboxPaginator, DEFAULT_SHAPE, DEFAULT_TIMEOUT_MS, } from "./sandbox.js";
4
4
  export { Commands, CommandHandle, pidOf, } from "./commands.js";
5
- export { Filesystem, FileType, } from "./filesystem.js";
5
+ export { Filesystem, FileType, FilesystemEventType, } from "./filesystem.js";
6
6
  export { SandboxError, TimeoutError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, FileNotFoundError, SandboxNotFoundError, TemplateError, RateLimitError, AuthenticationError, ServiceBusyError, CommandExitError, NotSupportedError, } from "./errors.js";
7
7
  export { Template, TemplateBase, Volume, Secret, waitForPort, waitForURL, waitForProcess, waitForFile, waitForTimeout, } from "./unsupported.js";
8
8
  export default Sandbox;
package/dist/errors.js CHANGED
@@ -15,6 +15,8 @@ export const WAITS_FOR_ROOM = new Set([
15
15
  "trial_capacity",
16
16
  "quota_exceeded",
17
17
  "no_capacity",
18
+ // A volume whose last sandbox is stopping: free within seconds.
19
+ "volume_releasing",
18
20
  ]);
19
21
  export class RuntimeError extends Error {
20
22
  code;
@@ -0,0 +1,27 @@
1
+ /** An identity token, as the API returns it. */
2
+ export type IdentityToken = {
3
+ /** The OIDC ID token (a JWT), signed by Runtime. */
4
+ token: string;
5
+ expiresAt: string;
6
+ /** https://withruntime.com/oidc */
7
+ issuer: string;
8
+ /** org:<org>:image:<image>:sandbox:<sandbox> */
9
+ subject: string;
10
+ audience: string;
11
+ };
12
+ /**
13
+ * Inside a Runtime sandbox: a short-lived OIDC token, signed by Runtime,
14
+ * naming this sandbox, its organization and its image, for `audience`. Trade
15
+ * it for cloud credentials without storing a key: AWS
16
+ * AssumeRoleWithWebIdentity (audience `sts.amazonaws.com`), Google Cloud
17
+ * workload identity federation, Azure federated credentials, or your own API.
18
+ * Needs no API key. Lives ten minutes unless asked (60 to 3600 seconds).
19
+ *
20
+ * import { Sandbox } from "withruntime";
21
+ * const { token } = await Sandbox.identityToken({ audience: "sts.amazonaws.com" });
22
+ */
23
+ export declare function identityToken(input: {
24
+ audience: string;
25
+ lifetimeSeconds?: number;
26
+ signal?: AbortSignal;
27
+ }): Promise<IdentityToken>;
@@ -0,0 +1,56 @@
1
+ import { RuntimeError } from "./errors.js";
2
+ async function requestCredentials() {
3
+ let url = process.env.RUNTIME_ID_TOKEN_REQUEST_URL;
4
+ let token = process.env.RUNTIME_ID_TOKEN_REQUEST_TOKEN;
5
+ if (!url || !token) {
6
+ // A process started before the guest wrote its environment still finds it.
7
+ try {
8
+ // Loaded here, so the SDK still loads where there is no file system.
9
+ const { readFile } = await import("node:fs/promises");
10
+ const env = JSON.parse(await readFile("/run/runtime/environment.json", "utf8"));
11
+ url = env.RUNTIME_ID_TOKEN_REQUEST_URL;
12
+ token = env.RUNTIME_ID_TOKEN_REQUEST_TOKEN;
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ return url && token ? { url, token } : null;
19
+ }
20
+ /**
21
+ * Inside a Runtime sandbox: a short-lived OIDC token, signed by Runtime,
22
+ * naming this sandbox, its organization and its image, for `audience`. Trade
23
+ * it for cloud credentials without storing a key: AWS
24
+ * AssumeRoleWithWebIdentity (audience `sts.amazonaws.com`), Google Cloud
25
+ * workload identity federation, Azure federated credentials, or your own API.
26
+ * Needs no API key. Lives ten minutes unless asked (60 to 3600 seconds).
27
+ *
28
+ * import { Sandbox } from "withruntime";
29
+ * const { token } = await Sandbox.identityToken({ audience: "sts.amazonaws.com" });
30
+ */
31
+ export async function identityToken(input) {
32
+ const found = await requestCredentials();
33
+ if (!found)
34
+ throw new RuntimeError({
35
+ code: "identity_unavailable",
36
+ status: 0,
37
+ message: "Identity tokens are issued only inside a Runtime sandbox (RUNTIME_ID_TOKEN_REQUEST_TOKEN is not set).",
38
+ });
39
+ const url = new URL(found.url);
40
+ url.searchParams.set("audience", input.audience);
41
+ if (input.lifetimeSeconds !== undefined)
42
+ url.searchParams.set("lifetimeSeconds", String(input.lifetimeSeconds));
43
+ const response = await fetch(url, {
44
+ headers: { authorization: `Bearer ${found.token}` },
45
+ signal: input.signal ?? AbortSignal.timeout(10_000),
46
+ });
47
+ const body = (await response.json().catch(() => ({})));
48
+ if (!response.ok || !body.token)
49
+ throw new RuntimeError({
50
+ code: body.error?.code ?? "identity_refused",
51
+ status: response.status,
52
+ message: body.error?.message ?? `Could not get an identity token (HTTP ${response.status}).`,
53
+ ...(body.error?.requestId ? { requestId: body.error.requestId } : {}),
54
+ });
55
+ return body;
56
+ }
package/dist/index.d.ts CHANGED
@@ -16,6 +16,11 @@ export { Page } from "./page.js";
16
16
  export type { KeyLimits } from "./products/limits.js";
17
17
  export type { AuditEvent, AuditPage } from "./products/audit.js";
18
18
  export type { Secret, SetSecret } from "./products/secrets.js";
19
+ export type { McpCatalogEntry, McpGateway, McpServerRequest } from "./products/mcp.js";
20
+ export type { FileEvent, FileEventType, WatchHandle, WatchNotice, WatchOptions, } from "./products/watch.js";
21
+ export type { Mount, MountBucket } from "./products/mounts.js";
22
+ export type { Volume, VolumeBackup, CreateVolume } from "./products/volumes.js";
23
+ export type { CheckedDomain, DnsRecord, Domain, EgressAddress, TcpPort, Tunnel as PrivateTunnel, TunnelPeer, TunnelPeerCreated, } from "./products/network-products.js";
19
24
  export { verifyWebhook, WebhookVerificationError, type MetricPoint, type MetricRange, type OtelExport, type RuntimeEvent, type SandboxMetrics, type Webhook, type WebhookDelivery, type WebhookEventType, } from "./products/observability.js";
20
25
  export { Snapshots, type Snapshot, type SnapshotOptions } from "./snapshots.js";
21
26
  export { Transport, VERSION, apiOrigin, type RequestOptions, type Call } from "./transport.js";
@@ -50,5 +55,14 @@ export declare class Sandbox extends SandboxClass {
50
55
  static list(filter?: Parameters<Runtime["sandboxes"]["list"]>[0], options?: {
51
56
  client?: Runtime;
52
57
  }): Promise<import("./page.js").Page<SandboxClass>>;
58
+ /** Inside a sandbox: an OIDC token naming this sandbox, for `audience`
59
+ * (AWS: "sts.amazonaws.com"). No API key needed. See `identityToken`. */
60
+ static identityToken(input: {
61
+ audience: string;
62
+ lifetimeSeconds?: number;
63
+ signal?: AbortSignal;
64
+ }): Promise<import("./identity.js").IdentityToken>;
53
65
  }
54
66
  export type { RuntimeOptions as ClientOptions };
67
+ export { identityToken, type IdentityToken } from "./identity.js";
68
+ export type { SsoConnection, SsoStatus } from "./products/sso.js";
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { defaultClient } from "./client.js";
2
2
  import { Sandbox as SandboxClass } from "./sandbox.js";
3
+ import { identityToken } from "./identity.js";
3
4
  export { Runtime, Sandboxes, FeedbackApi, SupportApi, } from "./client.js";
4
5
  export { Files, Process, Processes, Terminal } from "./sandbox.js";
5
6
  export { Tunnel, TunnelStream } from "./tunnel.js";
@@ -37,4 +38,10 @@ export class Sandbox extends SandboxClass {
37
38
  static list(filter = {}, options = {}) {
38
39
  return (options.client ?? defaultClient()).sandboxes.list(filter);
39
40
  }
41
+ /** Inside a sandbox: an OIDC token naming this sandbox, for `audience`
42
+ * (AWS: "sts.amazonaws.com"). No API key needed. See `identityToken`. */
43
+ static identityToken(input) {
44
+ return identityToken(input);
45
+ }
40
46
  }
47
+ export { identityToken } from "./identity.js";
package/dist/mcp.js CHANGED
@@ -158,7 +158,7 @@ export async function serveMcp(env = process.env, options = {}) {
158
158
  }));
159
159
  await post(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }));
160
160
  write({ jsonrpc: "2.0", method: "notifications/tools/list_changed" });
161
- return text(`Connected as ${outcome.agentName}. Runtime's tools are available now: list the tools again, then start with runtime_sandboxes_create.`);
161
+ return text(`Connected as ${outcome.agentName}. Runtime's tools are available now: list the tools again, then start with runtime_sandbox_create.`);
162
162
  }
163
163
  await new Promise((resolve) => setTimeout(resolve, 3000));
164
164
  }
@@ -208,9 +208,22 @@ export async function serveMcp(env = process.env, options = {}) {
208
208
  }
209
209
  });
210
210
  const finish = () => void Promise.allSettled([...inFlight]).then(() => resolve());
211
+ /* A signal ends the bridge at once: stop reading, give a reply already on
212
+ its way half a second to be written, and return. The client that sent
213
+ it has stopped listening; waiting on a long tool call, or on stdin it
214
+ left open, only makes it wait for us or kill us. */
215
+ const stop = () => {
216
+ process.stdin.removeAllListeners("data");
217
+ process.stdin.pause();
218
+ process.stdin.destroy();
219
+ void Promise.race([
220
+ Promise.allSettled([...inFlight]),
221
+ new Promise((settle) => setTimeout(settle, 500)),
222
+ ]).then(() => resolve());
223
+ };
211
224
  process.stdin.once("end", finish);
212
- process.once("SIGINT", finish);
213
- process.once("SIGTERM", finish);
225
+ process.once("SIGINT", stop);
226
+ process.once("SIGTERM", stop);
214
227
  });
215
228
  }
216
229
  async function openBrowser(url) {
@@ -0,0 +1,15 @@
1
+ import type { Runtime } from "./client.js";
2
+ type Out = {
3
+ json: boolean;
4
+ write: (text: string) => void;
5
+ error: (text: string) => void;
6
+ };
7
+ type Args = {
8
+ positional: string[];
9
+ flags: Map<string, string[]>;
10
+ };
11
+ export declare const NETWORK_PRODUCTS: readonly ["domain", "port", "address", "tunnel"];
12
+ export type NetworkProduct = (typeof NETWORK_PRODUCTS)[number];
13
+ export declare const NETWORK_HELP: Record<NetworkProduct, string>;
14
+ export declare function networkProductCommand(product: NetworkProduct, argv: string[], args: Args, out: Out, client: () => Promise<Runtime>): Promise<number>;
15
+ export {};