import { DEFAULT_AGENT_WORKSPACE_DIR } from "../../agents/workspace.js"; import { installSkill } from "../../agents/skills-install.js"; import { buildWorkspaceSkillStatus } from "../../agents/skills-status.js"; import type { ClawdisConfig } from "../../config/config.js"; import { loadConfig, writeConfigFile } from "../../config/config.js"; import { resolveUserPath } from "../../utils.js"; import { ErrorCodes, errorShape, formatValidationErrors, validateSkillsInstallParams, validateSkillsStatusParams, validateSkillsUpdateParams, } from "../protocol/index.js"; import type { GatewayRequestHandlers } from "./types.js"; export const skillsHandlers: GatewayRequestHandlers = { "skills.status": ({ params, respond }) => { if (!validateSkillsStatusParams(params)) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, `invalid skills.status params: ${formatValidationErrors(validateSkillsStatusParams.errors)}`, ), ); return; } const cfg = loadConfig(); const workspaceDirRaw = cfg.agent?.workspace ?? DEFAULT_AGENT_WORKSPACE_DIR; const workspaceDir = resolveUserPath(workspaceDirRaw); const report = buildWorkspaceSkillStatus(workspaceDir, { config: cfg, }); respond(true, report, undefined); }, "skills.install": async ({ params, respond }) => { if (!validateSkillsInstallParams(params)) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, `invalid skills.install params: ${formatValidationErrors(validateSkillsInstallParams.errors)}`, ), ); return; } const p = params as { name: string; installId: string; timeoutMs?: number; }; const cfg = loadConfig(); const workspaceDirRaw = cfg.agent?.workspace ?? DEFAULT_AGENT_WORKSPACE_DIR; const result = await installSkill({ workspaceDir: workspaceDirRaw, skillName: p.name, installId: p.installId, timeoutMs: p.timeoutMs, config: cfg, }); respond( result.ok, result, result.ok ? undefined : errorShape(ErrorCodes.UNAVAILABLE, result.message), ); }, "skills.update": async ({ params, respond }) => { if (!validateSkillsUpdateParams(params)) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, `invalid skills.update params: ${formatValidationErrors(validateSkillsUpdateParams.errors)}`, ), ); return; } const p = params as { skillKey: string; enabled?: boolean; apiKey?: string; env?: Record; }; const cfg = loadConfig(); const skills = cfg.skills ? { ...cfg.skills } : {}; const entries = skills.entries ? { ...skills.entries } : {}; const current = entries[p.skillKey] ? { ...entries[p.skillKey] } : {}; if (typeof p.enabled === "boolean") { current.enabled = p.enabled; } if (typeof p.apiKey === "string") { const trimmed = p.apiKey.trim(); if (trimmed) current.apiKey = trimmed; else delete current.apiKey; } if (p.env && typeof p.env === "object") { const nextEnv = current.env ? { ...current.env } : {}; for (const [key, value] of Object.entries(p.env)) { const trimmedKey = key.trim(); if (!trimmedKey) continue; const trimmedVal = value.trim(); if (!trimmedVal) delete nextEnv[trimmedKey]; else nextEnv[trimmedKey] = trimmedVal; } current.env = nextEnv; } entries[p.skillKey] = current; skills.entries = entries; const nextConfig: ClawdisConfig = { ...cfg, skills, }; await writeConfigFile(nextConfig); respond(true, { ok: true, skillKey: p.skillKey, config: current }, undefined); }, };