import type { AdapterSkillContext, AdapterSkillSnapshot, AdapterSkillEntry, } from "@paperclipai/adapter-utils"; import { readPaperclipRuntimeSkillEntries, resolvePaperclipDesiredSkillNames, readInstalledSkillTargets, } from "@paperclipai/adapter-utils/server-utils"; import path from "node:path"; const SKILLS_HOME = "/paperclip/.claude/skills"; async function buildK8sSkillSnapshot( config: Record, ): Promise { const availableEntries = await readPaperclipRuntimeSkillEntries(config, import.meta.dirname ?? __dirname); const availableByKey = new Map(availableEntries.map((e) => [e.key, e])); const desiredSkills = resolvePaperclipDesiredSkillNames(config, availableEntries); const desiredSet = new Set(desiredSkills); const installed = await readInstalledSkillTargets(SKILLS_HOME); const entries: AdapterSkillEntry[] = availableEntries.map((entry) => ({ key: entry.key, runtimeName: entry.runtimeName, desired: desiredSet.has(entry.key), managed: true, state: desiredSet.has(entry.key) ? "configured" : "available", origin: entry.required ? "paperclip_required" : "company_managed", originLabel: entry.required ? "Required by Paperclip" : "Managed by Paperclip", readOnly: false, sourcePath: entry.source, targetPath: null, detail: desiredSet.has(entry.key) ? "Materialized into the PVC-backed Claude prompt bundle before each K8s Job run." : null, required: Boolean(entry.required), requiredReason: entry.requiredReason ?? null, })); const warnings: string[] = []; for (const desiredSkill of desiredSkills) { if (availableByKey.has(desiredSkill)) continue; warnings.push(`Desired skill "${desiredSkill}" is not available from the Paperclip skills directory.`); entries.push({ key: desiredSkill, runtimeName: null, desired: true, managed: true, state: "missing", origin: "external_unknown", originLabel: "External or unavailable", readOnly: false, sourcePath: undefined, targetPath: undefined, detail: "Paperclip cannot find this skill in the runtime skills directory.", }); } for (const [name, installedEntry] of installed.entries()) { if (availableEntries.some((e) => e.runtimeName === name)) continue; entries.push({ key: name, runtimeName: name, desired: false, managed: false, state: "external", origin: "user_installed", originLabel: "User-installed", locationLabel: "~/.claude/skills", readOnly: true, sourcePath: null, targetPath: installedEntry.targetPath ?? path.join(SKILLS_HOME, name), detail: "Installed outside Paperclip management in the Claude skills home.", }); } entries.sort((a, b) => a.key.localeCompare(b.key)); return { adapterType: "claude_k8s", supported: true, mode: "ephemeral", desiredSkills, entries, warnings, }; } export async function listK8sSkills(ctx: AdapterSkillContext): Promise { return buildK8sSkillSnapshot(ctx.config); } export async function syncK8sSkills( ctx: AdapterSkillContext, _desiredSkills: string[], ): Promise { return buildK8sSkillSnapshot(ctx.config); }