Files
claw3d/src/app/api/gateway/skills/remove/route.ts
T
Luke The Dev 4fa4f13558 First Release of Claw3D (#11)
Co-authored-by: iamlukethedev <iamlukethedev@users.noreply.github.com>
2026-03-19 23:14:04 -05:00

90 lines
3.1 KiB
TypeScript

import { NextResponse } from "next/server";
import { isLocalGatewayUrl } from "@/lib/gateway/local-gateway";
import { removeSkillLocally } from "@/lib/skills/remove-local";
import type { RemovableSkillSource, SkillRemoveRequest } from "@/lib/skills/types";
import {
resolveConfiguredSshTarget,
resolveGatewaySshTargetFromGatewayUrl,
} from "@/lib/ssh/gateway-host";
import { removeSkillOverSsh } from "@/lib/ssh/skills-remove";
import { loadStudioSettings } from "@/lib/studio/settings-store";
export const runtime = "nodejs";
const REMOVABLE_SOURCES = new Set<RemovableSkillSource>([
"openclaw-managed",
"openclaw-workspace",
]);
const normalizeRequired = (value: unknown, field: string): string => {
if (typeof value !== "string") {
throw new Error(`${field} is required.`);
}
const trimmed = value.trim();
if (!trimmed) {
throw new Error(`${field} is required.`);
}
return trimmed;
};
const resolveSkillRemovalSshTarget = (): string | null => {
const configured = resolveConfiguredSshTarget(process.env);
if (configured) return configured;
const settings = loadStudioSettings();
const gatewayUrl = settings.gateway?.url ?? "";
if (isLocalGatewayUrl(gatewayUrl)) return null;
return resolveGatewaySshTargetFromGatewayUrl(gatewayUrl, process.env);
};
const normalizeRemoveRequest = (body: unknown): SkillRemoveRequest => {
if (!body || typeof body !== "object") {
throw new Error("Invalid request payload.");
}
const record = body as Partial<Record<keyof SkillRemoveRequest, unknown>>;
const sourceRaw = normalizeRequired(record.source, "source");
if (!REMOVABLE_SOURCES.has(sourceRaw as RemovableSkillSource)) {
throw new Error(`Unsupported skill source for removal: ${sourceRaw}`);
}
return {
skillKey: normalizeRequired(record.skillKey, "skillKey"),
source: sourceRaw as RemovableSkillSource,
baseDir: normalizeRequired(record.baseDir, "baseDir"),
workspaceDir: normalizeRequired(record.workspaceDir, "workspaceDir"),
managedSkillsDir: normalizeRequired(record.managedSkillsDir, "managedSkillsDir"),
};
};
export async function POST(request: Request) {
try {
const body = (await request.json()) as unknown;
const removeRequest = normalizeRemoveRequest(body);
const sshTarget = resolveSkillRemovalSshTarget();
const result = sshTarget
? removeSkillOverSsh({ sshTarget, request: removeRequest })
: removeSkillLocally(removeRequest);
return NextResponse.json({ result });
} catch (err) {
const message = err instanceof Error ? err.message : "Failed to remove skill.";
const status =
message.includes("required") ||
message.includes("Invalid request payload") ||
message.includes("Unsupported skill source") ||
message.includes("Refusing to remove") ||
message.includes("not a directory") ||
message.includes("Gateway URL is missing") ||
message.includes("Invalid gateway URL") ||
message.includes("require OPENCLAW_GATEWAY_SSH_TARGET")
? 400
: 500;
if (status >= 500) {
console.error(message);
}
return NextResponse.json({ error: message }, { status });
}
}