@@ -25,8 +25,7 @@ import {
2525 Eye ,
2626 EyeOff
2727} from 'lucide-react' ;
28- import { openExternalUrl , isTauri , runKiroAuth } from '@/services/tauri' ;
29- import { useCliProxyStore } from '@/stores' ;
28+ import { openExternalUrl , isTauri } from '@/services/tauri' ;
3029import {
3130 AlertDialog ,
3231 AlertDialogAction ,
@@ -72,12 +71,23 @@ function formatName(name: string | undefined | null): string {
7271 return name . replace ( / _ g m a i l _ c o m / g, '' ) . replace ( / \. j s o n $ / g, '' ) ;
7372}
7473
74+ // Helper to get provider icon path
75+ function getProviderIconPath ( providerId : string ) : string {
76+ const id = providerId . toLowerCase ( ) ;
77+ if ( id . includes ( 'antigravity' ) ) return '/antigravity/antigravity.png' ;
78+ if ( id . includes ( 'claude' ) || id . includes ( 'anthropic' ) ) return '/claude/claude.png' ;
79+ if ( id . includes ( 'gemini' ) ) return '/gemini/gemini.png' ;
80+ if ( id . includes ( 'codex' ) || id . includes ( 'openai' ) ) return '/openai/openai.png' ;
81+ if ( id . includes ( 'kiro' ) ) return '/kiro/kiro.png' ;
82+ return '' ;
83+ }
84+
7585
7686export function ProvidersPage ( ) {
7787 const { t } = useTranslation ( ) ;
7888 const { isAuthenticated } = useAuthStore ( ) ;
7989
80- // Confimration state
90+ // Confirmation state
8191 const [ fileToDelete , setFileToDelete ] = useState < string | null > ( null ) ;
8292
8393 // --- State: Connected Accounts ---
@@ -182,27 +192,45 @@ export function ProvidersPage() {
182192 updateProviderState ( providerId , { status : 'waiting' , error : undefined } ) ;
183193 setSelectedProvider ( providerId ) ;
184194
185- if ( providerId === 'kiro' ) {
186- try {
187- const { exePath } = useCliProxyStore . getState ( ) ;
188- if ( ! exePath ) {
189- throw new Error ( 'CLI Proxy path not configured. Please set it in Settings.' ) ;
190- }
191- await runKiroAuth ( exePath , 'google' ) ;
192- updateProviderState ( providerId , { status : 'success' } ) ;
193- setSelectedProvider ( null ) ;
194- await new Promise ( resolve => setTimeout ( resolve , 500 ) ) ;
195- await loadFiles ( ) ;
196- } catch ( err ) {
197- updateProviderState ( providerId , {
198- status : 'error' ,
199- error : ( err as Error ) . message ,
200- } ) ;
195+ try {
196+ // Kiro uses a dedicated web OAuth page
197+ if ( providerId === 'kiro' ) {
198+ const { apiBase } = useAuthStore . getState ( ) ;
199+ const kiroOAuthUrl = `${ apiBase } /v0/oauth/kiro` ;
200+
201+ const initialFiles = files . filter ( f =>
202+ f . provider ?. toLowerCase ( ) . includes ( 'kiro' ) ||
203+ f . filename ?. toLowerCase ( ) . includes ( 'kiro' )
204+ ) ;
205+ const initialKiroCount = initialFiles . length ;
206+
207+ await openInBrowser ( kiroOAuthUrl ) ;
208+ updateProviderState ( providerId , { url : kiroOAuthUrl , status : 'polling' } ) ;
209+
210+ const pollTimer = window . setInterval ( async ( ) => {
211+ try {
212+ const response = await authFilesApi . list ( ) ;
213+ const currentFiles = response ?. files ?? [ ] ;
214+ const currentKiroFiles = currentFiles . filter ( f =>
215+ f . provider ?. toLowerCase ( ) . includes ( 'kiro' ) ||
216+ f . filename ?. toLowerCase ( ) . includes ( 'kiro' )
217+ ) ;
218+
219+ // Detect if a new kiro file was added
220+ if ( currentKiroFiles . length > initialKiroCount ) {
221+ updateProviderState ( providerId , { status : 'success' } ) ;
222+ stopPolling ( providerId ) ;
223+ setSelectedProvider ( null ) ;
224+ setFiles ( currentFiles ) ;
225+ }
226+ } catch {
227+ // Ignore polling errors - expected during network issues
228+ }
229+ } , 2000 ) ;
230+ pollingTimers . current [ providerId ] = pollTimer ;
231+ return ;
201232 }
202- return ;
203- }
204233
205- try {
206234 const response = await oauthApi . startAuth ( providerId , options ) ;
207235 const url = response . url || response . auth_url ;
208236 const state = response . state ;
@@ -253,11 +281,11 @@ export function ProvidersPage() {
253281 }
254282 } ;
255283
256- const copyToClipboard = async ( text : string ) => {
284+ const copyToClipboard = useCallback ( async ( text : string ) => {
257285 try {
258286 await navigator . clipboard . writeText ( text ) ;
259287 } catch { /* ignore */ }
260- } ;
288+ } , [ ] ) ;
261289
262290 if ( ! isAuthenticated ) {
263291 return (
@@ -331,32 +359,34 @@ export function ProvidersPage() {
331359
332360 < div className = "space-y-1" >
333361 { files . map ( ( file ) => {
334- let iconPath = '' ;
362+ const iconPath = getProviderIconPath ( file . provider || '' ) ;
335363 const p = ( file . provider || '' ) . toLowerCase ( ) ;
336- if ( p . includes ( 'antigravity' ) ) iconPath = '/antigravity/antigravity.png' ;
337- else if ( p . includes ( 'claude' ) || p . includes ( 'anthropic' ) ) iconPath = '/claude/claude.png' ;
338- else if ( p . includes ( 'gemini' ) ) iconPath = '/gemini/gemini.png' ;
339- else if ( p . includes ( 'codex' ) || p . includes ( 'openai' ) ) iconPath = '/openai/openai.png' ;
340- else if ( p . includes ( 'kiro' ) ) iconPath = '/kiro/kiro.png' ;
341364
342365 let rawName : string ;
343366 if ( p . includes ( 'kiro' ) ) {
344- const filename = file . filename || file . id || '' ;
345- const match = filename . match ( / k i r o - ( \w + ) / i) ;
346-
347- if ( match && match [ 1 ] ) {
348- const method = match [ 1 ] . charAt ( 0 ) . toUpperCase ( ) + match [ 1 ] . slice ( 1 ) . toLowerCase ( ) ;
349- rawName = `Kiro (${ method } )` ;
367+ // First try top-level email/account fields
368+ const topEmail = ( file . email as string ) || ( file . account as string ) ;
369+ if ( topEmail && topEmail . trim ( ) !== '' ) {
370+ rawName = formatName ( topEmail ) ;
350371 } else {
351- const metaEmail = ( file . metadata ?. email as string ) || ( file [ 'email' ] as string ) ;
352- const authMethod = ( file . metadata ?. provider as string ) || ( file . metadata ?. auth_method as string ) ;
372+ // Fallback to filename parsing or metadata
373+ const filename = file . filename || file . id || '' ;
374+ const match = filename . match ( / k i r o - ( \\ w + ) / i) ;
353375
354- if ( metaEmail && metaEmail . trim ( ) !== '' ) {
355- rawName = formatName ( metaEmail ) ;
356- } else if ( authMethod ) {
357- rawName = `Kiro (${ authMethod } )` ;
376+ if ( match && match [ 1 ] ) {
377+ const method = match [ 1 ] . charAt ( 0 ) . toUpperCase ( ) + match [ 1 ] . slice ( 1 ) . toLowerCase ( ) ;
378+ rawName = `Kiro (${ method } )` ;
358379 } else {
359- rawName = 'Kiro' ;
380+ const metaEmail = ( file . metadata ?. email as string ) ;
381+ const authMethod = ( file . metadata ?. provider as string ) || ( file . metadata ?. auth_method as string ) ;
382+
383+ if ( metaEmail && metaEmail . trim ( ) !== '' ) {
384+ rawName = formatName ( metaEmail ) ;
385+ } else if ( authMethod ) {
386+ rawName = `Kiro (${ authMethod } )` ;
387+ } else {
388+ rawName = 'Kiro' ;
389+ }
360390 }
361391 }
362392 } else {
@@ -426,13 +456,7 @@ export function ProvidersPage() {
426456 const isWaiting = state . status === 'waiting' || state . status === 'polling' ;
427457 const isSuccess = state . status === 'success' ;
428458
429- let iconPath = '' ;
430- const pid = provider . id ;
431- if ( pid === 'antigravity' ) iconPath = '/antigravity/antigravity.png' ;
432- else if ( pid === 'anthropic' || ( pid as string ) === 'claude' ) iconPath = '/claude/claude.png' ;
433- else if ( pid === 'gemini-cli' ) iconPath = '/gemini/gemini.png' ;
434- else if ( pid === 'codex' ) iconPath = '/openai/openai.png' ;
435- else if ( pid === 'kiro' ) iconPath = '/kiro/kiro.png' ;
459+ const iconPath = getProviderIconPath ( provider . id ) ;
436460
437461 const isSelected = selectedProvider === provider . id ;
438462
0 commit comments