/* Claude — the management page for the Claude Code install agents run on: the account * login (moved here from the old Claude Login page), plus the operator-managed skills, * MCP connectors, plugins, and permission overrides. Everything except the login is a * plain DB write that the control container applies at the NEXT launch of every agent — * skills sync to the workers' user-level ~/.claude/skills, connectors become each run's * --mcp-config file, and plugins/permissions fold into the generated settings.json. */ "use client"; import { useState } from "react"; import { useDashboard } from "@/components/store"; import type { ConnectorBody, PluginBody, SkillBody } from "@/components/store"; import { Badge, Button, Card, Input, Select, Tabs, Textarea, Toggle } from "@/components/ui"; import type { ClaudeConnector, ClaudePlugin, ClaudeSkill } from "@/lib/api"; import { ClaudeLoginPanel } from "@/components/sections/LoginSection"; /* KEY=VALUE-per-line <-> map helpers for connector env/headers. */ function parseKeyValues(text: string): Record { const out: Record = {}; for (const line of text.split("\n")) { const trimmed = line.trim(); if (!trimmed) continue; const eq = trimmed.indexOf("="); if (eq <= 0) continue; out[trimmed.slice(0, eq).trim()] = trimmed.slice(eq + 1).trim(); } return out; } function formatKeyValues(map: Record | null | undefined): string { return Object.entries(map ?? {}) .map(([k, v]) => `${k}=${v}`) .join("\n"); } function parseLines(text: string): string[] { return text .split("\n") .map((l) => l.trim()) .filter(Boolean); } /* ---- Skills ------------------------------------------------------------------------ */ const emptySkill = { name: "", description: "", content: "", enabled: true }; function SkillsPanel() { const s = useDashboard(); const [form, setForm] = useState(emptySkill); const [editingId, setEditingId] = useState(null); const reset = () => { setForm(emptySkill); setEditingId(null); }; const save = async () => { const body: SkillBody = { ...form }; const ok = editingId != null ? await s.updateClaudeSkill(editingId, body) : await s.createClaudeSkill(body); if (ok) reset(); }; const edit = (sk: ClaudeSkill) => { setForm({ name: sk.name, description: sk.description ?? "", content: sk.content, enabled: sk.enabled, }); setEditingId(sk.id); }; return ( <>
Custom Claude Code skills, synced to every worker's{" "} ~/.claude/skills at each launch. The description is what makes Claude pick the skill up — say when to use it.
{editingId != null ? `Edit skill · ${form.name}` : "Add a skill"}
setForm({ ...form, name: v })} placeholder="deploy-checklist" /> setForm({ ...form, description: v })} placeholder="Use when preparing or reviewing a deploy." />