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"