import { Box, Text, useApp, useInput, useStdout } from "ink" import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { packageInfo } from "../app/package-info" import { clearRequestLogs, MAX_REQUEST_LOG_ENTRIES, readRecentRequestLogs, readRequestLogDetail } from "../core/request-logs" import type { RequestLogEntry, RequestLogMode } from "../core/types" import { claudeSettingsPathForScope, claudeSettingsScopeLabel, defaultClaudeEnvironment, detectShell, CLAUDE_MODEL_ENV_KEYS, ALL_EDITABLE_KEYS, readClaudeEnvironmentConfig, readClaudeSettingsEnvAsDraft, recommendedClaudeEnvironment, runClaudeEnvironmentSet, runClaudeEnvironmentUnset, writeClaudeEnvironmentConfig, type ClaudeSettingsScope, type ClaudeEnvironmentDraft, } from "./claude-env" import { getCommands } from "./commands" import { writeClipboard } from "./clipboard" import { AccountSelector } from "./components/account-selector" import { ClaudeEnvironmentEditor } from "./components/claude-environment-editor" import { ClaudeEnvironmentPresetSelector, PRESET_OPTIONS } from "./components/claude-environment-preset-selector" import { ClaudeEnvironmentScopeSelector } from "./components/claude-environment-scope-selector" import { ClaudeEnvironmentUnsetConfirm } from "./components/claude-environment-unset-confirm" import { CommandInput } from "./components/command-input" import { CommandOutput } from "./components/command-output" import { CodexFastModeSelector } from "./components/codex-fast-mode" import { ConnectAccountWizard } from "./components/connect-account-wizard" import { ConnectSourceSelector } from "./components/connect-source-selector" import { ProviderDashboard } from "./components/provider-dashboard" import { formatAllRequestLogs, formatRequestLogDetail, RequestLogsPanel, REQUEST_LOG_DETAIL_FAST_SCROLL_STEP, REQUEST_LOG_DETAIL_SCROLL_STEP, requestLogDetailMaxScroll, } from "./components/request-logs-panel" import { StatusHeader } from "./components/resource-usage-header" import { SwitchProviderConfirm } from "./components/switch-provider-confirm" import { nextProviderDefinition, providerDefinition } from "./providers/registry" import type { ProviderAccountData, ProviderConnectDraft, ProviderConnectField } from "./providers/types" import { useCodexFastMode } from "./providers/use-codex-fast-mode" import { useProviderLimits } from "./providers/use-provider-limits" import { useProviderRuntime } from "./providers/use-provider-runtime" import type { ProviderMode } from "./types" export function CodexCodeApp(props: { port?: number; hostname?: string; apiPassword?: string }) { const app = useApp() const { stdout } = useStdout() const hostname = props.hostname ?? process.env.HOST ?? "0.0.0.0" const port = props.port ?? Number(process.env.PORT || 8787) const apiPassword = props.apiPassword const [accountData, setAccountData] = useState() const [loadError, setLoadError] = useState() const [selected, setSelected] = useState(0) const [inputMessage, setInputMessage] = useState("↑↓ select · enter confirm") const [commandIndex, setCommandIndex] = useState(0) const [mode, setMode] = useState< | "home" | "account-selector" | "logs" | "codex-fast-mode" | "claude-env-scope" | "claude-env-preset" | "claude-env-editor" | "claude-env-confirm" | "claude-env-unset-confirm" | "connect-source" | "connect-account" | "switch-provider" >("home") const [selectorIndex, setSelectorIndex] = useState(0) const [requestLogs, setRequestLogs] = useState([]) const [requestLogDetails, setRequestLogDetails] = useState>({}) const [logsSelected, setLogsSelected] = useState(0) const [logsDetailOpen, setLogsDetailOpen] = useState(false) const [logsDetailScroll, setLogsDetailScroll] = useState(0) const [logsCopyStatus, setLogsCopyStatus] = useState<{ type: "success" | "error"; message: string }>() const [logsClearConfirm, setLogsClearConfirm] = useState(false) const [logsFileError, setLogsFileError] = useState() const [logsAutoFollow, setLogsAutoFollow] = useState(true) const [logsCaptureMode, setLogsCaptureMode] = useState>(() => initialLogsCaptureMode()) const requestLogModeRef = useRef("off") const [claudeEnvDraft, setClaudeEnvDraft] = useState(() => defaultClaudeEnvironment()) const [claudeEnvIndex, setClaudeEnvIndex] = useState(0) const [claudeEnvScopeIndex, setClaudeEnvScopeIndex] = useState(0) const [claudeEnvPresetIndex, setClaudeEnvPresetIndex] = useState(0) const [claudeEnvAction, setClaudeEnvAction] = useState<"set" | "unset">("set") const [commandOutput, setCommandOutput] = useState<{ title: string; output: string }>() const commandOutputRevision = useRef(0) const shell = useMemo(() => detectShell(), []) const [connectDraft, setConnectDraft] = useState({}) const [connectSourceIndex, setConnectSourceIndex] = useState(0) const [connectStep, setConnectStep] = useState(0) const [connectSaving, setConnectSaving] = useState(false) const [authRevision, setAuthRevision] = useState(0) const [accountKey, setAccountKey] = useState() const resetRuntimeLogs = useCallback(() => { setRequestLogs([]) setRequestLogDetails({}) }, []) const clearCommandOutput = useCallback(() => { commandOutputRevision.current += 1 setCommandOutput(undefined) }, []) const beginCommandOutput = useCallback(() => { commandOutputRevision.current += 1 setCommandOutput(undefined) return commandOutputRevision.current }, []) const setCommandOutputForRevision = useCallback((revision: number, output: { title: string; output: string }) => { if (commandOutputRevision.current === revision) setCommandOutput(output) }, []) const appendRuntimeLog = useCallback((entry: RequestLogEntry) => { setRequestLogs((logs) => { const updated = upsertRequestLog(logs, entry) setLogsAutoFollow((follow) => { if (follow) setLogsSelected(Math.max(0, updated.length - 1)) return follow }) return updated }) }, []) const resolveRequestLogMode = useCallback(() => requestLogModeRef.current, []) const providerRuntime = useProviderRuntime({ hostname, port, apiPassword, accountKey, authRevision, loadError, onMessage: setInputMessage, requestLogMode: resolveRequestLogMode, onRequestLogsReset: resetRuntimeLogs, onRequestLogStart: appendRuntimeLog, onRequestLog: appendRuntimeLog, }) const { authFile, providerReady, providerMode, providerInfo, runtime, setProviderInfo, setRuntimeError, switchingProvider, switchProvider, upstream, } = providerRuntime const provider = useMemo(() => providerDefinition(providerMode), [providerMode]) const accountCapability = provider.accounts const connectCapability = accountCapability?.connect const connectFields = connectCapability?.fields ?? [] const accounts = useMemo(() => (accountData && accountCapability ? accountCapability.toAccounts(accountData) : []), [accountCapability, accountData]) const account = accounts[selected] const codexFastMode = useCodexFastMode({ authFile, providerMode, providerReady, onMessage: setInputMessage }) const updateKiroInfo = useCallback((patch: { subscriptionTier?: string; email?: string }) => { setProviderInfo((prev) => prev.mode === "kiro" ? { ...prev, ...patch } : prev) }, [setProviderInfo]) const { activeAccountInfo, limitGroups, limitsLoading, limitsError, resetLimits } = useProviderLimits({ authFile, authRevision, accountKey, loadError, providerMode, providerReady, runtimeStatus: runtime.status, upstream, onKiroInfo: updateKiroInfo, onMessage: setInputMessage, }) const resetForProviderSwitch = useCallback((_targetMode: ProviderMode) => { resetRuntimeLogs() resetLimits() clearCommandOutput() setLoadError(undefined) setAccountData(undefined) setAccountKey(undefined) setSelected(0) setAuthRevision((value) => value + 1) }, [clearCommandOutput, resetLimits, resetRuntimeLogs]) const pkg = useMemo(() => packageInfo(), []) const activePort = runtime.status === "running" ? runtime.server.port ?? port : port const terminalColumns = stdout.columns ?? 120 const contentWidth = Math.max(40, terminalColumns - 2) const dashboardCompact = contentWidth < 106 const dashboardInnerWidth = Math.max(32, contentWidth - 4) const commands = useMemo(() => getCommands(providerMode), [providerMode]) const switchTarget = useMemo(() => nextProviderDefinition(providerMode), [providerMode]) const headerText = `v${pkg.version} · ${shortAuthor(pkg.author)}` const dashboardWidth = dashboardCompact ? contentWidth : 2 + 42 + 1 + Math.min(58, Math.max(42, contentWidth - 48)) const headerResourceWidth = contentWidth >= 104 ? 44 : contentWidth >= 88 ? 34 : 0 const visibleRequestLogs = useMemo( () => requestLogs.map((log) => requestLogDetails[log.id] ?? log), [requestLogDetails, requestLogs], ) const claudeEnvScopes: ClaudeSettingsScope[] = ["user", "project", "local"] const claudeEnvScope = claudeEnvScopes[claudeEnvScopeIndex] ?? "user" const claudeSettingsFile = claudeSettingsPathForScope(claudeEnvScope) const claudeSettingsTarget = claudeSettingsScopeLabel(claudeEnvScope) useEffect(() => { requestLogModeRef.current = mode === "logs" ? logsCaptureMode : "off" }, [logsCaptureMode, mode]) useEffect(() => { setCommandIndex((value) => Math.min(value, Math.max(0, commands.length - 1))) }, [commands.length]) useEffect(() => { if (!logsCopyStatus) return const timer = setTimeout(() => setLogsCopyStatus(undefined), 2000) return () => clearTimeout(timer) }, [logsCopyStatus]) useEffect(() => { if (!providerReady) return if (!accountCapability) { setAccountData(undefined) setAccountKey(undefined) setSelected(0) setLoadError(undefined) return } let active = true accountCapability.loadState(authFile) .then((state) => { if (!active) return const loadedAccounts = accountCapability.toAccounts(state.data) const nextSelected = Math.min(state.selected, Math.max(0, loadedAccounts.length - 1)) setLoadError(undefined) setAccountData(state.data) setSelected(nextSelected) setAccountKey(loadedAccounts[nextSelected]?.key) }) .catch((error) => { if (!active) return const message = error instanceof Error ? error.message : String(error) setAccountData(undefined) setAccountKey(undefined) setLoadError(message) setRuntimeError(message) }) return () => { active = false } }, [accountCapability, authFile, authRevision, providerReady, setRuntimeError]) useEffect(() => { if (mode !== "logs") return let active = true async function loadLogs() { try { const logs = await readRecentRequestLogs(authFile) if (!active) return setLogsFileError(undefined) setRequestLogs((currentLogs) => { const merged = mergeRequestLogs(logs, currentLogs) setLogsAutoFollow((follow) => { if (follow) { setLogsSelected(Math.max(0, merged.length - 1)) } else { setLogsSelected((prev) => Math.max(0, Math.min(prev, merged.length - 1))) } return follow }) return merged }) } catch (error) { if (!active) return const message = error instanceof Error ? error.message : String(error) setLogsFileError(`Failed to read log file: ${message}`) } } void loadLogs() // Poll every 2 s so new requests that arrive while the panel is open are // reflected without requiring the user to close and reopen the panel. const timer = setInterval(() => void loadLogs(), 2000) return () => { active = false clearInterval(timer) } }, [authFile, mode, authRevision, selected]) useEffect(() => { if (mode !== "logs" || !logsDetailOpen) return const log = requestLogs[logsSelected] if (!log || requestLogDetails[log.id]) return let active = true void readRequestLogDetail(authFile, log) .then((detail) => { if (!active) return setRequestLogDetails((details) => ({ ...details, [detail.id]: detail })) }) .catch((error) => { if (!active) return setLogsFileError(`Failed to read request detail: ${error instanceof Error ? error.message : String(error)}`) }) return () => { active = false } }, [authFile, logsDetailOpen, logsSelected, mode, requestLogDetails, requestLogs]) useInput((input, key) => { if (key.ctrl && input === "c") { if (runtime.status === "running") runtime.server.stop(true) app.exit() process.exit(0) return } if (switchingProvider) return if (mode === "logs") { if (logsClearConfirm) { if (input.toLowerCase() === "y") { void clearRequestLogs(authFile) .then(() => { setRequestLogs([]) setRequestLogDetails({}) setLogsSelected(0) setLogsDetailOpen(false) setLogsDetailScroll(0) setLogsCopyStatus(undefined) setLogsClearConfirm(false) setLogsFileError(undefined) setInputMessage("Cleared request logs") }) .catch((error) => { const message = `Clear request logs failed: ${error instanceof Error ? error.message : String(error)}` setLogsCopyStatus({ type: "error", message }) setLogsClearConfirm(false) setInputMessage(message) }) return } if (key.escape || input.toLowerCase() === "n") { setLogsClearConfirm(false) setInputMessage("Clear request logs cancelled") return } return } if (logsDetailOpen && input.toLowerCase() === "c") { const log = requestLogs[logsSelected] if (!log) return void readRequestLogDetail(authFile, log) .then((detail) => { setRequestLogDetails((details) => ({ ...details, [detail.id]: detail })) return writeClipboard(formatRequestLogDetail(detail)) }) .then(() => { setLogsCopyStatus({ type: "success", message: `Copied request ${log.id} to clipboard` }) setInputMessage(`Copied request ${log.id} to clipboard`) }) .catch((error) => { const message = `Clipboard copy failed: ${error instanceof Error ? error.message : String(error)}` setLogsCopyStatus({ type: "error", message }) setInputMessage(message) }) return } if (input.toLowerCase() === "l") { void Promise.all(requestLogs.map((log) => readRequestLogDetail(authFile, log))) .then((logs) => { setRequestLogDetails((details) => ({ ...details, ...Object.fromEntries(logs.map((log) => [log.id, log])), })) return writeClipboard(formatAllRequestLogs(logs)) }) .then(() => { setLogsCopyStatus({ type: "success", message: `Copied ${requestLogs.length} log(s) to clipboard` }) setInputMessage(`Copied ${requestLogs.length} log(s) to clipboard`) }) .catch((error) => { const message = `Clipboard copy failed: ${error instanceof Error ? error.message : String(error)}` setLogsCopyStatus({ type: "error", message }) setInputMessage(message) }) return } if (input.toLowerCase() === "x") { setLogsDetailOpen(false) setLogsDetailScroll(0) setLogsCopyStatus(undefined) setLogsClearConfirm(true) setInputMessage("Confirm clear request logs") return } if (input.toLowerCase() === "f") { setLogsAutoFollow((value) => { const next = !value if (next) { // Jump to the latest entry when enabling follow setLogsSelected(Math.max(0, requestLogs.length - 1)) setInputMessage("Auto-follow ON — tracking latest request") } else { setInputMessage("Auto-follow OFF — manual navigation") } return next }) return } if (input.toLowerCase() === "d") { setLogsCaptureMode((value) => { const next = value === "sync" ? "async" : "sync" requestLogModeRef.current = next setInputMessage(next === "sync" ? "Request logs SYNC — writes complete before responses finish" : "Request logs ASYNC — writes happen in the background") return next }) return } } if (mode === "home" && input === "q") { if (runtime.status === "running") runtime.server.stop(true) app.exit() process.exit(0) return } if (key.escape) { if (mode === "switch-provider") { setMode("home") setCommandIndex(0) setInputMessage("Provider switch cancelled") return } if (mode === "connect-source" || mode === "connect-account") { setMode("home") setConnectDraft(connectCapability?.defaultDraft() ?? {}) setConnectSourceIndex(0) setConnectStep(0) setInputMessage("Connect account cancelled") return } if (commandOutput) { clearCommandOutput() setInputMessage("Type / for commands") return } if (mode === "claude-env-scope" || mode === "claude-env-preset" || mode === "claude-env-editor" || mode === "claude-env-confirm" || mode === "claude-env-unset-confirm") { setMode("home") setCommandIndex(0) setInputMessage(mode === "claude-env-unset-confirm" ? "Claude environment unset cancelled" : "Claude environment edit cancelled") return } if (mode === "logs") { if (logsClearConfirm) { setLogsClearConfirm(false) setInputMessage("Clear request logs cancelled") return } if (logsDetailOpen) { setLogsDetailOpen(false) setLogsDetailScroll(0) setLogsCopyStatus(undefined) setInputMessage("Closed request detail") return } setMode("home") requestLogModeRef.current = "off" setCommandIndex(0) setLogsCopyStatus(undefined) setLogsClearConfirm(false) setInputMessage("Closed request logs") return } if (mode === "codex-fast-mode") { setMode("home") setCommandIndex(0) codexFastMode.resetSelection() setInputMessage("Codex fast mode unchanged") return } if (mode === "account-selector") { setMode("home") setCommandIndex(0) setInputMessage("Account switch cancelled") return } setCommandIndex(0) setInputMessage("↑↓ select · enter confirm") return } if (key.return) { if (mode === "switch-provider") { setMode("home") void switchProvider({ onBeforeApply: resetForProviderSwitch }) return } if (mode === "connect-source") { if (!connectCapability) { setMode("home") setInputMessage("Connect is not available for this provider") return } const selectedSource = connectCapability.sources[connectSourceIndex] if (selectedSource) { setConnectSaving(true) void selectedSource.import(authFile) .then((result) => { applyConnectedAccount(result.data, result.accountKey) setMode("home") setInputMessage(`Connected account ${result.accountKey}`) }) .catch((error) => setInputMessage(`Connect failed: ${error instanceof Error ? error.message : String(error)}`)) .finally(() => setConnectSaving(false)) return } // Last entry is Manual setConnectDraft(connectCapability.defaultDraft()) setConnectStep(0) setMode("connect-account") return } if (mode === "connect-account") { if (!connectCapability) { setMode("home") setInputMessage("Connect is not available for this provider") return } if (connectStep < connectFields.length - 1) { setConnectStep((step) => step + 1) return } setConnectSaving(true) void connectCapability.connectManual(authFile, connectDraft) .then((result) => { applyConnectedAccount(result.data, result.accountKey) setMode("home") setConnectDraft(connectCapability.defaultDraft()) setConnectStep(0) setInputMessage(`Connected account ${result.accountKey}`) }) .catch((error) => setInputMessage(`Connect failed: ${error instanceof Error ? error.message : String(error)}`)) .finally(() => setConnectSaving(false)) return } if (mode === "claude-env-scope") { if (claudeEnvAction === "set") { setClaudeEnvPresetIndex(0) setMode("claude-env-preset") } else { setMode("claude-env-unset-confirm") } return } if (mode === "claude-env-preset") { const preset = PRESET_OPTIONS[claudeEnvPresetIndex] if (preset?.key === "recommend") { setClaudeEnvDraft(recommendedClaudeEnvironment(providerMode)) setClaudeEnvIndex(0) setMode("claude-env-editor") setInputMessage("Loaded recommended settings") } else if (preset?.key === "latest") { void readClaudeSettingsEnvAsDraft(claudeSettingsFile, providerMode) .then((draft) => { setMode((current) => { if (current !== "claude-env-preset") return current setClaudeEnvDraft(draft) setClaudeEnvIndex(0) setInputMessage("Loaded latest settings from " + claudeSettingsTarget) return "claude-env-editor" }) }) .catch(() => { setMode((current) => { if (current !== "claude-env-preset") return current setClaudeEnvIndex(0) setInputMessage("Could not read settings file, using current draft") return "claude-env-editor" }) }) } else { setClaudeEnvIndex(0) setMode("claude-env-editor") } return } if (mode === "claude-env-confirm") { setMode("home") setInputMessage("Claude settings updated") const outputRevision = beginCommandOutput() void writeClaudeEnvironmentConfig(authFile, claudeEnvDraft, providerMode).catch((error) => setInputMessage(`Claude environment saved failed: ${error instanceof Error ? error.message : String(error)}`), ) void runClaudeEnvironmentSet(claudeEnvDraft, baseUrl(hostname, activePort), shell, { authFile, settingsFile: claudeSettingsFile, apiPassword }) .then((output) => setCommandOutputForRevision(outputRevision, { title: `Set Claude environment - ${claudeSettingsTarget}`, output })) .catch((error) => setCommandOutputForRevision(outputRevision, { title: "Set Claude environment failed", output: error instanceof Error ? error.message : String(error) })) return } if (mode === "claude-env-unset-confirm") { setMode("home") setInputMessage("Claude settings env entries removed") const outputRevision = beginCommandOutput() void runClaudeEnvironmentUnset(claudeEnvDraft, shell, { authFile, settingsFile: claudeSettingsFile }) .then((output) => setCommandOutputForRevision(outputRevision, { title: `Unset Claude environment - ${claudeSettingsTarget}`, output })) .catch((error) => setCommandOutputForRevision(outputRevision, { title: "Unset Claude environment echo failed", output: error instanceof Error ? error.message : String(error) })) return } if (mode === "claude-env-editor") { setMode("claude-env-confirm") return } if (mode === "codex-fast-mode") { codexFastMode.saveSelection() setMode("home") setCommandIndex(0) return } if (mode === "account-selector") { const nextAccount = accounts[selectorIndex] if (!nextAccount) { setMode("home") setCommandIndex(0) setInputMessage("No account selected") return } setSelected(selectorIndex) setAccountKey(nextAccount.key) if (accountCapability && accountData) void accountCapability.persistActive(authFile, accountData, nextAccount.key).catch(() => {}) setMode("home") setCommandIndex(0) setInputMessage(`Switched to ${nextAccount.name}`) return } const command = commands[commandIndex] if (mode === "logs") { if (requestLogs.length) { setLogsDetailOpen((value) => { const next = !value setLogsDetailScroll(0) setInputMessage(next ? `Showing request ${requestLogs[logsSelected]?.id ?? ""}` : "Closed request detail") return next }) } return } if (command?.name === "/quit") { if (runtime.status === "running") runtime.server.stop(true) app.exit() process.exit(0) return } if (command) { clearCommandOutput() if (command.name === "/switch-provider") { if (runtime.status === "starting" || switchingProvider) { setInputMessage("Provider switch already in progress") return } setMode("switch-provider") setCommandIndex(0) return } if (command.name === "/account") { if (!accountCapability) { setInputMessage("Account switching is not available for this provider") return } setSelectorIndex(selected) setMode("account-selector") return } if (command.name === "/logs") { requestLogModeRef.current = logsCaptureMode setLogsDetailOpen(false) setLogsDetailScroll(0) setLogsCopyStatus(undefined) setLogsClearConfirm(false) setLogsAutoFollow(true) setLogsSelected(Math.max(0, requestLogs.length - 1)) setMode("logs") setCommandIndex(0) setInputMessage("Showing request logs") return } if (command.name === "/codex-fast-mode") { codexFastMode.resetSelection() setMode("codex-fast-mode") setCommandIndex(0) setInputMessage("Select Codex fast mode") return } if (command.name === "/connect") { if (!connectCapability) { setInputMessage("Connect is not available for this provider") return } setConnectDraft(connectCapability.defaultDraft()) setConnectSourceIndex(0) setConnectStep(0) setMode("connect-source") setCommandIndex(0) return } if (command.name === "/set-claude-env") { void readClaudeEnvironmentConfig(authFile, providerMode) .then((draft) => setClaudeEnvDraft(draft)) .catch(() => setClaudeEnvDraft(defaultClaudeEnvironment(providerMode))) setClaudeEnvIndex(0) setClaudeEnvScopeIndex(0) setClaudeEnvAction("set") setMode("claude-env-scope") setCommandIndex(0) return } if (command.name === "/unset-claude-env") { void readClaudeEnvironmentConfig(authFile, providerMode) .then((draft) => setClaudeEnvDraft(draft)) .catch(() => setClaudeEnvDraft(defaultClaudeEnvironment(providerMode))) setClaudeEnvScopeIndex(0) setClaudeEnvAction("unset") setMode("claude-env-scope") setCommandIndex(0) return } setInputMessage(`${command.name} selected · implementation pending`) setCommandIndex(0) } return } if (key.backspace || key.delete) { if (mode === "connect-account") { setConnectDraft((draft) => updateConnectDraft(draft, connectFields, connectStep, (value) => value.slice(0, -1))) return } if (mode === "claude-env-editor") { setClaudeEnvDraft((draft) => updateClaudeEnvDraft(draft, claudeEnvIndex, (value) => value.slice(0, -1))) return } return } if (mode === "account-selector") { if (key.upArrow && accounts.length) setSelectorIndex((value) => (value - 1 + accounts.length) % accounts.length) if (key.downArrow && accounts.length) setSelectorIndex((value) => (value + 1) % accounts.length) return } if (mode === "codex-fast-mode") { if (key.upArrow || key.downArrow) codexFastMode.setSelected((value) => (value + 1) % 2) return } if (mode === "connect-source") { const sourceCount = (connectCapability?.sources.length ?? 0) + 1 // +1 for Manual if (key.upArrow) setConnectSourceIndex((value) => (value - 1 + sourceCount) % sourceCount) if (key.downArrow) setConnectSourceIndex((value) => (value + 1) % sourceCount) return } if (mode === "logs") { if (logsDetailOpen) { const detailLog = visibleRequestLogs[logsSelected] const maxDetailScroll = detailLog ? requestLogDetailMaxScroll(detailLog, stdout.columns) : 0 const scrollStep = key.pageUp || key.pageDown || key.meta ? REQUEST_LOG_DETAIL_FAST_SCROLL_STEP : REQUEST_LOG_DETAIL_SCROLL_STEP if (key.home) { setLogsCopyStatus(undefined) setLogsClearConfirm(false) setLogsDetailScroll(0) } if (key.end) { setLogsCopyStatus(undefined) setLogsClearConfirm(false) setLogsDetailScroll(maxDetailScroll) } if (key.upArrow || key.pageUp) { setLogsCopyStatus(undefined) setLogsClearConfirm(false) setLogsDetailScroll((value) => Math.max(0, Math.min(value, maxDetailScroll) - scrollStep)) } if (key.downArrow || key.pageDown) { setLogsCopyStatus(undefined) setLogsClearConfirm(false) setLogsDetailScroll((value) => Math.min(maxDetailScroll, value + scrollStep)) } return } if (key.upArrow) { setLogsAutoFollow(false) setLogsDetailOpen(false) setLogsDetailScroll(0) setLogsCopyStatus(undefined) setLogsClearConfirm(false) setLogsSelected((value) => Math.max(0, value - 1)) } if (key.downArrow) { setLogsAutoFollow(false) setLogsDetailOpen(false) setLogsDetailScroll(0) setLogsCopyStatus(undefined) setLogsClearConfirm(false) setLogsSelected((value) => Math.min(Math.max(0, requestLogs.length - 1), value + 1)) } return } if (mode === "claude-env-scope") { if (key.upArrow) setClaudeEnvScopeIndex((value) => (value - 1 + claudeEnvScopes.length) % claudeEnvScopes.length) if (key.downArrow) setClaudeEnvScopeIndex((value) => (value + 1) % claudeEnvScopes.length) return } if (mode === "claude-env-preset") { if (key.upArrow) setClaudeEnvPresetIndex((value) => (value - 1 + PRESET_OPTIONS.length) % PRESET_OPTIONS.length) if (key.downArrow) setClaudeEnvPresetIndex((value) => (value + 1) % PRESET_OPTIONS.length) return } if (mode === "claude-env-confirm") { if (input === "y") { setMode("home") setInputMessage("Claude settings updated") const outputRevision = beginCommandOutput() void writeClaudeEnvironmentConfig(authFile, claudeEnvDraft, providerMode).catch((error) => setInputMessage(`Claude environment saved failed: ${error instanceof Error ? error.message : String(error)}`), ) void runClaudeEnvironmentSet(claudeEnvDraft, baseUrl(hostname, activePort), shell, { authFile, settingsFile: claudeSettingsFile, apiPassword }) .then((output) => setCommandOutputForRevision(outputRevision, { title: `Set Claude environment - ${claudeSettingsTarget}`, output })) .catch((error) => setCommandOutputForRevision(outputRevision, { title: "Set Claude environment failed", output: error instanceof Error ? error.message : String(error) })) } if (input === "n") { setMode("home") setInputMessage("Claude environment edit cancelled") } return } if (mode === "claude-env-unset-confirm") { if (input === "y") { setMode("home") setInputMessage("Claude settings env entries removed") const outputRevision = beginCommandOutput() void runClaudeEnvironmentUnset(claudeEnvDraft, shell, { authFile, settingsFile: claudeSettingsFile }) .then((output) => setCommandOutputForRevision(outputRevision, { title: `Unset Claude environment - ${claudeSettingsTarget}`, output })) .catch((error) => setCommandOutputForRevision(outputRevision, { title: "Unset Claude environment echo failed", output: error instanceof Error ? error.message : String(error) })) } if (input === "n") { setMode("home") setInputMessage("Claude environment unset cancelled") } return } if (mode === "claude-env-editor") { if (key.upArrow) setClaudeEnvIndex((value) => (value - 1 + ALL_EDITABLE_KEYS.length) % ALL_EDITABLE_KEYS.length) else if (key.downArrow) setClaudeEnvIndex((value) => (value + 1) % ALL_EDITABLE_KEYS.length) else if (input && !key.leftArrow && !key.rightArrow) setClaudeEnvDraft((draft) => updateClaudeEnvDraft(draft, claudeEnvIndex, (value) => `${value}${input}`)) return } if (mode === "connect-account") { if (input && !key.upArrow && !key.downArrow && !key.leftArrow && !key.rightArrow) { setConnectDraft((draft) => updateConnectDraft(draft, connectFields, connectStep, (value) => `${value}${input}`)) } return } if (mode === "home") { if (key.upArrow) { clearCommandOutput() setCommandIndex((value) => (value - 1 + commands.length) % commands.length) return } if (key.downArrow) { clearCommandOutput() setCommandIndex((value) => (value + 1) % commands.length) return } } }) return ( {mode === "home" && } {mode === "account-selector" && accountCapability && } {mode === "codex-fast-mode" && providerMode === "codex" && } {mode === "connect-source" && connectCapability && } {mode === "connect-account" && connectCapability && } {mode === "switch-provider" && } {mode === "logs" && ( )} {mode === "claude-env-scope" && } {mode === "claude-env-preset" && } {(mode === "claude-env-editor" || mode === "claude-env-confirm") && ( )} {mode === "claude-env-unset-confirm" && } {commandOutput && } ) function applyConnectedAccount(data: ProviderAccountData, nextAccountKey: string) { const nextAccounts = accountCapability?.toAccounts(data) ?? [] const nextSelected = Math.max(0, nextAccounts.findIndex((item) => item.key === nextAccountKey)) setLoadError(undefined) setAccountData(data) setSelected(nextSelected) setAccountKey(nextAccounts[nextSelected]?.key) resetLimits({ loading: true }) setAuthRevision((value) => value + 1) } } function updateConnectDraft(draft: ProviderConnectDraft, fields: ProviderConnectField[], step: number, update: (value: string) => string): ProviderConnectDraft { const key = fields[step]?.key if (!key) return draft return { ...draft, [key]: update(draft[key] ?? ""), } } function updateClaudeEnvDraft(draft: ClaudeEnvironmentDraft, index: number, update: (value: string) => string): ClaudeEnvironmentDraft { const key = ALL_EDITABLE_KEYS[index] if (!key) return draft // Model keys are top-level on the draft if ((CLAUDE_MODEL_ENV_KEYS as readonly string[]).includes(key)) { return { ...draft, [key]: update(draft[key as keyof ClaudeEnvironmentDraft] as string), } } // Extra editable keys live in draft.extraEnv return { ...draft, extraEnv: { ...draft.extraEnv, [key]: update(draft.extraEnv[key] ?? ""), }, } } function baseUrl(hostname: string, port: number) { const displayHostname = hostname === "0.0.0.0" || hostname === "::" ? "127.0.0.1" : hostname return `http://${displayHostname}:${port}` } function shortAuthor(author: string) { return author.replace(/\s*<[^>]+>\s*$/, "").trim() || author } function initialLogsCaptureMode(): Exclude { const mode = process.env.REQUEST_LOG_MODE?.trim().toLowerCase() return mode === "sync" || mode === "live" || mode === "immediate" || mode === "1" ? "sync" : "async" } function mergeRequestLogs(storedLogs: RequestLogEntry[], currentLogs: RequestLogEntry[]) { // Start from stored logs (source of truth on disk), then overlay any // in-memory entries that are not yet persisted (pending or complete but // not yet flushed due to a race between write and the polling read). let merged = [...storedLogs] for (const log of currentLogs) { if (merged.some((storedLog) => storedLog.id === log.id)) continue merged = upsertRequestLog(merged, log) } return merged } function upsertRequestLog(logs: RequestLogEntry[], entry: RequestLogEntry) { const next = logs.filter((log) => log.id !== entry.id) next.push(entry) next.sort((left, right) => left.at.localeCompare(right.at)) return next.slice(-MAX_REQUEST_LOG_ENTRIES) }