@giselles-ai/agent-kit 0.1.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.
package/dist/cli.js ADDED
@@ -0,0 +1,413 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+
4
+ // src/cli.ts
5
+ import process from "process";
6
+
7
+ // src/build-snapshot.ts
8
+ import { readdir, readFile, stat } from "fs/promises";
9
+ import path from "path";
10
+ import { Sandbox } from "@vercel/sandbox";
11
+
12
+ // src/sandbox-utils.ts
13
+ async function runCommandOrThrow(sandbox, input) {
14
+ const result = await sandbox.runCommand({
15
+ cmd: input.cmd,
16
+ args: input.args,
17
+ cwd: input.cwd,
18
+ env: input.env
19
+ });
20
+ const stdout = await result.stdout().catch(() => "");
21
+ const stderr = await result.stderr().catch(() => "");
22
+ if (result.exitCode !== 0) {
23
+ const errorLines = [
24
+ `Command failed: ${input.cmd} ${(input.args ?? []).join(" ")}`,
25
+ `Exit code: ${result.exitCode}`
26
+ ];
27
+ if (stdout.trim().length > 0) {
28
+ errorLines.push(`stdout:
29
+ ${stdout}`);
30
+ }
31
+ if (stderr.trim().length > 0) {
32
+ errorLines.push(`stderr:
33
+ ${stderr}`);
34
+ }
35
+ throw new Error(errorLines.join("\n\n"));
36
+ }
37
+ return { stdout, stderr };
38
+ }
39
+
40
+ // src/build-snapshot.ts
41
+ var SKIP_DIR_NAMES = /* @__PURE__ */ new Set([
42
+ "node_modules",
43
+ "dist",
44
+ ".next",
45
+ ".git",
46
+ ".jj"
47
+ ]);
48
+ function toPosixPath(inputPath) {
49
+ return inputPath.split(path.sep).join("/");
50
+ }
51
+ async function collectFiles(targetPath, acc) {
52
+ const targetStat = await stat(targetPath);
53
+ if (targetStat.isFile()) {
54
+ acc.push(targetPath);
55
+ return;
56
+ }
57
+ if (!targetStat.isDirectory()) {
58
+ return;
59
+ }
60
+ const entries = await readdir(targetPath, { withFileTypes: true });
61
+ for (const entry of entries) {
62
+ const absolutePath = path.join(targetPath, entry.name);
63
+ if (entry.isDirectory()) {
64
+ if (SKIP_DIR_NAMES.has(entry.name)) {
65
+ continue;
66
+ }
67
+ await collectFiles(absolutePath, acc);
68
+ continue;
69
+ }
70
+ if (entry.isFile()) {
71
+ if (entry.name.endsWith(".tsbuildinfo")) {
72
+ continue;
73
+ }
74
+ acc.push(absolutePath);
75
+ }
76
+ }
77
+ }
78
+ async function buildUploadFileList(repoRoot, includePaths) {
79
+ const files = [];
80
+ for (const relativePath of includePaths) {
81
+ const absolutePath = path.join(repoRoot, relativePath);
82
+ await collectFiles(absolutePath, files);
83
+ }
84
+ files.sort();
85
+ return files;
86
+ }
87
+ async function installAllAgentCLIs(sandbox) {
88
+ console.log("[snapshot] installing gemini cli...");
89
+ await runCommandOrThrow(sandbox, {
90
+ cmd: "npm",
91
+ args: ["install", "-g", "@google/gemini-cli"]
92
+ });
93
+ console.log("[snapshot] installing codex cli...");
94
+ await runCommandOrThrow(sandbox, {
95
+ cmd: "npm",
96
+ args: ["install", "-g", "@openai/codex"]
97
+ });
98
+ console.log("[snapshot] validating agent CLIs...");
99
+ await runCommandOrThrow(sandbox, {
100
+ cmd: "bash",
101
+ args: [
102
+ "-lc",
103
+ ["set -e", "which gemini", "which codex", "codex --version"].join("\n")
104
+ ]
105
+ });
106
+ }
107
+ async function setupBrowserToolFromNpm(sandbox, version) {
108
+ const packageSpec = version ? `@giselles-ai/browser-tool@${version}` : "@giselles-ai/browser-tool";
109
+ console.log(`[snapshot] installing ${packageSpec} globally...`);
110
+ await runCommandOrThrow(sandbox, {
111
+ cmd: "npm",
112
+ args: ["install", "-g", packageSpec]
113
+ });
114
+ console.log("[snapshot] locating mcp-server entry point...");
115
+ const { stdout: globalRoot } = await runCommandOrThrow(sandbox, {
116
+ cmd: "npm",
117
+ args: ["root", "-g"]
118
+ });
119
+ const mcpServerPath = `${globalRoot.trim()}/@giselles-ai/browser-tool/dist/mcp-server/index.js`;
120
+ console.log(`[snapshot] mcp-server resolved at: ${mcpServerPath}`);
121
+ return mcpServerPath;
122
+ }
123
+ async function setupBrowserToolFromLocal(sandbox, sandboxRoot, repoRoot, includePaths) {
124
+ console.log("[snapshot] collecting local files...");
125
+ const uploadFiles = await buildUploadFileList(repoRoot, includePaths);
126
+ if (uploadFiles.length === 0) {
127
+ throw new Error("No files collected for snapshot upload.");
128
+ }
129
+ console.log(`[snapshot] files to upload: ${uploadFiles.length}`);
130
+ const filesForSandbox = await Promise.all(
131
+ uploadFiles.map(async (absolutePath) => {
132
+ const relativePath = path.relative(repoRoot, absolutePath);
133
+ const content = await readFile(absolutePath);
134
+ return {
135
+ path: toPosixPath(path.join(sandboxRoot, relativePath)),
136
+ content
137
+ };
138
+ })
139
+ );
140
+ console.log("[snapshot] uploading repository files...");
141
+ await sandbox.writeFiles(filesForSandbox);
142
+ console.log("[snapshot] preparing pnpm...");
143
+ await runCommandOrThrow(sandbox, {
144
+ cmd: "bash",
145
+ args: ["-lc", ["set -e", "corepack pnpm --version"].join("\n")]
146
+ });
147
+ console.log("[snapshot] installing dependencies...");
148
+ await runCommandOrThrow(sandbox, {
149
+ cmd: "bash",
150
+ args: [
151
+ "-lc",
152
+ [
153
+ "set -e",
154
+ `cd ${sandboxRoot}`,
155
+ [
156
+ "corepack pnpm install --no-frozen-lockfile",
157
+ "--filter @giselles-ai/browser-tool..."
158
+ ].join(" ")
159
+ ].join("\n")
160
+ ]
161
+ });
162
+ console.log("[snapshot] building browser-tool (mcp-server)...");
163
+ await runCommandOrThrow(sandbox, {
164
+ cmd: "bash",
165
+ args: [
166
+ "-lc",
167
+ [
168
+ "set -e",
169
+ `cd ${sandboxRoot}`,
170
+ "corepack pnpm --filter @giselles-ai/browser-tool run build"
171
+ ].join("\n")
172
+ ]
173
+ });
174
+ const mcpServerPath = `${sandboxRoot}/packages/browser-tool/dist/mcp-server/index.js`;
175
+ console.log("[snapshot] validating artifacts...");
176
+ await runCommandOrThrow(sandbox, {
177
+ cmd: "bash",
178
+ args: ["-lc", `set -e
179
+ test -f ${mcpServerPath}`]
180
+ });
181
+ return mcpServerPath;
182
+ }
183
+ function writeAgentConfigs(sandbox, mcpServerPath, sandboxRoot) {
184
+ const geminiSettings = {
185
+ security: {
186
+ auth: {
187
+ selectedType: "gemini-api-key"
188
+ }
189
+ },
190
+ mcpServers: {
191
+ browser_tool_relay: {
192
+ command: "node",
193
+ args: [mcpServerPath],
194
+ cwd: sandboxRoot,
195
+ env: {}
196
+ }
197
+ }
198
+ };
199
+ const codexConfigToml = `[mcp_servers.browser_tool_relay]
200
+ command = "node"
201
+ args = ["${mcpServerPath}"]
202
+ cwd = "${sandboxRoot}"
203
+
204
+ [mcp_servers.browser_tool_relay.env]
205
+ `;
206
+ return Promise.all([
207
+ sandbox.writeFiles([
208
+ {
209
+ path: "/home/vercel-sandbox/.gemini/settings.json",
210
+ content: Buffer.from(JSON.stringify(geminiSettings, null, 2))
211
+ }
212
+ ]),
213
+ sandbox.writeFiles([
214
+ {
215
+ path: "/home/vercel-sandbox/.codex/config.toml",
216
+ content: Buffer.from(codexConfigToml)
217
+ }
218
+ ])
219
+ ]);
220
+ }
221
+ var DEFAULT_LOCAL_INCLUDE_PATHS = [
222
+ "package.json",
223
+ "pnpm-lock.yaml",
224
+ "pnpm-workspace.yaml",
225
+ "tsconfig.base.json",
226
+ "packages/browser-tool"
227
+ ];
228
+ async function buildSnapshot(options = {}) {
229
+ const sandboxRoot = options.sandboxRoot ?? "/vercel/sandbox";
230
+ const runtime = options.runtime ?? "node24";
231
+ const timeoutMs = options.timeoutMs ?? 27e5;
232
+ const isLocal = options.local ?? false;
233
+ if (isLocal && !options.repoRoot) {
234
+ throw new Error("repoRoot is required when local mode is enabled.");
235
+ }
236
+ let baseSnapshotId = options.baseSnapshotId ?? "";
237
+ if (baseSnapshotId) {
238
+ console.log(`[snapshot] using existing base snapshot: ${baseSnapshotId}`);
239
+ } else {
240
+ console.log(
241
+ "[snapshot] BASE_SNAPSHOT_ID not set, creating base snapshot with all agent CLIs..."
242
+ );
243
+ const baseSandbox = await Sandbox.create({
244
+ runtime,
245
+ timeout: timeoutMs
246
+ });
247
+ console.log(`[snapshot] base sandbox created: ${baseSandbox.sandboxId}`);
248
+ try {
249
+ await installAllAgentCLIs(baseSandbox);
250
+ console.log("[snapshot] creating base snapshot...");
251
+ const baseSnapshot = await baseSandbox.snapshot();
252
+ baseSnapshotId = baseSnapshot.snapshotId;
253
+ console.log(`[snapshot] base snapshot created: ${baseSnapshotId}`);
254
+ console.log(
255
+ `[snapshot] reuse with: BASE_SNAPSHOT_ID="${baseSnapshotId}"`
256
+ );
257
+ } catch (error) {
258
+ console.error("[snapshot] base snapshot creation failed:");
259
+ console.error(error instanceof Error ? error.message : String(error));
260
+ try {
261
+ await baseSandbox.stop();
262
+ } catch {
263
+ }
264
+ throw error;
265
+ }
266
+ }
267
+ console.log(
268
+ `[snapshot] creating sandbox from base snapshot runtime=${runtime} timeoutMs=${timeoutMs}...`
269
+ );
270
+ const sandbox = await Sandbox.create({
271
+ source: { type: "snapshot", snapshotId: baseSnapshotId },
272
+ runtime,
273
+ timeout: timeoutMs
274
+ });
275
+ console.log(`[snapshot] sandbox created: ${sandbox.sandboxId}`);
276
+ try {
277
+ let mcpServerPath;
278
+ if (isLocal && options.repoRoot) {
279
+ mcpServerPath = await setupBrowserToolFromLocal(
280
+ sandbox,
281
+ sandboxRoot,
282
+ options.repoRoot,
283
+ options.localIncludePaths ?? DEFAULT_LOCAL_INCLUDE_PATHS
284
+ );
285
+ } else {
286
+ mcpServerPath = await setupBrowserToolFromNpm(
287
+ sandbox,
288
+ options.browserToolVersion
289
+ );
290
+ }
291
+ console.log("[snapshot] validating agent CLIs...");
292
+ await runCommandOrThrow(sandbox, {
293
+ cmd: "bash",
294
+ args: [
295
+ "-lc",
296
+ ["set -e", "which gemini", "which codex", "codex --version"].join("\n")
297
+ ]
298
+ });
299
+ console.log("[snapshot] writing agent configs...");
300
+ await writeAgentConfigs(sandbox, mcpServerPath, sandboxRoot);
301
+ console.log("[snapshot] creating snapshot (sandbox will be stopped)...");
302
+ const snapshot = await sandbox.snapshot();
303
+ console.log("\n=== Snapshot Created ===");
304
+ console.log(`snapshotId: ${snapshot.snapshotId}`);
305
+ console.log(`sourceSandboxId: ${snapshot.sourceSandboxId}`);
306
+ console.log(`status: ${snapshot.status}`);
307
+ console.log(`createdAt: ${snapshot.createdAt.toISOString()}`);
308
+ console.log(`expiresAt: ${snapshot.expiresAt?.toISOString()}`);
309
+ console.log("\nSet this in your .env.local:");
310
+ console.log(`SANDBOX_SNAPSHOT_ID=${snapshot.snapshotId}`);
311
+ return snapshot;
312
+ } catch (error) {
313
+ console.error("[snapshot] failed:");
314
+ console.error(error instanceof Error ? error.message : String(error));
315
+ console.error(`sandboxId: ${sandbox.sandboxId}`);
316
+ try {
317
+ await sandbox.stop();
318
+ console.error("[snapshot] sandbox stopped.");
319
+ } catch {
320
+ }
321
+ throw error;
322
+ }
323
+ }
324
+
325
+ // src/cli.ts
326
+ function printUsage() {
327
+ const usage = `Usage:
328
+ agent-kit build-snapshot [options]
329
+
330
+ Options:
331
+ --local Copy local files instead of npm install (monorepo only)
332
+ --repo-root <path> Monorepo root directory (required with --local)
333
+ --base-snapshot-id <id> Reuse an existing base snapshot
334
+ --sandbox-root <path> Sandbox working directory (default: /vercel/sandbox)
335
+ --runtime <runtime> Sandbox runtime (default: node24)
336
+ --timeout-ms <ms> Sandbox timeout in ms (default: 2700000)
337
+ --browser-tool-version <version> Version of @giselles-ai/browser-tool to install
338
+ --help, -h Show this help message
339
+ `;
340
+ process.stderr.write(usage);
341
+ }
342
+ function parseArgs(argv) {
343
+ const args = argv.slice(2);
344
+ const options = {};
345
+ let command;
346
+ for (let i = 0; i < args.length; i++) {
347
+ const arg = args[i];
348
+ if (arg === void 0) continue;
349
+ if (arg === "--help" || arg === "-h") {
350
+ options.help = true;
351
+ continue;
352
+ }
353
+ if (arg === "--local") {
354
+ options.local = true;
355
+ continue;
356
+ }
357
+ if (arg.startsWith("--") && i + 1 < args.length) {
358
+ const key = arg.slice(2);
359
+ i++;
360
+ const value = args[i];
361
+ if (value !== void 0) {
362
+ options[key] = value;
363
+ }
364
+ continue;
365
+ }
366
+ if (!arg.startsWith("-") && !command) {
367
+ command = arg;
368
+ }
369
+ }
370
+ return { command, options };
371
+ }
372
+ async function main() {
373
+ const { command, options } = parseArgs(process.argv);
374
+ if (options.help) {
375
+ printUsage();
376
+ process.exit(0);
377
+ }
378
+ if (command !== "build-snapshot") {
379
+ printUsage();
380
+ process.exit(command ? 1 : 1);
381
+ }
382
+ const isLocal = options.local === true;
383
+ const repoRoot = options["repo-root"] ?? process.env.BROWSER_TOOL_REPO_ROOT?.trim();
384
+ if (isLocal && !repoRoot) {
385
+ console.error("Error: --repo-root is required when --local is specified.");
386
+ process.exit(1);
387
+ }
388
+ const timeoutMsRaw = options["timeout-ms"] ?? process.env.BROWSER_TOOL_SNAPSHOT_TIMEOUT_MS?.trim();
389
+ const timeoutMs = timeoutMsRaw ? Number.parseInt(timeoutMsRaw, 10) : void 0;
390
+ if (timeoutMs !== void 0 && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {
391
+ console.error(`Error: Invalid timeout-ms: ${timeoutMsRaw}`);
392
+ process.exit(1);
393
+ }
394
+ try {
395
+ await buildSnapshot({
396
+ local: isLocal,
397
+ repoRoot,
398
+ baseSnapshotId: options["base-snapshot-id"] ?? process.env.BASE_SNAPSHOT_ID?.trim(),
399
+ sandboxRoot: options["sandbox-root"] ?? process.env.BROWSER_TOOL_SANDBOX_ROOT?.trim(),
400
+ runtime: options.runtime ?? process.env.BROWSER_TOOL_SNAPSHOT_RUNTIME?.trim(),
401
+ timeoutMs,
402
+ browserToolVersion: options["browser-tool-version"]
403
+ });
404
+ } catch {
405
+ process.exitCode = 1;
406
+ }
407
+ }
408
+ main().catch((err) => {
409
+ console.error(
410
+ `Unexpected error: ${err instanceof Error ? err.message : String(err)}`
411
+ );
412
+ process.exit(1);
413
+ });
@@ -0,0 +1,26 @@
1
+ import * as _vercel_sandbox from '@vercel/sandbox';
2
+
3
+ interface BuildSnapshotOptions {
4
+ sandboxRoot?: string;
5
+ runtime?: string;
6
+ timeoutMs?: number;
7
+ baseSnapshotId?: string;
8
+ browserToolVersion?: string;
9
+ /**
10
+ * When true, copy local files into the sandbox and build from source.
11
+ * When false (default), install @giselles-ai/browser-tool from npm.
12
+ */
13
+ local?: boolean;
14
+ /**
15
+ * Required when `local` is true. The root directory of the monorepo.
16
+ */
17
+ repoRoot?: string;
18
+ /**
19
+ * Paths to include when `local` is true.
20
+ * Defaults to the standard set for this monorepo.
21
+ */
22
+ localIncludePaths?: string[];
23
+ }
24
+ declare function buildSnapshot(options?: BuildSnapshotOptions): Promise<_vercel_sandbox.Snapshot>;
25
+
26
+ export { type BuildSnapshotOptions, buildSnapshot };
package/dist/index.js ADDED
@@ -0,0 +1,320 @@
1
+ // src/build-snapshot.ts
2
+ import { readdir, readFile, stat } from "fs/promises";
3
+ import path from "path";
4
+ import { Sandbox } from "@vercel/sandbox";
5
+
6
+ // src/sandbox-utils.ts
7
+ async function runCommandOrThrow(sandbox, input) {
8
+ const result = await sandbox.runCommand({
9
+ cmd: input.cmd,
10
+ args: input.args,
11
+ cwd: input.cwd,
12
+ env: input.env
13
+ });
14
+ const stdout = await result.stdout().catch(() => "");
15
+ const stderr = await result.stderr().catch(() => "");
16
+ if (result.exitCode !== 0) {
17
+ const errorLines = [
18
+ `Command failed: ${input.cmd} ${(input.args ?? []).join(" ")}`,
19
+ `Exit code: ${result.exitCode}`
20
+ ];
21
+ if (stdout.trim().length > 0) {
22
+ errorLines.push(`stdout:
23
+ ${stdout}`);
24
+ }
25
+ if (stderr.trim().length > 0) {
26
+ errorLines.push(`stderr:
27
+ ${stderr}`);
28
+ }
29
+ throw new Error(errorLines.join("\n\n"));
30
+ }
31
+ return { stdout, stderr };
32
+ }
33
+
34
+ // src/build-snapshot.ts
35
+ var SKIP_DIR_NAMES = /* @__PURE__ */ new Set([
36
+ "node_modules",
37
+ "dist",
38
+ ".next",
39
+ ".git",
40
+ ".jj"
41
+ ]);
42
+ function toPosixPath(inputPath) {
43
+ return inputPath.split(path.sep).join("/");
44
+ }
45
+ async function collectFiles(targetPath, acc) {
46
+ const targetStat = await stat(targetPath);
47
+ if (targetStat.isFile()) {
48
+ acc.push(targetPath);
49
+ return;
50
+ }
51
+ if (!targetStat.isDirectory()) {
52
+ return;
53
+ }
54
+ const entries = await readdir(targetPath, { withFileTypes: true });
55
+ for (const entry of entries) {
56
+ const absolutePath = path.join(targetPath, entry.name);
57
+ if (entry.isDirectory()) {
58
+ if (SKIP_DIR_NAMES.has(entry.name)) {
59
+ continue;
60
+ }
61
+ await collectFiles(absolutePath, acc);
62
+ continue;
63
+ }
64
+ if (entry.isFile()) {
65
+ if (entry.name.endsWith(".tsbuildinfo")) {
66
+ continue;
67
+ }
68
+ acc.push(absolutePath);
69
+ }
70
+ }
71
+ }
72
+ async function buildUploadFileList(repoRoot, includePaths) {
73
+ const files = [];
74
+ for (const relativePath of includePaths) {
75
+ const absolutePath = path.join(repoRoot, relativePath);
76
+ await collectFiles(absolutePath, files);
77
+ }
78
+ files.sort();
79
+ return files;
80
+ }
81
+ async function installAllAgentCLIs(sandbox) {
82
+ console.log("[snapshot] installing gemini cli...");
83
+ await runCommandOrThrow(sandbox, {
84
+ cmd: "npm",
85
+ args: ["install", "-g", "@google/gemini-cli"]
86
+ });
87
+ console.log("[snapshot] installing codex cli...");
88
+ await runCommandOrThrow(sandbox, {
89
+ cmd: "npm",
90
+ args: ["install", "-g", "@openai/codex"]
91
+ });
92
+ console.log("[snapshot] validating agent CLIs...");
93
+ await runCommandOrThrow(sandbox, {
94
+ cmd: "bash",
95
+ args: [
96
+ "-lc",
97
+ ["set -e", "which gemini", "which codex", "codex --version"].join("\n")
98
+ ]
99
+ });
100
+ }
101
+ async function setupBrowserToolFromNpm(sandbox, version) {
102
+ const packageSpec = version ? `@giselles-ai/browser-tool@${version}` : "@giselles-ai/browser-tool";
103
+ console.log(`[snapshot] installing ${packageSpec} globally...`);
104
+ await runCommandOrThrow(sandbox, {
105
+ cmd: "npm",
106
+ args: ["install", "-g", packageSpec]
107
+ });
108
+ console.log("[snapshot] locating mcp-server entry point...");
109
+ const { stdout: globalRoot } = await runCommandOrThrow(sandbox, {
110
+ cmd: "npm",
111
+ args: ["root", "-g"]
112
+ });
113
+ const mcpServerPath = `${globalRoot.trim()}/@giselles-ai/browser-tool/dist/mcp-server/index.js`;
114
+ console.log(`[snapshot] mcp-server resolved at: ${mcpServerPath}`);
115
+ return mcpServerPath;
116
+ }
117
+ async function setupBrowserToolFromLocal(sandbox, sandboxRoot, repoRoot, includePaths) {
118
+ console.log("[snapshot] collecting local files...");
119
+ const uploadFiles = await buildUploadFileList(repoRoot, includePaths);
120
+ if (uploadFiles.length === 0) {
121
+ throw new Error("No files collected for snapshot upload.");
122
+ }
123
+ console.log(`[snapshot] files to upload: ${uploadFiles.length}`);
124
+ const filesForSandbox = await Promise.all(
125
+ uploadFiles.map(async (absolutePath) => {
126
+ const relativePath = path.relative(repoRoot, absolutePath);
127
+ const content = await readFile(absolutePath);
128
+ return {
129
+ path: toPosixPath(path.join(sandboxRoot, relativePath)),
130
+ content
131
+ };
132
+ })
133
+ );
134
+ console.log("[snapshot] uploading repository files...");
135
+ await sandbox.writeFiles(filesForSandbox);
136
+ console.log("[snapshot] preparing pnpm...");
137
+ await runCommandOrThrow(sandbox, {
138
+ cmd: "bash",
139
+ args: ["-lc", ["set -e", "corepack pnpm --version"].join("\n")]
140
+ });
141
+ console.log("[snapshot] installing dependencies...");
142
+ await runCommandOrThrow(sandbox, {
143
+ cmd: "bash",
144
+ args: [
145
+ "-lc",
146
+ [
147
+ "set -e",
148
+ `cd ${sandboxRoot}`,
149
+ [
150
+ "corepack pnpm install --no-frozen-lockfile",
151
+ "--filter @giselles-ai/browser-tool..."
152
+ ].join(" ")
153
+ ].join("\n")
154
+ ]
155
+ });
156
+ console.log("[snapshot] building browser-tool (mcp-server)...");
157
+ await runCommandOrThrow(sandbox, {
158
+ cmd: "bash",
159
+ args: [
160
+ "-lc",
161
+ [
162
+ "set -e",
163
+ `cd ${sandboxRoot}`,
164
+ "corepack pnpm --filter @giselles-ai/browser-tool run build"
165
+ ].join("\n")
166
+ ]
167
+ });
168
+ const mcpServerPath = `${sandboxRoot}/packages/browser-tool/dist/mcp-server/index.js`;
169
+ console.log("[snapshot] validating artifacts...");
170
+ await runCommandOrThrow(sandbox, {
171
+ cmd: "bash",
172
+ args: ["-lc", `set -e
173
+ test -f ${mcpServerPath}`]
174
+ });
175
+ return mcpServerPath;
176
+ }
177
+ function writeAgentConfigs(sandbox, mcpServerPath, sandboxRoot) {
178
+ const geminiSettings = {
179
+ security: {
180
+ auth: {
181
+ selectedType: "gemini-api-key"
182
+ }
183
+ },
184
+ mcpServers: {
185
+ browser_tool_relay: {
186
+ command: "node",
187
+ args: [mcpServerPath],
188
+ cwd: sandboxRoot,
189
+ env: {}
190
+ }
191
+ }
192
+ };
193
+ const codexConfigToml = `[mcp_servers.browser_tool_relay]
194
+ command = "node"
195
+ args = ["${mcpServerPath}"]
196
+ cwd = "${sandboxRoot}"
197
+
198
+ [mcp_servers.browser_tool_relay.env]
199
+ `;
200
+ return Promise.all([
201
+ sandbox.writeFiles([
202
+ {
203
+ path: "/home/vercel-sandbox/.gemini/settings.json",
204
+ content: Buffer.from(JSON.stringify(geminiSettings, null, 2))
205
+ }
206
+ ]),
207
+ sandbox.writeFiles([
208
+ {
209
+ path: "/home/vercel-sandbox/.codex/config.toml",
210
+ content: Buffer.from(codexConfigToml)
211
+ }
212
+ ])
213
+ ]);
214
+ }
215
+ var DEFAULT_LOCAL_INCLUDE_PATHS = [
216
+ "package.json",
217
+ "pnpm-lock.yaml",
218
+ "pnpm-workspace.yaml",
219
+ "tsconfig.base.json",
220
+ "packages/browser-tool"
221
+ ];
222
+ async function buildSnapshot(options = {}) {
223
+ const sandboxRoot = options.sandboxRoot ?? "/vercel/sandbox";
224
+ const runtime = options.runtime ?? "node24";
225
+ const timeoutMs = options.timeoutMs ?? 27e5;
226
+ const isLocal = options.local ?? false;
227
+ if (isLocal && !options.repoRoot) {
228
+ throw new Error("repoRoot is required when local mode is enabled.");
229
+ }
230
+ let baseSnapshotId = options.baseSnapshotId ?? "";
231
+ if (baseSnapshotId) {
232
+ console.log(`[snapshot] using existing base snapshot: ${baseSnapshotId}`);
233
+ } else {
234
+ console.log(
235
+ "[snapshot] BASE_SNAPSHOT_ID not set, creating base snapshot with all agent CLIs..."
236
+ );
237
+ const baseSandbox = await Sandbox.create({
238
+ runtime,
239
+ timeout: timeoutMs
240
+ });
241
+ console.log(`[snapshot] base sandbox created: ${baseSandbox.sandboxId}`);
242
+ try {
243
+ await installAllAgentCLIs(baseSandbox);
244
+ console.log("[snapshot] creating base snapshot...");
245
+ const baseSnapshot = await baseSandbox.snapshot();
246
+ baseSnapshotId = baseSnapshot.snapshotId;
247
+ console.log(`[snapshot] base snapshot created: ${baseSnapshotId}`);
248
+ console.log(
249
+ `[snapshot] reuse with: BASE_SNAPSHOT_ID="${baseSnapshotId}"`
250
+ );
251
+ } catch (error) {
252
+ console.error("[snapshot] base snapshot creation failed:");
253
+ console.error(error instanceof Error ? error.message : String(error));
254
+ try {
255
+ await baseSandbox.stop();
256
+ } catch {
257
+ }
258
+ throw error;
259
+ }
260
+ }
261
+ console.log(
262
+ `[snapshot] creating sandbox from base snapshot runtime=${runtime} timeoutMs=${timeoutMs}...`
263
+ );
264
+ const sandbox = await Sandbox.create({
265
+ source: { type: "snapshot", snapshotId: baseSnapshotId },
266
+ runtime,
267
+ timeout: timeoutMs
268
+ });
269
+ console.log(`[snapshot] sandbox created: ${sandbox.sandboxId}`);
270
+ try {
271
+ let mcpServerPath;
272
+ if (isLocal && options.repoRoot) {
273
+ mcpServerPath = await setupBrowserToolFromLocal(
274
+ sandbox,
275
+ sandboxRoot,
276
+ options.repoRoot,
277
+ options.localIncludePaths ?? DEFAULT_LOCAL_INCLUDE_PATHS
278
+ );
279
+ } else {
280
+ mcpServerPath = await setupBrowserToolFromNpm(
281
+ sandbox,
282
+ options.browserToolVersion
283
+ );
284
+ }
285
+ console.log("[snapshot] validating agent CLIs...");
286
+ await runCommandOrThrow(sandbox, {
287
+ cmd: "bash",
288
+ args: [
289
+ "-lc",
290
+ ["set -e", "which gemini", "which codex", "codex --version"].join("\n")
291
+ ]
292
+ });
293
+ console.log("[snapshot] writing agent configs...");
294
+ await writeAgentConfigs(sandbox, mcpServerPath, sandboxRoot);
295
+ console.log("[snapshot] creating snapshot (sandbox will be stopped)...");
296
+ const snapshot = await sandbox.snapshot();
297
+ console.log("\n=== Snapshot Created ===");
298
+ console.log(`snapshotId: ${snapshot.snapshotId}`);
299
+ console.log(`sourceSandboxId: ${snapshot.sourceSandboxId}`);
300
+ console.log(`status: ${snapshot.status}`);
301
+ console.log(`createdAt: ${snapshot.createdAt.toISOString()}`);
302
+ console.log(`expiresAt: ${snapshot.expiresAt?.toISOString()}`);
303
+ console.log("\nSet this in your .env.local:");
304
+ console.log(`SANDBOX_SNAPSHOT_ID=${snapshot.snapshotId}`);
305
+ return snapshot;
306
+ } catch (error) {
307
+ console.error("[snapshot] failed:");
308
+ console.error(error instanceof Error ? error.message : String(error));
309
+ console.error(`sandboxId: ${sandbox.sandboxId}`);
310
+ try {
311
+ await sandbox.stop();
312
+ console.error("[snapshot] sandbox stopped.");
313
+ } catch {
314
+ }
315
+ throw error;
316
+ }
317
+ }
318
+ export {
319
+ buildSnapshot
320
+ };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@giselles-ai/agent-kit",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "bin": {
7
+ "agent-kit": "./dist/cli.js"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "build": "pnpm clean && tsup --config tsup.ts",
18
+ "clean": "rm -rf dist *.tsbuildinfo",
19
+ "dev": "tsx src/cli.ts",
20
+ "typecheck": "tsc -p tsconfig.json --noEmit",
21
+ "format": "pnpm exec biome check --write ."
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "license": "Apache-2.0",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/giselles-ai/agent-container.git",
30
+ "directory": "packages/agent-kit"
31
+ },
32
+ "engines": {
33
+ "node": ">=20"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "dependencies": {
39
+ "@vercel/sandbox": "1.8.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "25.3.0",
43
+ "tsup": "8.5.1",
44
+ "tsx": "4.21.0",
45
+ "typescript": "5.9.3"
46
+ }
47
+ }