'use strict'; const KEY_STORE = 'anthropic_api_key'; const MODEL_STORE = 'anthropic_model'; const API_URL = 'https://api.anthropic.com/v1/messages'; const el = (id) => document.getElementById(id); let SKILLS = []; let current = null; let controller = null; const TIER_META = { production: { label: 'Production-Ready', cls: 'tier-production', dot: 'π’' }, stable: { label: 'Stable', cls: 'tier-stable', dot: 'π΅' }, experimental: { label: 'Experimental', cls: 'tier-experimental', dot: 'π‘' }, }; // Small "eval-verified" badge for skills scored by the eval harness. function evalBadgeText(s) { return s.eval ? `β ${s.eval.score}/5` : ''; } init(); async function init() { const savedKey = localStorage.getItem(KEY_STORE); if (savedKey) el('apiKey').value = savedKey; const savedModel = localStorage.getItem(MODEL_STORE); if (savedModel) el('model').value = savedModel; el('apiKey').addEventListener('input', (e) => localStorage.setItem(KEY_STORE, e.target.value.trim())); el('model').addEventListener('change', (e) => localStorage.setItem(MODEL_STORE, e.target.value)); el('keyToggle').addEventListener('click', () => { const f = el('apiKey'); const show = f.type === 'password'; f.type = show ? 'text' : 'password'; el('keyToggle').textContent = show ? 'Hide' : 'Show'; }); el('search').addEventListener('input', renderGallery); el('pluginFilter').addEventListener('change', renderGallery); el('tierFilter').addEventListener('change', renderGallery); el('backBtn').addEventListener('click', showGallery); el('runBtn').addEventListener('click', run); el('stopBtn').addEventListener('click', () => controller && controller.abort()); el('copyBtn').addEventListener('click', () => navigator.clipboard.writeText(el('output').dataset.raw || '')); el('downloadBtn').addEventListener('click', downloadOutput); // Copy the skill's instructions formatted for another assistant. el('copyChatgpt').addEventListener('click', () => copyPrompt('chatgpt')); el('copyGemini').addEventListener('click', () => copyPrompt('gemini')); el('copyClaude').addEventListener('click', () => copyPrompt('claude')); // "Which skill do I need?" recommender + shareable links. el('recommendInput').addEventListener('input', renderRecommendations); el('shareBtn').addEventListener('click', shareSkill); try { const res = await fetch('skills.json'); const data = await res.json(); SKILLS = data.skills; } catch (e) { el('gallery').innerHTML = '
Could not load skills.json. Run node web/build-skills.mjs and serve this folder over HTTP.
No skills match your search.
'; return; } const frag = document.createDocumentFragment(); for (const s of matches) { const meta = TIER_META[s.tier] || TIER_META.stable; const card = document.createElement('button'); card.className = 'skill-card'; card.innerHTML = `` + ``; card.querySelector('.card-bundle').textContent = s.plugin; const evalEl = card.querySelector('.card-eval'); if (s.eval) { evalEl.textContent = evalBadgeText(s); evalEl.title = `Eval-scored ${s.eval.score}/5 across ${s.eval.runs} model runs`; } else { evalEl.remove(); } const tierEl = card.querySelector('.card-tier'); tierEl.textContent = `${meta.dot} ${meta.label}`; tierEl.classList.add(meta.cls); card.querySelector('.card-title').textContent = s.title; card.querySelector('.card-summary').textContent = s.summary || s.description; card.addEventListener('click', () => selectSkill(s)); frag.appendChild(card); } gallery.appendChild(frag); } function showGallery() { current = null; el('runner').hidden = true; el('gallery').hidden = false; el('controls').hidden = false; window.scrollTo({ top: 0 }); } // ---------- Select & build form ---------- function selectSkill(s) { current = s; el('gallery').hidden = true; el('controls').hidden = true; el('runner').hidden = false; el('skillBundle').textContent = s.plugin; const meta = TIER_META[s.tier] || TIER_META.stable; const tierTag = el('skillTier'); tierTag.textContent = `${meta.dot} ${meta.label}`; tierTag.className = 'tier-tag ' + meta.cls; const evalTag = el('skillEval'); if (s.eval) { evalTag.textContent = `β Eval-scored ${s.eval.score}/5`; evalTag.title = `Scored ${s.eval.score}/5 by an LLM judge across ${s.eval.runs} model runs`; evalTag.hidden = false; } else { evalTag.hidden = true; } el('skillTitle').textContent = s.title; el('skillDesc').textContent = s.description; el('elsewhere').open = false; el('copyMsg').textContent = ''; el('shareMsg').textContent = ''; el('outputWrap').hidden = true; el('output').innerHTML = ''; el('output').hidden = false; el('compareGrid').hidden = true; el('compareGrid').innerHTML = ''; setStatus(''); window.scrollTo({ top: 0 }); const form = el('inputForm'); form.innerHTML = ''; if (!s.inputs.length) { form.appendChild(makeField({ label: 'Your input / context', hint: 'Describe what you need. This skill did not declare structured inputs.', long: true }, 0)); } else { s.inputs.forEach((inp, i) => form.appendChild(makeField(inp, i))); } } function makeField(inp, i) { const wrap = document.createElement('div'); wrap.className = 'field'; const id = 'f_' + i; const opt = inp.optional ? ' (optional)' : ''; const hint = inp.hint ? ` β ${escapeHtml(inp.hint)}` : ''; wrap.innerHTML = ``; const input = inp.long ? document.createElement('textarea') : document.createElement('input'); input.id = id; input.dataset.label = inp.label; input.dataset.optional = inp.optional ? '1' : ''; wrap.appendChild(input); return wrap; } // Stream one completion from the API into a target node. Returns the full text. async function streamCompletion({ key, model, system, userMessage, node, signal }) { const res = await fetch(API_URL, { method: 'POST', headers: { 'content-type': 'application/json', 'x-api-key': key, 'anthropic-version': '2023-06-01', 'anthropic-dangerous-direct-browser-access': 'true', }, body: JSON.stringify({ model, max_tokens: 8192, stream: true, ...(system ? { system } : {}), messages: [{ role: 'user', content: userMessage }], }), signal, }); if (!res.ok) throw new Error(parseApiError(await res.text(), res.status)); const reader = res.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; let acc = ''; while (true) { const { value, done } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop(); for (const line of lines) { if (!line.startsWith('data:')) continue; const payload = line.slice(5).trim(); if (!payload || payload === '[DONE]') continue; let evt; try { evt = JSON.parse(payload); } catch (_) { continue; } if (evt.type === 'content_block_delta' && evt.delta && evt.delta.text) { acc += evt.delta.text; renderMarkdown(node, acc, true); } else if (evt.type === 'error') { throw new Error(evt.error ? evt.error.message : 'Stream error from the API.'); } } } renderMarkdown(node, acc, false); return acc; } const SKILL_SUFFIX = '\n\n---\nThe user has provided their inputs below. Execute this skill now and produce the complete output. Do not ask follow-up questions β work with what is given and note any reasonable assumptions.'; // ---------- Run ---------- async function run() { const key = el('apiKey').value.trim(); if (!key) return setStatus('Enter your Claude API key first.', true); if (!current) return; const fields = [...el('inputForm').querySelectorAll('input, textarea')]; const missing = fields.filter((f) => !f.dataset.optional && !f.value.trim()); if (missing.length) return setStatus(`Fill in: ${missing.map((f) => f.dataset.label).join(', ')}`, true); const userMessage = buildUserMessage(fields); const system = current.instructions + SKILL_SUFFIX; const model = el('model').value; const compare = el('compareToggle').checked; el('outputWrap').hidden = false; el('runBtn').disabled = true; el('stopBtn').hidden = false; controller = new AbortController(); // Single mode β #output. Compare mode β two panes in #compareGrid. const out = el('output'); const grid = el('compareGrid'); let withNode, plainNode; if (compare) { out.hidden = true; grid.hidden = false; grid.innerHTML = '