withruntime 0.6.1 → 0.7.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 (46) hide show
  1. package/E2B.md +32 -32
  2. package/README.md +3 -3
  3. package/dist/ai/index.d.ts +21 -5
  4. package/dist/ai/index.js +14 -21
  5. package/dist/ai-harness/index.js +15 -2
  6. package/dist/billing-cli.d.ts +21 -0
  7. package/dist/billing-cli.js +158 -0
  8. package/dist/cli-extras.d.ts +15 -0
  9. package/dist/cli-extras.js +60 -4
  10. package/dist/cli-name.d.ts +6 -0
  11. package/dist/cli-name.js +8 -0
  12. package/dist/cli.d.ts +17 -1
  13. package/dist/cli.js +535 -130
  14. package/dist/client.d.ts +35 -6
  15. package/dist/client.js +23 -0
  16. package/dist/credentials.js +1 -2
  17. package/dist/daytona/filesystem.d.ts +2 -0
  18. package/dist/daytona/filesystem.js +9 -7
  19. package/dist/e2b/filesystem.js +3 -2
  20. package/dist/e2b/sandbox.d.ts +9 -6
  21. package/dist/e2b/sandbox.js +31 -7
  22. package/dist/errors.d.ts +5 -0
  23. package/dist/errors.js +7 -0
  24. package/dist/index.d.ts +8 -6
  25. package/dist/index.js +1 -0
  26. package/dist/keys.js +1 -1
  27. package/dist/login.d.ts +2 -6
  28. package/dist/login.js +3 -8
  29. package/dist/network-cli.d.ts +1 -1
  30. package/dist/network-cli.js +64 -2
  31. package/dist/products/billing.d.ts +35 -0
  32. package/dist/products/billing.js +13 -0
  33. package/dist/products/index.d.ts +4 -1
  34. package/dist/products/index.js +4 -1
  35. package/dist/products/network-products.d.ts +45 -0
  36. package/dist/products/network-products.js +27 -0
  37. package/dist/products/previews.d.ts +1 -2
  38. package/dist/products/secrets.d.ts +14 -0
  39. package/dist/sandbox.d.ts +17 -3
  40. package/dist/sandbox.js +233 -62
  41. package/dist/transport.d.ts +17 -1
  42. package/dist/transport.js +124 -7
  43. package/dist/types.d.ts +0 -2
  44. package/dist/wallet.d.ts +30 -0
  45. package/dist/wallet.js +48 -0
  46. package/package.json +2 -2
package/dist/client.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { RuntimeError } from "./errors.js";
1
2
  import type { Page } from "./page.js";
2
3
  import { type ClientExtensions } from "./products/index.js";
3
4
  import { Sandbox } from "./sandbox.js";
@@ -33,6 +34,7 @@ export declare class Runtime {
33
34
  readonly snapshots: Snapshots;
34
35
  readonly feedback: FeedbackApi;
35
36
  readonly support: SupportApi;
37
+ readonly account: AccountApi;
36
38
  constructor(options?: RuntimeOptions);
37
39
  /** Who this key is: organization, agent and credential. */
38
40
  me(options?: RequestOptions): Promise<{
@@ -49,6 +51,16 @@ export declare class Runtime {
49
51
  /** Credit, holds, trial time and per-resource charges. */
50
52
  usage(options?: RequestOptions): Promise<Usage>;
51
53
  }
54
+ /** How `sandboxes.create` waits when every trial slot, the account's quota or
55
+ * the region is full. */
56
+ export type CreateOptions = RequestOptions & {
57
+ /** How long to keep retrying a refusal that clears by itself. Default: the
58
+ * client's, two minutes; 0 fails at once. */
59
+ waitForCapacityMs?: number;
60
+ /** Called before each wait, with the refusal and the milliseconds until the
61
+ * next try: to tell a person why nothing has happened yet. */
62
+ onCapacityWait?: (refusal: RuntimeError, waitMs: number) => void;
63
+ };
52
64
  export declare class Sandboxes {
53
65
  private readonly t;
54
66
  constructor(t: Transport);
@@ -59,18 +71,14 @@ export declare class Sandboxes {
59
71
  * free, up to `waitForCapacityMs` (the client's, two minutes by default). */
60
72
  create(input?: CreateSandbox & {
61
73
  wait?: boolean;
62
- }, options?: RequestOptions & {
63
- waitForCapacityMs?: number;
64
- }): Promise<Sandbox>;
74
+ }, options?: CreateOptions): Promise<Sandbox>;
65
75
  /** The sandbox named `name` in this account, ready to use: running as it
66
76
  * is, woken if it is paused, restarted if it is stopped and persistent, or
67
77
  * created with `input` when no sandbox has the name. `sandbox.info.reused`
68
78
  * says which. The other fields apply only when it is created. */
69
79
  getOrCreate(name: string, input?: Omit<CreateSandbox, "name" | "getOrCreate"> & {
70
80
  wait?: boolean;
71
- }, options?: RequestOptions & {
72
- waitForCapacityMs?: number;
73
- }): Promise<Sandbox>;
81
+ }, options?: CreateOptions): Promise<Sandbox>;
74
82
  get(id: string, options?: RequestOptions): Promise<Sandbox>;
75
83
  /** Live sandboxes, oldest first. Await for a page, or `for await` over all. */
76
84
  list(filter?: {
@@ -142,5 +150,26 @@ export declare class SupportApi {
142
150
  }): Promise<SupportReply>;
143
151
  read(conversationId: string): Promise<SupportReply>;
144
152
  }
153
+ /** What closing the account answered. */
154
+ export type ClosedAccount = {
155
+ orgId: string;
156
+ name: string;
157
+ closedAt: string;
158
+ /** False while what was running is still stopping; it is gone within minutes. */
159
+ released: boolean;
160
+ };
161
+ /** The account this key belongs to: `runtime.account`. */
162
+ export declare class AccountApi {
163
+ private readonly t;
164
+ constructor(t: Transport);
165
+ /** Close the account for good: stop and delete everything it runs and
166
+ * stores, end every key and dissolve it. `confirm` must be the account's
167
+ * name exactly (`me().orgName`). Needs a key for every product made by an
168
+ * owner. Moves no money: what is unspent of a purchase made in the last 15
169
+ * days is refunded on request; other credit is forfeited. */
170
+ close(input: {
171
+ confirm: string;
172
+ }, options?: RequestOptions): Promise<ClosedAccount>;
173
+ }
145
174
  /** The client behind the static helpers, from the environment. */
146
175
  export declare function defaultClient(): Runtime;
package/dist/client.js CHANGED
@@ -20,6 +20,7 @@ export class Runtime {
20
20
  snapshots;
21
21
  feedback;
22
22
  support;
23
+ account;
23
24
  constructor(options = {}) {
24
25
  this.transport = new Transport({
25
26
  // A key given, then RUNTIME_API_KEY, then (in Node and Bun) the key
@@ -38,6 +39,7 @@ export class Runtime {
38
39
  this.snapshots = new Snapshots(this.transport);
39
40
  this.feedback = new FeedbackApi(this.transport);
40
41
  this.support = new SupportApi(this.transport);
42
+ this.account = new AccountApi(this.transport);
41
43
  for (const [name, make] of clientFactories())
42
44
  Object.defineProperty(this, name, { value: make(this.transport), enumerable: true });
43
45
  }
@@ -165,6 +167,27 @@ export class SupportApi {
165
167
  });
166
168
  }
167
169
  }
170
+ /** The account this key belongs to: `runtime.account`. */
171
+ export class AccountApi {
172
+ t;
173
+ constructor(t) {
174
+ this.t = t;
175
+ }
176
+ /** Close the account for good: stop and delete everything it runs and
177
+ * stores, end every key and dissolve it. `confirm` must be the account's
178
+ * name exactly (`me().orgName`). Needs a key for every product made by an
179
+ * owner. Moves no money: what is unspent of a purchase made in the last 15
180
+ * days is refunded on request; other credit is forfeited. */
181
+ close(input, options = {}) {
182
+ return this.t.json({
183
+ method: "POST",
184
+ path: "/v1/account:close",
185
+ body: { confirm: input.confirm },
186
+ retry: false,
187
+ ...options,
188
+ });
189
+ }
190
+ }
168
191
  let shared;
169
192
  /** The client behind the static helpers, from the environment. */
170
193
  export function defaultClient() {
@@ -3,8 +3,7 @@ import { constants } from "node:fs";
3
3
  import { lstat, mkdir, open, rename, unlink } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { isAbsolute, join } from "node:path";
6
- import { apiOrigin } from "./index.js";
7
- import { missingKey } from "./transport.js";
6
+ import { apiOrigin, missingKey } from "./transport.js";
8
7
  /** How many other accounts' connections one machine keeps beside the one in use. */
9
8
  const SAVED_ACCOUNTS = 20;
10
9
  export function connectionOrigins(env) {
@@ -60,6 +60,8 @@ export declare class FileSystem {
60
60
  /** The file's bytes, or with a local path, the file written there. */
61
61
  downloadFile(remotePath: string, timeout?: number): Promise<Buffer>;
62
62
  downloadFile(remotePath: string, localPath: string, timeout?: number): Promise<void>;
63
+ /** The file as it arrives, without holding it in memory. It errors with
64
+ * `download_incomplete` if it ends short of the file's length. */
63
65
  downloadFileStream(remotePath: string): Promise<Readable>;
64
66
  /** Several files; a file that fails carries `error` instead of `result`. */
65
67
  downloadFiles(files: FileDownloadRequest[]): Promise<FileDownloadResponse[]>;
@@ -69,17 +69,19 @@ export class FileSystem {
69
69
  async downloadFile(remotePath, localOrTimeout) {
70
70
  const target = await this.#path(remotePath);
71
71
  const files = await this.#files();
72
- const bytes = Buffer.from(await guard("file", () => files.read(target)));
73
72
  if (typeof localOrTimeout !== "string")
74
- return bytes;
75
- const { mkdir, writeFile } = await import("node:fs/promises");
76
- const paths = await import("node:path");
77
- await mkdir(paths.dirname(localOrTimeout), { recursive: true });
78
- await writeFile(localOrTimeout, bytes);
73
+ return Buffer.from(await guard("file", () => files.read(target)));
74
+ // Streamed to disk, any size, checked against the file's length.
75
+ await guard("file", () => files.download(target, localOrTimeout));
79
76
  }
77
+ /** The file as it arrives, without holding it in memory. It errors with
78
+ * `download_incomplete` if it ends short of the file's length. */
80
79
  async downloadFileStream(remotePath) {
81
80
  const { Readable } = await import("node:stream");
82
- return Readable.from([await this.downloadFile(remotePath)]);
81
+ const target = await this.#path(remotePath);
82
+ const files = await this.#files();
83
+ const stream = await guard("file", () => files.readStream(target));
84
+ return Readable.fromWeb(stream);
83
85
  }
84
86
  /** Several files; a file that fails carries `error` instead of `result`. */
85
87
  async downloadFiles(files) {
@@ -79,14 +79,15 @@ export class Filesystem {
79
79
  }
80
80
  async read(path, opts = {}) {
81
81
  const file = await this.#path(path, opts);
82
+ // A real stream, any size, never the whole file held first.
83
+ if (opts.format === "stream")
84
+ return guard("file", () => this.#files.readStream(file, request(opts)));
82
85
  const bytes = await guard("file", () => this.#files.read(file, request(opts)));
83
86
  switch (opts.format) {
84
87
  case "bytes":
85
88
  return bytes;
86
89
  case "blob":
87
90
  return new Blob([bytes]);
88
- case "stream":
89
- return new Blob([bytes]).stream();
90
91
  default:
91
92
  return new TextDecoder().decode(bytes);
92
93
  }
@@ -86,8 +86,6 @@ export type SandboxListOpts = SandboxApiOpts & {
86
86
  limit?: number;
87
87
  nextToken?: string;
88
88
  };
89
- /** A sandbox, with E2B's methods. `sandbox.runtime` is the Runtime sandbox
90
- * underneath, for anything E2B has no name for. */
91
89
  export declare class Sandbox {
92
90
  #private;
93
91
  protected static readonly defaultTemplate: string;
@@ -156,12 +154,17 @@ export declare class Sandbox {
156
154
  name?: string;
157
155
  }): Promise<SnapshotInfo>;
158
156
  static deleteSnapshot(snapshotId: string, opts?: SandboxApiOpts): Promise<boolean>;
159
- /** Refused: E2B answers getHost without a call, and a Runtime port becomes
160
- * reachable only once it is shared. Use getPublicHost. */
157
+ /** E2B's getHost, answered at once as E2B answers it:
158
+ * `<port>-<id>.runtimehost.com`. A Runtime preview's address is made from
159
+ * the sandbox and the port alone, so it is known without a call; the port
160
+ * is shared publicly the first time it is asked for, in about a tenth of a
161
+ * second, beside the caller. A request that lands before the share may be
162
+ * answered 404 for up to two seconds; `await getPublicHost(port)` returns
163
+ * once the share has landed. */
161
164
  getHost(port: number): string;
162
165
  /** Shares `port` at a public HTTPS address (a Runtime preview) and returns
163
- * its host, `<port>-<id>.<domain>`, like E2B's getHost. Anyone with the
164
- * address can reach it; a browser sees a one-time page naming Runtime. */
166
+ * its host, `<port>-<id>.<domain>`, once the share has landed. Anyone with
167
+ * the address can reach it; a browser sees a one-time page naming Runtime. */
165
168
  getPublicHost(port: number): Promise<string>;
166
169
  get pty(): never;
167
170
  get git(): never;
@@ -128,6 +128,8 @@ function unsupported(feature, alternative) {
128
128
  }
129
129
  /** A sandbox, with E2B's methods. `sandbox.runtime` is the Runtime sandbox
130
130
  * underneath, for anything E2B has no name for. */
131
+ /** Where Runtime serves previews; getHost names a host under it. */
132
+ const PREVIEW_DOMAIN = "runtimehost.com";
131
133
  export class Sandbox {
132
134
  static defaultTemplate = "base";
133
135
  files;
@@ -136,6 +138,8 @@ export class Sandbox {
136
138
  #runtime;
137
139
  #envs;
138
140
  #home;
141
+ /** Ports shared as public previews, by port: the one share each asked for. */
142
+ #shares = new Map();
139
143
  /** Use Sandbox.create or Sandbox.connect. */
140
144
  constructor(runtime, client, envs = {}) {
141
145
  this.#runtime = runtime;
@@ -332,17 +336,37 @@ export class Sandbox {
332
336
  }
333
337
  }
334
338
  // ---- ports ------------------------------------------------------------
335
- /** Refused: E2B answers getHost without a call, and a Runtime port becomes
336
- * reachable only once it is shared. Use getPublicHost. */
339
+ /** E2B's getHost, answered at once as E2B answers it:
340
+ * `<port>-<id>.runtimehost.com`. A Runtime preview's address is made from
341
+ * the sandbox and the port alone, so it is known without a call; the port
342
+ * is shared publicly the first time it is asked for, in about a tenth of a
343
+ * second, beside the caller. A request that lands before the share may be
344
+ * answered 404 for up to two seconds; `await getPublicHost(port)` returns
345
+ * once the share has landed. */
337
346
  getHost(port) {
338
- throw new NotSupportedError(`sandbox.getHost(${port}), which answers without asking the server`, `Use \`await sandbox.getPublicHost(${port})\`: it shares the port at a public HTTPS address and returns its host, as getHost does.`);
347
+ if (!Number.isInteger(port) || port < 1 || port > 65_535)
348
+ throw new InvalidArgumentError(`port must be a whole number from 1 to 65535, not ${port}.`);
349
+ void this.#share(port).catch((error) => {
350
+ if (typeof process !== "undefined" && process.emitWarning)
351
+ process.emitWarning(`Sharing port ${port} for getHost failed: ${error instanceof Error ? error.message : String(error)}. getPublicHost(${port}) says why.`, { code: "RUNTIME_E2B_GET_HOST" });
352
+ });
353
+ return `${port}-${this.sandboxId.replaceAll("-", "").toLowerCase()}.${PREVIEW_DOMAIN}`;
339
354
  }
340
355
  /** Shares `port` at a public HTTPS address (a Runtime preview) and returns
341
- * its host, `<port>-<id>.<domain>`, like E2B's getHost. Anyone with the
342
- * address can reach it; a browser sees a one-time page naming Runtime. */
356
+ * its host, `<port>-<id>.<domain>`, once the share has landed. Anyone with
357
+ * the address can reach it; a browser sees a one-time page naming Runtime. */
343
358
  async getPublicHost(port) {
344
- const preview = await guard("sandbox", () => this.#runtime.previews.create(port, { visibility: "public" }));
345
- return new URL(preview.url).host;
359
+ return this.#share(port);
360
+ }
361
+ #share(port) {
362
+ let share = this.#shares.get(port);
363
+ if (!share) {
364
+ share = guard("sandbox", () => this.#runtime.previews.create(port, { visibility: "public" })).then((preview) => new URL(preview.url).host);
365
+ // A failed share is asked again next time, not remembered.
366
+ share.catch(() => this.#shares.delete(port));
367
+ this.#shares.set(port, share);
368
+ }
369
+ return share;
346
370
  }
347
371
  // ---- what Runtime does differently -----------------------------------
348
372
  get pty() {
package/dist/errors.d.ts CHANGED
@@ -45,6 +45,11 @@ export declare class RateLimitError extends RuntimeError {
45
45
  }
46
46
  export declare class ServiceUnavailableError extends RuntimeError {
47
47
  }
48
+ /** The account may not spend: a payment on it is disputed or under review
49
+ * (`account_blocked`, 402). The message says which and what clears it; a
50
+ * retry cannot, and adding credit alone may not. */
51
+ export declare class AccountBlockedError extends RuntimeError {
52
+ }
48
53
  /** No answer arrived. A write may or may not have happened; retrying with the
49
54
  * same idempotencyKey (the SDK does, automatically) settles it safely. */
50
55
  export declare class ConnectionError extends RuntimeError {
package/dist/errors.js CHANGED
@@ -74,6 +74,11 @@ export class RateLimitError extends RuntimeError {
74
74
  }
75
75
  export class ServiceUnavailableError extends RuntimeError {
76
76
  }
77
+ /** The account may not spend: a payment on it is disputed or under review
78
+ * (`account_blocked`, 402). The message says which and what clears it; a
79
+ * retry cannot, and adding credit alone may not. */
80
+ export class AccountBlockedError extends RuntimeError {
81
+ }
77
82
  /** No answer arrived. A write may or may not have happened; retrying with the
78
83
  * same idempotencyKey (the SDK does, automatically) settles it safely. */
79
84
  export class ConnectionError extends RuntimeError {
@@ -110,6 +115,8 @@ export function errorFor(status, body, idempotencyKey) {
110
115
  ...(typeof error.retryAfterMs === "number" ? { retryAfterMs: error.retryAfterMs } : {}),
111
116
  ...(idempotencyKey ? { idempotencyKey } : {}),
112
117
  };
118
+ if (init.code === "account_blocked")
119
+ return new AccountBlockedError(init);
113
120
  if (status === 401)
114
121
  return new AuthenticationError(init);
115
122
  if (status === 403)
package/dist/index.d.ts CHANGED
@@ -5,22 +5,22 @@
5
5
  * console.log((await sbx.exec("echo hello")).stdout);
6
6
  */
7
7
  import type { Runtime } from "./client.js";
8
- import { type RuntimeOptions } from "./client.js";
8
+ import { type CreateOptions, type RuntimeOptions } from "./client.js";
9
9
  import { Sandbox as SandboxClass } from "./sandbox.js";
10
10
  import type { CreateSandbox, KeepAliveOptions } from "./types.js";
11
11
  import type { RequestOptions } from "./transport.js";
12
- export { Runtime, Sandboxes, FeedbackApi, SupportApi, type RuntimeOptions, type SupportReply, } from "./client.js";
12
+ export { Runtime, Sandboxes, FeedbackApi, SupportApi, type CreateOptions, type RuntimeOptions, type SupportReply, } from "./client.js";
13
13
  export { Files, Process, Processes, Terminal } from "./sandbox.js";
14
14
  export { Tunnel, TunnelStream, type PortForward } from "./tunnel.js";
15
15
  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
- export type { Secret, SetSecret } from "./products/secrets.js";
18
+ export type { Secret, SecretRule, SetSecret } from "./products/secrets.js";
19
19
  export type { McpCatalogEntry, McpGateway, McpServerRequest } from "./products/mcp.js";
20
20
  export type { FileEvent, FileEventType, WatchHandle, WatchNotice, WatchOptions, } from "./products/watch.js";
21
21
  export type { Mount, MountBucket } from "./products/mounts.js";
22
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";
23
+ export type { CheckedDomain, DnsRecord, Domain, EgressAddress, TcpPort, Tunnel as PrivateTunnel, TunnelPeer, TunnelPeerCreated, SetUpstreamProxy, UpstreamProxy, } from "./products/network-products.js";
24
24
  export { verifyWebhook, WebhookVerificationError, type MetricPoint, type MetricRange, type OtelExport, type RuntimeEvent, type SandboxMetrics, type Webhook, type WebhookDelivery, type WebhookEventType, } from "./products/observability.js";
25
25
  export { Snapshots, type Snapshot, type SnapshotOptions } from "./snapshots.js";
26
26
  export { Transport, VERSION, apiOrigin, type RequestOptions, type Call } from "./transport.js";
@@ -35,7 +35,7 @@ export declare class Sandbox extends SandboxClass {
35
35
  static create(input?: CreateSandbox & {
36
36
  wait?: boolean;
37
37
  keepAlive?: boolean | KeepAliveOptions;
38
- }, options?: RequestOptions & {
38
+ }, options?: CreateOptions & {
39
39
  client?: Runtime;
40
40
  }): Promise<SandboxClass>;
41
41
  /** The sandbox named `name` in this account, ready to use: running as it
@@ -45,7 +45,7 @@ export declare class Sandbox extends SandboxClass {
45
45
  static getOrCreate(name: string, input?: Omit<CreateSandbox, "name" | "getOrCreate"> & {
46
46
  wait?: boolean;
47
47
  keepAlive?: boolean | KeepAliveOptions;
48
- }, options?: RequestOptions & {
48
+ }, options?: CreateOptions & {
49
49
  client?: Runtime;
50
50
  }): Promise<SandboxClass>;
51
51
  /** Reconnects to an existing sandbox by id. */
@@ -65,4 +65,6 @@ export declare class Sandbox extends SandboxClass {
65
65
  }
66
66
  export type { RuntimeOptions as ClientOptions };
67
67
  export { identityToken, type IdentityToken } from "./identity.js";
68
+ export { openWalletAccount, claimWalletAccount, type OpenedWalletAccount, type WalletClaim, type WalletOptions, } from "./wallet.js";
69
+ export type { Topup } from "./products/billing.js";
68
70
  export type { SsoConnection, SsoStatus } from "./products/sso.js";
package/dist/index.js CHANGED
@@ -45,3 +45,4 @@ export class Sandbox extends SandboxClass {
45
45
  }
46
46
  }
47
47
  export { identityToken } from "./identity.js";
48
+ export { openWalletAccount, claimWalletAccount, } from "./wallet.js";
package/dist/keys.js CHANGED
@@ -4,7 +4,7 @@ import { hostname } from "node:os";
4
4
  import { promisify } from "node:util";
5
5
  import { connectionOrigins } from "./credentials.js";
6
6
  import { RuntimeError } from "./errors.js";
7
- import { named } from "./login.js";
7
+ import { named } from "./cli-name.js";
8
8
  import { describeRoute, envFetch } from "./proxy.js";
9
9
  const KEY = /^rtcloud_([a-f0-9-]{36})_[A-Za-z0-9_-]{43}$/;
10
10
  /** How often to ask whether the owner has answered. The website answers
package/dist/login.d.ts CHANGED
@@ -1,8 +1,5 @@
1
- /** How this CLI was started, so what it tells you to type works: through npx
2
- * the command is `npx withruntime`, installed it is `runtime`. */
3
- export declare const me: string;
4
- /** Text that names a command, with the command spelled the way it was run. */
5
- export declare function named(text: string, name?: string): string;
1
+ import { me, named } from "./cli-name.js";
2
+ export { me, named };
6
3
  type LoginOptions = {
7
4
  readKey?: () => Promise<string>;
8
5
  fetch?: typeof fetch;
@@ -116,4 +113,3 @@ export type PendingLogin = {
116
113
  * waiting: `runtime login` waits on it, and the MCP bridge gives the link to
117
114
  * the agent, which shows it to the person it works for. */
118
115
  export declare function beginDeviceLogin(env: NodeJS.ProcessEnv, agentName: string, options?: LoginOptions): Promise<PendingLogin>;
119
- export {};
package/dist/login.js CHANGED
@@ -2,7 +2,8 @@ import { execFile } from "node:child_process";
2
2
  import { generateKeyPair, privateDecrypt } from "node:crypto";
3
3
  import { hostname } from "node:os";
4
4
  import { promisify } from "node:util";
5
- import { Runtime } from "./index.js";
5
+ import { me, named } from "./cli-name.js";
6
+ import { Runtime } from "./client.js";
6
7
  import { connectionOrigins, connectionStore } from "./credentials.js";
7
8
  import { describeRoute, envFetch } from "./proxy.js";
8
9
  class ConnectionError extends Error {
@@ -12,13 +13,7 @@ class ConnectionError extends Error {
12
13
  this.status = status;
13
14
  }
14
15
  }
15
- /** How this CLI was started, so what it tells you to type works: through npx
16
- * the command is `npx withruntime`, installed it is `runtime`. */
17
- export const me = /[\\/]_npx[\\/]/.test(process.argv[1] ?? "") ? "npx withruntime" : "runtime";
18
- /** Text that names a command, with the command spelled the way it was run. */
19
- export function named(text, name = me) {
20
- return name === "runtime" ? text : text.replace(/(^|[\s`(])runtime(?= [a-z<[])/g, `$1${name}`);
21
- }
16
+ export { me, named };
22
17
  /** The connection id recorded for a key pasted with `login --with-key`. */
23
18
  const PASTED = "pasted";
24
19
  /** A key from standard input: piped, or typed at a prompt that does not echo. */
@@ -8,7 +8,7 @@ type Args = {
8
8
  positional: string[];
9
9
  flags: Map<string, string[]>;
10
10
  };
11
- export declare const NETWORK_PRODUCTS: readonly ["domain", "port", "address", "tunnel"];
11
+ export declare const NETWORK_PRODUCTS: readonly ["domain", "port", "address", "tunnel", "network"];
12
12
  export type NetworkProduct = (typeof NETWORK_PRODUCTS)[number];
13
13
  export declare const NETWORK_HELP: Record<NetworkProduct, string>;
14
14
  export declare function networkProductCommand(product: NetworkProduct, argv: string[], args: Args, out: Out, client: () => Promise<Runtime>): Promise<number>;
@@ -1,6 +1,6 @@
1
1
  import { chmod, writeFile } from "node:fs/promises";
2
2
  import { RuntimeError } from "./errors.js";
3
- import { named } from "./login.js";
3
+ import { named } from "./cli-name.js";
4
4
  import { generateWireGuardKeyPair } from "./wireguard.js";
5
5
  function fundingState(value) {
6
6
  if (value.funded === false)
@@ -11,7 +11,7 @@ function fundingState(value) {
11
11
  ? `Funded until ${value.fundedUntil}.`
12
12
  : "Funding active; no deadline reported.";
13
13
  }
14
- export const NETWORK_PRODUCTS = ["domain", "port", "address", "tunnel"];
14
+ export const NETWORK_PRODUCTS = ["domain", "port", "address", "tunnel", "network"];
15
15
  export const NETWORK_HELP = {
16
16
  domain: `runtime domain <command>
17
17
 
@@ -54,6 +54,19 @@ export const NETWORK_HELP = {
54
54
  A new key; the old one stops working within seconds
55
55
  peer rm <peerId> Remove it; its tunnel ends within seconds
56
56
  --route: your own ranges behind the peer that sandboxes may reach. Paid accounts only.
57
+ `,
58
+ network: `runtime network <command>
59
+
60
+ upstream-proxy set <http://host:port|https://host:port> [--secret NAME] [--host <host>]...
61
+ Send your sandboxes' outbound connections through your
62
+ own proxy, after Runtime's own rules allow them.
63
+ --secret: one of your secrets, sent as Proxy-Authorization
64
+ (its hosts must name the proxy's host). --host: only
65
+ these destinations (or *.domain) go through it
66
+ upstream-proxy get The proxy in use
67
+ upstream-proxy remove Connections leave directly again
68
+ A proxy that refuses or cannot be reached fails the connection; nothing goes
69
+ around it. Paid accounts only.
57
70
  `,
58
71
  };
59
72
  function usage(product, message) {
@@ -319,5 +332,54 @@ export async function networkProductCommand(product, argv, args, out, client) {
319
332
  }
320
333
  }
321
334
  }
335
+ if (product === "network") {
336
+ if (verb !== "upstream-proxy")
337
+ throw usage(product, `Unknown network command ${verb}.`);
338
+ const upstream = runtime.network.upstreamProxy;
339
+ const describe = (proxy) => [
340
+ `Outbound connections${proxy.hosts?.length ? ` to ${proxy.hosts.join(", ")}` : ""} go through ${proxy.url}.`,
341
+ ...(proxy.secret ? [`${proxy.secret} is sent as Proxy-Authorization.`] : []),
342
+ ].join("\n");
343
+ switch (rest[0]) {
344
+ case "set": {
345
+ const hosts = args.flags.get("host");
346
+ const secret = flag("secret");
347
+ const saved = await upstream.set({
348
+ url: need(rest[1], "the proxy's url, such as http://proxy.example.com:3128"),
349
+ ...(secret ? { secret } : {}),
350
+ ...(hosts ? { hosts } : {}),
351
+ });
352
+ print([
353
+ describe(saved),
354
+ saved.enforced
355
+ ? "A connection your proxy refuses fails; nothing goes around it."
356
+ : "A host is catching up; running sandboxes use it within a minute.",
357
+ ].join("\n"), saved);
358
+ return 0;
359
+ }
360
+ case "get":
361
+ case undefined: {
362
+ let found;
363
+ try {
364
+ found = await upstream.get();
365
+ }
366
+ catch (error) {
367
+ if (!(error instanceof RuntimeError) || error.code !== "not_found")
368
+ throw error;
369
+ print(named("No upstream proxy. Set one: runtime network upstream-proxy set http://proxy.example.com:3128"), null);
370
+ return 0;
371
+ }
372
+ print(describe(found), found);
373
+ return 0;
374
+ }
375
+ case "remove":
376
+ case "rm": {
377
+ const gone = await upstream.remove();
378
+ print("Connections leave directly again.", gone);
379
+ return 0;
380
+ }
381
+ }
382
+ throw usage(product, "upstream-proxy takes set, get or remove.");
383
+ }
322
384
  throw usage(product, `Unknown ${product} command ${verb}.`);
323
385
  }
@@ -0,0 +1,35 @@
1
+ import type { RequestOptions, Transport } from "../transport.js";
2
+ /** Where to pay for a stablecoin top-up, and whether the payment arrived. */
3
+ export type Topup = {
4
+ /** Poll this until `status` is `paid`. */
5
+ purchaseId: string;
6
+ /** open: waiting for your payment. pending: Stripe saw it and is confirming.
7
+ * paid: the credit is on the account. expired: nothing arrived in time. */
8
+ status: "open" | "pending" | "paid" | "failed" | "expired";
9
+ /** The credit this buys, in dollars, such as "20.00". */
10
+ amountUsd: string;
11
+ /** The exact token amount to send, to six decimals, such as "20.000000".
12
+ * A different amount, token or network cannot be matched or returned
13
+ * automatically. */
14
+ amount: string;
15
+ /** Send before this. */
16
+ payBy: string | null;
17
+ /** Pay on any one of these, with one of its tokens. */
18
+ networks: {
19
+ network: string;
20
+ address: string;
21
+ tokens: {
22
+ currency: string;
23
+ contract: string;
24
+ }[];
25
+ }[];
26
+ };
27
+ export declare function billing(t: Transport): {
28
+ /** Open a stablecoin top-up of `usd` dollars ($10 to $10,000). Pass the
29
+ * same `idempotencyKey` when retrying to get the same top-up back. */
30
+ topup: (input: {
31
+ usd: number;
32
+ }, options?: RequestOptions) => Promise<Topup>;
33
+ /** Read a top-up: whether its payment has arrived. */
34
+ topupStatus: (purchaseId: string, options?: RequestOptions) => Promise<Topup>;
35
+ };
@@ -0,0 +1,13 @@
1
+ export function billing(t) {
2
+ return {
3
+ /** Open a stablecoin top-up of `usd` dollars ($10 to $10,000). Pass the
4
+ * same `idempotencyKey` when retrying to get the same top-up back. */
5
+ topup: (input, options) => t.json({ method: "POST", path: "/v1/billing/topups", body: input, ...options }),
6
+ /** Read a top-up: whether its payment has arrived. */
7
+ topupStatus: (purchaseId, options) => t.json({
8
+ method: "GET",
9
+ path: `/v1/billing/topups/${encodeURIComponent(purchaseId)}`,
10
+ ...options,
11
+ }),
12
+ };
13
+ }
@@ -4,9 +4,10 @@ import { images } from "./images.js";
4
4
  import { sandboxDesktop } from "./desktop.js";
5
5
  import { sandboxInterpreter } from "./interpreter.js";
6
6
  import { sandboxNetwork } from "./network.js";
7
- import { addresses, domains, ports, tunnel } from "./network-products.js";
7
+ import { addresses, domains, network, ports, tunnel } from "./network-products.js";
8
8
  import { sandboxPreviews } from "./previews.js";
9
9
  import { referrals } from "./referrals.js";
10
+ import { billing } from "./billing.js";
10
11
  import { audit } from "./audit.js";
11
12
  import { sso } from "./sso.js";
12
13
  import { switching } from "./switching.js";
@@ -20,6 +21,7 @@ export declare const clientExtensions: {
20
21
  images: typeof images;
21
22
  volumes: typeof volumes;
22
23
  referrals: typeof referrals;
24
+ billing: typeof billing;
23
25
  limits: typeof limits;
24
26
  switching: typeof switching;
25
27
  secrets: typeof secrets;
@@ -33,6 +35,7 @@ export declare const clientExtensions: {
33
35
  ports: typeof ports;
34
36
  addresses: typeof addresses;
35
37
  tunnel: typeof tunnel;
38
+ network: typeof network;
36
39
  };
37
40
  export declare const sandboxExtensions: {
38
41
  interpreter: typeof sandboxInterpreter;
@@ -2,9 +2,10 @@ import { images } from "./images.js";
2
2
  import { sandboxDesktop } from "./desktop.js";
3
3
  import { sandboxInterpreter } from "./interpreter.js";
4
4
  import { sandboxNetwork } from "./network.js";
5
- import { addresses, domains, ports, tunnel } from "./network-products.js";
5
+ import { addresses, domains, network, ports, tunnel } from "./network-products.js";
6
6
  import { sandboxPreviews } from "./previews.js";
7
7
  import { referrals } from "./referrals.js";
8
+ import { billing } from "./billing.js";
8
9
  import { audit } from "./audit.js";
9
10
  import { sso } from "./sso.js";
10
11
  import { switching } from "./switching.js";
@@ -33,6 +34,7 @@ export const clientExtensions = {
33
34
  images,
34
35
  volumes,
35
36
  referrals,
37
+ billing,
36
38
  limits,
37
39
  switching,
38
40
  secrets,
@@ -46,6 +48,7 @@ export const clientExtensions = {
46
48
  ports,
47
49
  addresses,
48
50
  tunnel,
51
+ network,
49
52
  };
50
53
  export const sandboxExtensions = {
51
54
  interpreter: sandboxInterpreter,