withruntime 0.11.2 → 0.14.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 (171) hide show
  1. package/BLAXEL.md +2 -2
  2. package/COMPATIBILITY.md +74 -9
  3. package/DAYTONA.md +33 -5
  4. package/E2B.md +85 -24
  5. package/README.md +11 -10
  6. package/README.src.md +10 -9
  7. package/VERCEL.md +43 -5
  8. package/compatibility-lock.json +58 -49
  9. package/dist/billing-cli.d.ts +1 -1
  10. package/dist/billing-cli.js +85 -5
  11. package/dist/blaxel/errors.d.ts +14 -0
  12. package/dist/blaxel/errors.js +29 -0
  13. package/dist/blaxel/index.d.ts +2 -2
  14. package/dist/blaxel/index.js +4 -4
  15. package/dist/blaxel/interpreter.d.ts +2 -5
  16. package/dist/blaxel/sandbox.d.ts +15 -6
  17. package/dist/blaxel/sandbox.js +64 -8
  18. package/dist/blaxel/snapshot.d.ts +4 -0
  19. package/dist/blaxel/snapshot.js +4 -0
  20. package/dist/cli/logs.d.ts +21 -0
  21. package/dist/cli/logs.js +188 -0
  22. package/dist/cli-extras.d.ts +3 -3
  23. package/dist/cli-extras.js +17 -12
  24. package/dist/cli-words.d.ts +10 -1
  25. package/dist/cli-words.js +18 -0
  26. package/dist/cli.d.ts +22 -3
  27. package/dist/cli.js +647 -273
  28. package/dist/client.d.ts +2 -4
  29. package/dist/client.js +27 -11
  30. package/dist/cloudflare/errors.d.ts +143 -0
  31. package/dist/cloudflare/errors.js +167 -0
  32. package/dist/cloudflare/index.d.ts +175 -6
  33. package/dist/cloudflare/index.js +416 -26
  34. package/dist/cloudflare/interpreter.d.ts +119 -0
  35. package/dist/cloudflare/interpreter.js +223 -0
  36. package/dist/cloudflare/streams.d.ts +38 -0
  37. package/dist/cloudflare/streams.js +145 -0
  38. package/dist/cloudflare/watch.d.ts +9 -0
  39. package/dist/cloudflare/watch.js +24 -0
  40. package/dist/codesandbox/index.d.ts +216 -28
  41. package/dist/codesandbox/index.js +478 -78
  42. package/dist/compat/core.d.ts +18 -0
  43. package/dist/compat/core.js +29 -0
  44. package/dist/compat/files.d.ts +1 -0
  45. package/dist/compat/files.js +25 -5
  46. package/dist/credentials.d.ts +3 -0
  47. package/dist/credentials.js +15 -0
  48. package/dist/daytona/computer-use.d.ts +13 -4
  49. package/dist/daytona/computer-use.js +66 -8
  50. package/dist/daytona/daytona.d.ts +7 -1
  51. package/dist/daytona/daytona.js +31 -4
  52. package/dist/daytona/errors.d.ts +11 -0
  53. package/dist/daytona/errors.js +18 -0
  54. package/dist/daytona/filesystem.js +2 -1
  55. package/dist/daytona/image.d.ts +13 -9
  56. package/dist/daytona/image.js +18 -12
  57. package/dist/daytona/index.d.ts +1 -1
  58. package/dist/daytona/sandbox.d.ts +8 -2
  59. package/dist/daytona/sandbox.js +16 -2
  60. package/dist/domains/index.d.ts +9 -0
  61. package/dist/domains/index.js +10 -0
  62. package/dist/e2b/commands.d.ts +1 -1
  63. package/dist/e2b/commands.js +6 -7
  64. package/dist/e2b/errors.d.ts +17 -0
  65. package/dist/e2b/errors.js +40 -1
  66. package/dist/e2b/index.d.ts +2 -2
  67. package/dist/e2b/index.js +1 -1
  68. package/dist/e2b/sandbox.d.ts +28 -22
  69. package/dist/e2b/sandbox.js +34 -8
  70. package/dist/e2b/unsupported.js +1 -1
  71. package/dist/errors.d.ts +11 -2
  72. package/dist/errors.js +17 -5
  73. package/dist/freestyle/index.d.ts +285 -37
  74. package/dist/freestyle/index.js +656 -144
  75. package/dist/identity.d.ts +3 -0
  76. package/dist/identity.js +27 -0
  77. package/dist/images/index.d.ts +9 -0
  78. package/dist/images/index.js +10 -0
  79. package/dist/index.d.ts +3 -1
  80. package/dist/ips/index.d.ts +9 -0
  81. package/dist/ips/index.js +10 -0
  82. package/dist/jobs/index.d.ts +9 -0
  83. package/dist/jobs/index.js +10 -0
  84. package/dist/keys.js +2 -0
  85. package/dist/login.js +2 -0
  86. package/dist/machine.d.ts +27 -4
  87. package/dist/machine.js +46 -14
  88. package/dist/mcp.js +144 -20
  89. package/dist/modal/index.d.ts +339 -61
  90. package/dist/modal/index.js +781 -121
  91. package/dist/network-cli.d.ts +3 -1
  92. package/dist/network-cli.js +78 -36
  93. package/dist/networks/index.d.ts +11 -0
  94. package/dist/networks/index.js +10 -0
  95. package/dist/ports/index.d.ts +9 -0
  96. package/dist/ports/index.js +10 -0
  97. package/dist/product-names.d.ts +52 -0
  98. package/dist/product-names.js +164 -0
  99. package/dist/product-renames.d.ts +103 -0
  100. package/dist/product-renames.js +189 -0
  101. package/dist/products/api-keys.d.ts +73 -0
  102. package/dist/products/api-keys.js +23 -0
  103. package/dist/products/apps.d.ts +157 -0
  104. package/dist/products/apps.js +60 -0
  105. package/dist/products/balancers.d.ts +87 -0
  106. package/dist/products/balancers.js +8 -0
  107. package/dist/products/billing.d.ts +41 -0
  108. package/dist/products/billing.js +13 -0
  109. package/dist/products/browser.d.ts +10 -0
  110. package/dist/products/browsers.d.ts +71 -0
  111. package/dist/products/browsers.js +102 -0
  112. package/dist/products/builds.d.ts +156 -0
  113. package/dist/products/builds.js +227 -0
  114. package/dist/products/index.d.ts +5 -16
  115. package/dist/products/index.js +5 -14
  116. package/dist/products/jobs.d.ts +19 -2
  117. package/dist/products/jobs.js +28 -11
  118. package/dist/products/logs.d.ts +245 -0
  119. package/dist/products/logs.js +274 -0
  120. package/dist/products/network-products.d.ts +38 -7
  121. package/dist/products/network-products.js +31 -18
  122. package/dist/products/observability.d.ts +8 -2
  123. package/dist/products/private-network.js +1 -1
  124. package/dist/products/queues.d.ts +149 -0
  125. package/dist/products/queues.js +213 -0
  126. package/dist/products/referrals.d.ts +21 -7
  127. package/dist/products/referrals.js +3 -3
  128. package/dist/products/resources.d.ts +54 -0
  129. package/dist/products/resources.js +122 -0
  130. package/dist/products/secrets.d.ts +29 -3
  131. package/dist/products/secrets.js +47 -18
  132. package/dist/products/signed-requests.d.ts +21 -0
  133. package/dist/products/signed-requests.js +11 -0
  134. package/dist/products/streams.d.ts +73 -0
  135. package/dist/products/streams.js +17 -0
  136. package/dist/products/switching.d.ts +35 -7
  137. package/dist/products/switching.js +4 -3
  138. package/dist/products/volumes.d.ts +6 -0
  139. package/dist/record-cli.d.ts +23 -0
  140. package/dist/record-cli.js +308 -0
  141. package/dist/renamed-products.d.ts +38 -0
  142. package/dist/renamed-products.js +34 -0
  143. package/dist/runloop/index.d.ts +9 -2
  144. package/dist/runloop/index.js +29 -8
  145. package/dist/runloop/sdk.d.ts +131 -0
  146. package/dist/runloop/sdk.js +167 -0
  147. package/dist/sandbox.d.ts +7 -2
  148. package/dist/sandbox.js +41 -11
  149. package/dist/sandboxes/index.d.ts +11 -0
  150. package/dist/sandboxes/index.js +12 -0
  151. package/dist/snapshots/index.d.ts +9 -0
  152. package/dist/snapshots/index.js +11 -0
  153. package/dist/snapshots.d.ts +7 -4
  154. package/dist/sprites/index.d.ts +343 -31
  155. package/dist/sprites/index.js +866 -84
  156. package/dist/ssh.d.ts +1 -1
  157. package/dist/ssh.js +11 -11
  158. package/dist/terminal.d.ts +24 -0
  159. package/dist/terminal.js +32 -0
  160. package/dist/transport.d.ts +18 -1
  161. package/dist/transport.js +80 -6
  162. package/dist/tunnel.js +1 -1
  163. package/dist/types.d.ts +71 -4
  164. package/dist/vercel/sandbox.d.ts +1 -1
  165. package/dist/vercel/sandbox.js +6 -4
  166. package/dist/vercel/snapshot.js +10 -2
  167. package/dist/volumes/index.d.ts +9 -0
  168. package/dist/volumes/index.js +10 -0
  169. package/dist/wallet.d.ts +4 -1
  170. package/dist/wallet.js +2 -1
  171. package/package.json +48 -14
@@ -40,6 +40,18 @@ export type SandboxArchiveOptions = {
40
40
  maxWait?: number;
41
41
  interval?: number;
42
42
  };
43
+ /** The longest creation `timeout` Blaxel accepts, in seconds. */
44
+ export declare const MAX_CREATION_TIMEOUT_SECONDS = 50;
45
+ /** Options of SandboxInstance.create / createIfNotExists, as Blaxel's. */
46
+ export type SandboxCreateOptions = {
47
+ /** List `/` once it runs, and delete it if that fails. */
48
+ safe?: boolean;
49
+ /** Answer the sandbox already holding the name instead of a 409. */
50
+ createIfNotExist?: boolean;
51
+ /** Give up after this many whole seconds (1 to 50): the sandbox is deleted
52
+ * and the call rejects with SandboxCreationTimeoutError. */
53
+ timeout?: number;
54
+ };
43
55
  /** What a create asked for that no label keeps: shown by `spec` in the
44
56
  * client that created the sandbox. */
45
57
  type Asked = {
@@ -127,15 +139,12 @@ export declare class SandboxInstance {
127
139
  get expiresIn(): number | undefined;
128
140
  /** Creates a sandbox and answers once it runs. Blaxel's defaults: the base
129
141
  * image (Runtime's stock image) and 4096 MB, with vCPUs at one per 2048 MB.
130
- * Funding is left to Runtime: the included usage first, then prepaid credit. A name another live sandbox holds is refused
142
+ * Funding is left to Runtime: the free usage first, then prepaid credit. A name another live sandbox holds is refused
131
143
  * with a 409, as in Blaxel. */
132
- static create<T extends typeof SandboxInstance>(this: T, sandbox?: SandboxCreateConfiguration | SandboxModel, { createIfNotExist }?: {
133
- safe?: boolean;
134
- createIfNotExist?: boolean;
135
- }): Promise<InstanceType<T>>;
144
+ static create<T extends typeof SandboxInstance>(this: T, sandbox?: SandboxCreateConfiguration | SandboxModel, options?: SandboxCreateOptions): Promise<InstanceType<T>>;
136
145
  /** The named sandbox, or a new one when no live sandbox has the name.
137
146
  * Runtime settles a race between two callers: both get the same sandbox. */
138
- static createIfNotExists<T extends typeof SandboxInstance>(this: T, sandbox: SandboxCreateConfiguration | SandboxModel): Promise<InstanceType<T>>;
147
+ static createIfNotExists<T extends typeof SandboxInstance>(this: T, sandbox: SandboxCreateConfiguration | SandboxModel, options?: Omit<SandboxCreateOptions, "createIfNotExist">): Promise<InstanceType<T>>;
139
148
  /** The live sandbox with this name (or Runtime id). It is not woken: the
140
149
  * next call wakes it. */
141
150
  static get<T extends typeof SandboxInstance>(this: T, sandboxName: string, options?: RuntimeOptions): Promise<InstanceType<T>>;
@@ -1,7 +1,7 @@
1
1
  import { RuntimeError } from "../errors.js";
2
2
  import { clientFor, defaultRegion, } from "./client.js";
3
3
  import { checkEnvNames, envFileCommand, envFileLines, parseProcessLine, toRuntimePath, } from "./context.js";
4
- import { codeOf, guard, NotSupportedError, responseError, translate } from "./errors.js";
4
+ import { codeOf, guard, NotSupportedError, responseError, SandboxCreationTimeoutError, translate, } from "./errors.js";
5
5
  import { SandboxFileSystem } from "./filesystem.js";
6
6
  import { ARCHIVED, DISPLAY_NAME, EXTERNAL_ID, IDLE_PAUSE, LABEL, findByExternalId, findSandbox, LEASE_SECONDS, ownLabels, STANDBY, userLabels, whenNameFree, } from "./lookup.js";
7
7
  import { paginate } from "./pagination.js";
@@ -38,6 +38,56 @@ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
38
38
  const DAY_MS = 86_400_000;
39
39
  /** The lease is renewed in the background once this little of it is left. */
40
40
  const RENEW_BELOW_MS = 10 * 60_000;
41
+ /** The longest creation `timeout` Blaxel accepts, in seconds. */
42
+ export const MAX_CREATION_TIMEOUT_SECONDS = 50;
43
+ /** Blaxel's own check of `timeout`, with its words. */
44
+ function creationTimeout({ timeout }) {
45
+ if (timeout === undefined)
46
+ return undefined;
47
+ if (!Number.isInteger(timeout) || timeout < 1 || timeout > MAX_CREATION_TIMEOUT_SECONDS)
48
+ throw new Error(`SandboxInstance.create: 'timeout' must be a whole number of seconds between 1 and ${MAX_CREATION_TIMEOUT_SECONDS}, got ${timeout}.`);
49
+ return timeout;
50
+ }
51
+ /** A create that must be running within `timeout` seconds of the call, the
52
+ * wait for capacity included. One that is not is deleted, so the name is
53
+ * free again, and the call rejects with SandboxCreationTimeoutError. */
54
+ async function createWithin(client, input, timeout) {
55
+ const deadline = performance.now() + timeout * 1000;
56
+ const runtime = await guard(() => whenNameFree(client, () => client.sandboxes.create({ ...input, wait: false }, { waitForCapacityMs: timeout * 1000 })));
57
+ // A sandbox the name already had is the caller's, whatever its state: it is
58
+ // answered as it is, as without a timeout, and never deleted here.
59
+ if (runtime.state === "running" || runtime.info.reused)
60
+ return runtime;
61
+ const left = deadline - performance.now();
62
+ if (left > 0)
63
+ try {
64
+ await runtime.waitFor("running", {
65
+ timeoutSeconds: Math.max(1, Math.ceil(left / 1000)),
66
+ timeoutMs: Math.ceil(left) + 5_000,
67
+ });
68
+ }
69
+ catch (error) {
70
+ if (!(error instanceof RuntimeError && error.code === "timeout")) {
71
+ await runtime.delete().catch(() => undefined);
72
+ throw translate(error);
73
+ }
74
+ }
75
+ // waitFor updated the state TypeScript narrowed above.
76
+ const state = runtime.state;
77
+ if (state === "running")
78
+ return runtime;
79
+ await runtime.delete().catch(() => undefined);
80
+ if (state !== "starting")
81
+ throw translate(new RuntimeError({
82
+ message: `Sandbox ${runtime.id} is ${runtime.state}, not running.`,
83
+ code: "start_failed",
84
+ status: 0,
85
+ hint: "Create it again; SandboxInstance.get(name) reads why it stopped.",
86
+ }));
87
+ throw new SandboxCreationTimeoutError(input.name, timeout, {
88
+ message: `It was still starting after ${timeout}s.`,
89
+ });
90
+ }
41
91
  /** The labels a new sandbox carries: the caller's, and under `blaxel/` what
42
92
  * Blaxel keeps and Runtime has no field for, so any client (the Python
43
93
  * adapter too) reads the same `spec`. */
@@ -212,7 +262,7 @@ async function resolveImage(client, image) {
212
262
  if (found && found.state === "ready")
213
263
  return { image: found.id };
214
264
  const feature = `The image ${image}, which is not a Runtime image`;
215
- const alternative = `Build it as a Runtime image named ${name}: \`npx withruntime image build --dockerfile Dockerfile --name ${name} -t ${name}:${tag}\`. The code can keep "${image}": the adapter starts from ${name}:${tag}.`;
265
+ const alternative = `Build it as a Runtime image named ${name}: \`npx withruntime images build --dockerfile Dockerfile --name ${name} -t ${name}:${tag}\`. The code can keep "${image}": the adapter starts from ${name}:${tag}.`;
216
266
  throw new NotSupportedError(feature, alternative, `${feature}, is not supported on Runtime. ${alternative}`);
217
267
  }
218
268
  /** Blaxel volumes (by name) as Runtime volumes. */
@@ -228,7 +278,7 @@ async function resolveVolumes(client, volumes) {
228
278
  return Promise.all(volumes.map(async (volume) => {
229
279
  const found = (await guard(() => client.volumes.list({ name: volume.name }))).data[0];
230
280
  if (!found)
231
- throw new NotSupportedError(`The volume ${volume.name}, which is not a Runtime volume`, `Create it first: runtime.volumes.create({ name: "${volume.name}" }) or \`npx withruntime volume create --name ${volume.name}\`.`);
281
+ throw new NotSupportedError(`The volume ${volume.name}, which is not a Runtime volume`, `Create it first: runtime.volumes.create({ name: "${volume.name}" }) or \`npx withruntime volumes create --name ${volume.name}\`.`);
232
282
  return { volumeId: found.id, path: toRuntimePath(volume.mountPath) };
233
283
  }));
234
284
  }
@@ -523,9 +573,11 @@ export class SandboxInstance {
523
573
  // ---- create, get, list ---------------------------------------------------
524
574
  /** Creates a sandbox and answers once it runs. Blaxel's defaults: the base
525
575
  * image (Runtime's stock image) and 4096 MB, with vCPUs at one per 2048 MB.
526
- * Funding is left to Runtime: the included usage first, then prepaid credit. A name another live sandbox holds is refused
576
+ * Funding is left to Runtime: the free usage first, then prepaid credit. A name another live sandbox holds is refused
527
577
  * with a 409, as in Blaxel. */
528
- static async create(sandbox, { createIfNotExist = false } = {}) {
578
+ static async create(sandbox, options = {}) {
579
+ const { createIfNotExist = false, safe = false } = options;
580
+ const timeout = creationTimeout(options);
529
581
  const config = configOf(sandbox);
530
582
  const region = config.region || defaultRegion();
531
583
  checkRegion(region);
@@ -559,7 +611,9 @@ export class SandboxInstance {
559
611
  ...(createIfNotExist && config.name ? { getOrCreate: true } : {}),
560
612
  ...config.withruntime?.create,
561
613
  };
562
- const runtime = await guard(() => whenNameFree(client, () => client.sandboxes.create(input)));
614
+ const runtime = timeout === undefined
615
+ ? await guard(() => whenNameFree(client, () => client.sandboxes.create(input)))
616
+ : await createWithin(client, input, timeout);
563
617
  const asked = {
564
618
  ...(Object.keys(envs).length ? { envs } : {}),
565
619
  ...(config.network ? { network: config.network } : {}),
@@ -571,6 +625,8 @@ export class SandboxInstance {
571
625
  const instance = new this({ runtime, client, asked });
572
626
  try {
573
627
  await instance.#setUp(envs, lifetime.days);
628
+ if (safe)
629
+ await instance.fs.ls("/");
574
630
  }
575
631
  catch (error) {
576
632
  await runtime.stop({ wait: false }).catch(() => undefined);
@@ -580,8 +636,8 @@ export class SandboxInstance {
580
636
  }
581
637
  /** The named sandbox, or a new one when no live sandbox has the name.
582
638
  * Runtime settles a race between two callers: both get the same sandbox. */
583
- static async createIfNotExists(sandbox) {
584
- return this.create(sandbox, { createIfNotExist: true });
639
+ static async createIfNotExists(sandbox, options = {}) {
640
+ return this.create(sandbox, { ...options, createIfNotExist: true });
585
641
  }
586
642
  /** The live sandbox with this name (or Runtime id). It is not woken: the
587
643
  * next call wakes it. */
@@ -71,6 +71,10 @@ export declare function appendEnvs(runtime: RuntimeSandbox, envs: Record<string,
71
71
  export declare function startFromSnapshot(client: Runtime, snapshotId: string, name: string, labels: Record<string, string>, envs: Record<string, string>): Promise<RuntimeSandbox>;
72
72
  declare function refuseApplication(options?: {
73
73
  targetType?: string;
74
+ port?: number;
75
+ traffic?: number;
76
+ customDomain?: string;
77
+ prefix?: string;
74
78
  }): void;
75
79
  /** A snapshot, as Blaxel's Snapshot class. */
76
80
  export declare class Snapshot {
@@ -63,6 +63,10 @@ export async function startFromSnapshot(client, snapshotId, name, labels, envs)
63
63
  function refuseApplication(options = {}) {
64
64
  if (options.targetType === "application")
65
65
  throw new NotSupportedError("Forking into an application", "Fork into a sandbox and share its port with sandbox.previews.create.");
66
+ // Blaxel's application-fork routing: a sandbox fork has no use for them.
67
+ for (const key of ["port", "traffic", "customDomain", "prefix"])
68
+ if (options[key] !== undefined)
69
+ throw new NotSupportedError(`A fork's ${key}`, "Fork without it, then share a port of the copy with sandbox.previews.create({ metadata: { name }, spec: { port } }).");
66
70
  }
67
71
  /** A snapshot, as Blaxel's Snapshot class. */
68
72
  export class Snapshot {
@@ -0,0 +1,21 @@
1
+ import type { ObservabilityClient } from "../products/logs.js";
2
+ type Product = "logs" | "traces" | "alerts";
3
+ type Args = {
4
+ positional: string[];
5
+ flags: Map<string, string[]>;
6
+ };
7
+ type Out = {
8
+ json: boolean;
9
+ tty?: boolean;
10
+ drain?: () => Promise<void>;
11
+ write(text: string): void;
12
+ error(text: string): void;
13
+ };
14
+ export declare const LOGS_HELP = "runtime logs tail <selector|app> [--since 1h] [--grep text] [--json] [--raw]\nruntime logs query '<LogQL>' [--from <time>] [--to <time>] [--limit <n>]\nruntime logs labels | values <label>\nruntime logs views ls|get|create|update|delete [id] [--body '<JSON>']\nruntime traces get <id> | search '<TraceQL>' | tags\nruntime traces views ls|get|create|update|delete [id] [--body '<JSON>']\nruntime logs ingest --body '<OTLP JSON>'\nruntime traces ingest --body '<OTLP JSON>'\nruntime alerts ls|get|create|update|delete|test|silence [id]\n create/update: --body '<JSON: name, labels, query, condition, for, actions>'\n silence: --until <RFC3339> [--reason <text>]\nEvery command takes --json. Writes take --idempotency-key <key>.\nOn a terminal, control characters in logs show as \\uXXXX; --raw prints them as written.";
15
+ /** Same parsed argument and output ports as the existing CLI. No registration
16
+ * occurs here: the launch owner supplies a client only after launch. */
17
+ export declare function logsProductCommand(product: Product, argv: string[], args: Args, out: Out, client: () => Promise<ObservabilityClient>, options?: {
18
+ signal?: AbortSignal;
19
+ now?: number;
20
+ }): Promise<number>;
21
+ export {};
@@ -0,0 +1,188 @@
1
+ import { RuntimeError } from "../errors.js";
2
+ import { terminalSafe } from "../terminal.js";
3
+ export const LOGS_HELP = `runtime logs tail <selector|app> [--since 1h] [--grep text] [--json] [--raw]
4
+ runtime logs query '<LogQL>' [--from <time>] [--to <time>] [--limit <n>]
5
+ runtime logs labels | values <label>
6
+ runtime logs views ls|get|create|update|delete [id] [--body '<JSON>']
7
+ runtime traces get <id> | search '<TraceQL>' | tags
8
+ runtime traces views ls|get|create|update|delete [id] [--body '<JSON>']
9
+ runtime logs ingest --body '<OTLP JSON>'
10
+ runtime traces ingest --body '<OTLP JSON>'
11
+ runtime alerts ls|get|create|update|delete|test|silence [id]
12
+ create/update: --body '<JSON: name, labels, query, condition, for, actions>'
13
+ silence: --until <RFC3339> [--reason <text>]
14
+ Every command takes --json. Writes take --idempotency-key <key>.
15
+ On a terminal, control characters in logs show as \\uXXXX; --raw prints them as written.`;
16
+ const usage = (message) => new RuntimeError({ code: "usage", status: 0, message, hint: LOGS_HELP });
17
+ function since(text, now) {
18
+ const match = /^(\d+)(s|m|h|d)$/.exec(text);
19
+ if (!match)
20
+ throw usage("Use --since with seconds, minutes, hours or days, such as 1h.");
21
+ const factors = { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 };
22
+ const at = now - Number(match[1]) * factors[match[2]];
23
+ if (!Number.isSafeInteger(at) || at < 0)
24
+ throw usage("--since is outside the supported time range.");
25
+ return new Date(at).toISOString();
26
+ }
27
+ /** Same parsed argument and output ports as the existing CLI. No registration
28
+ * occurs here: the launch owner supplies a client only after launch. */
29
+ export async function logsProductCommand(product, argv, args, out, client, options = {}) {
30
+ const verb = argv[0];
31
+ const rest = args.positional.slice(1);
32
+ const flag = (name) => args.flags.get(name)?.at(-1);
33
+ const json = out.json || flag("json") === "true";
34
+ // On a terminal, what customers' programs wrote is shown escaped, JSON
35
+ // included (an escape is still valid JSON); --raw prints it as it is.
36
+ const safe = (text) => (out.tty && flag("raw") !== "true" ? terminalSafe(text) : text);
37
+ const lines = (text) => text.split("\n").map(safe).join("\n");
38
+ const print = (value, text) => out.write(json ? safe(JSON.stringify(value)) : lines(text ?? JSON.stringify(value, null, 2)));
39
+ const need = (value, name) => {
40
+ if (!value)
41
+ throw usage(`Give ${name}.`);
42
+ return value;
43
+ };
44
+ const body = () => {
45
+ try {
46
+ const value = JSON.parse(need(flag("body"), "--body with a JSON object"));
47
+ if (!value || typeof value !== "object" || Array.isArray(value))
48
+ throw usage("--body must be a JSON object.");
49
+ return value;
50
+ }
51
+ catch (error) {
52
+ if (error instanceof RuntimeError)
53
+ throw error;
54
+ throw usage("--body is not valid JSON.");
55
+ }
56
+ };
57
+ const request = {
58
+ ...(options.signal ? { signal: options.signal } : {}),
59
+ ...(flag("idempotency-key") ? { idempotencyKey: flag("idempotency-key") } : {}),
60
+ };
61
+ if (!verb || verb === "help" || verb === "--help") {
62
+ print({ usage: LOGS_HELP }, LOGS_HELP);
63
+ return 0;
64
+ }
65
+ const runtime = await client();
66
+ if ((product === "logs" || product === "traces") && verb === "views") {
67
+ const views = runtime[product].views;
68
+ const action = need(rest[0], "a views command");
69
+ if (action === "ls") {
70
+ const result = await views.list({}, request);
71
+ print({ data: result.data, nextCursor: result.nextCursor });
72
+ }
73
+ else if (action === "create")
74
+ print(await views.create(body(), request));
75
+ else {
76
+ const id = need(rest[1], "a view id");
77
+ if (action === "get")
78
+ print(await views.get(id, request));
79
+ else if (action === "update")
80
+ print(await views.update(id, body(), request));
81
+ else if (action === "delete") {
82
+ await views.delete(id, request);
83
+ print({ deleted: id });
84
+ }
85
+ else
86
+ throw usage(`Unknown views command: ${action}.`);
87
+ }
88
+ return 0;
89
+ }
90
+ if ((product === "logs" || product === "traces") && verb === "ingest") {
91
+ const input = body();
92
+ if (product === "logs") {
93
+ if (!Array.isArray(input.resourceLogs))
94
+ throw usage("OTLP logs need resourceLogs.");
95
+ const resourceLogs = input.resourceLogs;
96
+ print(await runtime.logs.ingest({ resourceLogs }, request));
97
+ }
98
+ else {
99
+ if (!Array.isArray(input.resourceSpans))
100
+ throw usage("OTLP spans need resourceSpans.");
101
+ const resourceSpans = input.resourceSpans;
102
+ print(await runtime.traces.ingest({ resourceSpans }, request));
103
+ }
104
+ return 0;
105
+ }
106
+ if (product === "logs") {
107
+ if (verb === "tail") {
108
+ const selector = need(rest[0], "a selector or app name");
109
+ let query = selector.trim().startsWith("{") ? selector : `{app=${JSON.stringify(selector)}}`;
110
+ if (flag("grep") !== undefined)
111
+ query += ` |= ${JSON.stringify(flag("grep"))}`;
112
+ for await (const event of runtime.logs.tail(query, {
113
+ ...request,
114
+ ...(flag("since") ? { start: since(flag("since"), options.now ?? Date.now()) } : {}),
115
+ })) {
116
+ if (json)
117
+ out.write(safe(JSON.stringify(event)));
118
+ else if (event.gap) {
119
+ const text = safe(`Logs expired between ${event.from} and ${event.to}; continuing from retained logs.`);
120
+ out.write(out.tty ? `\x1b[2m${text}\x1b[0m` : text);
121
+ }
122
+ else {
123
+ const time = safe(event.time);
124
+ const text = safe(typeof event.body === "string" ? event.body : JSON.stringify(event.body));
125
+ out.write(`${out.tty ? `\x1b[2m${time}\x1b[0m` : time} ${safe(event.severityText)} ${text}`);
126
+ }
127
+ await out.drain?.();
128
+ }
129
+ }
130
+ else if (verb === "query") {
131
+ const limit = flag("limit") === undefined ? undefined : Number(flag("limit"));
132
+ if (limit !== undefined && (!Number.isSafeInteger(limit) || limit < 1))
133
+ throw usage("--limit must be a positive integer.");
134
+ print(await runtime.logs.queryRange({
135
+ query: need(rest[0], "a LogQL query"),
136
+ ...(flag("from") ? { start: flag("from") } : {}),
137
+ ...(flag("to") ? { end: flag("to") } : {}),
138
+ ...(limit !== undefined ? { limit } : {}),
139
+ }, request));
140
+ }
141
+ else if (verb === "labels")
142
+ print(await runtime.logs.labels({}, request));
143
+ else if (verb === "values")
144
+ print(await runtime.logs.labelValues(need(rest[0], "a label name"), {}, request));
145
+ else
146
+ throw usage(`Unknown logs command: ${verb}.`);
147
+ }
148
+ else if (product === "traces") {
149
+ if (verb === "get")
150
+ print(await runtime.traces.get(need(rest[0], "a trace id"), request));
151
+ else if (verb === "search")
152
+ print(await runtime.traces.search(need(rest[0], "a TraceQL query"), request));
153
+ else if (verb === "tags")
154
+ print(await runtime.traces.tags(request));
155
+ else
156
+ throw usage(`Unknown traces command: ${verb}.`);
157
+ }
158
+ else {
159
+ if (verb === "ls") {
160
+ const result = await runtime.alerts.list({}, request);
161
+ print({ data: result.data, nextCursor: result.nextCursor });
162
+ }
163
+ else if (verb === "create")
164
+ print(await runtime.alerts.create(body(), request));
165
+ else {
166
+ const id = need(rest[0], "an alert id");
167
+ if (verb === "get")
168
+ print(await runtime.alerts.get(id, request));
169
+ else if (verb === "update")
170
+ print(await runtime.alerts.update(id, body(), request));
171
+ else if (verb === "delete") {
172
+ await runtime.alerts.delete(id, request);
173
+ print({ deleted: id });
174
+ }
175
+ else if (verb === "test")
176
+ print(await runtime.alerts.test(id, request));
177
+ else if (verb === "silence") {
178
+ const until = need(flag("until"), "--until with an RFC3339 time");
179
+ if (!/T.*(?:Z|[+-]\d\d:\d\d)$/.test(until) || !Number.isFinite(Date.parse(until)))
180
+ throw usage("--until must be an RFC3339 time with timezone.");
181
+ print(await runtime.alerts.silence(id, { until, ...(flag("reason") ? { reason: flag("reason") } : {}) }, request));
182
+ }
183
+ else
184
+ throw usage(`Unknown alerts command: ${verb}.`);
185
+ }
186
+ }
187
+ return 0;
188
+ }
@@ -22,9 +22,9 @@ export declare function lostOutput(result: {
22
22
  stdoutTruncated?: boolean;
23
23
  stderrTruncated?: boolean;
24
24
  }, id: string): string | undefined;
25
- /** Feeds `runtime sandbox shell` from standard input. When piped input ends,
25
+ /** Feeds `runtime sandboxes shell` from standard input. When piped input ends,
26
26
  * the shell is told so the way a terminal says it, Ctrl-D, after a newline
27
- * if the last line had none: `echo ls | runtime sandbox shell <id>` runs `ls`
27
+ * if the last line had none: `echo ls | runtime sandboxes shell <id>` runs `ls`
28
28
  * and exits, as ssh does, where it used to wait forever (25 September 2026).
29
29
  * A keyboard never ends, so a terminal's input is left alone. Returns the
30
30
  * function that unhooks it. */
@@ -33,7 +33,7 @@ export declare function pipeShellInput(stdin: NodeJS.ReadableStream & {
33
33
  }, terminal: {
34
34
  write(data: Uint8Array | string): void;
35
35
  }): () => void;
36
- /** `runtime snapshot get`, as `runtime sandbox get` reads: one fact a line.
36
+ /** `runtime snapshots get`, as `runtime sandboxes get` reads: one fact a line.
37
37
  * It printed raw JSON until 25 September 2026; `--json` still does. */
38
38
  export declare function describeSnapshot(s: Snapshot, table: (rows: string[][]) => string): string;
39
39
  export {};
@@ -13,12 +13,12 @@ export async function recordCommand(sbx, argv, out, kit) {
13
13
  ...(integer(args, "max-seconds") ? { maxSeconds: integer(args, "max-seconds") } : {}),
14
14
  ...(integer(args, "max-mib") ? { maxMiB: integer(args, "max-mib") } : {}),
15
15
  });
16
- print(`Recording ${rec.id}. Stop it: ${me} sandbox desktop ${sbx.id} record stop ${rec.id}`, rec);
16
+ print(`Recording ${rec.id}. Stop it: ${me} sandboxes desktop ${sbx.id} record stop ${rec.id}`, rec);
17
17
  return 0;
18
18
  }
19
19
  case "stop": {
20
20
  const rec = await r.stop(need(rest[0], "the recording id"));
21
- print(`Stopped ${rec.id}: ${(rec.bytes / 1_048_576).toFixed(1)} MiB (${rec.reason ?? "stopped"}). Fetch it: ${me} sandbox desktop ${sbx.id} record fetch ${rec.id}`, rec);
21
+ print(`Stopped ${rec.id}: ${(rec.bytes / 1_048_576).toFixed(1)} MiB (${rec.reason ?? "stopped"}). Fetch it: ${me} sandboxes desktop ${sbx.id} record fetch ${rec.id}`, rec);
22
22
  return 0;
23
23
  }
24
24
  case "ls": {
@@ -47,7 +47,7 @@ export async function recordCommand(sbx, argv, out, kit) {
47
47
  print("Deleted.", await r.delete(need(rest[0], "the recording id")));
48
48
  return 0;
49
49
  }
50
- throw usage(`${me} sandbox desktop <id> record start|stop|ls|fetch|rm`);
50
+ throw usage(`${me} sandboxes desktop <id> record start|stop|ls|fetch|rm`);
51
51
  }
52
52
  export async function watchCommand(sbx, argv, out, kit) {
53
53
  const { parse, flag, has, need } = kit;
@@ -110,7 +110,7 @@ export async function mcpCommand(sbx, argv, out, kit) {
110
110
  case "start": {
111
111
  const args = parse(rest, ["replace"], ["secret", "env", "option", "port"]);
112
112
  if (!args.positional.length)
113
- throw usage("Name the servers: runtime sandbox mcp <id> start github fetch");
113
+ throw usage("Name the servers: runtime sandboxes mcp <id> start github fetch");
114
114
  const scoped = (name) => {
115
115
  const found = {};
116
116
  for (const [key, value] of Object.entries(pairs(args, name))) {
@@ -134,7 +134,7 @@ export async function mcpCommand(sbx, argv, out, kit) {
134
134
  ...(integer(args, "port") ? { port: integer(args, "port") } : {}),
135
135
  ...(has(args, "replace") ? { replace: true } : {}),
136
136
  });
137
- print(`${show(gw)}\nServers are installing; \`${me} sandbox mcp ${sbx.id}\` shows when they are ready.`, gw);
137
+ print(`${show(gw)}\nServers are installing; \`${me} sandboxes mcp ${sbx.id}\` shows when they are ready.`, gw);
138
138
  return 0;
139
139
  }
140
140
  case "status": {
@@ -146,7 +146,7 @@ export async function mcpCommand(sbx, argv, out, kit) {
146
146
  print("Stopped.", await sbx.mcp.stop());
147
147
  return 0;
148
148
  }
149
- throw usage(`${me} sandbox mcp <id> start|status|stop, or ${me} sandbox mcp catalog`);
149
+ throw usage(`${me} sandboxes mcp <id> start|status|stop, or ${me} sandboxes mcp catalog`);
150
150
  }
151
151
  /** Standard input when it is piped or redirected from a file, read whole, up
152
152
  * to the API's 1 MiB; undefined for a terminal. A pipe that says nothing for
@@ -184,7 +184,7 @@ export async function pipedStdin(options = {}) {
184
184
  clearTimeout(quiet);
185
185
  size += chunk.length;
186
186
  if (size > limit)
187
- return done(undefined, Object.assign(new Error(`Standard input is over ${limit / 1_048_576} MiB. Copy the file in with \`${me} sandbox cp\` and read it there.`), { code: "usage" }));
187
+ return done(undefined, Object.assign(new Error(`Standard input is over ${limit / 1_048_576} MiB. Copy the file in with \`${me} sandboxes cp\` and read it there.`), { code: "usage" }));
188
188
  chunks.push(chunk);
189
189
  };
190
190
  const onEnd = () => done(size ? new Uint8Array(Buffer.concat(chunks)) : undefined);
@@ -201,11 +201,11 @@ export async function pipedStdin(options = {}) {
201
201
  export function lostOutput(result, id) {
202
202
  if (!result.stdoutTruncated && !result.stderrTruncated)
203
203
  return undefined;
204
- return `runtime: some of the command's output was lost: it wrote faster than it was read, and the sandbox keeps the latest 1 MiB. Write it to a file and copy it out: ${me} sandbox cp ${id}:/workspace/out.txt .`;
204
+ return `runtime: some of the command's output was lost: it wrote faster than it was read, and the sandbox keeps the latest 1 MiB. Write it to a file and copy it out: ${me} sandboxes cp ${id}:/workspace/out.txt .`;
205
205
  }
206
- /** Feeds `runtime sandbox shell` from standard input. When piped input ends,
206
+ /** Feeds `runtime sandboxes shell` from standard input. When piped input ends,
207
207
  * the shell is told so the way a terminal says it, Ctrl-D, after a newline
208
- * if the last line had none: `echo ls | runtime sandbox shell <id>` runs `ls`
208
+ * if the last line had none: `echo ls | runtime sandboxes shell <id>` runs `ls`
209
209
  * and exits, as ssh does, where it used to wait forever (25 September 2026).
210
210
  * A keyboard never ends, so a terminal's input is left alone. Returns the
211
211
  * function that unhooks it. */
@@ -229,7 +229,7 @@ export function pipeShellInput(stdin, terminal) {
229
229
  stdin.off("end", onEnd);
230
230
  };
231
231
  }
232
- /** `runtime snapshot get`, as `runtime sandbox get` reads: one fact a line.
232
+ /** `runtime snapshots get`, as `runtime sandboxes get` reads: one fact a line.
233
233
  * It printed raw JSON until 25 September 2026; `--json` still does. */
234
234
  export function describeSnapshot(s, table) {
235
235
  const mib = (bytes) => bytes === null ? "-" : `${Math.ceil(bytes / 1_048_576).toLocaleString("en-US")} MiB`;
@@ -249,7 +249,12 @@ export function describeSnapshot(s, table) {
249
249
  : s.durability.state,
250
250
  ],
251
251
  ["created", s.createdAt],
252
- ["kept until", `${s.expiresAt} (${s.retentionDays} days)`],
252
+ [
253
+ "kept until",
254
+ s.expiresAt === null
255
+ ? "kept while you have credit"
256
+ : `${s.expiresAt} (${s.retentionDays} days)`,
257
+ ],
253
258
  [
254
259
  "labels",
255
260
  Object.entries(labels)
@@ -1,4 +1,5 @@
1
- import type { RuntimeError } from "./errors.js";
1
+ import { RuntimeError } from "./errors.js";
2
+ import type { ProductName } from "./product-names.js";
2
3
  import type { SandboxMetrics } from "./products/observability.js";
3
4
  /** A refusal of what a command sent, reworded for the command line: every
4
5
  * issue named by its option, since the CLI never prints details.issues, and
@@ -25,3 +26,11 @@ export declare function newestCpu(m: SandboxMetrics, now?: number): {
25
26
  * help` filed "help" as feedback, and `runtime support help` sent it to
26
27
  * support, until 30 September 2026. */
27
28
  export declare function topicHelp(help: string, command: string): string | null;
29
+ /** A command line's product words (CLOUD.md section 4): the plural, or the
30
+ * singular as a typing shortcut, reach the product silently; an old word
31
+ * reaches it with `warning` to print, and from its date throws name_retired
32
+ * naming the new command. Anything else passes through. */
33
+ export declare function productWords(argv: string[], now?: Date, table?: readonly ProductName[]): {
34
+ argv: string[];
35
+ warning?: string;
36
+ };
package/dist/cli-words.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { RuntimeError } from "./errors.js";
2
+ import { cliWords } from "./product-renames.js";
1
3
  /* What the CLI says in its own words where the API's would mislead: the
2
4
  option a refusal is about rather than the request field it was sent as,
3
5
  one fact a line for a resource read, and the newest CPU reading a
@@ -133,3 +135,19 @@ export function topicHelp(help, command) {
133
135
  }
134
136
  return out.length ? out.join("\n") : null;
135
137
  }
138
+ /** A command line's product words (CLOUD.md section 4): the plural, or the
139
+ * singular as a typing shortcut, reach the product silently; an old word
140
+ * reaches it with `warning` to print, and from its date throws name_retired
141
+ * naming the new command. Anything else passes through. */
142
+ export function productWords(argv, now = new Date(), table) {
143
+ const found = cliWords(argv, now, table);
144
+ if (!found)
145
+ return { argv };
146
+ const { use } = found;
147
+ if (use.kind === "retired")
148
+ throw new RuntimeError({ ...use.error });
149
+ return {
150
+ argv: found.args,
151
+ ...(use.kind === "renamed" ? { warning: use.warning } : {}),
152
+ };
153
+ }
package/dist/cli.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { RuntimeError } from "./errors.js";
3
- import type { Usage } from "./types.js";
3
+ import type { OutOfMemory, Usage } from "./types.js";
4
4
  import type { SwitchProvider } from "./products/switching.js";
5
- import type { SecretRule } from "./products/secrets.js";
5
+ import type { SecretRule, SecretScope } from "./products/secrets.js";
6
6
  type Out = {
7
7
  json: boolean;
8
8
  write: (text: string) => void;
@@ -30,7 +30,7 @@ declare const kit: {
30
30
  };
31
31
  export type CliKit = typeof kit;
32
32
  export declare function run(argv: string[], env?: NodeJS.ProcessEnv, out?: Out): Promise<number>;
33
- /** `runtime usage` for a person: the balance, the included usage and what each kind
33
+ /** `runtime usage` for a person: the balance, the free usage and what each kind
34
34
  * of resource was charged, in dollars. --json keeps every figure, and each
35
35
  * resource with its rates and CPU time. */
36
36
  /** One row per resource, for a spreadsheet: when it ran, how big it was, how
@@ -38,14 +38,33 @@ export declare function run(argv: string[], env?: NodeJS.ProcessEnv, out?: Out):
38
38
  * microdollar (the API's integer microdollars moved six places), so a column
39
39
  * sums to the ledger to the last digit. */
40
40
  export declare function usageCsv(u: Usage): string;
41
+ /** What a person reads when the sandbox's out-of-memory killer ended a
42
+ * command: the API's message and hint, on standard error. A bare exit -9 (or
43
+ * 137) said nothing of memory until 9 October 2026. */
44
+ export declare function outOfMemoryLines(memory: OutOfMemory | undefined): string | undefined;
41
45
  /** How to move a rival's code across, when one import does it. */
42
46
  export declare function dropInLines(provider: SwitchProvider | undefined): string[];
47
+ /** A secret's value, from standard input only, so it never sits in shell
48
+ * history or a process list. Piped: everything, less one trailing newline.
49
+ * A terminal: one line, not shown. */
50
+ /** A pasted secret as the header it becomes: no line breaks (a token copied
51
+ * from a terminal arrives wrapped), no bracketed-paste markers, no space at
52
+ * either end. 13 October 2026: Marc's pasted Claude token was refused as
53
+ * "not visible ASCII". A header cannot hold a line break, so none is lost. */
54
+ export declare function cleanSecret(raw: string): string;
43
55
  /** `--allow` values as secret rules: each is `[METHOD[,METHOD]...] PATH`, and
44
56
  * rules naming the same methods share one rule's paths. Only the shape is read
45
57
  * here; the API holds every rule to the same check the host makes
46
58
  * (ARCHITECTURE.md section 10, "Per-path rules on an egress secret") and says
47
59
  * which one it refused. */
48
60
  export declare function parseSecretRules(values: string[]): SecretRule[];
61
+ /** `--only-label role=hub,tier=x --only-sandbox <id>`: a sandbox whose labels
62
+ * hold every pair of any one --only-label gets the secret, and so does each
63
+ * --only-sandbox. `--all-sandboxes`: every sandbox again. Nothing: undefined,
64
+ * so a replacement keeps the scope it has. */
65
+ export declare function parseSecretScope(labels: string[] | undefined, sandboxes: string[] | undefined, all: boolean): SecretScope | undefined;
66
+ /** Who gets a secret, in a few words. */
67
+ export declare const scopeText: (only: SecretScope | undefined) => string;
49
68
  export declare function secretsCommand(argv: string[], env: NodeJS.ProcessEnv, out: Out,
50
69
  /** Tests give the value here; the CLI reads standard input. */
51
70
  read?: () => Promise<string>): Promise<number>;