import { useEffect, useState } from "react"; import { api, get, post } from "../../api"; import { confirmDialog, toast } from "../../dialogs"; import { useT } from "../../i18n"; import { Icon } from "../Icon"; import { Spinner } from "../Activity"; import { useStore } from "../../store"; interface Status { installed: boolean; logged_in?: boolean; method?: string; email?: string; org?: string; expires?: string; login_in_progress?: boolean; error?: string; version?: string | null; latest?: string | null; update_available?: boolean; output?: string; } /** * Claude Code: sign in from the browser. The CLI prints a sign-in URL; the Anthropic page shows * a code after login that is pasted here and handed back to the CLI on the server. */ export function ClaudeCode() { const t = useT(); const [status, setStatus] = useState(); const [url, setUrl] = useState(); const [code, setCode] = useState(""); const [busy, setBusy] = useState(false); const [console_, setConsole] = useState(false); const isAdmin = useStore((s) => s.me?.role === "admin"); const [updating, setUpdating] = useState(false); const [checking, setChecking] = useState(false); const [updateLog, setUpdateLog] = useState(); const check = async () => { setChecking(true); try { setStatus(await get("/claude/status?refresh=true")); } catch (e: any) { toast(e.message); } finally { setChecking(false); } }; const update = async () => { setUpdating(true); setUpdateLog(undefined); try { const r = await post("/claude/update", {}); setStatus(r); setUpdateLog(r.output); toast(t("claude.updated", { version: r.version ?? "" }), "success"); } catch (e: any) { setUpdateLog(e.message); toast(t("claude.updateFailed")); } finally { setUpdating(false); } }; const load = () => get("/claude/status").then(setStatus, (e) => setStatus({ installed: false, error: e.message })); useEffect(() => void load(), []); const start = async () => { setBusy(true); try { const r = await post<{ url: string }>("/claude/login", { console: console_ }); setUrl(r.url); setCode(""); } catch (e: any) { toast(e.message); } finally { setBusy(false); } }; const submit = async () => { setBusy(true); try { setStatus(await post("/claude/login/code", { code })); setUrl(undefined); setCode(""); toast(t("claude.signedIn"), "success"); } catch (e: any) { toast(e.message); } finally { setBusy(false); } }; const cancel = async () => { await api("/claude/login", { method: "DELETE" }).catch(() => undefined); setUrl(undefined); setCode(""); }; const logout = async () => { if (!(await confirmDialog({ message: t("claude.logoutConfirm"), danger: true, confirmLabel: t("claude.logout") }))) return; try { setStatus(await post("/claude/logout", {})); } catch (e: any) { toast(e.message); } }; return (

{t("claude.title")} {status?.logged_in && ( )}

{t("claude.intro")}
{!status && } {status && !status.installed &&
{status.error ?? t("claude.notInstalled")}
} {status?.installed && (
{t("claude.version", { version: status.version ?? "?" })} {status.update_available ? ( <> {t("claude.updateAvailable", { version: status.latest ?? "" })} {isAdmin ? ( ) : ( {t("claude.updateAdmin")} )} ) : ( status.latest && {t("claude.upToDate")} )}
)} {updateLog && (
{t("claude.updateLog")}
{updateLog}
)} {status?.installed && status.logged_in && (
{t("claude.signedInAs", { who: status.email || status.org || status.method || "" })} {status.method && {status.method}} {status.expires && {t("claude.expires", { date: new Date(status.expires).toLocaleDateString() })}}
)} {status?.installed && !status.logged_in && !url && ( <> )} {url && (
  1. {t("claude.openLogin")}
    {t("claude.step1")}
  2. {t("claude.step2")}
    setCode(e.target.value)} onKeyDown={(e) => e.key === "Enter" && code.trim() && void submit()} />
)}
); }