99 REDIRECT_URI ,
1010} from "./auth/auth.js" ;
1111import { startLocalOAuthServer } from "./auth/server.js" ;
12- import { copyTextToClipboard , openBrowserUrl } from "./auth/browser.js" ;
12+ import { copyTextToClipboard , isBrowserLaunchSuppressed , openBrowserUrl } from "./auth/browser.js" ;
1313import { promptAddAnotherAccount , promptLoginMode , type ExistingAccountInfo } from "./cli.js" ;
1414import {
1515 extractAccountEmail ,
@@ -291,7 +291,7 @@ function printUsage(): void {
291291 "Codex Multi-Auth CLI" ,
292292 "" ,
293293 "Usage:" ,
294- " codex auth login" ,
294+ " codex auth login [--manual|--no-browser] " ,
295295 " codex auth list" ,
296296 " codex auth status" ,
297297 " codex auth switch <index>" ,
@@ -311,6 +311,37 @@ function printUsage(): void {
311311 ) ;
312312}
313313
314+ type AuthLoginOptions = {
315+ manual : boolean ;
316+ } ;
317+
318+ type ParsedAuthLoginArgs =
319+ | { ok : true ; options : AuthLoginOptions }
320+ | { ok : false ; message : string } ;
321+
322+ function parseAuthLoginArgs ( args : string [ ] ) : ParsedAuthLoginArgs {
323+ const options : AuthLoginOptions = {
324+ manual : false ,
325+ } ;
326+
327+ for ( const arg of args ) {
328+ if ( arg === "--manual" || arg === "--no-browser" ) {
329+ options . manual = true ;
330+ continue ;
331+ }
332+ if ( arg === "--help" || arg === "-h" ) {
333+ printUsage ( ) ;
334+ return { ok : false , message : "" } ;
335+ }
336+ return {
337+ ok : false ,
338+ message : `Unknown login option: ${ arg } ` ,
339+ } ;
340+ }
341+
342+ return { ok : true , options } ;
343+ }
344+
314345interface ImplementedFeature {
315346 id : number ;
316347 name : string ;
@@ -1095,16 +1126,22 @@ function applyTokenAccountIdentity(
10951126 return true ;
10961127}
10971128
1098- async function promptManualCallback ( state : string ) : Promise < string | null > {
1099- if ( ! input . isTTY || ! output . isTTY ) {
1129+ async function promptManualCallback (
1130+ state : string ,
1131+ options : { allowNonTty ?: boolean } = { } ,
1132+ ) : Promise < string | null > {
1133+ const useInteractivePrompt = input . isTTY && output . isTTY ;
1134+ if ( ! useInteractivePrompt && ! options . allowNonTty ) {
11001135 return null ;
11011136 }
11021137
11031138 const rl = createInterface ( { input, output } ) ;
11041139 try {
1105- console . log ( "" ) ;
1106- console . log ( stylePromptText ( UI_COPY . oauth . pastePrompt , "accent" ) ) ;
1107- const answer = await rl . question ( "◆ " ) ;
1140+ if ( useInteractivePrompt ) {
1141+ console . log ( "" ) ;
1142+ console . log ( stylePromptText ( UI_COPY . oauth . pastePrompt , "accent" ) ) ;
1143+ }
1144+ const answer = await rl . question ( useInteractivePrompt ? "◆ " : "" ) ;
11081145 if ( answer . includes ( "\u001b" ) ) {
11091146 return null ;
11101147 }
@@ -1425,12 +1462,21 @@ async function runActionPanel(
14251462 }
14261463}
14271464
1428- async function runOAuthFlow ( forceNewLogin : boolean ) : Promise < TokenResult > {
1465+ async function runOAuthFlow (
1466+ forceNewLogin : boolean ,
1467+ options : AuthLoginOptions = { manual : false } ,
1468+ ) : Promise < TokenResult > {
14291469 const { pkce, state, url } = await createAuthorizationFlow ( { forceNewLogin } ) ;
1430- const oauthServer = await startLocalOAuthServer ( { state } ) ;
1470+ const preferManualMode = options . manual || isBrowserLaunchSuppressed ( ) ;
1471+ let oauthServer : Awaited < ReturnType < typeof startLocalOAuthServer > > | null = null ;
1472+ try {
1473+ oauthServer = await startLocalOAuthServer ( { state } ) ;
1474+ } catch {
1475+ oauthServer = null ;
1476+ }
14311477 let code : string | null = null ;
14321478 try {
1433- const signInMode = await promptOAuthSignInMode ( ) ;
1479+ const signInMode = preferManualMode ? "manual" : await promptOAuthSignInMode ( ) ;
14341480 if ( signInMode === "cancel" ) {
14351481 return {
14361482 type : "failed" ,
@@ -1465,18 +1511,23 @@ async function runOAuthFlow(forceNewLogin: boolean): Promise<TokenResult> {
14651511 ) ;
14661512 }
14671513
1468- if ( oauthServer . ready ) {
1514+ if ( oauthServer ? .ready ) {
14691515 console . log ( stylePromptText ( UI_COPY . oauth . waitingCallback , "muted" ) ) ;
14701516 const callbackResult = await oauthServer . waitForCode ( state ) ;
14711517 code = callbackResult ?. code ?? null ;
14721518 }
14731519
14741520 if ( ! code ) {
1475- console . log ( stylePromptText ( UI_COPY . oauth . callbackMissed , "warning" ) ) ;
1476- code = await promptManualCallback ( state ) ;
1521+ console . log (
1522+ stylePromptText (
1523+ oauthServer ?. ready ? UI_COPY . oauth . callbackMissed : UI_COPY . oauth . callbackUnavailable ,
1524+ "warning" ,
1525+ ) ,
1526+ ) ;
1527+ code = await promptManualCallback ( state , { allowNonTty : preferManualMode } ) ;
14771528 }
14781529 } finally {
1479- oauthServer . close ( ) ;
1530+ oauthServer ? .close ( ) ;
14801531 }
14811532
14821533 if ( ! code ) {
@@ -4099,7 +4150,7 @@ async function handleManageAction(
40994150 const existing = storage . accounts [ idx ] ;
41004151 if ( ! existing ) return ;
41014152
4102- const tokenResult = await runOAuthFlow ( true ) ;
4153+ const tokenResult = await runOAuthFlow ( true , { manual : false } ) ;
41034154 if ( tokenResult . type !== "success" ) {
41044155 console . error ( `Refresh failed: ${ tokenResult . message ?? tokenResult . reason ?? "unknown error" } ` ) ;
41054156 return ;
@@ -4112,7 +4163,18 @@ async function handleManageAction(
41124163 }
41134164}
41144165
4115- async function runAuthLogin ( ) : Promise < number > {
4166+ async function runAuthLogin ( args : string [ ] ) : Promise < number > {
4167+ const parsedArgs = parseAuthLoginArgs ( args ) ;
4168+ if ( ! parsedArgs . ok ) {
4169+ if ( parsedArgs . message ) {
4170+ console . error ( parsedArgs . message ) ;
4171+ printUsage ( ) ;
4172+ return 1 ;
4173+ }
4174+ return 0 ;
4175+ }
4176+
4177+ const loginOptions = parsedArgs . options ;
41164178 setStoragePath ( null ) ;
41174179 let pendingMenuQuotaRefresh : Promise < void > | null = null ;
41184180 let menuQuotaRefreshStatus : string | undefined ;
@@ -4231,7 +4293,7 @@ async function runAuthLogin(): Promise<number> {
42314293 const existingCount = refreshedStorage ?. accounts . length ?? 0 ;
42324294 let forceNewLogin = existingCount > 0 ;
42334295 while ( true ) {
4234- const tokenResult = await runOAuthFlow ( forceNewLogin ) ;
4296+ const tokenResult = await runOAuthFlow ( forceNewLogin , loginOptions ) ;
42354297 if ( tokenResult . type !== "success" ) {
42364298 if ( isUserCancelledOAuth ( tokenResult ) ) {
42374299 if ( existingCount > 0 ) {
@@ -4719,7 +4781,7 @@ export async function runCodexMultiAuthCli(rawArgs: string[]): Promise<number> {
47194781 return 0 ;
47204782 }
47214783 if ( command === "login" ) {
4722- return runAuthLogin ( ) ;
4784+ return runAuthLogin ( rest ) ;
47234785 }
47244786 if ( command === "list" || command === "status" ) {
47254787 await showAccountStatus ( ) ;
0 commit comments