withruntime 0.7.1 → 0.7.2

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.
@@ -467,6 +467,11 @@ export function networkRules(policy) {
467
467
  if (policy.mode === "deny-all")
468
468
  return { internet: false };
469
469
  const allow = [...(policy.allowedHosts ?? []), ...(policy.allowedCIDRs ?? [])];
470
+ // `custom` is an allow list; one that allows nothing is `deny-all` in the
471
+ // harness's own terms. Runtime reads an empty allow as no allow list at all,
472
+ // which would open the whole internet.
473
+ if (!allow.length)
474
+ return { internet: false };
470
475
  const deny = [...(policy.deniedCIDRs ?? [])];
471
476
  return { internet: true, allow, ...(deny.length ? { deny } : {}) };
472
477
  }
package/dist/cli.js CHANGED
@@ -140,7 +140,7 @@ const SANDBOX_HELP = `runtime sandbox <command>
140
140
  [--option SERVER.NAME=value]... [--port 8765] [--replace]
141
141
  Start them; prints each URL and the header to send
142
142
  mcp <id> [status] | mcp <id> stop Their state and fresh URLs; stop them
143
- preview <id> <port> [--public] [--ttl <s>] Share a port at an HTTPS address
143
+ preview <id> <port> [--public] [--ttl <s>] Share a port at an HTTPS address (--public: paid only)
144
144
  preview rotate <id> <port> Refuse every token given out for a private port so
145
145
  far; prints the new token
146
146
  previews <id> Every shared port
@@ -494,7 +494,10 @@ function remote(spec) {
494
494
  return match ? { id: match[1], path: match[2] } : undefined;
495
495
  }
496
496
  export async function run(argv, env = process.env, out = defaultOut(argv)) {
497
- const [first, ...remaining] = argv.filter((arg) => arg !== "--json" || (argv.indexOf("--") !== -1 && argv.indexOf(arg) > argv.indexOf("--")));
497
+ // Only what comes before -- is the CLI's: a --json after it is the
498
+ // command's, whether or not the CLI was given its own.
499
+ const dash = argv.indexOf("--");
500
+ const [first, ...remaining] = argv.filter((arg, index) => arg !== "--json" || (dash !== -1 && index > dash));
498
501
  // Sandbox is one product of several, so its commands live under it
499
502
  // (AGENTS.md, 23 September 2026): `runtime sandbox run`, `sandbox ssh` and
500
503
  // `sandbox port-forward`. The old top-level forms were removed, not aliased.
@@ -980,6 +983,14 @@ function usageSummary(u) {
980
983
  "free trial",
981
984
  `${hours(u.trial.availableMs)} of ${hours(u.trial.totalMs)} hours left${u.trial.reservedMs > 0 ? `, ${hours(u.trial.reservedMs)} held by running sandboxes` : ""}`,
982
985
  ]);
986
+ if (u.outbound) {
987
+ const gib = (bytes) => `${(bytes / 1_073_741_824).toLocaleString("en-US", { maximumFractionDigits: 1 })} GiB`;
988
+ const left = Math.max(0, u.outbound.allowanceBytes - u.outbound.freeBytes);
989
+ rows.push([
990
+ "outbound traffic this month",
991
+ `${gib(u.outbound.sentBytes)} sent, ${gib(left)} of ${gib(u.outbound.allowanceBytes)} free left, ${dollars(micros(u.outbound.chargedMicros))} charged`,
992
+ ]);
993
+ }
983
994
  const kinds = new Map();
984
995
  for (const resource of u.resources ?? []) {
985
996
  const kind = typeof resource.kind === "string" ? resource.kind : "other";
@@ -1044,13 +1055,14 @@ async function outputSoFar(runtime, sandboxId, processId) {
1044
1055
  chunks,
1045
1056
  };
1046
1057
  }
1047
- /** Dollars from integer microdollars: cents from a dollar up, and four
1048
- * places below one, so a few minutes of use does not round to $0.01. */
1058
+ /** Dollars from integer microdollars: cents from a dollar up, four places
1059
+ * below one, and every place below a hundredth of a cent, so a short trial
1060
+ * test never reads as $0.0000. */
1049
1061
  function dollars(micros) {
1050
1062
  const value = BigInt(micros);
1051
1063
  const sign = value < 0n ? "-" : "";
1052
1064
  const size = value < 0n ? -value : value;
1053
- const places = size < 1000000n ? 4 : 2;
1065
+ const places = size >= 1000000n ? 2 : size >= 100n || size === 0n ? 4 : 6;
1054
1066
  const unit = 10n ** BigInt(6 - places);
1055
1067
  const rounded = (size + unit / 2n) / unit;
1056
1068
  const whole = rounded / 10n ** BigInt(places);
@@ -1112,11 +1124,28 @@ function switchingLine(summary, provider) {
1112
1124
  : `${recorded}; its first top-up was not matched. Ask support if you think that is wrong.${once}`;
1113
1125
  }
1114
1126
  }
1127
+ /** Running time in the largest unit that keeps it above zero: a short
1128
+ * trial test is seconds, not "0 hours". */
1129
+ function runningTime(seconds) {
1130
+ const [amount, unit] = seconds < 60
1131
+ ? [seconds, "second"]
1132
+ : seconds < 3600
1133
+ ? [seconds / 60, "minute"]
1134
+ : [seconds / 3600, "hour"];
1135
+ const shown = amount.toLocaleString("en-US", {
1136
+ maximumFractionDigits: unit === "second" ? 1 : 2,
1137
+ });
1138
+ return `${shown} ${unit}${shown === "1" ? "" : "s"}`;
1139
+ }
1115
1140
  /** The sandboxes the free trial paid for, said beside what was priced. */
1116
- function trialNote(trial, all) {
1141
+ function trialNote(trial, all, trialSeconds) {
1117
1142
  if (!trial)
1118
1143
  return "";
1119
- const which = trial === all ? (all === 1 ? "It" : "All of them") : `${trial} of them`;
1144
+ const which = trial === all
1145
+ ? all === 1
1146
+ ? "It"
1147
+ : "All of them"
1148
+ : `${trial} of them, ${runningTime(trialSeconds)} of that time,`;
1120
1149
  return ` ${which} ran on the free trial, which charged nothing; Runtime's side prices ${trial === 1 ? "it" : "them"} at the standard rates, what the same work costs on paid credit.`;
1121
1150
  }
1122
1151
  async function switching(command, argv, env, out) {
@@ -1178,13 +1207,13 @@ async function switching(command, argv, env, out) {
1178
1207
  const lines = c.basis === "usage"
1179
1208
  ? [
1180
1209
  `Your last ${c.window.days} days: ${figures}${pace}`,
1181
- `Priced: ${c.usage.sandboxes.toLocaleString("en-US")} sandbox${c.usage.sandboxes === 1 ? "" : "es"}, ${(c.usage.runSeconds / 3600).toLocaleString("en-US", { maximumFractionDigits: 2 })} hours running.${trialNote(c.usage.trialSandboxes, c.usage.sandboxes)}${c.usage.unpricedSandboxes ? ` ${c.usage.unpricedSandboxes} left out: ${c.rival.name} publishes no price for their size.` : ""}`,
1210
+ `Priced: ${c.usage.sandboxes.toLocaleString("en-US")} sandbox${c.usage.sandboxes === 1 ? "" : "es"}, ${runningTime(c.usage.runSeconds)} running.${trialNote(c.usage.trialSandboxes, c.usage.sandboxes, c.usage.trialRunSeconds)}${c.usage.unpricedSandboxes ? ` ${c.usage.unpricedSandboxes} left out: ${c.rival.name} publishes no price for their size.` : ""}`,
1182
1211
  ]
1183
1212
  : [
1184
1213
  `No settled sandboxes in your last ${c.window.days} days yet, so this prices an example: ${c.usage.sandboxes.toLocaleString("en-US")} runs of ${(c.usage.runSeconds / c.usage.sandboxes).toLocaleString("en-US")} seconds using ${(c.usage.activeCpuSeconds / c.usage.sandboxes).toLocaleString("en-US")} CPU-seconds each.`,
1185
1214
  `The example: ${figures}.`,
1186
1215
  ];
1187
- lines.push(`${c.rival.name}'s published rates, checked ${longDate(`${c.rival.checked}T12:00:00Z`)}: ${c.rival.rates.join("; ")}. Compute only: storage, network, plan fees and free allowances are left out.`);
1216
+ lines.push(`${c.rival.name}'s published rates, checked ${longDate(`${c.rival.checked}T12:00:00Z`)}: ${c.rival.rates.join("; ")}. Compute only, with the disk where a rival bills it while running: storage at rest, network, plan fees and free allowances are left out.`);
1188
1217
  const line = switchingLine(c.switching, Object.hasOwn(SWITCH_FROM, from) ? SWITCH_FROM[from] : undefined);
1189
1218
  if (line)
1190
1219
  lines.push("", line);
@@ -2642,8 +2671,10 @@ ownInterrupt = true) {
2642
2671
  }
2643
2672
  if (out.json)
2644
2673
  out.write(JSON.stringify(result));
2674
+ // Here the output streamed, whose timeout is 24 hours unless one was given
2675
+ // (Sandbox.exec); `--json` answers the result and prints no such line.
2645
2676
  else if (result.timedOut)
2646
- out.error(`runtime: timed out after ${timeout ?? 60} s; the command's process group was killed.`);
2677
+ out.error(`runtime: timed out after ${timeout ? `${timeout} s` : "24 h"}; the command's process group was killed.`);
2647
2678
  // Never drop output silently: say so, and do not exit 0.
2648
2679
  const lost = extras.lostOutput(result, sbx.id);
2649
2680
  if (lost && !out.json)
package/dist/login.d.ts CHANGED
@@ -15,34 +15,11 @@ type LoginOptions = {
15
15
  * at the same machine to press Connect agent. */
16
16
  export declare const AGENT_WAIT_MS = 50000;
17
17
  export declare function authenticationCommand(args: string[], env: NodeJS.ProcessEnv, options?: LoginOptions): Promise<{
18
- connected: boolean;
19
- source: string;
20
- reason?: undefined;
21
- agentName?: undefined;
22
- orgId?: undefined;
23
- disconnected?: undefined;
24
- pending?: undefined;
25
- url?: undefined;
26
- userCode?: undefined;
27
- expiresAt?: undefined;
28
- } | {
29
- connected: boolean;
30
- source?: undefined;
31
- reason?: undefined;
18
+ disconnected: boolean;
19
+ connected?: undefined;
32
20
  agentName?: undefined;
33
21
  orgId?: undefined;
34
- disconnected?: undefined;
35
- pending?: undefined;
36
- url?: undefined;
37
- userCode?: undefined;
38
- expiresAt?: undefined;
39
- } | {
40
- connected: boolean;
41
- reason: string;
42
22
  source?: undefined;
43
- agentName?: undefined;
44
- orgId?: undefined;
45
- disconnected?: undefined;
46
23
  pending?: undefined;
47
24
  url?: undefined;
48
25
  userCode?: undefined;
@@ -51,18 +28,16 @@ export declare function authenticationCommand(args: string[], env: NodeJS.Proces
51
28
  connected: boolean;
52
29
  agentName: string;
53
30
  orgId: string;
54
- source?: undefined;
55
- reason?: undefined;
31
+ source: string;
56
32
  disconnected?: undefined;
57
33
  pending?: undefined;
58
34
  url?: undefined;
59
35
  userCode?: undefined;
60
36
  expiresAt?: undefined;
61
37
  } | {
62
- disconnected: boolean;
63
- connected?: undefined;
64
- source?: undefined;
65
- reason?: undefined;
38
+ connected: boolean;
39
+ source: string;
40
+ disconnected?: undefined;
66
41
  agentName?: undefined;
67
42
  orgId?: undefined;
68
43
  pending?: undefined;
@@ -73,9 +48,8 @@ export declare function authenticationCommand(args: string[], env: NodeJS.Proces
73
48
  connected: boolean;
74
49
  agentName: string;
75
50
  orgId: string;
76
- source: string;
77
- reason?: undefined;
78
51
  disconnected?: undefined;
52
+ source?: undefined;
79
53
  pending?: undefined;
80
54
  url?: undefined;
81
55
  userCode?: undefined;
@@ -86,11 +60,10 @@ export declare function authenticationCommand(args: string[], env: NodeJS.Proces
86
60
  url: string;
87
61
  userCode: string;
88
62
  expiresAt: string;
89
- source?: undefined;
90
- reason?: undefined;
63
+ disconnected?: undefined;
91
64
  agentName?: undefined;
92
65
  orgId?: undefined;
93
- disconnected?: undefined;
66
+ source?: undefined;
94
67
  }>;
95
68
  export type PendingLogin = {
96
69
  /** Open this in a browser, where the account owner approves the agent. */
package/dist/login.js CHANGED
@@ -109,28 +109,6 @@ export async function authenticationCommand(args, env, options = {}) {
109
109
  const notify = options.notify ?? ((message) => process.stderr.write(`${message}\n`));
110
110
  const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
111
111
  const [command, ...rest] = args;
112
- if (command === "auth" && rest.length === 1 && rest[0] === "status") {
113
- if (env.RUNTIME_API_KEY) {
114
- await new Runtime({
115
- apiKey: env.RUNTIME_API_KEY,
116
- baseUrl: origins.api,
117
- fetch: fetcher,
118
- }).usage();
119
- return { connected: true, source: "environment" };
120
- }
121
- const saved = await store.read();
122
- if (!saved)
123
- return { connected: false };
124
- try {
125
- await request("confirm", undefined, saved.key);
126
- }
127
- catch (error) {
128
- if (error instanceof ConnectionError && error.status === 401)
129
- return { connected: false, reason: "revoked" };
130
- throw error;
131
- }
132
- return { connected: true, agentName: saved.agentName, orgId: saved.orgId };
133
- }
134
112
  if (command === "logout" && rest.length === 0) {
135
113
  if (env.RUNTIME_API_KEY)
136
114
  throw new Error("This shell uses RUNTIME_API_KEY. Remove that override before disconnecting the saved connection.");
package/dist/sandbox.d.ts CHANGED
@@ -218,8 +218,9 @@ export declare class Files {
218
218
  */
219
219
  readStream(path: string, options?: RequestOptions): Promise<ReadableStream<Uint8Array>>;
220
220
  readText(path: string, options?: RequestOptions): Promise<string>;
221
- /** Writes a file of any size, atomically, making parent directories. Large
222
- * ones go in parallel 1 MiB chunks checked against their SHA-256. `mode`
221
+ /** Writes a file, atomically, making parent directories. Under /workspace
222
+ * any size, large ones in parallel 1 MiB chunks checked against their
223
+ * SHA-256; elsewhere, with the sandbox user's rights, up to 1 MiB. `mode`
223
224
  * sets its permissions (0o755 for a program); 0o644 when left out. */
224
225
  write(path: string, data: string | Uint8Array, writeOptions?: RequestOptions & {
225
226
  mode?: number;
package/dist/sandbox.js CHANGED
@@ -718,8 +718,9 @@ export class Files {
718
718
  async readText(path, options = {}) {
719
719
  return new TextDecoder().decode(await this.read(path, options));
720
720
  }
721
- /** Writes a file of any size, atomically, making parent directories. Large
722
- * ones go in parallel 1 MiB chunks checked against their SHA-256. `mode`
721
+ /** Writes a file, atomically, making parent directories. Under /workspace
722
+ * any size, large ones in parallel 1 MiB chunks checked against their
723
+ * SHA-256; elsewhere, with the sandbox user's rights, up to 1 MiB. `mode`
723
724
  * sets its permissions (0o755 for a program); 0o644 when left out. */
724
725
  async write(path, data, writeOptions = {}) {
725
726
  const { mode, ...options } = writeOptions;
package/dist/tar.js CHANGED
@@ -1,5 +1,5 @@
1
- import { mkdir, readdir, readFile, lstat, writeFile, symlink, chmod } from "node:fs/promises";
2
- import { join, relative, resolve, sep } from "node:path";
1
+ import { chmod, lstat, mkdir, readdir, readFile, realpath, symlink, unlink, writeFile, } from "node:fs/promises";
2
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
3
3
  import { gunzipSync, gzipSync } from "node:zlib";
4
4
  /* A small ustar writer and reader for directory uploads and downloads, so the
5
5
  SDK needs no dependency. Regular files, directories and symbolic links;
@@ -68,17 +68,62 @@ export async function packDirectory(root) {
68
68
  parts.push(new Uint8Array(1024));
69
69
  return gzipSync(Buffer.concat(parts));
70
70
  }
71
+ /* The archive comes from a sandbox, whose contents the customer's untrusted code
72
+ controls, so nothing in it may write outside `target`. A lexical check on each
73
+ name is not enough: a link `x -> ../outside` followed by `x/file` passes it and
74
+ writes through the link. So no write ever passes through a link, links are made
75
+ only after every file and directory is in place, and each link is then walked
76
+ through the finished tree and removed, failing the unpack, if it leads out. */
77
+ async function linkAt(path) {
78
+ try {
79
+ return (await lstat(path)).isSymbolicLink();
80
+ }
81
+ catch {
82
+ return false;
83
+ }
84
+ }
85
+ /** Throws unless `destination` is inside `root` and no part of the way to it,
86
+ itself included, is a link. */
87
+ async function assertPlain(root, destination, name) {
88
+ if (destination !== root && !destination.startsWith(root + sep))
89
+ throw new Error(`Refusing an archive entry outside the target: ${name}`);
90
+ let path = root;
91
+ for (const part of relative(root, destination).split(sep).filter(Boolean)) {
92
+ path = join(path, part);
93
+ if (await linkAt(path))
94
+ throw new Error(`Refusing an archive entry that passes through a link: ${name}`);
95
+ }
96
+ }
97
+ /** Where following `link` from `directory` leads, or undefined when the way
98
+ leaves `root` or turns at a link before its last step. The last step may
99
+ be a link; that link is checked on its own. */
100
+ async function linkStaysInside(root, directory, link) {
101
+ if (isAbsolute(link))
102
+ return false;
103
+ const parts = link.split("/").filter((part) => part && part !== ".");
104
+ let path = directory;
105
+ for (const [index, part] of parts.entries()) {
106
+ path = part === ".." ? dirname(path) : join(path, part);
107
+ if (path !== root && !path.startsWith(root + sep))
108
+ return false;
109
+ if (index < parts.length - 1 && part !== ".." && (await linkAt(path)))
110
+ return false;
111
+ }
112
+ return true;
113
+ }
71
114
  /** Unpacks into `target`, refusing any entry that would land outside it. */
72
115
  export async function unpackArchive(archive, target) {
73
116
  const data = gunzipSync(archive);
74
- const root = resolve(target);
75
- await mkdir(root, { recursive: true });
117
+ await mkdir(resolve(target), { recursive: true });
118
+ const root = await realpath(resolve(target));
76
119
  const text = (start, length) => {
77
120
  const slice = data.subarray(start, start + length);
78
121
  const end = slice.indexOf(0);
79
122
  return new TextDecoder().decode(end < 0 ? slice : slice.subarray(0, end));
80
123
  };
124
+ const links = [];
81
125
  let longName;
126
+ let longLink;
82
127
  for (let offset = 0; offset + 512 <= data.length;) {
83
128
  if (data.subarray(offset, offset + 512).every((byte) => byte === 0))
84
129
  break;
@@ -86,31 +131,44 @@ export async function unpackArchive(archive, target) {
86
131
  const type = String.fromCharCode(data[offset + 156] ?? 48);
87
132
  const prefix = text(offset + 345, 155);
88
133
  let name = longName ?? (prefix ? `${prefix}/${text(offset, 100)}` : text(offset, 100));
134
+ const link = longLink ?? text(offset + 157, 100);
89
135
  longName = undefined;
136
+ longLink = undefined;
90
137
  const mode = parseInt(text(offset + 100, 8).trim() || "644", 8);
91
- const link = text(offset + 157, 100);
92
138
  const body = data.subarray(offset + 512, offset + 512 + size);
93
139
  offset += 512 + Math.ceil(size / 512) * 512;
94
- if (type === "L") {
95
- longName = new TextDecoder().decode(body).replace(/\0.*$/s, "");
140
+ if (type === "L" || type === "K") {
141
+ const value = new TextDecoder().decode(body).replace(/\0.*$/s, "");
142
+ if (type === "L")
143
+ longName = value;
144
+ else
145
+ longLink = value;
96
146
  continue;
97
147
  }
98
148
  name = name.replace(/^\.\//, "");
99
149
  if (!name || name === "." || name === "./")
100
150
  continue;
101
151
  const destination = resolve(root, name);
102
- if (destination !== root && !destination.startsWith(root + sep))
103
- throw new Error(`Refusing an archive entry outside the target: ${name}`);
152
+ await assertPlain(root, destination, name);
104
153
  if (type === "5")
105
154
  await mkdir(destination, { recursive: true });
106
- else if (type === "2") {
107
- await mkdir(resolve(destination, ".."), { recursive: true });
108
- await symlink(link, destination).catch(() => undefined);
109
- }
155
+ else if (type === "2")
156
+ links.push({ destination, link, name });
110
157
  else if (type === "0" || type === "\0" || type === "7") {
111
- await mkdir(resolve(destination, ".."), { recursive: true });
158
+ await mkdir(dirname(destination), { recursive: true });
112
159
  await writeFile(destination, body);
113
160
  await chmod(destination, mode & 0o777);
114
161
  }
115
162
  }
163
+ for (const { destination, link, name } of links) {
164
+ await assertPlain(root, destination, name);
165
+ await mkdir(dirname(destination), { recursive: true });
166
+ await symlink(link, destination).catch(() => undefined);
167
+ }
168
+ for (const { destination, link, name } of links) {
169
+ if (await linkStaysInside(root, dirname(destination), link))
170
+ continue;
171
+ await unlink(destination).catch(() => undefined);
172
+ throw new Error(`Refusing an archive link that leads outside the target: ${name} -> ${link}`);
173
+ }
116
174
  }
@@ -1,5 +1,5 @@
1
1
  import { RuntimeError } from "./errors.js";
2
- export declare const VERSION = "0.7.1";
2
+ export declare const VERSION = "0.7.2";
3
3
  export declare const DEFAULT_BASE_URL = "https://api.withruntime.com";
4
4
  export type RequestOptions = {
5
5
  /** Sent as Idempotency-Key. Omit it: the SDK makes one per call and keeps
package/dist/transport.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ConnectionError, errorFor, RuntimeError, WAITS_FOR_ROOM } from "./errors.js";
2
2
  import { describeRoute, envFetch, openWebSocket } from "./proxy.js";
3
- export const VERSION = "0.7.1";
3
+ export const VERSION = "0.7.2";
4
4
  export const DEFAULT_BASE_URL = "https://api.withruntime.com";
5
5
  export function apiOrigin(value) {
6
6
  const url = new URL(value);
package/dist/types.d.ts CHANGED
@@ -118,7 +118,8 @@ export type ExecOptions = {
118
118
  env?: Record<string, string>;
119
119
  /** Given to standard input, then closed. */
120
120
  stdin?: string | Uint8Array;
121
- /** Default 60 000; up to 24 hours. A timeout is a result (timedOut), not an error. */
121
+ /** Default 60 000, or 24 hours when the output streams (onStdout, onStderr or
122
+ * execStream); up to 24 hours. A timeout is a result (timedOut), not an error. */
122
123
  timeoutMs?: number;
123
124
  onStdout?: (text: string) => void;
124
125
  onStderr?: (text: string) => void;
@@ -201,6 +202,18 @@ export type Usage = {
201
202
  reservedMs: number;
202
203
  availableMs: number;
203
204
  } | null;
205
+ /** Outbound traffic this calendar month, UTC: the first `allowanceBytes`
206
+ * (100 GiB) an account sends are free, the rest $0.02 per decimal GB. */
207
+ outbound?: {
208
+ month: string;
209
+ sentBytes: number;
210
+ freeBytes: number;
211
+ billableBytes: number;
212
+ allowanceBytes: number;
213
+ chargedMicros: string;
214
+ /** What the balance or a spending limit could not cover; never charged. */
215
+ writtenOffMicros: string;
216
+ } | null;
204
217
  resources: Array<Record<string, unknown>>;
205
218
  [key: string]: unknown;
206
219
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "withruntime",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "license": "Apache-2.0",
5
5
  "mcpName": "com.withruntime/runtime",
6
6
  "type": "module",
@@ -76,7 +76,7 @@
76
76
  "prepack": "bun run build",
77
77
  "check-types": "tsc --noEmit",
78
78
  "lint": "eslint",
79
- "test": "bun test && bun scripts/test-python.ts && bun scripts/test-go.ts && bun ../../sdks/java/scripts/check.ts && bun ../../sdks/ruby/scripts/check.ts",
79
+ "test": "bun test --parallel && bun scripts/test-python.ts && bun scripts/test-go.ts && bun ../../sdks/java/scripts/check.ts && bun ../../sdks/ruby/scripts/check.ts",
80
80
  "test:java": "bun ../../sdks/java/scripts/check.ts",
81
81
  "test:ruby": "bun ../../sdks/ruby/scripts/check.ts",
82
82
  "test:python": "bun scripts/test-python.ts",