From 13e4baca228313462b3e0e83d0b97594654a989b Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Wed, 24 Dec 2025 05:02:33 -0500 Subject: [PATCH 1/3] fix(kiro): add --no-incognito option for normal browser auth Kiro OAuth always opens in incognito browser mode, which doesn't save login credentials. Users need to re-enter email each authentication. Changes: - Add noIncognito option to OAuthOptions interface - Add kiro_no_incognito config option to CLIProxyConfig - Pass --no-incognito flag to CLIProxyAPI binary when enabled - Support both CLI flag (--no-incognito) and config.yaml setting Usage: - CLI: ccs kiro --auth --no-incognito - Config: cliproxy.kiro_no_incognito: true in config.yaml --- src/cliproxy/auth/auth-types.ts | 2 ++ src/cliproxy/auth/oauth-handler.ts | 6 +++++- src/cliproxy/cliproxy-executor.ts | 7 +++++++ src/config/unified-config-types.ts | 2 ++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/cliproxy/auth/auth-types.ts b/src/cliproxy/auth/auth-types.ts index 4bb06256..4bcc476c 100644 --- a/src/cliproxy/auth/auth-types.ts +++ b/src/cliproxy/auth/auth-types.ts @@ -171,4 +171,6 @@ export interface OAuthOptions { nickname?: string; /** If true, triggered from Web UI (enables project selection prompt) */ fromUI?: boolean; + /** If true, use --no-incognito flag (Kiro only - use normal browser instead of incognito) */ + noIncognito?: boolean; } diff --git a/src/cliproxy/auth/oauth-handler.ts b/src/cliproxy/auth/oauth-handler.ts index f2bb51c2..2d73e7d0 100644 --- a/src/cliproxy/auth/oauth-handler.ts +++ b/src/cliproxy/auth/oauth-handler.ts @@ -126,7 +126,7 @@ export async function triggerOAuth( options: OAuthOptions = {} ): Promise { const oauthConfig = getOAuthConfig(provider); - const { verbose = false, add = false, nickname, fromUI = false } = options; + const { verbose = false, add = false, nickname, fromUI = false, noIncognito = false } = options; const callbackPort = OAUTH_PORTS[provider]; const isCLI = !fromUI; const headless = options.headless ?? isHeadlessEnvironment(); @@ -175,6 +175,10 @@ export async function triggerOAuth( if (headless) { args.push('--no-browser'); } + // Kiro-specific: --no-incognito to use normal browser (saves login credentials) + if (provider === 'kiro' && noIncognito) { + args.push('--no-incognito'); + } // Show step based on flow type if (isDeviceCodeFlow) { diff --git a/src/cliproxy/cliproxy-executor.ts b/src/cliproxy/cliproxy-executor.ts index 6152be27..6f5d20a6 100644 --- a/src/cliproxy/cliproxy-executor.ts +++ b/src/cliproxy/cliproxy-executor.ts @@ -250,6 +250,11 @@ export async function execClaudeWithCLIProxy( const forceConfig = argsWithoutProxy.includes('--config'); const addAccount = argsWithoutProxy.includes('--add'); const showAccounts = argsWithoutProxy.includes('--accounts'); + // Kiro-specific: --no-incognito to use normal browser (saves login credentials) + const noIncognitoFlag = argsWithoutProxy.includes('--no-incognito'); + // Also check config.yaml for kiro_no_incognito setting + const kiroNoIncognitoConfig = provider === 'kiro' && unifiedConfig.cliproxy?.kiro_no_incognito; + const noIncognito = noIncognitoFlag || kiroNoIncognitoConfig; // Parse --use flag let useAccount: string | undefined; @@ -364,6 +369,7 @@ export async function execClaudeWithCLIProxy( add: addAccount, ...(forceHeadless ? { headless: true } : {}), ...(setNickname ? { nickname: setNickname } : {}), + ...(noIncognito ? { noIncognito: true } : {}), }); if (!authSuccess) { throw new Error(`Authentication required for ${providerConfig.displayName}`); @@ -591,6 +597,7 @@ export async function execClaudeWithCLIProxy( '--accounts', '--use', '--nickname', + '--no-incognito', // Proxy flags are handled by resolveProxyConfig, but list for documentation ...PROXY_CLI_FLAGS, ]; diff --git a/src/config/unified-config-types.ts b/src/config/unified-config-types.ts index 333ef307..53760348 100644 --- a/src/config/unified-config-types.ts +++ b/src/config/unified-config-types.ts @@ -89,6 +89,8 @@ export interface CLIProxyConfig { variants: Record; /** Logging configuration (disabled by default) */ logging?: CLIProxyLoggingConfig; + /** Kiro: disable incognito browser mode (use normal browser to save credentials) */ + kiro_no_incognito?: boolean; } /** From 083e67426c382ce534bed4830bedbede94cfdca7 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Wed, 24 Dec 2025 05:26:02 -0500 Subject: [PATCH 2/3] feat(kiro): add UI toggle and auth hint for --no-incognito option - Add "Kiro: Use normal browser" toggle in config dashboard (Proxy section) - Add --no-incognito documentation to ccs --help output - Show hint after successful Kiro auth about saving AWS credentials --- src/cliproxy/auth/oauth-handler.ts | 12 +++- src/commands/help-command.ts | 1 + .../pages/settings/sections/proxy/index.tsx | 70 ++++++++++++++++++- 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/src/cliproxy/auth/oauth-handler.ts b/src/cliproxy/auth/oauth-handler.ts index 2d73e7d0..877c9e8f 100644 --- a/src/cliproxy/auth/oauth-handler.ts +++ b/src/cliproxy/auth/oauth-handler.ts @@ -202,7 +202,7 @@ export async function triggerOAuth( } // Execute OAuth process - return executeOAuthProcess({ + const account = await executeOAuthProcess({ provider, binaryPath, args, @@ -214,6 +214,16 @@ export async function triggerOAuth( isCLI, nickname, }); + + // Show hint for Kiro users about --no-incognito option (first-time auth only) + if (account && provider === 'kiro' && !noIncognito) { + console.log(''); + console.log(info('Tip: To save your AWS login credentials for future sessions:')); + console.log(' Use: ccs kiro --no-incognito'); + console.log(' Or enable "Kiro: Use normal browser" in: ccs config'); + } + + return account; } /** diff --git a/src/commands/help-command.ts b/src/commands/help-command.ts index 224230ae..984b1ed3 100644 --- a/src/commands/help-command.ts +++ b/src/commands/help-command.ts @@ -173,6 +173,7 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim(); ['ccs --config', 'Change model (agy, gemini)'], ['ccs --logout', 'Clear authentication'], ['ccs --headless', 'Headless auth (for SSH)'], + ['ccs kiro --no-incognito', 'Use normal browser (saves AWS login)'], ['ccs codex "explain code"', 'Use with prompt'], ] ); diff --git a/ui/src/pages/settings/sections/proxy/index.tsx b/ui/src/pages/settings/sections/proxy/index.tsx index 14a8b666..18b0737e 100644 --- a/ui/src/pages/settings/sections/proxy/index.tsx +++ b/ui/src/pages/settings/sections/proxy/index.tsx @@ -3,7 +3,7 @@ * Settings section for CLIProxyAPI configuration (local/remote) */ -import { useEffect } from 'react'; +import { useEffect, useState, useCallback } from 'react'; import { Button } from '@/components/ui/button'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { ScrollArea } from '@/components/ui/scroll-area'; @@ -12,6 +12,7 @@ import { RefreshCw, CheckCircle2, AlertCircle, Laptop, Cloud } from 'lucide-reac import { useProxyConfig, useRawConfig } from '../../hooks'; import { LocalProxyCard } from './local-proxy-card'; import { RemoteProxyCard } from './remote-proxy-card'; +import { api } from '@/lib/api-client'; export default function ProxySection() { const { @@ -37,11 +38,54 @@ export default function ProxySection() { const { fetchRawConfig } = useRawConfig(); + // Kiro provider settings state + const [kiroNoIncognito, setKiroNoIncognito] = useState(false); + const [kiroSettingsLoading, setKiroSettingsLoading] = useState(true); + const [kiroSaving, setKiroSaving] = useState(false); + + // Fetch Kiro settings from unified config + const fetchKiroSettings = useCallback(async () => { + try { + setKiroSettingsLoading(true); + const unifiedConfig = await api.config.get(); + const cliproxyConfig = unifiedConfig.cliproxy as { kiro_no_incognito?: boolean } | undefined; + setKiroNoIncognito(cliproxyConfig?.kiro_no_incognito ?? false); + } catch { + // Config may not exist yet, use default + setKiroNoIncognito(false); + } finally { + setKiroSettingsLoading(false); + } + }, []); + + // Save Kiro no-incognito setting + const saveKiroNoIncognito = useCallback(async (enabled: boolean) => { + setKiroNoIncognito(enabled); // Optimistic update + setKiroSaving(true); + try { + const unifiedConfig = await api.config.get(); + const existingCliproxy = (unifiedConfig.cliproxy ?? {}) as Record; + await api.config.update({ + ...unifiedConfig, + cliproxy: { + ...existingCliproxy, + kiro_no_incognito: enabled, + }, + }); + } catch { + // Revert on error + setKiroNoIncognito(!enabled); + } finally { + setKiroSaving(false); + } + }, []); + // Load data on mount useEffect(() => { fetchConfig(); fetchRawConfig(); - }, [fetchConfig, fetchRawConfig]); + fetchKiroSettings(); + }, [fetchConfig, fetchRawConfig, fetchKiroSettings]); if (loading || !config) { return ( @@ -265,6 +309,27 @@ export default function ProxySection() { onSaveConfig={saveConfig} /> )} + + {/* Provider Settings */} +
+

Provider Settings

+
+ {/* Kiro: Use normal browser */} +
+
+

Kiro: Use normal browser

+

+ Save AWS login credentials (disable incognito mode) +

+
+ +
+
+
@@ -276,6 +341,7 @@ export default function ProxySection() { onClick={() => { fetchConfig(); fetchRawConfig(); + fetchKiroSettings(); }} disabled={loading || saving} className="w-full" From df0c94781e5f198f867723e1b5bccf17d6c4b250 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Wed, 24 Dec 2025 06:17:17 -0500 Subject: [PATCH 3/3] feat(kiro): improve auth UX with normal browser default and URL display - Change default to noIncognito=true (normal browser) for reliability - Always print OAuth URL to terminal for VS Code popup detection - Move incognito toggle from Proxy settings to Kiro provider page - Add --incognito flag to opt into incognito mode (was --no-incognito) - Update help text to reflect new defaults --- src/cliproxy/auth/oauth-handler.ts | 2 +- src/cliproxy/auth/oauth-process.ts | 4 +- src/cliproxy/cliproxy-executor.ts | 14 ++-- src/commands/help-command.ts | 2 +- src/web-server/routes/cliproxy-auth-routes.ts | 12 +++- .../provider-editor/accounts-section.tsx | 30 +++++++- .../cliproxy/provider-editor/index.tsx | 1 + .../provider-editor/model-config-tab.tsx | 57 ++++++++++++++- .../pages/settings/sections/proxy/index.tsx | 70 +------------------ 9 files changed, 113 insertions(+), 79 deletions(-) diff --git a/src/cliproxy/auth/oauth-handler.ts b/src/cliproxy/auth/oauth-handler.ts index 877c9e8f..caba977c 100644 --- a/src/cliproxy/auth/oauth-handler.ts +++ b/src/cliproxy/auth/oauth-handler.ts @@ -126,7 +126,7 @@ export async function triggerOAuth( options: OAuthOptions = {} ): Promise { const oauthConfig = getOAuthConfig(provider); - const { verbose = false, add = false, nickname, fromUI = false, noIncognito = false } = options; + const { verbose = false, add = false, nickname, fromUI = false, noIncognito = true } = options; const callbackPort = OAUTH_PORTS[provider]; const isCLI = !fromUI; const headless = options.headless ?? isHeadlessEnvironment(); diff --git a/src/cliproxy/auth/oauth-process.ts b/src/cliproxy/auth/oauth-process.ts index 60396c07..6a16ebff 100644 --- a/src/cliproxy/auth/oauth-process.ts +++ b/src/cliproxy/auth/oauth-process.ts @@ -172,8 +172,8 @@ async function handleStdout( state.browserOpened = true; } - // Display OAuth URLs in headless mode (for non-device-code flows) - if (!isDeviceCodeFlow && options.headless && !state.urlDisplayed) { + // Display OAuth URL for all modes (enables VS Code terminal URL detection popup) + if (!isDeviceCodeFlow && !state.urlDisplayed) { const urlMatch = output.match(/https?:\/\/[^\s]+/); if (urlMatch) { console.log(''); diff --git a/src/cliproxy/cliproxy-executor.ts b/src/cliproxy/cliproxy-executor.ts index 6f5d20a6..758bf39b 100644 --- a/src/cliproxy/cliproxy-executor.ts +++ b/src/cliproxy/cliproxy-executor.ts @@ -250,11 +250,16 @@ export async function execClaudeWithCLIProxy( const forceConfig = argsWithoutProxy.includes('--config'); const addAccount = argsWithoutProxy.includes('--add'); const showAccounts = argsWithoutProxy.includes('--accounts'); - // Kiro-specific: --no-incognito to use normal browser (saves login credentials) + // Kiro-specific: browser mode for OAuth + // Default to normal browser (noIncognito=true) for reliability - incognito often fails on Linux + // --incognito flag opts into incognito mode, --no-incognito is legacy (now default) + const incognitoFlag = argsWithoutProxy.includes('--incognito'); const noIncognitoFlag = argsWithoutProxy.includes('--no-incognito'); - // Also check config.yaml for kiro_no_incognito setting - const kiroNoIncognitoConfig = provider === 'kiro' && unifiedConfig.cliproxy?.kiro_no_incognito; - const noIncognito = noIncognitoFlag || kiroNoIncognitoConfig; + // Config setting (defaults to true = normal browser) + const kiroNoIncognitoConfig = + provider === 'kiro' ? (unifiedConfig.cliproxy?.kiro_no_incognito ?? true) : false; + // --incognito flag overrides everything to use incognito + const noIncognito = incognitoFlag ? false : noIncognitoFlag || kiroNoIncognitoConfig; // Parse --use flag let useAccount: string | undefined; @@ -597,6 +602,7 @@ export async function execClaudeWithCLIProxy( '--accounts', '--use', '--nickname', + '--incognito', '--no-incognito', // Proxy flags are handled by resolveProxyConfig, but list for documentation ...PROXY_CLI_FLAGS, diff --git a/src/commands/help-command.ts b/src/commands/help-command.ts index 984b1ed3..82f70a86 100644 --- a/src/commands/help-command.ts +++ b/src/commands/help-command.ts @@ -173,7 +173,7 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim(); ['ccs --config', 'Change model (agy, gemini)'], ['ccs --logout', 'Clear authentication'], ['ccs --headless', 'Headless auth (for SSH)'], - ['ccs kiro --no-incognito', 'Use normal browser (saves AWS login)'], + ['ccs kiro --incognito', 'Use incognito browser (default: normal)'], ['ccs codex "explain code"', 'Use with prompt'], ] ); diff --git a/src/web-server/routes/cliproxy-auth-routes.ts b/src/web-server/routes/cliproxy-auth-routes.ts index 1abf36d2..637cf2d3 100644 --- a/src/web-server/routes/cliproxy-auth-routes.ts +++ b/src/web-server/routes/cliproxy-auth-routes.ts @@ -23,6 +23,7 @@ import { } from '../../cliproxy/account-manager'; import { getProxyTarget } from '../../cliproxy/proxy-target-resolver'; import { fetchRemoteAuthStatus } from '../../cliproxy/remote-auth-fetcher'; +import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader'; import type { CLIProxyProvider } from '../../cliproxy/types'; const router = Router(); @@ -268,7 +269,7 @@ router.delete('/accounts/:provider/:accountId', (req: Request, res: Response): v */ router.post('/:provider/start', async (req: Request, res: Response): Promise => { const { provider } = req.params; - const { nickname } = req.body; + const { nickname, noIncognito: noIncognitoBody } = req.body; // Validate provider if (!validProviders.includes(provider as CLIProxyProvider)) { @@ -276,6 +277,14 @@ router.post('/:provider/start', async (req: Request, res: Response): Promise void; isRemovingAccount?: boolean; privacyMode?: boolean; + /** Kiro-specific: show "use normal browser" toggle */ + isKiro?: boolean; + kiroNoIncognito?: boolean; + onKiroNoIncognitoChange?: (enabled: boolean) => void; + kiroSettingsLoading?: boolean; } export function AccountsSection({ @@ -25,6 +31,10 @@ export function AccountsSection({ onRemoveAccount, isRemovingAccount, privacyMode, + isKiro, + kiroNoIncognito, + onKiroNoIncognitoChange, + kiroSettingsLoading, }: AccountsSectionProps) { return (
@@ -64,6 +74,24 @@ export function AccountsSection({

Add an account to get started

)} + + {/* Kiro-specific: Incognito browser setting - users complain "it keeps opening incognito" */} + {isKiro && onKiroNoIncognitoChange && ( +
+
+
+ + Use incognito +
+ onKiroNoIncognitoChange(!v)} + disabled={kiroSettingsLoading} + className="scale-90" + /> +
+
+ )} ); } diff --git a/ui/src/components/cliproxy/provider-editor/index.tsx b/ui/src/components/cliproxy/provider-editor/index.tsx index ab278d5e..ac17648e 100644 --- a/ui/src/components/cliproxy/provider-editor/index.tsx +++ b/ui/src/components/cliproxy/provider-editor/index.tsx @@ -157,6 +157,7 @@ export function ProviderEditor({ className="flex-1 mt-0 border-0 p-0 data-[state=inactive]:hidden flex flex-col overflow-hidden" > { + if (!isKiro) return; + try { + setKiroSettingsLoading(true); + const unifiedConfig = await api.config.get(); + const cliproxyConfig = unifiedConfig.cliproxy as { kiro_no_incognito?: boolean } | undefined; + setKiroNoIncognito(cliproxyConfig?.kiro_no_incognito ?? true); + } catch { + setKiroNoIncognito(true); + } finally { + setKiroSettingsLoading(false); + } + }, [isKiro]); + + // Save Kiro no-incognito setting + const saveKiroNoIncognito = useCallback(async (enabled: boolean) => { + setKiroNoIncognito(enabled); // Optimistic update + setKiroSaving(true); + try { + const unifiedConfig = await api.config.get(); + const existingCliproxy = (unifiedConfig.cliproxy ?? {}) as Record; + await api.config.update({ + ...unifiedConfig, + cliproxy: { + ...existingCliproxy, + kiro_no_incognito: enabled, + }, + }); + } catch { + setKiroNoIncognito(!enabled); // Revert on error + } finally { + setKiroSaving(false); + } + }, []); + + // Load Kiro settings on mount + useEffect(() => { + fetchKiroSettings(); + }, [fetchKiroSettings]); + return (
@@ -82,6 +133,10 @@ export function ModelConfigTab({ onRemoveAccount={onRemoveAccount} isRemovingAccount={isRemovingAccount} privacyMode={privacyMode} + isKiro={isKiro} + kiroNoIncognito={kiroNoIncognito} + onKiroNoIncognitoChange={saveKiroNoIncognito} + kiroSettingsLoading={kiroSettingsLoading || kiroSaving} />
diff --git a/ui/src/pages/settings/sections/proxy/index.tsx b/ui/src/pages/settings/sections/proxy/index.tsx index 18b0737e..14a8b666 100644 --- a/ui/src/pages/settings/sections/proxy/index.tsx +++ b/ui/src/pages/settings/sections/proxy/index.tsx @@ -3,7 +3,7 @@ * Settings section for CLIProxyAPI configuration (local/remote) */ -import { useEffect, useState, useCallback } from 'react'; +import { useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { ScrollArea } from '@/components/ui/scroll-area'; @@ -12,7 +12,6 @@ import { RefreshCw, CheckCircle2, AlertCircle, Laptop, Cloud } from 'lucide-reac import { useProxyConfig, useRawConfig } from '../../hooks'; import { LocalProxyCard } from './local-proxy-card'; import { RemoteProxyCard } from './remote-proxy-card'; -import { api } from '@/lib/api-client'; export default function ProxySection() { const { @@ -38,54 +37,11 @@ export default function ProxySection() { const { fetchRawConfig } = useRawConfig(); - // Kiro provider settings state - const [kiroNoIncognito, setKiroNoIncognito] = useState(false); - const [kiroSettingsLoading, setKiroSettingsLoading] = useState(true); - const [kiroSaving, setKiroSaving] = useState(false); - - // Fetch Kiro settings from unified config - const fetchKiroSettings = useCallback(async () => { - try { - setKiroSettingsLoading(true); - const unifiedConfig = await api.config.get(); - const cliproxyConfig = unifiedConfig.cliproxy as { kiro_no_incognito?: boolean } | undefined; - setKiroNoIncognito(cliproxyConfig?.kiro_no_incognito ?? false); - } catch { - // Config may not exist yet, use default - setKiroNoIncognito(false); - } finally { - setKiroSettingsLoading(false); - } - }, []); - - // Save Kiro no-incognito setting - const saveKiroNoIncognito = useCallback(async (enabled: boolean) => { - setKiroNoIncognito(enabled); // Optimistic update - setKiroSaving(true); - try { - const unifiedConfig = await api.config.get(); - const existingCliproxy = (unifiedConfig.cliproxy ?? {}) as Record; - await api.config.update({ - ...unifiedConfig, - cliproxy: { - ...existingCliproxy, - kiro_no_incognito: enabled, - }, - }); - } catch { - // Revert on error - setKiroNoIncognito(!enabled); - } finally { - setKiroSaving(false); - } - }, []); - // Load data on mount useEffect(() => { fetchConfig(); fetchRawConfig(); - fetchKiroSettings(); - }, [fetchConfig, fetchRawConfig, fetchKiroSettings]); + }, [fetchConfig, fetchRawConfig]); if (loading || !config) { return ( @@ -309,27 +265,6 @@ export default function ProxySection() { onSaveConfig={saveConfig} /> )} - - {/* Provider Settings */} -
-

Provider Settings

-
- {/* Kiro: Use normal browser */} -
-
-

Kiro: Use normal browser

-

- Save AWS login credentials (disable incognito mode) -

-
- -
-
-
@@ -341,7 +276,6 @@ export default function ProxySection() { onClick={() => { fetchConfig(); fetchRawConfig(); - fetchKiroSettings(); }} disabled={loading || saving} className="w-full"