bashkit 0.2.4 → 0.3.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/AGENTS.md CHANGED
@@ -34,9 +34,12 @@ Runs in isolated Firecracker microVMs on Vercel's infrastructure.
34
34
  ```typescript
35
35
  import { createAgentTools, createVercelSandbox } from "bashkit";
36
36
 
37
- const sandbox = createVercelSandbox({
37
+ // Async - automatically installs ripgrep for Grep tool
38
+ const sandbox = await createVercelSandbox({
38
39
  runtime: "node22",
39
40
  resources: { vcpus: 2 },
41
+ // ensureTools: true (default) - auto-setup ripgrep
42
+ // ensureTools: false - skip for faster startup if you don't need Grep
40
43
  });
41
44
  const { tools } = createAgentTools(sandbox);
42
45
 
@@ -51,8 +54,11 @@ Runs in E2B's cloud sandboxes. Requires `@e2b/code-interpreter` peer dependency.
51
54
  ```typescript
52
55
  import { createAgentTools, createE2BSandbox } from "bashkit";
53
56
 
54
- const sandbox = createE2BSandbox({
57
+ // Async - automatically installs ripgrep for Grep tool
58
+ const sandbox = await createE2BSandbox({
55
59
  apiKey: process.env.E2B_API_KEY,
60
+ // ensureTools: true (default) - auto-setup ripgrep
61
+ // ensureTools: false - skip for faster startup if you don't need Grep
56
62
  });
57
63
  const { tools } = createAgentTools(sandbox);
58
64
 
@@ -65,18 +71,17 @@ Cloud sandboxes (E2B, Vercel) support reconnection via the `id` property and `sa
65
71
 
66
72
  ```typescript
67
73
  // Create a new sandbox
68
- const sandbox = createE2BSandbox({ apiKey: process.env.E2B_API_KEY });
74
+ const sandbox = await createE2BSandbox({ apiKey: process.env.E2B_API_KEY });
69
75
 
70
- // After first operation, the sandbox ID is available
71
- await sandbox.exec("echo hello");
76
+ // Sandbox ID is available immediately after creation
72
77
  const sandboxId = sandbox.id; // "sbx_abc123..."
73
78
 
74
79
  // Store sandboxId in your database (e.g., chat metadata)
75
80
  await db.chat.update({ where: { id: chatId }, data: { sandboxId } });
76
81
 
77
- // Later: reconnect to the same sandbox
82
+ // Later: reconnect to the same sandbox (fast - ripgrep already installed)
78
83
  const savedId = chat.sandboxId;
79
- const reconnected = createE2BSandbox({
84
+ const reconnected = await createE2BSandbox({
80
85
  apiKey: process.env.E2B_API_KEY,
81
86
  sandboxId: savedId, // Reconnects instead of creating new
82
87
  });
@@ -442,7 +447,7 @@ const config = {
442
447
  },
443
448
  };
444
449
 
445
- const sandbox = createVercelSandbox({});
450
+ const sandbox = await createVercelSandbox({});
446
451
  const { skills } = await setupAgentEnvironment(sandbox, config);
447
452
 
448
453
  // Use same config in prompt - stays in sync!
package/README.md CHANGED
@@ -69,7 +69,8 @@ import { anthropic } from '@ai-sdk/anthropic';
69
69
  import { streamText, stepCountIs } from 'ai';
70
70
 
71
71
  // Create a Vercel sandbox (isolated Firecracker microVM)
72
- const sandbox = createVercelSandbox({
72
+ // Note: async - automatically sets up ripgrep for Grep tool
73
+ const sandbox = await createVercelSandbox({
73
74
  runtime: 'node22',
74
75
  resources: { vcpus: 2 },
75
76
  });
@@ -144,17 +145,19 @@ Runs in isolated Firecracker microVMs on Vercel's infrastructure. **Use when you
144
145
  ```typescript
145
146
  import { createVercelSandbox } from 'bashkit';
146
147
 
147
- const sandbox = createVercelSandbox({
148
+ // Async - automatically installs ripgrep for Grep tool
149
+ const sandbox = await createVercelSandbox({
148
150
  runtime: 'node22',
149
151
  resources: { vcpus: 2 },
152
+ // ensureTools: true (default) - auto-setup ripgrep
153
+ // ensureTools: false - skip for faster startup if you don't need Grep
150
154
  });
151
155
 
152
- // After first operation, get the sandbox ID for persistence
153
- await sandbox.exec('echo hello');
156
+ // Sandbox ID available immediately after creation
154
157
  console.log(sandbox.id); // Sandbox ID for reconnection
155
158
 
156
- // Later: reconnect to the same sandbox
157
- const reconnected = createVercelSandbox({
159
+ // Later: reconnect to the same sandbox (fast - ripgrep already installed)
160
+ const reconnected = await createVercelSandbox({
158
161
  sandboxId: 'existing-sandbox-id',
159
162
  });
160
163
  ```
@@ -166,16 +169,18 @@ Runs in E2B's cloud sandboxes. Requires `@e2b/code-interpreter` peer dependency.
166
169
  ```typescript
167
170
  import { createE2BSandbox } from 'bashkit';
168
171
 
169
- const sandbox = createE2BSandbox({
172
+ // Async - automatically installs ripgrep for Grep tool
173
+ const sandbox = await createE2BSandbox({
170
174
  apiKey: process.env.E2B_API_KEY,
175
+ // ensureTools: true (default) - auto-setup ripgrep
176
+ // ensureTools: false - skip for faster startup if you don't need Grep
171
177
  });
172
178
 
173
- // After first operation, get the sandbox ID for persistence
174
- await sandbox.exec('echo hello');
179
+ // Sandbox ID available immediately after creation
175
180
  console.log(sandbox.id); // "sbx_abc123..."
176
181
 
177
- // Later: reconnect to the same sandbox
178
- const reconnected = createE2BSandbox({
182
+ // Later: reconnect to the same sandbox (fast - ripgrep already installed)
183
+ const reconnected = await createE2BSandbox({
179
184
  apiKey: process.env.E2B_API_KEY,
180
185
  sandboxId: 'sbx_abc123...', // Reconnect to existing sandbox
181
186
  });
@@ -782,14 +787,20 @@ interface Sandbox {
782
787
  writeFile(path: string, content: string): Promise<void>;
783
788
  readDir(path: string): Promise<string[]>;
784
789
  fileExists(path: string): Promise<boolean>;
790
+ isDirectory(path: string): Promise<boolean>;
785
791
  destroy(): Promise<void>;
786
792
 
787
793
  // Optional: Sandbox ID for reconnection (cloud providers only)
788
794
  readonly id?: string;
795
+
796
+ // Path to ripgrep binary (set by ensureSandboxTools)
797
+ rgPath?: string;
789
798
  }
790
799
  ```
791
800
 
792
- The `id` property is available on cloud sandboxes (E2B, Vercel) after the first operation. Use it to persist the sandbox ID and reconnect later.
801
+ The `id` property is available on cloud sandboxes (E2B, Vercel) after creation. Use it to persist the sandbox ID and reconnect later.
802
+
803
+ The `rgPath` property is set by `ensureSandboxTools()` (called automatically during sandbox creation). It points to the ripgrep binary for the Grep tool. Supports x86_64 and ARM64 architectures.
793
804
 
794
805
  ### Custom Sandbox Example
795
806
 
@@ -866,9 +877,10 @@ Creates a set of agent tools bound to a sandbox instance.
866
877
 
867
878
  ### Sandbox Factories
868
879
 
869
- - `createLocalSandbox(config?)` - Local execution sandbox
870
- - `createVercelSandbox(config?)` - Vercel Firecracker sandbox
871
- - `createE2BSandbox(config?)` - E2B cloud sandbox
880
+ - `createLocalSandbox(config?)` - Local execution sandbox (sync)
881
+ - `createVercelSandbox(config?)` - Vercel Firecracker sandbox (async, auto-installs ripgrep)
882
+ - `createE2BSandbox(config?)` - E2B cloud sandbox (async, auto-installs ripgrep)
883
+ - `ensureSandboxTools(sandbox)` - Manually setup tools (called automatically by default)
872
884
 
873
885
  ### Workflow Tools
874
886
 
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export type { UIMessageStreamWriter, StreamTextResult, Tool, ToolSet, LanguageModel, LanguageModelMiddleware, Output, } from "ai";
2
2
  export { anthropicPromptCacheMiddleware, anthropicPromptCacheMiddlewareV2, } from "./middleware";
3
3
  export type { E2BSandboxConfig, LocalSandboxConfig, VercelSandboxConfig, } from "./sandbox";
4
- export { createE2BSandbox, createLocalSandbox, createVercelSandbox, } from "./sandbox";
4
+ export { createE2BSandbox, createLocalSandbox, createVercelSandbox, ensureSandboxTools, } from "./sandbox";
5
5
  export type { ExecOptions, ExecResult, Sandbox } from "./sandbox/interface";
6
6
  export type { AgentToolsResult, AskUserError, AskUserOutput, AskUserResponseHandler, BashError, BashOutput, EditError, EditOutput, EnterPlanModeError, EnterPlanModeOutput, ExitPlanModeError, ExitPlanModeOutput, PlanModeState, GlobError, GlobOutput, GrepContentOutput, GrepCountOutput, GrepError, GrepFilesOutput, GrepMatch, GrepOutput, ReadDirectoryOutput, ReadError, ReadOutput, ReadTextOutput, SkillError, SkillOutput, SkillToolConfig, SubagentEventData, SubagentStepEvent, SubagentTypeConfig, TaskError, TaskOutput, TaskToolConfig, TodoItem, TodoState, TodoWriteError, TodoWriteOutput, WebFetchError, WebFetchOutput, WebSearchError, WebSearchOutput, WebSearchResult, WriteError, WriteOutput, } from "./tools";
7
7
  export { createAgentTools, createAskUserTool, createBashTool, createEditTool, createEnterPlanModeTool, createExitPlanModeTool, createGlobTool, createGrepTool, createReadTool, createSkillTool, createTaskTool, createTodoWriteTool, createWebFetchTool, createWebSearchTool, createWriteTool, } from "./tools";
package/dist/index.js CHANGED
@@ -49,15 +49,71 @@ var anthropicPromptCacheMiddleware = {
49
49
  specificationVersion: "v3",
50
50
  transformParams: async ({ params }) => applyCacheMarkers(params)
51
51
  };
52
+ // src/sandbox/lazy-singleton.ts
53
+ function createLazySingleton(factory) {
54
+ let promise = null;
55
+ return {
56
+ get: () => {
57
+ if (!promise) {
58
+ promise = factory();
59
+ }
60
+ return promise;
61
+ },
62
+ reset: () => {
63
+ promise = null;
64
+ }
65
+ };
66
+ }
67
+
68
+ // src/sandbox/ensure-tools.ts
69
+ import { rgPath as bundledRgPath } from "@vscode/ripgrep";
70
+ var RIPGREP_VERSION = "14.1.0";
71
+ var ARCH_MAP = {
72
+ x86_64: "x86_64-unknown-linux-musl",
73
+ aarch64: "aarch64-unknown-linux-gnu",
74
+ arm64: "aarch64-unknown-linux-gnu"
75
+ };
76
+ async function ensureSandboxTools(sandbox) {
77
+ const bundledCheck = await sandbox.exec(`test -x "${bundledRgPath}" && echo found`);
78
+ if (bundledCheck.stdout.includes("found")) {
79
+ sandbox.rgPath = bundledRgPath;
80
+ return;
81
+ }
82
+ const tmpCheck = await sandbox.exec("test -x /tmp/rg && echo found");
83
+ if (tmpCheck.stdout.includes("found")) {
84
+ sandbox.rgPath = "/tmp/rg";
85
+ return;
86
+ }
87
+ const systemCheck = await sandbox.exec("which rg 2>/dev/null");
88
+ if (systemCheck.exitCode === 0 && systemCheck.stdout.trim()) {
89
+ sandbox.rgPath = systemCheck.stdout.trim();
90
+ return;
91
+ }
92
+ const archResult = await sandbox.exec("uname -m");
93
+ const arch = archResult.stdout.trim();
94
+ const ripgrepArch = ARCH_MAP[arch];
95
+ if (!ripgrepArch) {
96
+ throw new Error(`Unsupported architecture: ${arch}. Supported: ${Object.keys(ARCH_MAP).join(", ")}`);
97
+ }
98
+ const ripgrepUrl = `https://github.com/BurntSushi/ripgrep/releases/download/${RIPGREP_VERSION}/ripgrep-${RIPGREP_VERSION}-${ripgrepArch}.tar.gz`;
99
+ const tarPath = `ripgrep-${RIPGREP_VERSION}-${ripgrepArch}/rg`;
100
+ const installResult = await sandbox.exec(`
101
+ curl -sL "${ripgrepUrl}" |
102
+ tar xzf - -C /tmp --strip-components=1 ${tarPath} &&
103
+ chmod +x /tmp/rg
104
+ `);
105
+ if (installResult.exitCode !== 0) {
106
+ throw new Error(`Failed to install ripgrep: ${installResult.stderr}`);
107
+ }
108
+ sandbox.rgPath = "/tmp/rg";
109
+ }
110
+
52
111
  // src/sandbox/e2b.ts
53
- function createE2BSandbox(config = {}) {
54
- let sandbox = null;
112
+ async function createE2BSandbox(config = {}) {
55
113
  let sandboxId = config.sandboxId;
56
114
  const workingDirectory = config.cwd || "/home/user";
57
115
  const timeout = config.timeout ?? 300000;
58
- const ensureSandbox = async () => {
59
- if (sandbox)
60
- return sandbox;
116
+ const sandbox = createLazySingleton(async () => {
61
117
  let E2BSandboxSDK;
62
118
  try {
63
119
  const module = await import("@e2b/code-interpreter");
@@ -65,20 +121,21 @@ function createE2BSandbox(config = {}) {
65
121
  } catch {
66
122
  throw new Error("E2BSandbox requires @e2b/code-interpreter. Install with: npm install @e2b/code-interpreter");
67
123
  }
124
+ let sbx;
68
125
  if (config.sandboxId) {
69
- sandbox = await E2BSandboxSDK.connect(config.sandboxId);
126
+ sbx = await E2BSandboxSDK.connect(config.sandboxId);
70
127
  } else {
71
- sandbox = await E2BSandboxSDK.create({
128
+ sbx = await E2BSandboxSDK.create({
72
129
  apiKey: config.apiKey,
73
130
  timeoutMs: timeout,
74
131
  metadata: config.metadata
75
132
  });
76
- sandboxId = sandbox.sandboxId;
133
+ sandboxId = sbx.sandboxId;
77
134
  }
78
- return sandbox;
79
- };
135
+ return sbx;
136
+ });
80
137
  const exec = async (command, options) => {
81
- const sbx = await ensureSandbox();
138
+ const sbx = await sandbox.get();
82
139
  const startTime = performance.now();
83
140
  try {
84
141
  const result = await sbx.commands.run(command, {
@@ -118,11 +175,18 @@ function createE2BSandbox(config = {}) {
118
175
  throw error;
119
176
  }
120
177
  };
121
- return {
178
+ let rgPath;
179
+ const sandboxObj = {
122
180
  exec,
123
181
  get id() {
124
182
  return sandboxId;
125
183
  },
184
+ get rgPath() {
185
+ return rgPath;
186
+ },
187
+ set rgPath(path) {
188
+ rgPath = path;
189
+ },
126
190
  async readFile(path) {
127
191
  const result = await exec(`cat "${path}"`);
128
192
  if (result.exitCode !== 0) {
@@ -131,7 +195,7 @@ function createE2BSandbox(config = {}) {
131
195
  return result.stdout;
132
196
  },
133
197
  async writeFile(path, content) {
134
- const sbx = await ensureSandbox();
198
+ const sbx = await sandbox.get();
135
199
  await sbx.files.write(path, content);
136
200
  },
137
201
  async readDir(path) {
@@ -151,15 +215,21 @@ function createE2BSandbox(config = {}) {
151
215
  return result.exitCode === 0;
152
216
  },
153
217
  async destroy() {
154
- if (sandbox) {
155
- await sandbox.kill();
156
- sandbox = null;
157
- }
218
+ try {
219
+ const sbx = await sandbox.get();
220
+ await sbx.kill();
221
+ } catch {}
222
+ sandbox.reset();
158
223
  }
159
224
  };
225
+ if (config.ensureTools !== false) {
226
+ await ensureSandboxTools(sandboxObj);
227
+ }
228
+ return sandboxObj;
160
229
  }
161
230
  // src/sandbox/local.ts
162
231
  import { existsSync, mkdirSync } from "node:fs";
232
+ import { rgPath as bundledRgPath2 } from "@vscode/ripgrep";
163
233
  function createLocalSandbox(config = {}) {
164
234
  const workingDirectory = config.cwd || "/tmp";
165
235
  if (!existsSync(workingDirectory)) {
@@ -201,6 +271,7 @@ function createLocalSandbox(config = {}) {
201
271
  };
202
272
  return {
203
273
  exec,
274
+ rgPath: bundledRgPath2,
204
275
  async readFile(path) {
205
276
  const fullPath = path.startsWith("/") ? path : `${workingDirectory}/${path}`;
206
277
  const file = Bun.file(fullPath);
@@ -239,8 +310,7 @@ function createLocalSandbox(config = {}) {
239
310
  };
240
311
  }
241
312
  // src/sandbox/vercel.ts
242
- function createVercelSandbox(config = {}) {
243
- let sandbox = null;
313
+ async function createVercelSandbox(config = {}) {
244
314
  let sandboxId = config.sandboxId;
245
315
  const workingDirectory = config.cwd || "/vercel/sandbox";
246
316
  const resolvedConfig = {
@@ -248,9 +318,7 @@ function createVercelSandbox(config = {}) {
248
318
  resources: config.resources ?? { vcpus: 2 },
249
319
  timeout: config.timeout ?? 300000
250
320
  };
251
- const ensureSandbox = async () => {
252
- if (sandbox)
253
- return sandbox;
321
+ const sandbox = createLazySingleton(async () => {
254
322
  let VercelSandboxSDK;
255
323
  try {
256
324
  const module = await import("@vercel/sandbox");
@@ -269,16 +337,17 @@ function createVercelSandbox(config = {}) {
269
337
  token: config.token
270
338
  });
271
339
  }
340
+ let sbx;
272
341
  if (config.sandboxId) {
273
- sandbox = await VercelSandboxSDK.get({ sandboxId: config.sandboxId });
342
+ sbx = await VercelSandboxSDK.get({ sandboxId: config.sandboxId });
274
343
  } else {
275
- sandbox = await VercelSandboxSDK.create(createOptions);
344
+ sbx = await VercelSandboxSDK.create(createOptions);
276
345
  }
277
- sandboxId = sandbox.sandboxId;
278
- return sandbox;
279
- };
346
+ sandboxId = sbx.sandboxId;
347
+ return sbx;
348
+ });
280
349
  const exec = async (command, options) => {
281
- const sbx = await ensureSandbox();
350
+ const sbx = await sandbox.get();
282
351
  const startTime = performance.now();
283
352
  let interrupted = false;
284
353
  const abortController = new AbortController;
@@ -326,13 +395,20 @@ function createVercelSandbox(config = {}) {
326
395
  throw error;
327
396
  }
328
397
  };
329
- return {
398
+ let rgPath;
399
+ const sandboxObj = {
330
400
  exec,
331
401
  get id() {
332
402
  return sandboxId;
333
403
  },
404
+ get rgPath() {
405
+ return rgPath;
406
+ },
407
+ set rgPath(path) {
408
+ rgPath = path;
409
+ },
334
410
  async readFile(path) {
335
- const sbx = await ensureSandbox();
411
+ const sbx = await sandbox.get();
336
412
  const stream = await sbx.readFile({ path });
337
413
  if (!stream) {
338
414
  throw new Error(`File not found: ${path}`);
@@ -344,7 +420,7 @@ function createVercelSandbox(config = {}) {
344
420
  return Buffer.concat(chunks).toString("utf-8");
345
421
  },
346
422
  async writeFile(path, content) {
347
- const sbx = await ensureSandbox();
423
+ const sbx = await sandbox.get();
348
424
  await sbx.writeFiles([
349
425
  {
350
426
  path,
@@ -369,12 +445,17 @@ function createVercelSandbox(config = {}) {
369
445
  return result.exitCode === 0;
370
446
  },
371
447
  async destroy() {
372
- if (sandbox) {
373
- await sandbox.stop();
374
- sandbox = null;
375
- }
448
+ try {
449
+ const sbx = await sandbox.get();
450
+ await sbx.stop();
451
+ } catch {}
452
+ sandbox.reset();
376
453
  }
377
454
  };
455
+ if (config.ensureTools !== false) {
456
+ await ensureSandboxTools(sandboxObj);
457
+ }
458
+ return sandboxObj;
378
459
  }
379
460
  // src/cache/lru.ts
380
461
  class LRUCacheStore {
@@ -991,7 +1072,6 @@ function createGlobTool(sandbox, config) {
991
1072
  // src/tools/grep.ts
992
1073
  import { tool as tool7, zodSchema as zodSchema7 } from "ai";
993
1074
  import { z as z7 } from "zod";
994
- import { rgPath } from "@vscode/ripgrep";
995
1075
  var grepInputSchema = z7.object({
996
1076
  pattern: z7.string().describe("The regular expression pattern to search for in file contents"),
997
1077
  path: z7.string().optional().describe("File or directory to search in (defaults to cwd)"),
@@ -1057,7 +1137,13 @@ function createGrepTool(sandbox, config) {
1057
1137
  }
1058
1138
  }
1059
1139
  try {
1140
+ if (!sandbox.rgPath) {
1141
+ return {
1142
+ error: "Ripgrep not available. Call ensureSandboxTools(sandbox) before using Grep with remote sandboxes."
1143
+ };
1144
+ }
1060
1145
  const cmd = buildRipgrepCommand({
1146
+ rgPath: sandbox.rgPath,
1061
1147
  pattern,
1062
1148
  searchPath,
1063
1149
  output_mode,
@@ -1106,7 +1192,7 @@ function buildRipgrepCommand(opts) {
1106
1192
  if (opts.type)
1107
1193
  flags.push(`-t ${opts.type}`);
1108
1194
  const flagStr = flags.join(" ");
1109
- return `${rgPath} ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null`;
1195
+ return `${opts.rgPath} ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null`;
1110
1196
  }
1111
1197
  function parseFilesOutput(stdout) {
1112
1198
  const files = new Set;
@@ -2778,6 +2864,7 @@ export {
2778
2864
  estimateTokens,
2779
2865
  estimateMessagesTokens,
2780
2866
  estimateMessageTokens,
2867
+ ensureSandboxTools,
2781
2868
  discoverSkills,
2782
2869
  createWriteTool,
2783
2870
  createWebSearchTool,
@@ -7,5 +7,10 @@ export interface E2BSandboxConfig {
7
7
  timeout?: number;
8
8
  cwd?: string;
9
9
  metadata?: Record<string, string>;
10
+ /**
11
+ * Ensure tools like ripgrep are available in the sandbox.
12
+ * Defaults to true. Set to false for faster startup if you don't need Grep.
13
+ */
14
+ ensureTools?: boolean;
10
15
  }
11
- export declare function createE2BSandbox(config?: E2BSandboxConfig): Sandbox;
16
+ export declare function createE2BSandbox(config?: E2BSandboxConfig): Promise<Sandbox>;
@@ -0,0 +1,22 @@
1
+ import type { Sandbox } from "./interface";
2
+ /**
3
+ * Ensures required tools (ripgrep) are available in the sandbox.
4
+ * Call this once during sandbox setup, before using tools like Grep.
5
+ *
6
+ * - For local sandboxes: verifies bundled binary exists
7
+ * - For remote sandboxes: installs ripgrep to /tmp/rg if not present
8
+ *
9
+ * Supports x86_64 and ARM64 architectures.
10
+ *
11
+ * After calling, `sandbox.rgPath` will be set to the correct path.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const sandbox = await createVercelSandbox();
16
+ * await ensureSandboxTools(sandbox);
17
+ *
18
+ * const { tools } = createAgentTools(sandbox);
19
+ * // Grep now works
20
+ * ```
21
+ */
22
+ export declare function ensureSandboxTools(sandbox: Sandbox): Promise<void>;
@@ -1,4 +1,5 @@
1
1
  export { createE2BSandbox, type E2BSandboxConfig } from "./e2b";
2
+ export { ensureSandboxTools } from "./ensure-tools";
2
3
  export type { ExecOptions, ExecResult, Sandbox } from "./interface";
3
4
  export { createLocalSandbox, type LocalSandboxConfig } from "./local";
4
5
  export { createVercelSandbox, type VercelSandboxConfig } from "./vercel";
@@ -25,4 +25,9 @@ export interface Sandbox {
25
25
  * - For local sandboxes: always undefined
26
26
  */
27
27
  readonly id?: string;
28
+ /**
29
+ * Path to ripgrep binary for this sandbox.
30
+ * Set by ensureSandboxTools() or defaults to bundled binary for local sandboxes.
31
+ */
32
+ rgPath?: string;
28
33
  }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Creates a lazy singleton that initializes on first access.
3
+ * Safe for concurrent calls - all callers await the same promise.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * const sandbox = createLazySingleton(async () => {
8
+ * const sdk = await import("@vercel/sandbox");
9
+ * return sdk.Sandbox.create({ ... });
10
+ * });
11
+ *
12
+ * // Safe for parallel calls:
13
+ * const [a, b] = await Promise.all([sandbox.get(), sandbox.get()]);
14
+ * // a === b (same instance)
15
+ *
16
+ * // Reset for cleanup:
17
+ * sandbox.reset();
18
+ * ```
19
+ */
20
+ export declare function createLazySingleton<T>(factory: () => Promise<T>): {
21
+ /** Get the singleton instance, creating it if needed */
22
+ get: () => Promise<T>;
23
+ /** Reset the singleton, allowing a new instance to be created */
24
+ reset: () => void;
25
+ };
@@ -11,5 +11,10 @@ export interface VercelSandboxConfig {
11
11
  teamId?: string;
12
12
  projectId?: string;
13
13
  token?: string;
14
+ /**
15
+ * Ensure tools like ripgrep are available in the sandbox.
16
+ * Defaults to true. Set to false for faster startup if you don't need Grep.
17
+ */
18
+ ensureTools?: boolean;
14
19
  }
15
- export declare function createVercelSandbox(config?: VercelSandboxConfig): Sandbox;
20
+ export declare function createVercelSandbox(config?: VercelSandboxConfig): Promise<Sandbox>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bashkit",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "Agentic coding tools for the Vercel AI SDK",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",