"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { CLAUDE_CODE_MINIMUM_VERSION, type ClaudeCodeStatus, type UserSettings } from "@/src/core"; import { notifySettingsSaved, useSettingsSaved } from "@/components/settings-signal"; import { Button } from "@/components/ui/button"; import { Toggle } from "./controls"; export function claudeCodeStatusLabel(status: ClaudeCodeStatus | null): string { switch (status?.state) { case "not-found": return "Not found"; case "meets-minimum": return `Version ${status.version} · meets minimum`; case "update-required": return `Version ${status.version} · update required`; default: return "Version could not be checked"; } } export function ClaudeCodeInstallationStatus() { const [enabled, setEnabled] = useState(false); const [loaded, setLoaded] = useState(false); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [status, setStatus] = useState(null); const [busy, setBusy] = useState(false); const request = useRef(null); const settingsRead = useRef(null); const settingsWrite = useRef(null); const read = useCallback(async () => { settingsRead.current?.abort(); const controller = new AbortController(); settingsRead.current = controller; setLoaded(false); try { const response = await fetch("/api/settings", { cache: "no-store", signal: controller.signal }); if (!response.ok) throw new Error("settings unavailable"); const body = await response.json() as { settings: UserSettings | null }; if (!controller.signal.aborted) { setEnabled(body.settings?.claudeCode?.enabled === true); setLoaded(true); setError(null); } } catch { if (!controller.signal.aborted) setError("Could not load Claude Code settings."); } }, []); useEffect(() => { void read(); return () => { settingsRead.current?.abort(); settingsWrite.current?.abort(); }; }, [read]); useSettingsSaved((section) => { if (section === "claudeCode") void read(); }); const save = async (next: boolean) => { const controller = new AbortController(); settingsWrite.current = controller; settingsRead.current?.abort(); setSaving(true); setError(null); try { const response = await fetch("/api/settings/claudeCode", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ enabled: next }), signal: controller.signal }); if (!response.ok) throw new Error("settings write failed"); const body = await response.json() as { settings: UserSettings }; if (typeof body.settings?.claudeCode?.enabled !== "boolean") throw new Error("invalid settings response"); if (!controller.signal.aborted) { setEnabled(body.settings.claudeCode.enabled); notifySettingsSaved("claudeCode"); } } catch { if (!controller.signal.aborted) setError("Could not save Claude Code settings."); } finally { if (!controller.signal.aborted) setSaving(false); } }; const refresh = useCallback(async () => { if (!enabled) return; request.current?.abort(); const controller = new AbortController(); request.current = controller; setBusy(true); try { const response = await fetch("/api/claude-code", { cache: "no-store", signal: controller.signal, headers: { "x-learnharness-claude-code": "1" } }); if (!response.ok) throw new Error("status unavailable"); const value = await response.json() as ClaudeCodeStatus; if (!["not-found", "meets-minimum", "update-required", "version-unchecked"].includes(value.state) || value.minimumVersion !== CLAUDE_CODE_MINIMUM_VERSION || (value.version !== null && (typeof value.version !== "string" || !/^\d+\.\d+\.\d+$/.test(value.version)))) throw new Error("invalid status"); if (!controller.signal.aborted) setStatus(value); } catch { if (!controller.signal.aborted) setStatus(null); } finally { if (!controller.signal.aborted) setBusy(false); } }, [enabled]); useEffect(() => { if (enabled) void refresh(); else { request.current?.abort(); setStatus(null); setBusy(false); } return () => request.current?.abort(); }, [enabled, refresh]); return
Claude Code void save(next)} />
{error &&

{error}

} {enabled && <>
{busy ? "Checking…" : claudeCodeStatusLabel(status)}

Install and authenticate Claude Code in your terminal, then select a Claude Code model here. LearnHarness starts it when needed—no background process required.

Checks installation/version only—not authentication, model access, or quota. Claude Code controls billing. Requires v{CLAUDE_CODE_MINIMUM_VERSION}+. Setup ↗

Verify AI claims independently—they may be inaccurate, incomplete, misleading, or outdated.

}
; }