withruntime 0.5.1 → 0.6.1

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/LICENSE +55 -0
  2. package/README.md +8 -4
  3. package/dist/ai-harness/index.d.ts +194 -0
  4. package/dist/ai-harness/index.js +504 -0
  5. package/dist/cli-extras.d.ts +24 -0
  6. package/dist/cli-extras.js +205 -0
  7. package/dist/cli.d.ts +20 -0
  8. package/dist/cli.js +403 -28
  9. package/dist/e2b/filesystem.d.ts +26 -1
  10. package/dist/e2b/filesystem.js +30 -2
  11. package/dist/e2b/index.d.ts +1 -1
  12. package/dist/e2b/index.js +1 -1
  13. package/dist/errors.js +2 -0
  14. package/dist/identity.d.ts +27 -0
  15. package/dist/identity.js +56 -0
  16. package/dist/index.d.ts +14 -0
  17. package/dist/index.js +7 -0
  18. package/dist/mcp.js +16 -3
  19. package/dist/network-cli.d.ts +15 -0
  20. package/dist/network-cli.js +323 -0
  21. package/dist/products/desktop.d.ts +41 -4
  22. package/dist/products/desktop.js +73 -3
  23. package/dist/products/index.d.ts +12 -0
  24. package/dist/products/index.js +12 -0
  25. package/dist/products/interpreter.d.ts +5 -3
  26. package/dist/products/interpreter.js +2 -1
  27. package/dist/products/mcp.d.ts +79 -0
  28. package/dist/products/mcp.js +37 -0
  29. package/dist/products/mounts.d.ts +49 -0
  30. package/dist/products/mounts.js +21 -0
  31. package/dist/products/network-products.d.ts +167 -0
  32. package/dist/products/network-products.js +82 -0
  33. package/dist/products/network.d.ts +1 -1
  34. package/dist/products/sso.d.ts +44 -0
  35. package/dist/products/sso.js +8 -0
  36. package/dist/products/volumes.d.ts +68 -5
  37. package/dist/products/volumes.js +52 -4
  38. package/dist/products/watch.d.ts +91 -0
  39. package/dist/products/watch.js +135 -0
  40. package/dist/sandbox.d.ts +32 -2
  41. package/dist/sandbox.js +93 -16
  42. package/dist/snapshots.d.ts +11 -1
  43. package/dist/transport.d.ts +1 -1
  44. package/dist/transport.js +4 -1
  45. package/dist/wireguard.d.ts +7 -0
  46. package/dist/wireguard.js +13 -0
  47. package/package.json +49 -18
@@ -0,0 +1,205 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import { me } from "./login.js";
3
+ export async function recordCommand(sbx, argv, out, kit) {
4
+ const { parse, integer, need, table, usage } = kit;
5
+ const [action, ...rest] = argv;
6
+ const print = (human, value) => out.write(out.json ? JSON.stringify(value) : human);
7
+ const r = sbx.desktop.recordings;
8
+ switch (action) {
9
+ case "start": {
10
+ const args = parse(rest);
11
+ const rec = await r.start({
12
+ ...(integer(args, "fps") ? { fps: integer(args, "fps") } : {}),
13
+ ...(integer(args, "max-seconds") ? { maxSeconds: integer(args, "max-seconds") } : {}),
14
+ ...(integer(args, "max-mib") ? { maxMiB: integer(args, "max-mib") } : {}),
15
+ });
16
+ print(`Recording ${rec.id}. Stop it: ${me} sandbox desktop ${sbx.id} record stop ${rec.id}`, rec);
17
+ return 0;
18
+ }
19
+ case "stop": {
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);
22
+ return 0;
23
+ }
24
+ case "ls": {
25
+ const list = await r.list();
26
+ print(list.length
27
+ ? table([
28
+ ["ID", "STATE", "MIB", "REASON"],
29
+ ...list.map((rec) => [
30
+ rec.id,
31
+ rec.state,
32
+ (rec.bytes / 1_048_576).toFixed(1),
33
+ rec.reason ?? "-",
34
+ ]),
35
+ ])
36
+ : "No recordings.", list);
37
+ return 0;
38
+ }
39
+ case "fetch": {
40
+ const id = need(rest[0], "the recording id");
41
+ const file = rest[1] ?? `${id}.mp4`;
42
+ await writeFile(file, await r.download(id));
43
+ print(`Saved ${file}`, { file });
44
+ return 0;
45
+ }
46
+ case "rm":
47
+ print("Deleted.", await r.delete(need(rest[0], "the recording id")));
48
+ return 0;
49
+ }
50
+ throw usage(`${me} sandbox desktop <id> record start|stop|ls|fetch|rm`);
51
+ }
52
+ export async function watchCommand(sbx, argv, out, kit) {
53
+ const { parse, flag, has, need } = kit;
54
+ const args = parse(argv, ["recursive"]);
55
+ const path = need(args.positional[0], "a directory, e.g. /workspace");
56
+ const events = flag(args, "events")?.split(",");
57
+ const watch = await sbx.files.watch(path, (event) => {
58
+ if (out.json)
59
+ out.write(JSON.stringify(event));
60
+ else
61
+ out.write(`${event.type.padEnd(6)} ${event.oldPath ? `${event.oldPath} -> ` : ""}${event.path}${event.isDir ? "/" : ""}${event.count && event.count > 1 ? ` (x${event.count})` : ""}`);
62
+ }, {
63
+ recursive: has(args, "recursive"),
64
+ ...(events ? { events } : {}),
65
+ ...(args.flags.get("include") ? { include: args.flags.get("include") } : {}),
66
+ ...(args.flags.get("exclude") ? { exclude: args.flags.get("exclude") } : {}),
67
+ onNotice: (notice) => out.error(notice.k === "overflow"
68
+ ? `runtime: ${notice.dropped ?? "some"} events were dropped (${notice.reason}); rescan to catch up.`
69
+ : notice.k === "limit"
70
+ ? `runtime: only ${notice.watches} directories are watched; exclude some.`
71
+ : `runtime: ${notice.bytes} bytes of events were lost while this reader was behind.`),
72
+ onExit: (reason) => {
73
+ if (reason !== "stopped")
74
+ out.error(`runtime: the watch ended (${reason}).`);
75
+ },
76
+ });
77
+ const stop = () => void watch.stop();
78
+ process.once("SIGINT", stop);
79
+ try {
80
+ await watch.done;
81
+ }
82
+ finally {
83
+ process.off("SIGINT", stop);
84
+ }
85
+ return 0;
86
+ }
87
+ export async function mcpCommand(sbx, argv, out, kit) {
88
+ const { parse, has, integer, pairs, usage } = kit;
89
+ const [action = "status", ...rest] = argv;
90
+ const print = (human, value) => out.write(out.json ? JSON.stringify(value) : human);
91
+ const show = (gw) => {
92
+ if (!gw.running)
93
+ return "No MCP servers run in this sandbox.";
94
+ const auth = gw.headers?.Authorization ?? "";
95
+ const lines = gw.servers.map((server) => `${server.name.padEnd(20)} ${server.status.padEnd(10)} ${server.url ?? "(no link)"}${server.message ? `\n ${server.message}` : ""}`);
96
+ return [
97
+ ...lines,
98
+ "",
99
+ `Send the header: Authorization: ${auth.replace(/^Bearer /, "Bearer ")}`,
100
+ ...(gw.servers[0]?.url
101
+ ? [
102
+ "Claude Code:",
103
+ ...gw.servers.map((server) => ` claude mcp add --transport http ${server.name} '${server.url}' --header 'Authorization: ${auth}'`),
104
+ ]
105
+ : []),
106
+ ...gw.warnings.map((warning) => `Warning: ${warning}`),
107
+ ].join("\n");
108
+ };
109
+ switch (action) {
110
+ case "start": {
111
+ const args = parse(rest, ["replace"]);
112
+ if (!args.positional.length)
113
+ throw usage("Name the servers: runtime sandbox mcp <id> start github fetch");
114
+ const scoped = (name) => {
115
+ const found = {};
116
+ for (const [key, value] of Object.entries(pairs(args, name))) {
117
+ const dot = key.indexOf(".");
118
+ if (dot < 1)
119
+ throw usage(`--${name} takes SERVER.NAME=VALUE.`);
120
+ (found[key.slice(0, dot)] ??= {})[key.slice(dot + 1)] = value;
121
+ }
122
+ return found;
123
+ };
124
+ const secrets = scoped("secret");
125
+ const env = scoped("env");
126
+ const options = scoped("option");
127
+ const servers = args.positional.map((id) => ({
128
+ id,
129
+ ...(secrets[id] ? { secrets: secrets[id] } : {}),
130
+ ...(env[id] ? { env: env[id] } : {}),
131
+ ...(options[id] ? { options: options[id] } : {}),
132
+ }));
133
+ const gw = await sbx.mcp.start(servers, {
134
+ ...(integer(args, "port") ? { port: integer(args, "port") } : {}),
135
+ ...(has(args, "replace") ? { replace: true } : {}),
136
+ });
137
+ print(`${show(gw)}\nServers are installing; \`${me} sandbox mcp ${sbx.id}\` shows when they are ready.`, gw);
138
+ return 0;
139
+ }
140
+ case "status": {
141
+ const gw = await sbx.mcp.get();
142
+ print(show(gw), gw);
143
+ return 0;
144
+ }
145
+ case "stop":
146
+ print("Stopped.", await sbx.mcp.stop());
147
+ return 0;
148
+ }
149
+ throw usage(`${me} sandbox mcp <id> start|status|stop, or ${me} sandbox mcp catalog`);
150
+ }
151
+ /** Standard input when it is piped or redirected from a file, read whole, up
152
+ * to the API's 1 MiB; undefined for a terminal. A pipe that says nothing for
153
+ * `quietMs` is taken as no input, so a script that leaves standard input open
154
+ * and silent never hangs a command. */
155
+ export async function pipedStdin(options = {}) {
156
+ const { fstatSync } = await import("node:fs");
157
+ const stdin = process.stdin;
158
+ if (stdin.isTTY)
159
+ return undefined;
160
+ let kind;
161
+ try {
162
+ kind = fstatSync(0);
163
+ }
164
+ catch {
165
+ return undefined;
166
+ }
167
+ if (!kind.isFIFO() && !kind.isFile() && !kind.isSocket())
168
+ return undefined;
169
+ const limit = options.limit ?? 1_048_576;
170
+ return new Promise((resolve, reject) => {
171
+ const chunks = [];
172
+ let size = 0;
173
+ const done = (value, error) => {
174
+ clearTimeout(quiet);
175
+ stdin.off("data", onData);
176
+ stdin.off("end", onEnd);
177
+ stdin.pause();
178
+ if (error)
179
+ reject(error);
180
+ else
181
+ resolve(value);
182
+ };
183
+ const onData = (chunk) => {
184
+ clearTimeout(quiet);
185
+ size += chunk.length;
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" }));
188
+ chunks.push(chunk);
189
+ };
190
+ const onEnd = () => done(size ? new Uint8Array(Buffer.concat(chunks)) : undefined);
191
+ const quiet = setTimeout(() => {
192
+ if (!size)
193
+ done(undefined);
194
+ }, options.quietMs ?? 100);
195
+ stdin.on("data", onData);
196
+ stdin.once("end", onEnd);
197
+ stdin.resume();
198
+ });
199
+ }
200
+ /** What to say when a command's streamed output lost bytes, or nothing. */
201
+ export function lostOutput(result, id) {
202
+ if (!result.stdoutTruncated && !result.stderrTruncated)
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 .`;
205
+ }
package/dist/cli.d.ts CHANGED
@@ -1,9 +1,29 @@
1
1
  #!/usr/bin/env node
2
+ import { RuntimeError } from "./errors.js";
2
3
  type Out = {
3
4
  json: boolean;
4
5
  write: (text: string) => void;
5
6
  error: (text: string) => void;
6
7
  };
8
+ type Args = {
9
+ positional: string[];
10
+ flags: Map<string, string[]>;
11
+ rest: string[] | undefined;
12
+ };
13
+ /** What the commands in cli-extras.ts use of this file. */
14
+ declare const kit: {
15
+ parse: (argv: string[], booleans?: string[]) => Args;
16
+ flag: (args: Args, name: string) => string | undefined;
17
+ has: (args: Args, name: string) => boolean;
18
+ integer: (args: Args, name: string) => number | undefined;
19
+ pairs: (args: Args, name: string) => {
20
+ [k: string]: string;
21
+ };
22
+ need: (value: string | undefined, what: string) => string;
23
+ table: (rows: string[][]) => string;
24
+ usage: (message: string) => RuntimeError;
25
+ };
26
+ export type CliKit = typeof kit;
7
27
  export declare function run(argv: string[], env?: NodeJS.ProcessEnv, out?: Out): Promise<number>;
8
28
  export declare function secretsCommand(argv: string[], env: NodeJS.ProcessEnv, out: Out,
9
29
  /** Tests give the value here; the CLI reads standard input. */