@@ -16,6 +16,8 @@ import { formatComplianceWarnings } from "./compliance.js";
1616import { readFile , mkdir , writeFile , stat , access } from "fs/promises" ;
1717import { join , resolve } from "path" ;
1818import { execSync } from "child_process" ;
19+ import { initLocalSession } from "./session.js" ;
20+ import type { LocalSession } from "./session.js" ;
1921
2022// ANSI helpers
2123const dim = ( s : string ) => `\x1b[2m${ s } \x1b[0m` ;
@@ -31,6 +33,9 @@ interface ParsedArgs {
3133 sandbox ? : boolean ;
3234 sandboxRepo ? : string ;
3335 sandboxToken ? : string ;
36+ repo ? : string ;
37+ pat ? : string ;
38+ session ? : string ;
3439}
3540
3641function parseArgs ( argv : string [ ] ) : ParsedArgs {
@@ -42,6 +47,9 @@ function parseArgs(argv: string[]): ParsedArgs {
4247 let sandbox = false ;
4348 let sandboxRepo : string | undefined ;
4449 let sandboxToken : string | undefined ;
50+ let repo : string | undefined ;
51+ let pat : string | undefined ;
52+ let session : string | undefined ;
4553
4654 for ( let i = 0 ; i < args . length ; i ++ ) {
4755 switch ( args [ i ] ) {
@@ -71,6 +79,16 @@ function parseArgs(argv: string[]): ParsedArgs {
7179 case "--sandbox-token" :
7280 sandboxToken = args [ ++ i ] ;
7381 break ;
82+ case "--repo" :
83+ case "-r" :
84+ repo = args [ ++ i ] ;
85+ break ;
86+ case "--pat" :
87+ pat = args [ ++ i ] ;
88+ break ;
89+ case "--session" :
90+ session = args [ ++ i ] ;
91+ break ;
7492 default :
7593 if ( ! args [ i ] . startsWith ( "-" ) ) {
7694 prompt = args [ i ] ;
@@ -79,7 +97,7 @@ function parseArgs(argv: string[]): ParsedArgs {
7997 }
8098 }
8199
82- return { model, dir, prompt, env, sandbox, sandboxRepo, sandboxToken } ;
100+ return { model, dir, prompt, env, sandbox, sandboxRepo, sandboxToken, repo , pat , session } ;
83101}
84102
85103function handleEvent (
@@ -257,11 +275,41 @@ async function ensureRepo(dir: string, model?: string): Promise<string> {
257275}
258276
259277async function main ( ) : Promise < void > {
260- const { model, dir : rawDir , prompt, env, sandbox : useSandbox , sandboxRepo, sandboxToken } = parseArgs ( process . argv ) ;
278+ const { model, dir : rawDir , prompt, env, sandbox : useSandbox , sandboxRepo, sandboxToken, repo , pat , session : sessionBranch } = parseArgs ( process . argv ) ;
261279
262- // If no --dir given interactively, ask for it
280+ // If --repo is given, derive a default dir from the repo URL (skip interactive prompt)
263281 let dir = rawDir ;
264- if ( dir === process . cwd ( ) && ! prompt ) {
282+ let localSession : LocalSession | undefined ;
283+
284+ if ( repo ) {
285+ // Validate mutually exclusive flags
286+ if ( useSandbox ) {
287+ console . error ( red ( "Error: --repo and --sandbox are mutually exclusive" ) ) ;
288+ process . exit ( 1 ) ;
289+ }
290+
291+ const token = pat || process . env . GITHUB_TOKEN || process . env . GIT_TOKEN ;
292+ if ( ! token ) {
293+ console . error ( red ( "Error: --pat, GITHUB_TOKEN, or GIT_TOKEN is required with --repo" ) ) ;
294+ process . exit ( 1 ) ;
295+ }
296+
297+ // Default dir: /tmp/gitclaw/<repo-name> if no --dir given
298+ if ( dir === process . cwd ( ) ) {
299+ const repoName = repo . split ( "/" ) . pop ( ) ?. replace ( / \. g i t $ / , "" ) || "repo" ;
300+ dir = resolve ( `/tmp/gitclaw/${ repoName } ` ) ;
301+ }
302+
303+ localSession = initLocalSession ( {
304+ url : repo ,
305+ token,
306+ dir,
307+ session : sessionBranch ,
308+ } ) ;
309+ dir = localSession . dir ;
310+ console . log ( dim ( `Local session: ${ localSession . branch } (${ localSession . dir } )` ) ) ;
311+ } else if ( dir === process . cwd ( ) && ! prompt ) {
312+ // No --repo: interactive prompt for dir
265313 const answer = await askQuestion ( green ( "? " ) + bold ( "Repository path" ) + dim ( " (. for current dir)" ) + green ( ": " ) ) ;
266314 if ( answer ) {
267315 dir = resolve ( answer === "." ? process . cwd ( ) : answer ) ;
@@ -282,8 +330,10 @@ async function main(): Promise<void> {
282330 console . log ( dim ( `Sandbox ready (repo: ${ sandboxCtx . repoPath } )` ) ) ;
283331 }
284332
285- // Ensure the target is a valid gitclaw repo (skip in sandbox mode — gitmachine clones the repo)
286- if ( ! useSandbox ) {
333+ // Ensure the target is a valid gitclaw repo (skip in sandbox/local-repo mode)
334+ if ( localSession ) {
335+ // Already cloned and scaffolded by initLocalSession
336+ } else if ( ! useSandbox ) {
287337 dir = await ensureRepo ( dir , model ) ;
288338 } else {
289339 dir = resolve ( dir ) ;
@@ -420,6 +470,10 @@ async function main(): Promise<void> {
420470 }
421471 throw err ;
422472 } finally {
473+ if ( localSession ) {
474+ console . log ( dim ( "Finalizing session..." ) ) ;
475+ localSession . finalize ( ) ;
476+ }
423477 if ( sandboxCtx ) {
424478 console . log ( dim ( "Stopping sandbox..." ) ) ;
425479 await sandboxCtx . gitMachine . stop ( ) ;
@@ -445,6 +499,10 @@ async function main(): Promise<void> {
445499
446500 if ( trimmed === "/quit" || trimmed === "/exit" ) {
447501 rl . close ( ) ;
502+ if ( localSession ) {
503+ console . log ( dim ( "Finalizing session..." ) ) ;
504+ localSession . finalize ( ) ;
505+ }
448506 await stopSandbox ( ) ;
449507 process . exit ( 0 ) ;
450508 }
@@ -523,6 +581,9 @@ async function main(): Promise<void> {
523581 } else {
524582 console . log ( "\nBye!" ) ;
525583 rl . close ( ) ;
584+ if ( localSession ) {
585+ try { localSession . finalize ( ) ; } catch { /* best-effort */ }
586+ }
526587 stopSandbox ( ) . finally ( ( ) => process . exit ( 0 ) ) ;
527588 }
528589 } ) ;
0 commit comments