88 displayCliNotInstalledError ,
99 isCommandNotFoundError ,
1010 ensureAuthDirectory ,
11- createCredentialFile ,
1211 cleanupAuthFiles ,
1312 getNextAuthAction ,
1413} from '../../core/auth.js' ;
@@ -74,6 +73,86 @@ export async function isAuthenticated(options?: ClaudeAuthOptions): Promise<bool
7473 }
7574}
7675
76+ /**
77+ * Polls for credentials file and terminates process when found
78+ */
79+ function watchForCredentials (
80+ credPath : string ,
81+ proc : ReturnType < typeof Bun . spawn > ,
82+ timeoutMs : number = 600000 ,
83+ pollIntervalMs : number = 500
84+ ) : Promise < boolean > {
85+ return new Promise ( ( resolve ) => {
86+ let resolved = false ;
87+ let pollInterval : ReturnType < typeof setInterval > | null = null ;
88+
89+ const cleanup = ( ) => {
90+ if ( pollInterval ) {
91+ clearInterval ( pollInterval ) ;
92+ pollInterval = null ;
93+ }
94+ clearTimeout ( timeout ) ;
95+ } ;
96+
97+ const finish = ( success : boolean ) => {
98+ if ( resolved ) return ;
99+ resolved = true ;
100+ cleanup ( ) ;
101+ resolve ( success ) ;
102+ } ;
103+
104+ // Overall timeout
105+ const timeout = setTimeout ( async ( ) => {
106+ if ( resolved ) return ;
107+ proc . kill ( 'SIGTERM' ) ;
108+ finish ( false ) ;
109+ } , timeoutMs ) ;
110+
111+ // Poll for credential file
112+ pollInterval = setInterval ( async ( ) => {
113+ if ( resolved ) return ;
114+
115+ try {
116+ await stat ( credPath ) ;
117+ } catch {
118+ return ; // File doesn't exist yet
119+ }
120+
121+ // File exists - wait for it to be fully written
122+ await new Promise ( ( r ) => setTimeout ( r , 200 ) ) ;
123+
124+ // Verify file still exists
125+ try {
126+ await stat ( credPath ) ;
127+ } catch {
128+ return ; // File disappeared
129+ }
130+
131+ console . log ( '\nAuthentication detected, closing Claude...\n' ) ;
132+ proc . kill ( 'SIGTERM' ) ;
133+
134+ // Fallback to SIGKILL if process doesn't exit
135+ const killTimeout = setTimeout ( ( ) => proc . kill ( 'SIGKILL' ) , 2000 ) ;
136+ await proc . exited ;
137+ clearTimeout ( killTimeout ) ;
138+
139+ finish ( true ) ;
140+ } , pollIntervalMs ) ;
141+
142+ // Also resolve if process exits on its own
143+ proc . exited . then ( async ( ) => {
144+ if ( resolved ) return ;
145+ // Check if credentials exist after natural exit
146+ try {
147+ await stat ( credPath ) ;
148+ finish ( true ) ;
149+ } catch {
150+ finish ( false ) ;
151+ }
152+ } ) ;
153+ } ) ;
154+ }
155+
77156/**
78157 * Ensures Claude is authenticated, running setup-token if needed
79158 */
@@ -101,25 +180,36 @@ export async function ensureAuth(options?: ClaudeAuthOptions): Promise<boolean>
101180 throw new Error ( `${ metadata . name } CLI is not installed.` ) ;
102181 }
103182
104- // Run interactive setup-token via Claude CLI with proper env
183+ // Ensure config directory exists for watcher
184+ await ensureAuthDirectory ( configDir ) ;
185+
105186 console . log ( `\nRunning Claude authentication...\n` ) ;
106187 console . log ( `Config directory: ${ configDir } \n` ) ;
107188
108189 try {
109- // Resolve claude command to handle Windows .cmd files
110190 const resolvedClaude = Bun . which ( 'claude' ) ?? 'claude' ;
111191
112- const proc = Bun . spawn ( [ resolvedClaude , 'setup-token' ] , {
192+ // Spawn claude (not setup-token) - interactive
193+ const proc = Bun . spawn ( [ resolvedClaude ] , {
113194 env : { ...process . env , CLAUDE_CONFIG_DIR : configDir } ,
114195 stdio : [ 'inherit' , 'inherit' , 'inherit' ] ,
115196 } ) ;
116- await proc . exited ;
197+
198+ // Poll for credentials and terminate on success
199+ const success = await watchForCredentials ( credPath , proc ) ;
200+
201+ if ( success ) {
202+ return true ;
203+ }
204+
205+ // Auth failed or timed out
206+ throw new Error ( 'Authentication timed out or was not completed.' ) ;
117207 } catch ( error ) {
118208 if ( isCommandNotFoundError ( error ) ) {
119209 console . error ( `\n────────────────────────────────────────────────────────────` ) ;
120210 console . error ( ` ⚠️ ${ metadata . name } CLI Not Found` ) ;
121211 console . error ( `────────────────────────────────────────────────────────────` ) ;
122- console . error ( `\n'${ metadata . cliBinary } setup-token ' failed because the CLI is missing.` ) ;
212+ console . error ( `\n'${ metadata . cliBinary } ' failed because the CLI is missing.` ) ;
123213 console . error ( `Please install ${ metadata . name } CLI before trying again:\n` ) ;
124214 console . error ( ` ${ metadata . installCommand } \n` ) ;
125215 console . error ( `────────────────────────────────────────────────────────────\n` ) ;
@@ -128,25 +218,6 @@ export async function ensureAuth(options?: ClaudeAuthOptions): Promise<boolean>
128218
129219 throw error ;
130220 }
131-
132- // Verify the credentials were created
133- try {
134- await stat ( credPath ) ;
135- return true ;
136- } catch {
137- // Credentials file wasn't created - Claude CLI returned token instead
138- console . error ( `\n────────────────────────────────────────────────────────────` ) ;
139- console . error ( ` ℹ️ Claude CLI Authentication Notice` ) ;
140- console . error ( `────────────────────────────────────────────────────────────` ) ;
141- console . error ( `\nYour Claude CLI installation uses token-based authentication.` ) ;
142- console . error ( `Please set the token you received as an environment variable:\n` ) ;
143- console . error ( ` export CODEMACHINE_CLAUDE_OAUTH_TOKEN=<your-token>\n` ) ;
144- console . error ( `For persistence, add this line to your shell configuration:` ) ;
145- console . error ( ` ~/.bashrc (Bash) or ~/.zshrc (Zsh)\n` ) ;
146- console . error ( `────────────────────────────────────────────────────────────\n` ) ;
147-
148- throw new Error ( 'Authentication incomplete. Please set CODEMACHINE_CLAUDE_OAUTH_TOKEN environment variable.' ) ;
149- }
150221}
151222
152223/**
0 commit comments