'use client'; import React, { useState, useEffect, useCallback } from 'react'; import { Button } from '@/components/ui/button'; import { Label } from '@/components/ui/label'; import { ExternalLink, Loader2, Terminal, TriangleAlert } from 'lucide-react'; import { ConnectionBadge } from '@/components/settings/connection-badge'; import { toast } from 'sonner'; import { configManager } from '@/lib/config/storage'; import { parseCodexAuthJson, connectCodex, disconnectCodex, checkCodexStatus } from '@/lib/auth/codex-auth'; import { track } from '@/lib/telemetry'; interface CodexAuthPanelProps { onAuthChange?: () => void; } export function CodexAuthPanel({ onAuthChange }: CodexAuthPanelProps) { const [isAuthenticated, setIsAuthenticated] = useState(() => !!configManager.getCodexAuth() ); const [pasteValue, setPasteValue] = useState(''); const [isLoading, setIsLoading] = useState(false); const auth = configManager.getCodexAuth(); const dispatchAuthEvent = useCallback((hasKey: boolean) => { onAuthChange?.(); window.dispatchEvent(new CustomEvent('apiKeyUpdated', { detail: { provider: 'openai-codex', hasKey } })); }, [onAuthChange]); // Reconcile localStorage vs HttpOnly cookie on mount useEffect(() => { let cancelled = false; async function reconcile() { try { const hasCookie = await checkCodexStatus(); if (cancelled) return; const localAuth = configManager.getCodexAuth(); if (localAuth && !hasCookie) { // localStorage present but cookie gone (e.g., expired) → stale configManager.clearCodexAuth(); if (!cancelled) { setIsAuthenticated(false); dispatchAuthEvent(false); } } else if (!localAuth && hasCookie) { // Orphaned cookie, no localStorage → clean up await disconnectCodex(); if (!cancelled) { setIsAuthenticated(false); dispatchAuthEvent(false); } } } catch { // Network error — leave state as-is } } reconcile(); return () => { cancelled = true; }; }, [dispatchAuthEvent]); const handlePasteToken = async () => { setIsLoading(true); try { const parsed = parseCodexAuthJson(pasteValue); // Send to server — stores refresh_token in HttpOnly cookie const serverResult = await connectCodex(parsed); // Store only non-sensitive fields in localStorage configManager.setCodexAuth(serverResult); setIsAuthenticated(true); setPasteValue(''); toast.success('Token saved! Tokens will refresh automatically.'); track('connection_added', { provider: 'openai-codex' }); dispatchAuthEvent(true); } catch (err) { const msg = err instanceof Error ? err.message : 'Invalid JSON'; toast.error(msg); } finally { setIsLoading(false); } }; const handleDisconnect = async () => { setIsLoading(true); try { await disconnectCodex(); configManager.clearModelCache('openai-codex'); setIsAuthenticated(false); toast.success('Disconnected from ChatGPT'); track('connection_removed', { provider: 'openai-codex' }); dispatchAuthEvent(false); } catch { toast.error('Failed to disconnect. Please try again.'); } finally { setIsLoading(false); } }; const formatExpiry = () => { if (!auth?.expires_at) return ''; const diff = auth.expires_at - Math.floor(Date.now() / 1000); if (diff <= 0) return 'Expired (will auto-refresh)'; const mins = Math.floor(diff / 60); if (mins < 60) return `${mins}m`; const hrs = Math.floor(mins / 60); return `${hrs}h ${mins % 60}m`; }; const warningBanner = (

Use at your own risk.{' '} This routes requests through an unofficial backend using your ChatGPT session token. Your token is sent to ChatGPT servers but the usage is outside the intended Codex CLI.

OpenAI may restrict or revoke access to this endpoint at any time. For reliable, long-term use consider an{' '} OpenAI API key{' '} instead.

); // --- Authenticated state --- if (isAuthenticated && auth) { return (
{warningBanner}
); } // --- Unauthenticated state --- return (

Use your ChatGPT Plus/Pro subscription instead of an API key. Tokens refresh automatically once connected.

{warningBanner}
Setup Instructions
  1. Install the{' '} Codex CLI {': '} npm i -g @openai/codex
  2. Run codex login and follow the browser prompts
  3. Copy your token by running:
    cat ~/.codex/auth.json | pbcopy (macOS)
    cat ~/.codex/auth.json | xclip -sel c (Linux)
  4. Paste below with Cmd+V / Ctrl+V