@@ -7,7 +7,7 @@ import { type AppError } from "@effect-template/lib/usecases/errors"
77import { defaultProjectsRoot } from "@effect-template/lib/usecases/menu-helpers"
88import { autoSyncState } from "@effect-template/lib/usecases/state-repo"
99
10- import { countAuthAccountDirectories } from "./menu-auth-helpers .js"
10+ import { countAuthAccountEntries } from "./menu-auth-snapshot-builder .js"
1111import { buildLabeledEnvKey , countKeyEntries , normalizeLabel } from "./menu-labeled-env.js"
1212import type { AuthFlow , AuthSnapshot , MenuEnv } from "./menu-types.js"
1313
@@ -21,7 +21,7 @@ type AuthMenuItem = {
2121export type AuthEnvFlow = Extract < AuthFlow , "GithubRemove" | "GitSet" | "GitRemove" >
2222
2323export type AuthPromptStep = {
24- readonly key : "label" | "token" | "user"
24+ readonly key : "label" | "token" | "user" | "apiKey"
2525 readonly label : string
2626 readonly required : boolean
2727 readonly secret : boolean
@@ -34,6 +34,9 @@ const authMenuItems: ReadonlyArray<AuthMenuItem> = [
3434 { action : "GitRemove" , label : "Git: remove credentials" } ,
3535 { action : "ClaudeOauth" , label : "Claude Code: login via OAuth (web)" } ,
3636 { action : "ClaudeLogout" , label : "Claude Code: logout (clear cache)" } ,
37+ { action : "GeminiOauth" , label : "Gemini CLI: login via OAuth (Google account)" } ,
38+ { action : "GeminiApiKey" , label : "Gemini CLI: set API key" } ,
39+ { action : "GeminiLogout" , label : "Gemini CLI: logout (clear credentials)" } ,
3740 { action : "Refresh" , label : "Refresh snapshot" } ,
3841 { action : "Back" , label : "Back to main menu" }
3942]
@@ -58,6 +61,16 @@ const flowSteps: Readonly<Record<AuthFlow, ReadonlyArray<AuthPromptStep>>> = {
5861 ] ,
5962 ClaudeLogout : [
6063 { key : "label" , label : "Label to logout (empty = default)" , required : false , secret : false }
64+ ] ,
65+ GeminiOauth : [
66+ { key : "label" , label : "Label (empty = default)" , required : false , secret : false }
67+ ] ,
68+ GeminiApiKey : [
69+ { key : "label" , label : "Label (empty = default)" , required : false , secret : false } ,
70+ { key : "apiKey" , label : "Gemini API key (from ai.google.dev)" , required : true , secret : true }
71+ ] ,
72+ GeminiLogout : [
73+ { key : "label" , label : "Label to logout (empty = default)" , required : false , secret : false }
6174 ]
6275}
6376
@@ -69,6 +82,9 @@ const flowTitle = (flow: AuthFlow): string =>
6982 Match . when ( "GitRemove" , ( ) => "Git remove" ) ,
7083 Match . when ( "ClaudeOauth" , ( ) => "Claude Code OAuth" ) ,
7184 Match . when ( "ClaudeLogout" , ( ) => "Claude Code logout" ) ,
85+ Match . when ( "GeminiOauth" , ( ) => "Gemini CLI OAuth" ) ,
86+ Match . when ( "GeminiApiKey" , ( ) => "Gemini CLI API key" ) ,
87+ Match . when ( "GeminiLogout" , ( ) => "Gemini CLI logout" ) ,
7288 Match . exhaustive
7389 )
7490
@@ -80,17 +96,22 @@ export const successMessage = (flow: AuthFlow, label: string): string =>
8096 Match . when ( "GitRemove" , ( ) => `Removed Git credentials (${ label } ).` ) ,
8197 Match . when ( "ClaudeOauth" , ( ) => `Saved Claude Code login (${ label } ).` ) ,
8298 Match . when ( "ClaudeLogout" , ( ) => `Logged out Claude Code (${ label } ).` ) ,
99+ Match . when ( "GeminiOauth" , ( ) => `Saved Gemini CLI OAuth login (${ label } ).` ) ,
100+ Match . when ( "GeminiApiKey" , ( ) => `Saved Gemini API key (${ label } ).` ) ,
101+ Match . when ( "GeminiLogout" , ( ) => `Logged out Gemini CLI (${ label } ).` ) ,
83102 Match . exhaustive
84103 )
85104
86105const buildGlobalEnvPath = ( cwd : string ) : string => `${ defaultProjectsRoot ( cwd ) } /.orch/env/global.env`
87106const buildClaudeAuthPath = ( cwd : string ) : string => `${ defaultProjectsRoot ( cwd ) } /.orch/auth/claude`
107+ const buildGeminiAuthPath = ( cwd : string ) : string => `${ defaultProjectsRoot ( cwd ) } /.orch/auth/gemini`
88108
89109type AuthEnvText = {
90110 readonly fs : FileSystem . FileSystem
91111 readonly path : Path . Path
92112 readonly globalEnvPath : string
93113 readonly claudeAuthPath : string
114+ readonly geminiAuthPath : string
94115 readonly envText : string
95116}
96117
@@ -102,27 +123,29 @@ const loadAuthEnvText = (
102123 const path = yield * _ ( Path . Path )
103124 const globalEnvPath = buildGlobalEnvPath ( cwd )
104125 const claudeAuthPath = buildClaudeAuthPath ( cwd )
126+ const geminiAuthPath = buildGeminiAuthPath ( cwd )
105127 yield * _ ( ensureEnvFile ( fs , path , globalEnvPath ) )
106128 const envText = yield * _ ( readEnvText ( fs , globalEnvPath ) )
107- return { fs, path, globalEnvPath, claudeAuthPath, envText }
129+ return { fs, path, globalEnvPath, claudeAuthPath, geminiAuthPath , envText }
108130 } )
109131
110132export const readAuthSnapshot = (
111133 cwd : string
112134) : Effect . Effect < AuthSnapshot , AppError , MenuEnv > =>
113135 pipe (
114136 loadAuthEnvText ( cwd ) ,
115- Effect . flatMap ( ( { claudeAuthPath, envText, fs, globalEnvPath, path } ) =>
116- pipe (
117- countAuthAccountDirectories ( fs , path , claudeAuthPath ) ,
118- Effect . map ( ( claudeAuthEntries ) => ( {
137+ Effect . flatMap ( ( { claudeAuthPath, envText, fs, geminiAuthPath, globalEnvPath, path } ) =>
138+ countAuthAccountEntries ( fs , path , claudeAuthPath , geminiAuthPath ) . pipe (
139+ Effect . map ( ( { claudeAuthEntries, geminiAuthEntries } ) => ( {
119140 globalEnvPath,
120141 claudeAuthPath,
142+ geminiAuthPath,
121143 totalEntries : parseEnvEntries ( envText ) . filter ( ( entry ) => entry . value . trim ( ) . length > 0 ) . length ,
122144 githubTokenEntries : countKeyEntries ( envText , "GITHUB_TOKEN" ) ,
123145 gitTokenEntries : countKeyEntries ( envText , "GIT_AUTH_TOKEN" ) ,
124146 gitUserEntries : countKeyEntries ( envText , "GIT_AUTH_USER" ) ,
125- claudeAuthEntries
147+ claudeAuthEntries,
148+ geminiAuthEntries
126149 } ) )
127150 )
128151 )
0 commit comments