// login.tsx: 真正的登录逻辑 + UI import { feature } from 'bun:bundle'; import * as React from 'react'; // TODO import { useMemo, useState } from 'react' import { resetCostState } from '../../bootstrap/state.js'; import { clearTrustedDeviceToken, enrollTrustedDevice } from '../../bridge/trustedDevice.js'; import type { LocalJSXCommandContext } from '../../commands.js'; import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js'; import { ConsoleOAuthFlow } from '../../components/ConsoleOAuthFlow.js'; // TODO import { Select } from '../../components/CustomSelect/select.js'; import { Dialog } from '../../components/design-system/Dialog.js'; // TODO import { OpenAILoginFlow } from '../../components/OpenAILoginFlow.js' import { OpenRouterLoginFlow } from '../../components/OpenRouterLoginFlow.js' import { LocalLoginFlow } from '../../components/LocalLoginFlow.js' import { OpenCodeLoginFlow } from '../../components/OpenCodeLoginFlow.js' import { NvidiaLoginFlow } from '../../components/NvidiaLoginFlow.js' import { useMainLoopModel } from '../../hooks/useMainLoopModel.js'; // TODO Box import { Box, Text } from '../../ink.js'; import { refreshGrowthBookAfterAuthChange } from '../../services/analytics/growthbook.js'; import { refreshPolicyLimits } from '../../services/policyLimits/index.js'; import { refreshRemoteManagedSettings } from '../../services/remoteManagedSettings/index.js'; import type { LocalJSXCommandOnDone } from '../../types/command.js'; // TODO import { getConfiguredAuthProvider } from '../../utils/auth.js'; import { stripSignatureBlocks } from '../../utils/messages.js'; import { checkAndDisableAutoModeIfNeeded, checkAndDisableBypassPermissionsIfNeeded, resetAutoModeGateCheck, resetBypassPermissionsCheck } from '../../utils/permissions/bypassPermissionsKillswitch.js'; import { resetUserCache } from '../../utils/user.js'; // TODO type AuthProviderChoice = 'anthropic' | 'openai' | 'openrouter' | 'local' | 'opencode' | 'nvidia' /* 第一层: 入口函数 call() * CLI 执行 /login 真正被调用的函数 * 登录成功后的 核心逻辑 * */ export async function call( onDone: LocalJSXCommandOnDone, context: LocalJSXCommandContext ): Promise { // 渲染 React 组件: return ( { // 更新 API Key, 通知系统 身份变了 context.onChangeAPIKey(); // Signature-bearing blocks (thinking, connector_text) are bound to the API key — // strip them so the new key doesn't reject stale signatures. // 清理旧消息签名, 防止 新账号 + 旧签名 = 校验失败 context.setMessages(stripSignatureBlocks); if (success) { // These reset: switching account = full reset resetCostState(); void refreshRemoteManagedSettings(); void refreshPolicyLimits(); resetUserCache(); refreshGrowthBookAfterAuthChange(); clearTrustedDeviceToken(); void enrollTrustedDevice(); resetBypassPermissionsCheck(); const appState = context.getAppState(); void checkAndDisableBypassPermissionsIfNeeded(appState.toolPermissionContext, context.setAppState); if (feature('TRANSCRIPT_CLASSIFIER')) { resetAutoModeGateCheck(); void checkAndDisableAutoModeIfNeeded(appState.toolPermissionContext, context.setAppState, appState.fastMode); } // Pre-fetch models for providers that support dynamic model listing const { getAPIProvider } = await import('../../utils/model/providers.js') const provider = getAPIProvider() if (provider === 'opencode') { const { fetchOpencodeModels } = await import('../../services/api/opencodeClient.js') await fetchOpencodeModels() } else if (provider === 'nvidia') { const { fetchNvidiaModels } = await import('../../services/api/nvidiaClient.js') await fetchNvidiaModels() } // Clear cached model strings so they re-initialize with the new provider const { clearModelStrings } = await import('../../utils/model/modelStrings.js') clearModelStrings() // Increment authVersion to trigger re-fetching of auth-dependent data context.setAppState(prev => ({ ...prev, authVersion: prev.authVersion + 1, mainLoopModel: provider === 'nvidia' ? null : 'big-pickle', mainLoopModelForSession: null, })); } // 返回结果 onDone(success ? 'Login successful' : 'Login interrupted'); }} /> ) } /* 第二层: UI 组件 Login, CLI UI(用 React + Ink) * UI 结构 * Dialog 是 CLI 弹窗 * 真正执行 OAuth 登录逻辑, 打开浏览器, 获取 token, 回传 CLI * * */ export function Login(props: { onDone: (success: boolean, mainLoopModel: string) => void startingMessage?: string }): React.ReactNode { const mainLoopModel = useMainLoopModel() const configuredAuthProvider = getConfiguredAuthProvider() const [selectedProvider, setSelectedProvider] = useState(null) const providerOptions = useMemo( () => [ { label: ( Anthropic{' '} Subscription login, Console API billing, or Bedrock/Foundry/Vertex {'\n'} ), value: 'anthropic', }, { label: ( OpenAI / Codex{' '} Codex login, Codex auth import, or OpenAI API key {'\n'} ), value: 'openai', }, { label: ( OpenRouter{' '} OpenRouter API key via Responses API {'\n'} ), value: 'openrouter', }, { label: ( OpenCode Zen{' '} Free models (Big Pickle, GPT 5 Nano) or Zen API key {'\n'} ), value: 'opencode', }, { label: ( Local{' '} Local model server (Ollama, LM Studio, vLLM, etc.) {'\n'} ), value: 'local', }, { label: ( NVIDIA{' '} NVIDIA NIM API key from build.nvidia.com {'\n'} ), value: 'nvidia', }, ], [], ) const onCancel = () => props.onDone(false, mainLoopModel) const onFlowDone = () => props.onDone(true, mainLoopModel) const body = selectedProvider === null ? ( {props.startingMessage ?? 'Choose which provider you want Better-Clawd to use.'} Current default: {configuredAuthProvider}. Pick a provider first, then choose the login method inside that flow. Select provider: