@@ -4,10 +4,9 @@ import { createInterface } from "readline";
44import { Agent } from "@mariozechner/pi-agent-core" ;
55import type { AgentEvent , AgentTool } from "@mariozechner/pi-agent-core" ;
66import { loadAgent } from "./loader.js" ;
7- import { createCliTool } from "./tools/cli.js" ;
8- import { createReadTool } from "./tools/read.js" ;
9- import { createWriteTool } from "./tools/write.js" ;
10- import { createMemoryTool } from "./tools/memory.js" ;
7+ import { createBuiltinTools } from "./tools/index.js" ;
8+ import { createSandboxContext } from "./sandbox.js" ;
9+ import type { SandboxContext , SandboxConfig } from "./sandbox.js" ;
1110import { expandSkillCommand } from "./skills.js" ;
1211import { loadHooksConfig , runHooks , wrapToolWithHooks } from "./hooks.js" ;
1312import type { HooksConfig } from "./hooks.js" ;
@@ -24,12 +23,13 @@ const bold = (s: string) => `\x1b[1m${s}\x1b[0m`;
2423const red = ( s : string ) => `\x1b[31m${ s } \x1b[0m` ;
2524const green = ( s : string ) => `\x1b[32m${ s } \x1b[0m` ;
2625
27- function parseArgs ( argv : string [ ] ) : { model ? : string ; dir: string ; prompt ? : string ; env ? : string } {
26+ function parseArgs ( argv : string [ ] ) : { model ? : string ; dir: string ; prompt ? : string ; env ? : string ; sandbox ? : boolean } {
2827 const args = argv . slice ( 2 ) ;
2928 let model : string | undefined ;
3029 let dir = process . cwd ( ) ;
3130 let prompt : string | undefined ;
3231 let env : string | undefined ;
32+ let sandbox = false ;
3333
3434 for ( let i = 0 ; i < args . length ; i ++ ) {
3535 switch ( args [ i ] ) {
@@ -49,6 +49,10 @@ function parseArgs(argv: string[]): { model?: string; dir: string; prompt?: stri
4949 case "-e" :
5050 env = args [ ++ i ] ;
5151 break ;
52+ case "--sandbox" :
53+ case "-s" :
54+ sandbox = true ;
55+ break ;
5256 default :
5357 if ( ! args [ i ] . startsWith ( "-" ) ) {
5458 prompt = args [ i ] ;
@@ -57,7 +61,7 @@ function parseArgs(argv: string[]): { model?: string; dir: string; prompt?: stri
5761 }
5862 }
5963
60- return { model, dir, prompt, env } ;
64+ return { model, dir, prompt, env, sandbox } ;
6165}
6266
6367function handleEvent (
@@ -235,7 +239,7 @@ async function ensureRepo(dir: string, model?: string): Promise<string> {
235239}
236240
237241async function main ( ) : Promise < void > {
238- const { model, dir : rawDir , prompt, env } = parseArgs ( process . argv ) ;
242+ const { model, dir : rawDir , prompt, env, sandbox : useSandbox } = parseArgs ( process . argv ) ;
239243
240244 // If no --dir given interactively, ask for it
241245 let dir = rawDir ;
@@ -246,8 +250,22 @@ async function main(): Promise<void> {
246250 }
247251 }
248252
249- // Ensure the target is a valid gitclaw repo
250- dir = await ensureRepo ( dir , model ) ;
253+ // Create sandbox context if --sandbox flag is set
254+ let sandboxCtx : SandboxContext | undefined ;
255+ if ( useSandbox ) {
256+ const sandboxConfig : SandboxConfig = { provider : "e2b" } ;
257+ sandboxCtx = await createSandboxContext ( sandboxConfig , resolve ( dir ) ) ;
258+ console . log ( dim ( "Starting sandbox VM..." ) ) ;
259+ await sandboxCtx . gitMachine . start ( ) ;
260+ console . log ( dim ( `Sandbox ready (repo: ${ sandboxCtx . repoPath } )` ) ) ;
261+ }
262+
263+ // Ensure the target is a valid gitclaw repo (skip in sandbox mode — gitmachine clones the repo)
264+ if ( ! useSandbox ) {
265+ dir = await ensureRepo ( dir , model ) ;
266+ } else {
267+ dir = resolve ( dir ) ;
268+ }
251269
252270 let loaded ;
253271 try {
@@ -312,12 +330,11 @@ async function main(): Promise<void> {
312330 }
313331
314332 // Build tools — built-in + declarative
315- let tools : AgentTool < any > [ ] = [
316- createCliTool ( dir , manifest . runtime . timeout ) ,
317- createReadTool ( dir ) ,
318- createWriteTool ( dir ) ,
319- createMemoryTool ( dir ) ,
320- ] ;
333+ let tools : AgentTool < any > [ ] = createBuiltinTools ( {
334+ dir,
335+ timeout : manifest . runtime . timeout ,
336+ sandbox : sandboxCtx ,
337+ } ) ;
321338
322339 // Load declarative tools from tools/*.yaml (Phase 2.2)
323340 const declarativeTools = await loadDeclarativeTools ( agentDir ) ;
@@ -380,6 +397,11 @@ async function main(): Promise<void> {
380397 } ) . catch ( ( ) => { } ) ;
381398 }
382399 throw err ;
400+ } finally {
401+ if ( sandboxCtx ) {
402+ console . log ( dim ( "Stopping sandbox..." ) ) ;
403+ await sandboxCtx . gitMachine . stop ( ) ;
404+ }
383405 }
384406 return ;
385407 }
@@ -401,6 +423,7 @@ async function main(): Promise<void> {
401423
402424 if ( trimmed === "/quit" || trimmed === "/exit" ) {
403425 rl . close ( ) ;
426+ await stopSandbox ( ) ;
404427 process . exit ( 0 ) ;
405428 }
406429
@@ -463,14 +486,22 @@ async function main(): Promise<void> {
463486 } ) ;
464487 } ;
465488
489+ // Sandbox cleanup helper
490+ const stopSandbox = async ( ) = > {
491+ if ( sandboxCtx ) {
492+ console . log ( dim ( "Stopping sandbox..." ) ) ;
493+ await sandboxCtx . gitMachine . stop ( ) ;
494+ }
495+ } ;
496+
466497 // Handle Ctrl+C during streaming
467498 rl . on ( "SIGINT" , ( ) => {
468499 if ( agent . state . isStreaming ) {
469500 agent . abort ( ) ;
470501 } else {
471502 console . log ( "\nBye!" ) ;
472503 rl . close ( ) ;
473- process . exit ( 0 ) ;
504+ stopSandbox ( ) . finally ( ( ) => process . exit ( 0 ) ) ;
474505 }
475506 } ) ;
476507
0 commit comments