@@ -5,6 +5,7 @@ import type * as Path from "@effect/platform/Path"
55import { Effect } from "effect"
66
77import type { AgentMode , ParseError , TemplateConfig } from "../core/domain.js"
8+ import { hasGrokCredentials } from "./auth-grok-helpers.js"
89import { normalizeAccountLabel } from "./auth-helpers.js"
910import { hasNonEmptyFile } from "./auth-sync-helpers.js"
1011
@@ -14,6 +15,16 @@ const autoOptionError = (reason: string): ParseError => ({
1415 reason
1516} )
1617
18+ type AvailableAgentAuth = {
19+ readonly claudeAvailable : boolean
20+ readonly codexAvailable : boolean
21+ readonly grokAvailable : boolean
22+ }
23+
24+ const claudeMode : ReadonlyArray < AgentMode > = [ "claude" ]
25+ const codexMode : ReadonlyArray < AgentMode > = [ "codex" ]
26+ const grokMode : ReadonlyArray < AgentMode > = [ "grok" ]
27+
1728const isRegularFile = (
1829 fs : FileSystem . FileSystem ,
1930 filePath : string
@@ -39,6 +50,15 @@ const hasCodexAuth = (
3950 return hasNonEmptyFile ( fs , authPath )
4051}
4152
53+ const hasGrokAuth = (
54+ fs : FileSystem . FileSystem ,
55+ rootPath : string ,
56+ label : string | undefined
57+ ) : Effect . Effect < boolean , PlatformError > => {
58+ const normalized = normalizeAccountLabel ( label ?? null , "default" )
59+ return hasGrokCredentials ( fs , `${ rootPath } /${ normalized } ` )
60+ }
61+
4262const resolveClaudeAccountPath = ( rootPath : string , label : string | undefined ) : ReadonlyArray < string > => {
4363 const normalized = normalizeAccountLabel ( label ?? null , "default" )
4464 if ( normalized !== "default" ) {
@@ -78,18 +98,22 @@ const resolveClaudeRoot = (codexSharedAuthPath: string): string =>
7898
7999const resolveAvailableAgentAuth = (
80100 fs : FileSystem . FileSystem ,
81- config : Pick < TemplateConfig , "claudeAuthLabel" | "codexAuthLabel" | "codexSharedAuthPath" >
82- ) : Effect . Effect < { readonly claudeAvailable : boolean ; readonly codexAvailable : boolean } , PlatformError > =>
101+ config : Pick <
102+ TemplateConfig ,
103+ "claudeAuthLabel" | "codexAuthLabel" | "codexSharedAuthPath" | "grokAuthLabel" | "grokAuthPath"
104+ >
105+ ) : Effect . Effect < AvailableAgentAuth , PlatformError > =>
83106 Effect . gen ( function * ( _ ) {
84107 const claudeAvailable = yield * _ (
85108 hasClaudeAuth ( fs , resolveClaudeRoot ( config . codexSharedAuthPath ) , config . claudeAuthLabel )
86109 )
87110 const codexAvailable = yield * _ ( hasCodexAuth ( fs , config . codexSharedAuthPath , config . codexAuthLabel ) )
88- return { claudeAvailable, codexAvailable }
111+ const grokAvailable = yield * _ ( hasGrokAuth ( fs , config . grokAuthPath , config . grokAuthLabel ) )
112+ return { claudeAvailable, codexAvailable, grokAvailable }
89113 } )
90114
91115const resolveExplicitAutoAgentMode = (
92- available : { readonly claudeAvailable : boolean ; readonly codexAvailable : boolean } ,
116+ available : AvailableAgentAuth ,
93117 mode : AgentMode | undefined
94118) : Effect . Effect < AgentMode | undefined , ParseError > => {
95119 if ( mode === "claude" ) {
@@ -102,26 +126,43 @@ const resolveExplicitAutoAgentMode = (
102126 ? Effect . succeed ( "codex" )
103127 : Effect . fail ( autoOptionError ( "Codex auth not found" ) )
104128 }
129+ if ( mode === "grok" ) {
130+ return available . grokAvailable
131+ ? Effect . succeed ( "grok" )
132+ : Effect . fail ( autoOptionError ( "Grok auth not found" ) )
133+ }
105134 return Effect . sync ( ( ) => mode )
106135}
107136
108- const pickRandomAutoAgentMode = (
109- available : { readonly claudeAvailable : boolean ; readonly codexAvailable : boolean }
110- ) : Effect . Effect < AgentMode , ParseError > => {
111- if ( ! available . claudeAvailable && ! available . codexAvailable ) {
112- return Effect . fail ( autoOptionError ( "no Claude or Codex auth found" ) )
113- }
114- if ( available . claudeAvailable && ! available . codexAvailable ) {
115- return Effect . succeed ( "claude" )
137+ const availableAgentModes = ( available : AvailableAgentAuth ) : ReadonlyArray < AgentMode > => [
138+ ...( available . claudeAvailable ? claudeMode : [ ] ) ,
139+ ...( available . codexAvailable ? codexMode : [ ] ) ,
140+ ...( available . grokAvailable ? grokMode : [ ] )
141+ ]
142+
143+ const pickRandomAutoAgentMode = ( available : AvailableAgentAuth ) : Effect . Effect < AgentMode , ParseError > => {
144+ const modes = availableAgentModes ( available )
145+ const firstMode = modes [ 0 ]
146+ if ( firstMode === undefined ) {
147+ return Effect . fail ( autoOptionError ( "no Claude, Codex or Grok auth found" ) )
116148 }
117- if ( ! available . claudeAvailable && available . codexAvailable ) {
118- return Effect . succeed ( "codex" )
149+ if ( modes . length === 1 ) {
150+ return Effect . succeed ( firstMode )
119151 }
120- return Effect . sync ( ( ) => ( process . hrtime . bigint ( ) % 2n === 0n ? "claude" : "codex" ) )
152+ return Effect . sync ( ( ) => modes [ Number ( process . hrtime . bigint ( ) % BigInt ( modes . length ) ) ] ?? firstMode )
121153}
122154
123155export const resolveAutoAgentMode = (
124- config : Pick < TemplateConfig , "agentAuto" | "agentMode" | "claudeAuthLabel" | "codexAuthLabel" | "codexSharedAuthPath" >
156+ config : Pick <
157+ TemplateConfig ,
158+ | "agentAuto"
159+ | "agentMode"
160+ | "claudeAuthLabel"
161+ | "codexAuthLabel"
162+ | "codexSharedAuthPath"
163+ | "grokAuthLabel"
164+ | "grokAuthPath"
165+ >
125166) : Effect . Effect < AgentMode | undefined , ParseError | PlatformError , FileSystem . FileSystem | Path . Path > =>
126167 Effect . gen ( function * ( _ ) {
127168 const fs = yield * _ ( FileSystem . FileSystem )
0 commit comments