withruntime 0.6.0 → 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 (47) hide show
  1. package/E2B.md +32 -32
  2. package/README.md +8 -5
  3. package/dist/ai/index.d.ts +21 -5
  4. package/dist/ai/index.js +14 -21
  5. package/dist/ai-harness/index.d.ts +194 -0
  6. package/dist/ai-harness/index.js +517 -0
  7. package/dist/billing-cli.d.ts +21 -0
  8. package/dist/billing-cli.js +158 -0
  9. package/dist/cli-extras.d.ts +15 -0
  10. package/dist/cli-extras.js +60 -4
  11. package/dist/cli-name.d.ts +6 -0
  12. package/dist/cli-name.js +8 -0
  13. package/dist/cli.d.ts +17 -1
  14. package/dist/cli.js +537 -130
  15. package/dist/client.d.ts +35 -6
  16. package/dist/client.js +23 -0
  17. package/dist/credentials.js +1 -2
  18. package/dist/daytona/filesystem.d.ts +2 -0
  19. package/dist/daytona/filesystem.js +9 -7
  20. package/dist/e2b/filesystem.js +3 -2
  21. package/dist/e2b/sandbox.d.ts +9 -6
  22. package/dist/e2b/sandbox.js +31 -7
  23. package/dist/errors.d.ts +5 -0
  24. package/dist/errors.js +7 -0
  25. package/dist/index.d.ts +8 -6
  26. package/dist/index.js +1 -0
  27. package/dist/keys.js +1 -1
  28. package/dist/login.d.ts +2 -6
  29. package/dist/login.js +3 -8
  30. package/dist/network-cli.d.ts +1 -1
  31. package/dist/network-cli.js +64 -2
  32. package/dist/products/billing.d.ts +35 -0
  33. package/dist/products/billing.js +13 -0
  34. package/dist/products/index.d.ts +4 -1
  35. package/dist/products/index.js +4 -1
  36. package/dist/products/network-products.d.ts +45 -0
  37. package/dist/products/network-products.js +27 -0
  38. package/dist/products/previews.d.ts +1 -2
  39. package/dist/products/secrets.d.ts +14 -0
  40. package/dist/sandbox.d.ts +17 -3
  41. package/dist/sandbox.js +233 -62
  42. package/dist/transport.d.ts +17 -1
  43. package/dist/transport.js +124 -7
  44. package/dist/types.d.ts +0 -2
  45. package/dist/wallet.d.ts +30 -0
  46. package/dist/wallet.js +48 -0
  47. package/package.json +44 -17
@@ -0,0 +1,158 @@
1
+ import { RuntimeError } from "./errors.js";
2
+ import { named } from "./cli-name.js";
3
+ export const BILLING_HELP = `runtime billing <command>
4
+
5
+ topup <usd> [--wait] Buy credit with stablecoins (USDC on Base, Solana or Tempo).
6
+ Prints an address per network and the exact amount to send.
7
+ Send exactly that, from any wallet, before the time shown.
8
+ --wait keeps checking until the credit lands.
9
+ status <id> [--wait] Whether a top-up's payment has arrived
10
+
11
+ No account yet? An agent can open one with a wallet alone:
12
+ topup <usd> --new-account --accept-terms [--wait]
13
+ Opens an account and its first top-up. Prints a claim code,
14
+ shown once. --wait saves the key here once the money lands.
15
+ claim <code> [--wait] Exchange a paid account's claim code for its key, saved here
16
+
17
+ $10 to $10,000 a top-up; $500 in an account's first week. By card:
18
+ https://withruntime.com/account/billing
19
+ `;
20
+ function usage(message) {
21
+ return new RuntimeError({
22
+ message,
23
+ code: "usage",
24
+ status: 0,
25
+ hint: `Run \`${named("runtime")} billing help\`.`,
26
+ });
27
+ }
28
+ const DONE = new Set(["paid", "failed", "expired"]);
29
+ function describe(topup) {
30
+ const me = named("runtime");
31
+ if (topup.status === "paid")
32
+ return `Paid: $${topup.amountUsd} of credit is on the account.`;
33
+ if (topup.status === "expired")
34
+ return `Expired: no payment arrived in time. Open a new one with \`${me} billing topup ${topup.amountUsd}\`.`;
35
+ if (topup.status === "failed")
36
+ return "Failed. Nothing was credited.";
37
+ if (topup.status === "pending")
38
+ return `Stripe has seen the payment and is confirming it. Check again with \`${me} billing status ${topup.purchaseId}\`.`;
39
+ const lines = [`Send exactly ${topup.amount} of one of these tokens, on one network:`, ""];
40
+ for (const network of topup.networks)
41
+ for (const token of network.tokens)
42
+ lines.push(` ${network.network.padEnd(7)} ${token.currency.toUpperCase().padEnd(6)} to ${network.address}`, ` ${"".padEnd(7)} ${"".padEnd(6)} token ${token.contract}`);
43
+ lines.push("", ...(topup.payBy ? [`Send before ${topup.payBy}.`] : []), "A different amount, token or network cannot be matched or returned automatically.", `The credit lands a few minutes after the transfer: \`${me} billing status ${topup.purchaseId}\`.`);
44
+ return lines.join("\n");
45
+ }
46
+ const FLAGS = new Set(["wait", "new-account", "accept-terms"]);
47
+ export async function billingCommand(argv, args, out, client, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), wallet) {
48
+ const [verb, value] = args.positional;
49
+ const has = (name) => args.flags.get(name)?.at(-1) === "true";
50
+ const wait = has("wait");
51
+ for (const name of args.flags.keys())
52
+ if (!FLAGS.has(name))
53
+ throw usage(`Unknown option --${name}.`);
54
+ if (!verb || verb === "help") {
55
+ out.write(BILLING_HELP);
56
+ return 0;
57
+ }
58
+ if ((verb === "topup" && has("new-account")) || verb === "claim") {
59
+ if (!wallet)
60
+ throw usage("Wallet accounts are not available here.");
61
+ return walletCommand(verb, value, has("accept-terms"), wait, out, sleep, wallet);
62
+ }
63
+ const api = (await client()).billing;
64
+ let topup;
65
+ if (verb === "topup") {
66
+ topup = await api.topup({ usd: dollarsOf(value) }, { idempotencyKey: crypto.randomUUID() });
67
+ }
68
+ else if (verb === "status") {
69
+ if (!value)
70
+ throw usage("Give the top-up's id.");
71
+ topup = await api.topupStatus(value);
72
+ }
73
+ else {
74
+ throw usage(`Unknown billing command ${verb}.`);
75
+ }
76
+ const shown = new Set();
77
+ const show = (current) => {
78
+ if (out.json)
79
+ out.write(JSON.stringify(current));
80
+ else if (!shown.has(current.status))
81
+ out.write(describe(current));
82
+ shown.add(current.status);
83
+ };
84
+ show(topup);
85
+ if (!wait)
86
+ return 0;
87
+ /* Stripe takes a few minutes to see a transfer; checking every ten seconds
88
+ is quick enough and costs nothing. Stops once the top-up has an end, or
89
+ an hour after its deadline. */
90
+ const until = (topup.payBy ? Date.parse(topup.payBy) : Date.now()) + 60 * 60_000;
91
+ while (!DONE.has(topup.status) && Date.now() < until) {
92
+ await sleep(10_000);
93
+ topup = await api.topupStatus(topup.purchaseId);
94
+ show(topup);
95
+ }
96
+ return topup.status === "paid" ? 0 : 1;
97
+ }
98
+ function dollarsOf(value) {
99
+ const usd = Number(value?.replace(/^\$/, ""));
100
+ if (!value || !Number.isFinite(usd) || usd <= 0)
101
+ throw usage("Say how many dollars of credit to buy, such as `billing topup 20`.");
102
+ return usd;
103
+ }
104
+ /** `topup <usd> --new-account` and `claim <code>`: an account with no key. */
105
+ async function walletCommand(verb, value, acceptTerms, wait, out, sleep, wallet) {
106
+ const me = named("runtime");
107
+ let code;
108
+ let payBy = null;
109
+ if (verb === "topup") {
110
+ if (!acceptTerms)
111
+ throw usage("Opening an account means agreeing to https://withruntime.com/legal/terms. Add --accept-terms once the person you work for agrees.");
112
+ const opened = await wallet.open({ usd: dollarsOf(value), acceptTerms: true });
113
+ code = opened.claimCode;
114
+ payBy = opened.payBy;
115
+ if (out.json)
116
+ out.write(JSON.stringify(opened));
117
+ else
118
+ out.write([
119
+ describe(opened),
120
+ "",
121
+ `Claim code (shown once, keep it secret): ${code}`,
122
+ `Once paid: \`${me} billing claim ${code}\` gives this machine the account's key.`,
123
+ "A person can take the account over at https://withruntime.com/account/claim with the same code.",
124
+ ].join("\n"));
125
+ if (!wait)
126
+ return 0;
127
+ }
128
+ else {
129
+ if (!value)
130
+ throw usage("Give the claim code.");
131
+ code = value;
132
+ }
133
+ const until = (payBy ? Date.parse(payBy) : Date.now()) + 60 * 60_000;
134
+ for (let first = verb === "claim";; first = false) {
135
+ if (!first)
136
+ await sleep(10_000);
137
+ const claim = await wallet.claim(code);
138
+ if (claim.status === "paid") {
139
+ await wallet.save(claim.apiKey, claim.orgId);
140
+ out.write(out.json
141
+ ? JSON.stringify({ status: "paid", orgId: claim.orgId, connected: true })
142
+ : "Paid. This machine is connected to the new account; its key is saved, not shown.");
143
+ return 0;
144
+ }
145
+ if (claim.status === "expired" || claim.status === "failed") {
146
+ out.write(out.json
147
+ ? JSON.stringify(claim)
148
+ : `The top-up ${claim.status}: nothing was credited. Open a new one with \`${me} billing topup <usd> --new-account --accept-terms\`.`);
149
+ return 1;
150
+ }
151
+ if (!wait || Date.now() >= until) {
152
+ out.write(out.json
153
+ ? JSON.stringify(claim)
154
+ : `Not paid yet (${claim.status}). Run \`${me} billing claim ${code}\` again once the transfer has gone through.`);
155
+ return wait ? 1 : 0;
156
+ }
157
+ }
158
+ }
@@ -1,5 +1,6 @@
1
1
  import type { CliKit } from "./cli.js";
2
2
  import type { Sandbox } from "./sandbox.js";
3
+ import type { Snapshot } from "./snapshots.js";
3
4
  type Out = {
4
5
  json: boolean;
5
6
  write: (text: string) => void;
@@ -21,4 +22,18 @@ export declare function lostOutput(result: {
21
22
  stdoutTruncated?: boolean;
22
23
  stderrTruncated?: boolean;
23
24
  }, id: string): string | undefined;
25
+ /** Feeds `runtime sandbox shell` from standard input. When piped input ends,
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`
28
+ * and exits, as ssh does, where it used to wait forever (25 September 2026).
29
+ * A keyboard never ends, so a terminal's input is left alone. Returns the
30
+ * function that unhooks it. */
31
+ export declare function pipeShellInput(stdin: NodeJS.ReadableStream & {
32
+ isTTY?: boolean;
33
+ }, terminal: {
34
+ write(data: Uint8Array | string): void;
35
+ }): () => void;
36
+ /** `runtime snapshot get`, as `runtime sandbox get` reads: one fact a line.
37
+ * It printed raw JSON until 25 September 2026; `--json` still does. */
38
+ export declare function describeSnapshot(s: Snapshot, table: (rows: string[][]) => string): string;
24
39
  export {};
@@ -1,5 +1,5 @@
1
1
  import { writeFile } from "node:fs/promises";
2
- import { me } from "./login.js";
2
+ import { me } from "./cli-name.js";
3
3
  export async function recordCommand(sbx, argv, out, kit) {
4
4
  const { parse, integer, need, table, usage } = kit;
5
5
  const [action, ...rest] = argv;
@@ -7,7 +7,7 @@ export async function recordCommand(sbx, argv, out, kit) {
7
7
  const r = sbx.desktop.recordings;
8
8
  switch (action) {
9
9
  case "start": {
10
- const args = parse(rest);
10
+ const args = parse(rest, [], ["fps", "max-seconds", "max-mib"]);
11
11
  const rec = await r.start({
12
12
  ...(integer(args, "fps") ? { fps: integer(args, "fps") } : {}),
13
13
  ...(integer(args, "max-seconds") ? { maxSeconds: integer(args, "max-seconds") } : {}),
@@ -51,7 +51,7 @@ export async function recordCommand(sbx, argv, out, kit) {
51
51
  }
52
52
  export async function watchCommand(sbx, argv, out, kit) {
53
53
  const { parse, flag, has, need } = kit;
54
- const args = parse(argv, ["recursive"]);
54
+ const args = parse(argv, ["recursive"], ["events", "include", "exclude"]);
55
55
  const path = need(args.positional[0], "a directory, e.g. /workspace");
56
56
  const events = flag(args, "events")?.split(",");
57
57
  const watch = await sbx.files.watch(path, (event) => {
@@ -108,7 +108,7 @@ export async function mcpCommand(sbx, argv, out, kit) {
108
108
  };
109
109
  switch (action) {
110
110
  case "start": {
111
- const args = parse(rest, ["replace"]);
111
+ const args = parse(rest, ["replace"], ["secret", "env", "option", "port"]);
112
112
  if (!args.positional.length)
113
113
  throw usage("Name the servers: runtime sandbox mcp <id> start github fetch");
114
114
  const scoped = (name) => {
@@ -203,3 +203,59 @@ export function lostOutput(result, id) {
203
203
  return undefined;
204
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 .`;
205
205
  }
206
+ /** Feeds `runtime sandbox shell` from standard input. When piped input ends,
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`
209
+ * and exits, as ssh does, where it used to wait forever (25 September 2026).
210
+ * A keyboard never ends, so a terminal's input is left alone. Returns the
211
+ * function that unhooks it. */
212
+ export function pipeShellInput(stdin, terminal) {
213
+ let last = 10;
214
+ const onData = (chunk) => {
215
+ const bytes = typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk;
216
+ if (bytes.length)
217
+ last = bytes[bytes.length - 1];
218
+ terminal.write(new Uint8Array(bytes));
219
+ };
220
+ const onEnd = () => {
221
+ if (stdin.isTTY)
222
+ return;
223
+ terminal.write(last === 10 ? "\x04" : "\n\x04");
224
+ };
225
+ stdin.on("data", onData);
226
+ stdin.on("end", onEnd);
227
+ return () => {
228
+ stdin.off("data", onData);
229
+ stdin.off("end", onEnd);
230
+ };
231
+ }
232
+ /** `runtime snapshot get`, as `runtime sandbox get` reads: one fact a line.
233
+ * It printed raw JSON until 25 September 2026; `--json` still does. */
234
+ export function describeSnapshot(s, table) {
235
+ const mib = (bytes) => bytes === null ? "-" : `${Math.ceil(bytes / 1_048_576).toLocaleString("en-US")} MiB`;
236
+ const labels = (s.labels ?? {});
237
+ return table([
238
+ ["id", s.id],
239
+ ["name", s.name ?? "-"],
240
+ ["state", s.state],
241
+ ["from", s.sourceSandboxId],
242
+ ["shape", `${s.shape.vcpu} vCPU, ${s.shape.memoryMiB} MiB memory, ${s.shape.diskMiB} MiB disk`],
243
+ ["stored", mib(s.storedBytes)],
244
+ ["billed for", mib(s.meteredBytes)],
245
+ [
246
+ "backed up",
247
+ s.backedUp
248
+ ? `yes${s.durability.durableAt ? `, ${s.durability.durableAt}` : ""}`
249
+ : s.durability.state,
250
+ ],
251
+ ["created", s.createdAt],
252
+ ["kept until", `${s.expiresAt} (${s.retentionDays} days)`],
253
+ [
254
+ "labels",
255
+ Object.entries(labels)
256
+ .map(([k, v]) => `${k}=${v}`)
257
+ .join(", ") || "-",
258
+ ],
259
+ ...(s.error ? [["error", s.error]] : []),
260
+ ]);
261
+ }
@@ -0,0 +1,6 @@
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`. Its own module
3
+ * so a command that only names itself does not load the login flow. */
4
+ export declare const me: string;
5
+ /** Text that names a command, with the command spelled the way it was run. */
6
+ export declare function named(text: string, name?: string): string;
@@ -0,0 +1,8 @@
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`. Its own module
3
+ * so a command that only names itself does not load the login flow. */
4
+ export const me = /[\\/]_npx[\\/]/.test(process.argv[1] ?? "") ? "npx withruntime" : "runtime";
5
+ /** Text that names a command, with the command spelled the way it was run. */
6
+ export function named(text, name = me) {
7
+ return name === "runtime" ? text : text.replace(/(^|[\s`(])runtime(?= [a-z<[])/g, `$1${name}`);
8
+ }
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { RuntimeError } from "./errors.js";
3
+ import type { Usage } from "./types.js";
4
+ import type { SecretRule } from "./products/secrets.js";
3
5
  type Out = {
4
6
  json: boolean;
5
7
  write: (text: string) => void;
@@ -12,7 +14,7 @@ type Args = {
12
14
  };
13
15
  /** What the commands in cli-extras.ts use of this file. */
14
16
  declare const kit: {
15
- parse: (argv: string[], booleans?: string[]) => Args;
17
+ parse: (argv: string[], booleans: readonly string[], values: readonly string[]) => Args;
16
18
  flag: (args: Args, name: string) => string | undefined;
17
19
  has: (args: Args, name: string) => boolean;
18
20
  integer: (args: Args, name: string) => number | undefined;
@@ -25,6 +27,20 @@ declare const kit: {
25
27
  };
26
28
  export type CliKit = typeof kit;
27
29
  export declare function run(argv: string[], env?: NodeJS.ProcessEnv, out?: Out): Promise<number>;
30
+ /** `runtime usage` for a person: the balance, the trial and what each kind
31
+ * of resource was charged, in dollars. --json keeps every figure, and each
32
+ * resource with its rates and CPU time. */
33
+ /** One row per resource, for a spreadsheet: when it ran, how big it was, how
34
+ * long it ran, the CPU it used and what it cost. Money is exact dollars to the
35
+ * microdollar (the API's integer microdollars moved six places), so a column
36
+ * sums to the ledger to the last digit. */
37
+ export declare function usageCsv(u: Usage): string;
38
+ /** `--allow` values as secret rules: each is `[METHOD[,METHOD]...] PATH`, and
39
+ * rules naming the same methods share one rule's paths. Only the shape is read
40
+ * here; the API holds every rule to the same check the host makes
41
+ * (ARCHITECTURE.md section 10, "Per-path rules on an egress secret") and says
42
+ * which one it refused. */
43
+ export declare function parseSecretRules(values: string[]): SecretRule[];
28
44
  export declare function secretsCommand(argv: string[], env: NodeJS.ProcessEnv, out: Out,
29
45
  /** Tests give the value here; the CLI reads standard input. */
30
46
  read?: () => Promise<string>): Promise<number>;