forked from farhoodlabs/paperclip
Add DELETE endpoint for company skills and fix skills.sh URL resolution
- Add DELETE /api/companies/:companyId/skills/:skillId endpoint with same permission model as other skill mutations. Deleting a skill removes it from the DB, cleans up materialized runtime files, and automatically strips it from any agent desiredSkills that reference it. - Fix parseSkillImportSourceInput to detect skills.sh URLs (e.g. https://skills.sh/org/repo/skill) and resolve them to the underlying GitHub repo + skill slug, instead of fetching the HTML page. - Add tests for skills.sh URL resolution with and without skill slug. Co-Authored-By: Paperclip <noreply@paperclip.ing> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import { fileURLToPath } from "node:url";
|
||||
import { and, asc, eq } from "drizzle-orm";
|
||||
import type { Db } from "@paperclipai/db";
|
||||
import { companySkills } from "@paperclipai/db";
|
||||
import { readPaperclipSkillSyncPreference } from "@paperclipai/adapter-utils/server-utils";
|
||||
import { readPaperclipSkillSyncPreference, writePaperclipSkillSyncPreference } from "@paperclipai/adapter-utils/server-utils";
|
||||
import type { PaperclipSkillEntry } from "@paperclipai/adapter-utils/server-utils";
|
||||
import type {
|
||||
CompanySkill,
|
||||
@@ -578,6 +578,17 @@ export function parseSkillImportSourceInput(rawInput: string): ParsedSkillImport
|
||||
};
|
||||
}
|
||||
|
||||
// Detect skills.sh URLs and resolve to GitHub: https://skills.sh/org/repo/skill → org/repo/skill key
|
||||
const skillsShMatch = normalizedSource.match(/^https?:\/\/(?:www\.)?skills\.sh\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)(?:\/([A-Za-z0-9_.-]+))?(?:[?#].*)?$/i);
|
||||
if (skillsShMatch) {
|
||||
const [, owner, repo, skillSlugRaw] = skillsShMatch;
|
||||
return {
|
||||
resolvedSource: `https://github.com/${owner}/${repo}`,
|
||||
requestedSkillSlug: skillSlugRaw ? normalizeSkillSlug(skillSlugRaw) : requestedSkillSlug,
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
resolvedSource: normalizedSource,
|
||||
requestedSkillSlug,
|
||||
@@ -2195,6 +2206,48 @@ export function companySkillService(db: Db) {
|
||||
return { imported, warnings };
|
||||
}
|
||||
|
||||
async function deleteSkill(companyId: string, skillId: string): Promise<CompanySkill | null> {
|
||||
const row = await db
|
||||
.select()
|
||||
.from(companySkills)
|
||||
.where(and(eq(companySkills.id, skillId), eq(companySkills.companyId, companyId)))
|
||||
.then((rows) => rows[0] ?? null);
|
||||
if (!row) return null;
|
||||
|
||||
const skill = toCompanySkill(row);
|
||||
|
||||
// Remove from any agent desiredSkills that reference this skill
|
||||
const agentRows = await agents.list(companyId);
|
||||
const allSkills = await listFull(companyId);
|
||||
for (const agent of agentRows) {
|
||||
const config = agent.adapterConfig as Record<string, unknown>;
|
||||
const preference = readPaperclipSkillSyncPreference(config);
|
||||
const referencesSkill = preference.desiredSkills.some((ref) => {
|
||||
const resolved = resolveSkillReference(allSkills, ref);
|
||||
return resolved.skill?.id === skillId;
|
||||
});
|
||||
if (referencesSkill) {
|
||||
const filtered = preference.desiredSkills.filter((ref) => {
|
||||
const resolved = resolveSkillReference(allSkills, ref);
|
||||
return resolved.skill?.id !== skillId;
|
||||
});
|
||||
await agents.update(agent.id, {
|
||||
adapterConfig: writePaperclipSkillSyncPreference(config, filtered),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Delete DB row
|
||||
await db
|
||||
.delete(companySkills)
|
||||
.where(eq(companySkills.id, skillId));
|
||||
|
||||
// Clean up materialized runtime files
|
||||
await fs.rm(resolveRuntimeSkillMaterializedPath(companyId, skill), { recursive: true, force: true });
|
||||
|
||||
return skill;
|
||||
}
|
||||
|
||||
return {
|
||||
list,
|
||||
listFull,
|
||||
@@ -2209,6 +2262,7 @@ export function companySkillService(db: Db) {
|
||||
readFile,
|
||||
updateFile,
|
||||
createLocalSkill,
|
||||
deleteSkill,
|
||||
importFromSource,
|
||||
scanProjectWorkspaces,
|
||||
importPackageFiles,
|
||||
|
||||
Reference in New Issue
Block a user