@@ -35,6 +35,10 @@ import {
3535 loadCodexCliState ,
3636} from "./codex-cli/state.js" ;
3737import { setCodexCliActiveSelection } from "./codex-cli/writer.js" ;
38+ import {
39+ type BestCliOptions ,
40+ runBestCommand ,
41+ } from "./codex-manager/commands/best.js" ;
3842import { runCheckCommand } from "./codex-manager/commands/check.js" ;
3943import { runForecastCommand } from "./codex-manager/commands/forecast.js" ;
4044import { runReportCommand } from "./codex-manager/commands/report.js" ;
@@ -2247,13 +2251,6 @@ async function runHealthCheck(options: HealthCheckOptions = {}): Promise<void> {
22472251 ) ;
22482252}
22492253
2250- interface BestCliOptions {
2251- live : boolean ;
2252- json : boolean ;
2253- model : string ;
2254- modelProvided : boolean ;
2255- }
2256-
22572254interface FixCliOptions {
22582255 dryRun : boolean ;
22592256 json : boolean ;
@@ -4282,279 +4279,26 @@ async function persistAndSyncSelectedAccount({
42824279}
42834280
42844281async function runBest ( args : string [ ] ) : Promise < number > {
4285- if ( args . includes ( "--help" ) || args . includes ( "-h" ) ) {
4286- printBestUsage ( ) ;
4287- return 0 ;
4288- }
4289-
4290- const parsedArgs = parseBestArgs ( args ) ;
4291- if ( ! parsedArgs . ok ) {
4292- console . error ( parsedArgs . message ) ;
4293- printBestUsage ( ) ;
4294- return 1 ;
4295- }
4296- const options = parsedArgs . options ;
4297- if ( options . modelProvided && ! options . live ) {
4298- console . error ( "--model requires --live for codex auth best" ) ;
4299- printBestUsage ( ) ;
4300- return 1 ;
4301- }
4302-
4303- setStoragePath ( null ) ;
4304- const storage = await loadAccounts ( ) ;
4305- if ( ! storage || storage . accounts . length === 0 ) {
4306- if ( options . json ) {
4307- console . log (
4308- JSON . stringify ( { error : "No accounts configured." } , null , 2 ) ,
4309- ) ;
4310- } else {
4311- console . log ( "No accounts configured." ) ;
4312- }
4313- return 1 ;
4314- }
4315-
4316- const now = Date . now ( ) ;
4317- const refreshFailures = new Map < number , TokenFailure > ( ) ;
4318- const liveQuotaByIndex = new Map <
4319- number ,
4320- Awaited < ReturnType < typeof fetchCodexQuotaSnapshot > >
4321- > ( ) ;
4322- const probeIdTokenByIndex = new Map < number , string > ( ) ;
4323- const probeRefreshedIndices = new Set < number > ( ) ;
4324- const probeErrors : string [ ] = [ ] ;
4325- let changed = false ;
4326-
4327- const printProbeNotes = ( ) : void => {
4328- if ( probeErrors . length === 0 ) return ;
4329- console . log ( `Live check notes (${ probeErrors . length } ):` ) ;
4330- for ( const error of probeErrors ) {
4331- console . log ( ` - ${ error } ` ) ;
4332- }
4333- } ;
4334-
4335- const persistProbeChangesIfNeeded = async ( ) : Promise < void > => {
4336- if ( ! changed ) return ;
4337- await saveAccounts ( storage ) ;
4338- changed = false ;
4339- } ;
4340-
4341- for ( let i = 0 ; i < storage . accounts . length ; i += 1 ) {
4342- const account = storage . accounts [ i ] ;
4343- if ( ! account || ! options . live ) continue ;
4344- if ( account . enabled === false ) continue ;
4345-
4346- let probeAccessToken = account . accessToken ;
4347- let probeAccountId =
4348- account . accountId ?? extractAccountId ( account . accessToken ) ;
4349- if ( ! hasUsableAccessToken ( account , now ) ) {
4350- const refreshResult = await queuedRefresh ( account . refreshToken ) ;
4351- if ( refreshResult . type !== "success" ) {
4352- refreshFailures . set ( i , {
4353- ...refreshResult ,
4354- message : normalizeFailureDetail (
4355- refreshResult . message ,
4356- refreshResult . reason ,
4357- ) ,
4358- } ) ;
4359- continue ;
4360- }
4361-
4362- const refreshedEmail = sanitizeEmail (
4363- extractAccountEmail ( refreshResult . access , refreshResult . idToken ) ,
4364- ) ;
4365- const refreshedAccountId = extractAccountId ( refreshResult . access ) ;
4366-
4367- if ( account . refreshToken !== refreshResult . refresh ) {
4368- account . refreshToken = refreshResult . refresh ;
4369- changed = true ;
4370- }
4371- if ( account . accessToken !== refreshResult . access ) {
4372- account . accessToken = refreshResult . access ;
4373- changed = true ;
4374- }
4375- if ( account . expiresAt !== refreshResult . expires ) {
4376- account . expiresAt = refreshResult . expires ;
4377- changed = true ;
4378- }
4379- if ( refreshedEmail && refreshedEmail !== account . email ) {
4380- account . email = refreshedEmail ;
4381- changed = true ;
4382- }
4383- if ( refreshedAccountId && refreshedAccountId !== account . accountId ) {
4384- account . accountId = refreshedAccountId ;
4385- account . accountIdSource = "token" ;
4386- changed = true ;
4387- }
4388- if ( refreshResult . idToken ) {
4389- probeIdTokenByIndex . set ( i , refreshResult . idToken ) ;
4390- }
4391- probeRefreshedIndices . add ( i ) ;
4392-
4393- probeAccessToken = account . accessToken ;
4394- probeAccountId = account . accountId ?? refreshedAccountId ;
4395- }
4396-
4397- if ( ! probeAccessToken || ! probeAccountId ) {
4398- probeErrors . push (
4399- `${ formatAccountLabel ( account , i ) } : missing accountId for live probe` ,
4400- ) ;
4401- continue ;
4402- }
4403-
4404- try {
4405- const liveQuota = await fetchCodexQuotaSnapshot ( {
4406- accountId : probeAccountId ,
4407- accessToken : probeAccessToken ,
4408- model : options . model ,
4409- } ) ;
4410- liveQuotaByIndex . set ( i , liveQuota ) ;
4411- } catch ( error ) {
4412- const message = normalizeFailureDetail (
4413- error instanceof Error ? error . message : String ( error ) ,
4414- undefined ,
4415- ) ;
4416- probeErrors . push ( `${ formatAccountLabel ( account , i ) } : ${ message } ` ) ;
4417- }
4418- }
4419-
4420- const forecastInputs = storage . accounts . map ( ( account , index ) => ( {
4421- index,
4422- account,
4423- isCurrent : index === resolveActiveIndex ( storage , "codex" ) ,
4424- now,
4425- refreshFailure : refreshFailures . get ( index ) ,
4426- liveQuota : liveQuotaByIndex . get ( index ) ,
4427- } ) ) ;
4428-
4429- const forecastResults = evaluateForecastAccounts ( forecastInputs ) ;
4430- const recommendation = recommendForecastAccount ( forecastResults ) ;
4431-
4432- if ( recommendation . recommendedIndex === null ) {
4433- await persistProbeChangesIfNeeded ( ) ;
4434- if ( options . json ) {
4435- console . log (
4436- JSON . stringify (
4437- {
4438- error : recommendation . reason ,
4439- ...( probeErrors . length > 0 ? { probeErrors } : { } ) ,
4440- } ,
4441- null ,
4442- 2 ,
4443- ) ,
4444- ) ;
4445- } else {
4446- console . log ( `No best account available: ${ recommendation . reason } ` ) ;
4447- printProbeNotes ( ) ;
4448- }
4449- return 1 ;
4450- }
4451-
4452- const bestIndex = recommendation . recommendedIndex ;
4453- const bestAccount = storage . accounts [ bestIndex ] ;
4454- if ( ! bestAccount ) {
4455- await persistProbeChangesIfNeeded ( ) ;
4456- if ( options . json ) {
4457- console . log (
4458- JSON . stringify ( { error : "Best account not found." } , null , 2 ) ,
4459- ) ;
4460- } else {
4461- console . log ( "Best account not found." ) ;
4462- }
4463- return 1 ;
4464- }
4465-
4466- // Check if already on best account
4467- const currentIndex = resolveActiveIndex ( storage , "codex" ) ;
4468- if ( currentIndex === bestIndex ) {
4469- const shouldSyncCurrentBest =
4470- probeRefreshedIndices . has ( bestIndex ) ||
4471- probeIdTokenByIndex . has ( bestIndex ) ;
4472- let alreadyBestSynced : boolean | undefined ;
4473- if ( changed ) {
4474- bestAccount . lastUsed = now ;
4475- await persistProbeChangesIfNeeded ( ) ;
4476- }
4477- if ( shouldSyncCurrentBest ) {
4478- alreadyBestSynced = await setCodexCliActiveSelection ( {
4479- accountId : bestAccount . accountId ,
4480- email : bestAccount . email ,
4481- accessToken : bestAccount . accessToken ,
4482- refreshToken : bestAccount . refreshToken ,
4483- expiresAt : bestAccount . expiresAt ,
4484- ...( probeIdTokenByIndex . has ( bestIndex )
4485- ? { idToken : probeIdTokenByIndex . get ( bestIndex ) }
4486- : { } ) ,
4487- } ) ;
4488- if ( ! alreadyBestSynced && ! options . json ) {
4489- console . warn (
4490- "Codex auth sync did not complete. Multi-auth routing will still use this account." ,
4491- ) ;
4492- }
4493- }
4494- if ( options . json ) {
4495- console . log (
4496- JSON . stringify (
4497- {
4498- message : `Already on best account: ${ formatAccountLabel ( bestAccount , bestIndex ) } ` ,
4499- accountIndex : bestIndex + 1 ,
4500- reason : recommendation . reason ,
4501- ...( alreadyBestSynced !== undefined
4502- ? { synced : alreadyBestSynced }
4503- : { } ) ,
4504- ...( probeErrors . length > 0 ? { probeErrors } : { } ) ,
4505- } ,
4506- null ,
4507- 2 ,
4508- ) ,
4509- ) ;
4510- } else {
4511- console . log (
4512- `Already on best account ${ bestIndex + 1 } : ${ formatAccountLabel ( bestAccount , bestIndex ) } ` ,
4513- ) ;
4514- console . log ( `Reason: ${ recommendation . reason } ` ) ;
4515- printProbeNotes ( ) ;
4516- }
4517- return 0 ;
4518- }
4519-
4520- const targetIndex = bestIndex ;
4521- const parsed = targetIndex + 1 ;
4522- const { synced, wasDisabled } = await persistAndSyncSelectedAccount ( {
4523- storage,
4524- targetIndex,
4525- parsed,
4526- switchReason : "best" ,
4527- initialSyncIdToken : probeIdTokenByIndex . get ( bestIndex ) ,
4282+ return runBestCommand ( args , {
4283+ setStoragePath,
4284+ loadAccounts,
4285+ saveAccounts,
4286+ parseBestArgs,
4287+ printBestUsage,
4288+ resolveActiveIndex,
4289+ hasUsableAccessToken,
4290+ queuedRefresh,
4291+ normalizeFailureDetail,
4292+ extractAccountId,
4293+ extractAccountEmail,
4294+ sanitizeEmail,
4295+ formatAccountLabel,
4296+ fetchCodexQuotaSnapshot,
4297+ evaluateForecastAccounts,
4298+ recommendForecastAccount,
4299+ persistAndSyncSelectedAccount,
4300+ setCodexCliActiveSelection,
45284301 } ) ;
4529-
4530- if ( options . json ) {
4531- console . log (
4532- JSON . stringify (
4533- {
4534- message : `Switched to best account: ${ formatAccountLabel ( bestAccount , targetIndex ) } ` ,
4535- accountIndex : parsed ,
4536- reason : recommendation . reason ,
4537- synced,
4538- wasDisabled,
4539- ...( probeErrors . length > 0 ? { probeErrors } : { } ) ,
4540- } ,
4541- null ,
4542- 2 ,
4543- ) ,
4544- ) ;
4545- } else {
4546- console . log (
4547- `Switched to best account ${ parsed } : ${ formatAccountLabel ( bestAccount , targetIndex ) } ${ wasDisabled ? " (re-enabled)" : "" } ` ,
4548- ) ;
4549- console . log ( `Reason: ${ recommendation . reason } ` ) ;
4550- printProbeNotes ( ) ;
4551- if ( ! synced ) {
4552- console . warn (
4553- "Codex auth sync did not complete. Multi-auth routing will still use this account." ,
4554- ) ;
4555- }
4556- }
4557- return 0 ;
45584302}
45594303
45604304export async function autoSyncActiveAccountToCodex ( ) : Promise < boolean > {
0 commit comments