Add sync-to-DB and delete-from-DB actions for skills

- POST /api/admin/skills/{slug}/sync — read SKILL.md from disk, insert/update DB
- DELETE /api/admin/skills/{slug} — remove skill from DB (keeps disk files)
- UI: Sync/Re-sync and Delete buttons per skill in the skills list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-08 17:52:00 +00:00
parent 2d265d2f0e
commit d8e888ad6a
2 changed files with 130 additions and 0 deletions

View File

@@ -1293,6 +1293,16 @@ async function loadSkillList() {
const updatedStr = s.updated_at ? new Date(s.updated_at).toLocaleDateString('he-IL') : '—';
const inDb = s.db_markdown_chars > 0;
const onDisk = s.disk_exists;
// Action buttons
const actions = [];
if (onDisk && !inDb) {
actions.push(`<button class="btn btn-sm btn-primary" onclick="syncSkill('${esc(s.slug)}')">Sync to DB</button>`);
} else if (onDisk && inDb) {
actions.push(`<button class="btn btn-sm btn-secondary" onclick="syncSkill('${esc(s.slug)}')">Re-sync</button>`);
}
if (inDb) {
actions.push(`<button class="btn btn-sm btn-outline" style="color:#e94560;border-color:#e94560" onclick="deleteSkill('${esc(s.slug)}')">Delete from DB</button>`);
}
return `
<div class="skill-item">
<span class="skill-icon">&#128268;</span>
@@ -1309,6 +1319,7 @@ async function loadSkillList() {
${inDb ? '<span class="badge-ok">DB</span>' : '<span class="badge-warn">No DB</span>'}
${onDisk ? '<span class="badge-ok">Disk</span>' : '<span class="badge-warn">No Disk</span>'}
</div>
<div style="display:flex;gap:6px;flex-shrink:0">${actions.join('')}</div>
</div>`;
}).join('');
} catch (e) {
@@ -1376,6 +1387,37 @@ async function installSkill(file) {
}
}
async function syncSkill(slug) {
try {
const res = await fetch(API + '/admin/skills/' + encodeURIComponent(slug) + '/sync', { method: 'POST' });
const data = await res.json();
if (!res.ok) {
toast(data.detail || 'Sync failed', 'error');
return;
}
toast(`${data.action}: ${slug} (${data.markdown_chars.toLocaleString()} chars, ${data.file_inventory.length} files)`, 'success');
loadSkillList();
} catch (e) {
toast('Network error', 'error');
}
}
async function deleteSkill(slug) {
if (!confirm(`Delete "${slug}" from DB? Files on disk will NOT be removed.`)) return;
try {
const res = await fetch(API + '/admin/skills/' + encodeURIComponent(slug), { method: 'DELETE' });
const data = await res.json();
if (!res.ok) {
toast(data.detail || 'Delete failed', 'error');
return;
}
toast(`Deleted: ${slug}`, 'success');
loadSkillList();
} catch (e) {
toast('Network error', 'error');
}
}
function confirmRestart() {
if (!confirm('Restart Paperclip?')) return;
restartPaperclip();