import { useState, useEffect, useCallback, useRef } from "react"; import { useAuth } from "../contexts/AuthContext"; import { useNavigate } from "react-router"; import { Masthead } from "../components/Masthead"; import { listDaemons, restartDaemon, triggerDaemonReauth, submitDaemonAuthCode, getDaemonReauthStatus, type Daemon, type DaemonListResponse, } from "../lib/api"; // ============================================================================= // Section Header Component // ============================================================================= function SectionHeader({ children }: { children: React.ReactNode }) { return (

{children}

); } // ============================================================================= // Alert Component // ============================================================================= function ErrorAlert({ children }: { children: React.ReactNode }) { return (
{children}
); } // ============================================================================= // Reauth Modal Component // ============================================================================= type ReauthState = | { phase: "initiating" } | { phase: "url_ready"; loginUrl: string; requestId: string } | { phase: "submitting"; requestId: string } | { phase: "success" } | { phase: "error"; message: string }; function ReauthModal({ daemon, onClose, }: { daemon: Daemon; onClose: () => void; }) { const [state, setState] = useState({ phase: "initiating" }); const [authCode, setAuthCode] = useState(""); const pollingRef = useRef | null>(null); // Cleanup polling on unmount useEffect(() => { return () => { if (pollingRef.current) { clearInterval(pollingRef.current); } }; }, []); // Start polling for status updates (URL ready, then completion) const startPolling = useCallback( (requestId: string) => { if (pollingRef.current) { clearInterval(pollingRef.current); } pollingRef.current = setInterval(async () => { try { const status = await getDaemonReauthStatus(daemon.id, requestId); if (status.status === "completed") { setState({ phase: "success" }); if (pollingRef.current) { clearInterval(pollingRef.current); pollingRef.current = null; } } else if (status.status === "url_ready" && status.loginUrl) { setState((prev) => { // Only update if we haven't already shown the URL if (prev.phase === "initiating") { return { phase: "url_ready", loginUrl: status.loginUrl!, requestId, }; } return prev; }); // Keep polling for completion - don't stop here } else if (status.status === "failed") { setState({ phase: "error", message: status.error || "Reauth failed", }); if (pollingRef.current) { clearInterval(pollingRef.current); pollingRef.current = null; } } } catch { // Polling errors are non-fatal, keep trying } }, 2000); }, [daemon.id], ); // Trigger reauth on mount useEffect(() => { let cancelled = false; const trigger = async () => { try { const res = await triggerDaemonReauth(daemon.id); if (cancelled) return; startPolling(res.requestId); } catch (err) { if (cancelled) return; setState({ phase: "error", message: err instanceof Error ? err.message : "Failed to trigger reauth", }); } }; trigger(); return () => { cancelled = true; }; }, [daemon.id, startPolling]); const handleSubmitCode = useCallback( async (e: React.FormEvent) => { e.preventDefault(); if (!authCode.trim() || state.phase !== "url_ready") return; const requestId = state.requestId; setState({ phase: "submitting", requestId }); try { await submitDaemonAuthCode(daemon.id, authCode.trim(), requestId); // Poll for completion pollingRef.current = setInterval(async () => { try { const status = await getDaemonReauthStatus( daemon.id, requestId, ); if (status.status === "completed") { setState({ phase: "success" }); if (pollingRef.current) { clearInterval(pollingRef.current); pollingRef.current = null; } } else if (status.status === "failed") { setState({ phase: "error", message: status.error || "Auth code submission failed", }); if (pollingRef.current) { clearInterval(pollingRef.current); pollingRef.current = null; } } } catch { // Keep polling } }, 2000); // Also set a timeout so we don't poll forever setTimeout(() => { if (pollingRef.current) { clearInterval(pollingRef.current); pollingRef.current = null; } // If still submitting after 30s, assume success (setup-token completed) setState((prev) => prev.phase === "submitting" ? { phase: "success" } : prev, ); }, 30000); } catch (err) { setState({ phase: "error", message: err instanceof Error ? err.message : "Failed to submit auth code", }); } }, [authCode, daemon.id, state], ); const handleRetry = useCallback(() => { setAuthCode(""); setState({ phase: "initiating" }); const trigger = async () => { try { const res = await triggerDaemonReauth(daemon.id); startPolling(res.requestId); } catch (err) { setState({ phase: "error", message: err instanceof Error ? err.message : "Failed to trigger reauth", }); } }; trigger(); }, [daemon.id, startPolling]); return (
{/* Header */}

Reauthorize Daemon

{daemon.hostname || "Unknown Host"}

{/* Initiating */} {state.phase === "initiating" && (
Initiating reauthorization...
)} {/* URL Ready */} {state.phase === "url_ready" && (

Click the button below to open the OAuth login page, then paste the code:

1. Login to Claude
setAuthCode(e.target.value)} placeholder="2. Paste authentication code" className="flex-1 bg-[#0a1525] border border-amber-500/30 px-3 py-2 text-xs font-mono text-amber-100 placeholder-amber-500/50 focus:outline-none focus:border-amber-400" />
)} {/* Submitting */} {state.phase === "submitting" && (
Submitting auth code...
)} {/* Success */} {state.phase === "success" && (
Authentication successful

The daemon's OAuth token has been refreshed. Tasks can now run normally.

)} {/* Error */} {state.phase === "error" && (
{state.message}
)}
); } // ============================================================================= // Daemons Page // ============================================================================= export default function DaemonsPage() { const { isAuthenticated, isAuthConfigured } = useAuth(); const navigate = useNavigate(); // Daemon state const [daemons, setDaemons] = useState([]); const [daemonsLoading, setDaemonsLoading] = useState(true); const [daemonsError, setDaemonsError] = useState(null); const [restartingDaemonId, setRestartingDaemonId] = useState(null); const [restartConfirmDaemonId, setRestartConfirmDaemonId] = useState(null); const [reauthDaemon, setReauthDaemon] = useState(null); // Redirect if not authenticated useEffect(() => { if (isAuthConfigured && !isAuthenticated) { navigate("/login"); } }, [isAuthConfigured, isAuthenticated, navigate]); const loadDaemons = async () => { try { setDaemonsError(null); const response: DaemonListResponse = await listDaemons(); setDaemons(response.daemons); } catch (err) { setDaemonsError(err instanceof Error ? err.message : "Failed to load daemons"); } finally { setDaemonsLoading(false); } }; const handleRestartDaemon = async (id: string) => { try { setRestartingDaemonId(id); setDaemonsError(null); await restartDaemon(id); // Daemon will restart, so refresh the list after a short delay setTimeout(() => { loadDaemons(); }, 2000); } catch (err) { setDaemonsError(err instanceof Error ? err.message : "Failed to restart daemon"); } finally { setRestartingDaemonId(null); setRestartConfirmDaemonId(null); } }; // Static platform data for download links const downloadPlatforms = [ { key: "linux-x86_64", label: "Linux (Intel/AMD)", filename: "makima-vX.X.X-linux-x86_64.tar.gz" }, { key: "linux-arm64", label: "Linux (ARM64)", filename: "makima-vX.X.X-linux-arm64.tar.gz" }, { key: "macos-x86_64", label: "macOS (Intel)", filename: "makima-vX.X.X-macos-x86_64.tar.gz" }, { key: "macos-arm64", label: "macOS (Apple Silicon)", filename: "makima-vX.X.X-macos-arm64.tar.gz" }, ]; // Initial load useEffect(() => { loadDaemons(); }, []); // Auto-refresh daemons every 30 seconds useEffect(() => { const interval = setInterval(() => { loadDaemons(); }, 30000); return () => clearInterval(interval); }, []); return (
{/* Page Header */}

Daemons

Daemons are worker processes that connect to Makima and execute tasks on your machines.

{/* Left Column */}
{/* Download Section */}
Download Daemon

Download the pre-compiled daemon binary for your platform. The daemon connects to the Makima server and executes tasks.

{downloadPlatforms.map((p) => ( {p.label} {p.filename} ))}

Quick Install

curl -fsSL https://raw.githubusercontent.com/soryu-co/makima/master/install.sh | bash
{/* Daemon Setup */}
Daemon Setup

Set your API key as an environment variable:

export MAKIMA_API_KEY="your-key"

Then run: makima-daemon

{/* Kubernetes Section */}
Run in Kubernetes

Deploy daemons as containers in Kubernetes for scalable task execution.

Pull Container Image

docker pull ghcr.io/soryu-co/makima-daemon:latest

Environment Variables

MAKIMA_API_KEY="your-key"
MAKIMA_SERVER_URL="https://your-server"
GITHUB_TOKEN="ghp_..." # optional, for repo access

Kubernetes manifests available at{" "} github.com/soryu-co/makima

{/* Right Column */}
{/* Connected Daemons */}

Connected Daemons

{daemons.length > 0 && ( ({daemons.filter(d => d.status === "connected").length} connected / {daemons.length} total) )}
{daemonsError && {daemonsError}} {daemonsLoading && daemons.length === 0 ? (

Loading...

) : daemons.length === 0 ? (

No daemons connected

Start a daemon to enable task execution

) : (
{daemons.map((daemon) => (
{daemon.hostname || "Unknown Host"}
{daemon.status}
Tasks {daemon.currentTaskCount} / {daemon.maxConcurrentTasks}
Connected {new Date(daemon.connectedAt).toLocaleString()}
{daemon.machineId && (
Machine {daemon.machineId.substring(0, 16)}...
)}
{/* Actions Section */} {daemon.status === "connected" && (
{restartConfirmDaemonId === daemon.id ? (
Restart daemon? Running tasks will be interrupted.
) : (
)}
)}
))}
)}
{/* Reauth Modal */} {reauthDaemon && ( setReauthDaemon(null)} /> )}
); }