import { useState, useEffect, useCallback } from "react";
import { useNavigate } from "react-router";
import { Masthead } from "../components/Masthead";
import { useAuth } from "../contexts/AuthContext";
import {
listDaemons,
restartDaemon,
type Daemon,
} from "../lib/api";
// =============================================================================
// Types
// =============================================================================
interface GitHubAsset {
name: string;
browser_download_url: string;
size: number;
}
interface GitHubRelease {
tag_name: string;
name: string;
published_at: string;
html_url: string;
assets: GitHubAsset[];
}
interface PlatformDownload {
label: string;
arch: string;
pattern: string;
asset: GitHubAsset | null;
recommended: boolean;
}
// =============================================================================
// Helpers
// =============================================================================
function detectPlatform(): string {
const ua = navigator.userAgent.toLowerCase();
const platform = navigator.platform?.toLowerCase() || "";
if (platform.includes("mac") || ua.includes("macintosh")) {
// Check for Apple Silicon
// navigator.platform is "MacIntel" even on ARM for some browsers,
// but we can check userAgent for hints or default to arm64 for modern Macs
if (
ua.includes("arm") ||
ua.includes("aarch64") ||
// Chrome 93+ on ARM Macs reports this
(typeof navigator !== "undefined" &&
"userAgentData" in navigator &&
// @ts-expect-error -- userAgentData may not be typed
navigator.userAgentData?.platform === "macOS")
) {
return "macos-arm64";
}
return "macos-arm64"; // Default to ARM64 for modern Macs
}
if (platform.includes("linux") || ua.includes("linux")) {
return "linux-x86_64";
}
return "linux-x86_64"; // fallback
}
function formatBytes(bytes: number): string {
if (bytes === 0) return "0 B";
const k = 1024;
const sizes = ["B", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
}
function formatDate(dateStr: string): string {
return new Date(dateStr).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
});
}
// =============================================================================
// Sub-components
// =============================================================================
function SectionHeader({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
function ErrorAlert({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
function CodeBlock({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
function StepNumber({ n }: { n: number }) {
return (
{n}
);
}
// =============================================================================
// Download Section
// =============================================================================
function DownloadSection() {
const [release, setRelease] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [userPlatform] = useState(detectPlatform);
useEffect(() => {
const fetchRelease = async () => {
try {
setLoading(true);
setError(null);
const res = await fetch(
"https://api.github.com/repos/soryu-co/makima/releases/latest"
);
if (!res.ok) {
throw new Error(`GitHub API returned ${res.status}`);
}
const data: GitHubRelease = await res.json();
setRelease(data);
} catch (err) {
setError(
err instanceof Error ? err.message : "Failed to fetch release info"
);
} finally {
setLoading(false);
}
};
fetchRelease();
}, []);
const platforms: PlatformDownload[] = [
{
label: "Linux x86_64",
arch: "linux-x86_64",
pattern: "linux-x86_64.tar.gz",
asset: null,
recommended: userPlatform === "linux-x86_64",
},
{
label: "macOS Intel (x86_64)",
arch: "macos-x86_64",
pattern: "macos-x86_64.tar.gz",
asset: null,
recommended: userPlatform === "macos-x86_64",
},
{
label: "macOS Apple Silicon (ARM64)",
arch: "macos-arm64",
pattern: "macos-arm64.tar.gz",
asset: null,
recommended: userPlatform === "macos-arm64",
},
];
// Match assets to platforms
if (release) {
for (const p of platforms) {
p.asset =
release.assets.find((a) => a.name.includes(p.pattern)) || null;
}
}
// Sort recommended first
const sortedPlatforms = [...platforms].sort(
(a, b) => (b.recommended ? 1 : 0) - (a.recommended ? 1 : 0)
);
return (
Download Daemon
{loading && (
Fetching latest release...
)}
{error && Failed to load release: {error} }
{release && (
<>
{sortedPlatforms.map((p) => (
{p.label}
{p.recommended && (
Detected
)}
{p.asset && (
{formatBytes(p.asset.size)}
)}
{p.asset ? (
Download
) : (
Not available
)}
))}
>
)}
);
}
// =============================================================================
// Setup Instructions Section
// =============================================================================
function SetupSection() {
const [showConfig, setShowConfig] = useState(false);
return (
Setup Instructions
Download the binary for your platform above
Extract the archive
tar xzf makima-*.tar.gz
Move to PATH
sudo mv makima /usr/local/bin/
Set server URL
export MAKIMA_DAEMON_SERVER_URL="ws://your-server:8080"
Run the daemon
makima daemon
{/* Config file alternative */}
setShowConfig(!showConfig)}
className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] transition-colors bg-transparent border-none cursor-pointer p-0"
>
{showConfig ? "- Hide" : "+"} Config file alternative
{showConfig && (
Create makima-daemon.toml{" "}
in the working directory:
{`[daemon]
api_key = "your-key"
server_url = "ws://your-server:8080"
max_concurrent_tasks = 4`}
)}
);
}
// =============================================================================
// Cloudflare Edge Deployment Section
// =============================================================================
function CloudflareAgentSection() {
const [showSetup, setShowSetup] = useState(false);
const benefits = [
{
label: "Global edge presence",
desc: "Lower latency from 300+ Cloudflare locations worldwide",
},
{
label: "Auto-scaling & hibernation",
desc: "Cost-efficient — only runs when needed",
},
{
label: "WebSocket relay",
desc: "Coordinate remote daemon instances through persistent connections",
},
{
label: "Durable Objects",
desc: "Built on Cloudflare's stateful edge compute primitives",
},
];
return (
Edge Deployment
Deploy a lightweight Makima relay agent on Cloudflare's edge network for
global, low-latency daemon coordination. Ideal for distributed teams or
production deployments requiring high availability.
{/* Benefits */}
{benefits.map((b) => (
))}
{/* Quick Setup */}
setShowSetup(!showSetup)}
className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] transition-colors bg-transparent border-none cursor-pointer p-0"
>
{showSetup ? "- Hide" : "+"} Quick setup
{showSetup && (
Navigate to the Cloudflare agent directory
cd makima/cloudflare-agent
Run the setup script
./setup.sh
Deploy to Cloudflare
npx wrangler deploy
)}
{/* Link to repo */}
);
}
// =============================================================================
// Connected Daemons Section
// =============================================================================
function ConnectedDaemonsSection() {
const [daemons, setDaemons] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [restartingDaemonId, setRestartingDaemonId] = useState(
null
);
const [restartConfirmDaemonId, setRestartConfirmDaemonId] = useState<
string | null
>(null);
const loadDaemons = useCallback(async () => {
try {
setError(null);
const response = await listDaemons();
setDaemons(response.daemons);
} catch (err) {
setError(
err instanceof Error ? err.message : "Failed to load daemons"
);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
loadDaemons();
}, [loadDaemons]);
// Auto-refresh every 30 seconds
useEffect(() => {
const interval = setInterval(() => {
loadDaemons();
}, 30000);
return () => clearInterval(interval);
}, [loadDaemons]);
const handleRestartDaemon = async (id: string) => {
try {
setRestartingDaemonId(id);
setError(null);
await restartDaemon(id);
setRestartConfirmDaemonId(null);
// Daemon will restart, so refresh the list after a short delay
setTimeout(() => {
loadDaemons();
}, 2000);
} catch (err) {
setError(
err instanceof Error ? err.message : "Failed to restart daemon"
);
} finally {
setRestartingDaemonId(null);
}
};
return (
Connected Daemons
{daemons.length > 0 && (
({daemons.filter((d) => d.status === "connected").length}{" "}
connected / {daemons.length} total)
)}
{loading ? "..." : "\u21BB"}
{error && {error} }
{loading && daemons.length === 0 ? (
Loading...
) : daemons.length === 0 ? (
No daemons connected
Follow the setup instructions above to connect a daemon
) : (
{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)}...
)}
{/* Restart Section */}
{daemon.status === "connected" && (
{restartConfirmDaemonId === daemon.id ? (
Restart daemon? Running tasks will be interrupted.
setRestartConfirmDaemonId(null)}
className="text-[10px] font-mono text-[#7788aa] hover:text-[#9bc3ff] px-2 py-1 bg-transparent border-none cursor-pointer"
disabled={restartingDaemonId === daemon.id}
>
Cancel
handleRestartDaemon(daemon.id)}
disabled={restartingDaemonId === daemon.id}
className="text-[10px] font-mono text-red-400 hover:text-red-300 px-2 py-1 border border-red-700/50 bg-red-900/20 disabled:opacity-50 cursor-pointer"
>
{restartingDaemonId === daemon.id
? "Restarting..."
: "Confirm"}
) : (
setRestartConfirmDaemonId(daemon.id)}
className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] bg-transparent border-none cursor-pointer p-0"
>
⟳ Restart Daemon
)}
)}
))}
)}
);
}
// =============================================================================
// Main Page
// =============================================================================
export default function DaemonPage() {
const {
isAuthenticated,
isAuthConfigured,
isLoading: authLoading,
} = useAuth();
const navigate = useNavigate();
useEffect(() => {
if (!authLoading && isAuthConfigured && !isAuthenticated) {
navigate("/login");
}
}, [authLoading, isAuthConfigured, isAuthenticated, navigate]);
if (authLoading) {
return (
);
}
return (
{/* Page header */}
Daemon Management
Download, configure, and monitor Makima daemons
{/* Left Column: Downloads & Setup */}
{/* Right Column: Edge Deployment & Connected Daemons */}
);
}