/* Repositories — the projects Handler manages. Adding one is now server-first: pick a * configured git server, type owner/name, and Handler derives the remote, decides where * the clone lives, and pulls it (stateless workflows don't care about disk paths). * Manual mode (an existing checkout) remains for everything else. */ "use client"; import { useMemo, useState } from "react"; import { useDashboard, type NewProjectBody } from "@/components/store"; import { Badge, Button, Card, Input, Select, Tabs } from "@/components/ui"; import { fmtFull } from "@/lib/format"; import type { Project } from "@/lib/api"; const CRED_HELP = "Optional override — projects on a configured git server use its stored token automatically. " + "credential_ref is a pointer, never the token (env: / file: / db:host:)."; const empty: NewProjectBody = { mode: "server", git_server: "", repo: "", id: "", root_dir: "", git_remote: "", credential_ref: "", init_mise: false, }; export function RepositoriesSection() { const s = useDashboard(); const [form, setForm] = useState(empty); const [editing, setEditing] = useState(false); const agentCount = useMemo(() => { const m = new Map(); for (const a of s.agents) m.set(a.project_id, (m.get(a.project_id) ?? 0) + 1); return m; }, [s.agents]); const serverOpts = useMemo( () => [ { value: "", label: s.hosts.length ? "Pick a git server…" : "No git servers configured" }, ...s.hosts.map((h) => ({ value: h.hostname, label: `${h.hostname} (${h.forge_type})` })), ], [s.hosts], ); const reset = () => { setForm(empty); setEditing(false); }; const save = async () => { const ok = editing ? await s.updateProject(form.id, form) : await s.createProject(form); if (ok) reset(); }; const edit = (p: Project) => { setForm({ ...empty, mode: "manual", id: p.id, root_dir: p.root_dir, git_remote: p.git_remote ?? "", credential_ref: p.credential_ref ?? "", }); setEditing(true); }; const canSave = editing ? !!form.root_dir.trim() : form.mode === "server" ? !!form.git_server && /^[\w.-]+\/[\w.-]+$/.test(form.repo.trim()) : !!form.id.trim() && !!form.root_dir.trim(); return ( <>
Repositories
Repos Handler manages. Each carries its own agents, history, and credentials.
{editing ? `Edit repository · ${form.id}` : "Add a repository"}
{!editing && (
setForm({ ...form, mode: v as "server" | "manual" })} />
)} {!editing && form.mode === "server" ? ( <>
setForm({ ...form, repo: v })} placeholder="me/coolproj" /> setForm({ ...form, id: v })} placeholder="coolproj" />

The repo is always pulled: Handler derives the remote from the server (ssh when it has a deploy key, https via the stored token otherwise), clones it under PROJECTS_ROOT, and keeps it fresh before every run.

) : ( <>
setForm({ ...form, id: v })} placeholder="leeworks-api" disabled={editing} /> setForm({ ...form, root_dir: v })} placeholder="/var/lib/handler/projects/leeworks" /> setForm({ ...form, git_remote: v })} placeholder="git@github.com:user/repo.git (optional)" /> setForm({ ...form, credential_ref: v })} placeholder="env:VAR / file:/path / db:host:github.com" />

{CRED_HELP}

)} {!editing && ( )} {!editing && form.init_mise && form.mode === "manual" && !form.git_remote.trim() && (

A git remote is required to push the new .mise.toml — add one above, or mise won’t be initialized.

)}
{editing && ( )}
{s.projects.length === 0 &&
No repositories registered.
} {s.projects.map((p) => (
{p.id} {agentCount.get(p.id) ?? 0} {(agentCount.get(p.id) ?? 0) === 1 ? "agent" : "agents"}
{p.root_dir} {p.git_remote ? ` · ${p.git_remote}` : ""}
cred {p.credential_ref || "server default"} · added {fmtFull(p.created_at)}
{p.git_remote && ( )}
))}
); }