@@ -7,9 +7,9 @@ import type * as Scope from "effect/Scope"
77import * as Stream from "effect/Stream"
88
99import { stripAnsi , writeChunkToFd } from "../shell/ansi-strip.js"
10+ import { runCommandCapture , runCommandExitCode } from "../shell/command-runner.js"
1011import { resolveDockerVolumeHostPath } from "../shell/docker-auth.js"
1112import { AuthError , CommandFailedError } from "../shell/errors.js"
12- import { runCommandCapture , runCommandExitCode } from "../shell/command-runner.js"
1313
1414// CHANGE: add Gemini CLI OAuth authentication flow
1515// WHY: enable Gemini CLI OAuth login in headless/Docker environments
@@ -48,31 +48,23 @@ const detectAuthResult = (output: string): GeminiAuthResult => {
4848 const normalized = stripAnsi ( output ) . toLowerCase ( )
4949
5050 // Markers that indicate we are in the middle of or after an auth flow
51- const authInitiated =
52- normalized . includes ( "please visit the following url" ) ||
53- normalized . includes ( "enter the authorization code" ) ||
54- normalized . includes ( "authorized the application" )
55-
56- for ( const pattern of authSuccessPatterns ) {
57- if ( normalized . includes ( pattern . toLowerCase ( ) ) ) {
58- // If we saw auth initiation, any success pattern is a real success
59- if ( authInitiated ) {
60- return "success"
61- }
62- // If we didn't see initiation but see success, it might be the banner
63- // BUT if it's "Logged in with Google" and we're NOT in initiation,
64- // it means we're ALREADY logged in, so we can also stop.
65- if ( normalized . includes ( "logged in with google" ) ) {
66- return "success"
67- }
68- }
69- }
51+ const authInitiated = [
52+ "please visit the following url" ,
53+ "enter the authorization code" ,
54+ "authorized the application"
55+ ] . some ( ( m ) => normalized . includes ( m ) )
56+
57+ const isSuccess = authSuccessPatterns . some (
58+ ( pattern ) =>
59+ normalized . includes ( pattern . toLowerCase ( ) ) &&
60+ ( authInitiated || normalized . includes ( "logged in with google" ) )
61+ )
7062
71- for ( const pattern of authFailurePatterns ) {
72- if ( normalized . includes ( pattern . toLowerCase ( ) ) ) {
73- return "failure"
74- }
75- }
63+ if ( isSuccess ) return "success"
64+
65+ const isFailure = authFailurePatterns . some ( ( pattern ) => normalized . includes ( pattern . toLowerCase ( ) ) )
66+
67+ if ( isFailure ) return "failure"
7668
7769 return "pending"
7870}
@@ -166,7 +158,7 @@ const cleanupExistingContainers = (
166158 cwd : process . cwd ( ) ,
167159 command : "docker" ,
168160 args : [ "rm" , "-f" , ...ids ]
169- } ) . pipe ( Effect . catchAll ( ( ) => Effect . succeed ( 0 ) ) )
161+ } ) . pipe ( Effect . orElse ( ( ) => Effect . succeed ( 0 ) ) )
170162 )
171163 }
172164 } )
@@ -189,7 +181,7 @@ const pumpDockerOutput = (
189181 source : Stream . Stream < Uint8Array , PlatformError > ,
190182 fd : number ,
191183 resultBox : { value : GeminiAuthResult } ,
192- authDeferred : Deferred . Deferred < void , never >
184+ authDeferred : Deferred . Deferred < undefined >
193185) : Effect . Effect < void , PlatformError > => {
194186 const decoder = new TextDecoder ( "utf-8" )
195187 let outputWindow = ""
@@ -198,7 +190,9 @@ const pumpDockerOutput = (
198190 source ,
199191 Stream . runForEach ( ( chunk ) =>
200192 Effect . gen ( function * ( _ ) {
201- yield * _ ( Effect . sync ( ( ) => writeChunkToFd ( fd , chunk ) ) )
193+ yield * _ ( Effect . sync ( ( ) => {
194+ writeChunkToFd ( fd , chunk )
195+ } ) )
202196 outputWindow += decoder . decode ( chunk )
203197 if ( outputWindow . length > outputWindowSize ) {
204198 outputWindow = outputWindow . slice ( - outputWindowSize )
@@ -276,6 +270,26 @@ const printOauthInstructions = (): Effect.Effect<void> =>
276270
277271// CHANGE: run Gemini CLI OAuth login with interactive prompt and port forwarding
278272// WHY: Gemini CLI OAuth callback now works in Docker via fixed port forwarding
273+ const fixGeminiAuthPermissions = ( hostPath : string , containerPath : string ) =>
274+ runCommandExitCode ( {
275+ cwd : process . cwd ( ) ,
276+ command : "docker" ,
277+ args : [
278+ "run" ,
279+ "--rm" ,
280+ "-v" ,
281+ `${ hostPath } :${ containerPath } ` ,
282+ "alpine" ,
283+ "chmod" ,
284+ "-R" ,
285+ "777" ,
286+ containerPath
287+ ]
288+ } ) . pipe (
289+ Effect . tapError ( ( err ) => Effect . logWarning ( `Failed to fix Gemini auth permissions: ${ String ( err ) } ` ) ) ,
290+ Effect . orElse ( ( ) => Effect . succeed ( 0 ) )
291+ )
292+
279293// QUOTE(ТЗ): "Типо ждал пока мы вставим ссылку"
280294// REF: issue-146, PR-147 comment
281295// SOURCE: https://github.com/google-gemini/gemini-cli
@@ -304,7 +318,7 @@ export const runGeminiOauthLoginWithPrompt = (
304318 const spec = buildDockerGeminiAuthSpec ( cwd , hostPath , options . image , options . containerPath , port )
305319 const proc = yield * _ ( startDockerProcess ( executor , spec ) )
306320
307- const authDeferred = yield * _ ( Deferred . make < void , never > ( ) )
321+ const authDeferred = yield * _ ( Deferred . make < undefined > ( ) )
308322 const resultBox : { value : GeminiAuthResult } = { value : "pending" }
309323 const stdoutFiber = yield * _ ( Effect . forkScoped ( pumpDockerOutput ( proc . stdout , 1 , resultBox , authDeferred ) ) )
310324 const stderrFiber = yield * _ ( Effect . forkScoped ( pumpDockerOutput ( proc . stderr , 2 , resultBox , authDeferred ) ) )
@@ -318,32 +332,13 @@ export const runGeminiOauthLoginWithPrompt = (
318332 Effect . map ( ( ) => 0 )
319333 )
320334 )
321- ) as Effect . Effect < number , PlatformError >
335+ )
322336
323337 yield * _ ( Fiber . join ( stdoutFiber ) )
324338 yield * _ ( Fiber . join ( stderrFiber ) )
325339
326340 // Fix permissions for all files created by root in the volume
327- yield * _ (
328- runCommandExitCode ( {
329- cwd : process . cwd ( ) ,
330- command : "docker" ,
331- args : [
332- "run" ,
333- "--rm" ,
334- "-v" ,
335- `${ hostPath } :${ spec . containerPath } ` ,
336- "alpine" ,
337- "chmod" ,
338- "-R" ,
339- "777" ,
340- spec . containerPath
341- ]
342- } ) . pipe (
343- Effect . tapError ( ( err ) => Effect . logWarning ( `Failed to fix Gemini auth permissions: ${ err } ` ) ) ,
344- Effect . catchAll ( ( ) => Effect . succeed ( 0 ) )
345- )
346- )
341+ yield * _ ( fixGeminiAuthPermissions ( hostPath , spec . containerPath ) )
347342
348343 return yield * _ ( resolveGeminiLoginResult ( resultBox . value , exitCode ) )
349344 } )
0 commit comments