/** * @file CodexHooksSettings.tsx * @description Settings → Codex → Overview → Codex hooks: whether AgentHub's * hooks are in `$CODEX_HOME/hooks.json` and trusted by Codex, per event, with * Install/Reinstall and Remove. Nothing is written until the user clicks; * Codex itself asks the user to trust the hooks before it runs them, and this * card says so while they wait, with a Refresh to re-read once they have * (#40 part C2). * @author Nguyễn Ngọc Trí Vĩ */ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { AlertTriangle, CheckCircle, Plug, RefreshCw, RotateCcw, Trash2, XCircle, } from "lucide-react"; import { api } from "../../lib/api"; import type { CodexHooksStatus } from "../../lib/types"; import { Skeleton } from "../Skeleton"; const CHIP = "inline-flex items-center gap-1.5 text-xs font-medium px-2.5 py-1 rounded-full border"; /** * The Codex hooks card. * @returns A section with id `codex-hooks` for deep links. */ export function CodexHooksSettings() { const { t } = useTranslation("settings"); const [status, setStatus] = useState(null); const [busy, setBusy] = useState<"install" | "remove" | "get" | null>(null); const [error, setError] = useState(null); useEffect(() => { api.settings.codexHooks .get() .then(setStatus) .catch((err) => setError(err instanceof Error ? err.message : String(err))); }, []); const run = async (kind: "install" | "remove" | "get") => { setBusy(kind); setError(null); try { setStatus(await api.settings.codexHooks[kind]()); } catch (err) { setError( t("codexHooks.failed", { message: err instanceof Error ? err.message : String(err) }) ); } finally { setBusy(null); } }; // Some of ours present but not all (an event added since, or one taken out // by hand): still show them, and still offer Remove. const anyInstalled = !!status && Object.values(status.events).some((e) => e.installed); const eventLabel = (e: { installed: boolean; trusted: boolean }) => !e.installed ? t("codexHooks.eventMissing") : e.trusted ? t("codexHooks.eventTrusted") : t("codexHooks.eventUntrusted"); const chip = !status?.installed ? ( {t("codexHooks.notInstalled")} ) : status.trusted ? ( {t("codexHooks.trusted")} ) : ( {t("codexHooks.untrusted")} ); return (

{t("codexHooks.title")}

{t("codexHooks.description")}

{!status && !error ? (
) : null} {status && !status.supported ? (

{t("codexHooks.unsupported")}

) : null} {status && status.supported ? ( <>
{chip}
{anyInstalled ? ( ) : null}
{!status.readable ? (

{t("codexHooks.unreadable", { path: status.path, error: status.error ?? "" })}

) : null} {status.installed && !status.trusted ? (

{t("codexHooks.trustNote")}

) : null} {anyInstalled ? (
{Object.entries(status.events).map(([event, e]) => (
{!e.installed ? (
))}
) : null}

{status.path}

) : null} {error ? (
{error}
) : null}
); }