feat: workflow recipes, eval badges, one-click MCP, playground upgrades, sample gallery, skill-of-the-week

Signature features that turn breadth (174 skills) into a differentiated product:

- Workflow recipes: 5 cross-profession chains (workflows.json) that pass each
  output forward — slash commands (/ship-a-feature etc.), WORKFLOWS.md generated
  by scripts/build-workflows.mjs, README + MCP (list_workflows/get_workflow) wired
- Eval-backed quality: real per-skill scores from evals/results.json surfaced as
  badges in the playground and an honest README section (6 scored skills)
- One-click MCP: 'claude mcp add' install + workflow tools, works in any MCP client
- Playground: 'which skill?' recommender, with/without compare toggle, shareable
  ?skill= deep-links with prefilled inputs
- Sample-output gallery: hand-written examples for the hero five + generator
  (scripts/build-samples.mjs) + web/examples.html
- Skill-of-the-week: scheduled workflow + script that composes X/LinkedIn posts
  and posts to an optional webhook

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Mohit
2026-06-19 09:56:11 +01:00
parent 7f06f0a993
commit 54f76456ab
30 changed files with 1168 additions and 67 deletions
+192 -58
View File
@@ -15,6 +15,11 @@ const TIER_META = {
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() {
@@ -46,6 +51,10 @@ async function init() {
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();
@@ -64,6 +73,84 @@ async function init() {
sel.appendChild(o);
}
renderGallery();
applyShareLink(); // open a skill (and prefill inputs) if the URL points to one
}
// ---------- Recommender: rank skills by a free-text task description ----------
function scoreSkill(s, terms) {
const name = s.name.toLowerCase();
const desc = (s.description || '').toLowerCase();
const hay = (name + ' ' + desc + ' ' + (s.instructions || '')).toLowerCase();
let score = 0;
for (const t of terms) {
if (name.includes(t)) score += 5;
if (desc.includes(t)) score += 3;
else if (hay.includes(t)) score += 1;
}
return score;
}
function renderRecommendations() {
const box = el('recommendResults');
const q = el('recommendInput').value.toLowerCase().trim();
if (q.length < 3) { box.hidden = true; box.innerHTML = ''; return; }
const terms = [...new Set(q.split(/\s+/).filter((t) => t.length > 2))];
const ranked = SKILLS.map((s) => ({ s, score: scoreSkill(s, terms) }))
.filter((x) => x.score > 0)
.sort((a, b) => b.score - a.score || (b.s.eval?.score || 0) - (a.s.eval?.score || 0))
.slice(0, 4);
box.innerHTML = '';
if (!ranked.length) { box.hidden = false; box.innerHTML = '<span class="recommend-empty">No close match — try the search below.</span>'; return; }
const lead = document.createElement('span');
lead.className = 'recommend-lead';
lead.textContent = 'Top matches:';
box.appendChild(lead);
for (const { s } of ranked) {
const chip = document.createElement('button');
chip.className = 'recommend-chip';
chip.type = 'button';
chip.textContent = s.title + (s.eval ? `${s.eval.score}/5` : '');
chip.addEventListener('click', () => { el('recommendInput').value = ''; box.hidden = true; selectSkill(s); });
box.appendChild(chip);
}
box.hidden = false;
}
// ---------- Shareable links: ?skill=<name>&i=<base64 inputs> ----------
function shareSkill() {
if (!current) return;
const fields = [...el('inputForm').querySelectorAll('input, textarea')];
const values = fields.map((f) => f.value);
const url = new URL(location.href.split('?')[0]);
url.searchParams.set('skill', current.name);
if (values.some((v) => v.trim())) {
try {
const packed = btoa(unescape(encodeURIComponent(JSON.stringify(values))));
if (packed.length < 1800) url.searchParams.set('i', packed); // keep links sane
} catch (_) { /* skip inputs if they don't encode */ }
}
const link = url.toString();
navigator.clipboard.writeText(link).then(
() => { el('shareMsg').textContent = 'Link copied — anyone who opens it lands on this skill, prefilled.'; },
() => { el('shareMsg').textContent = link; }
);
}
function applyShareLink() {
const params = new URLSearchParams(location.search);
const name = params.get('skill');
if (!name) return;
const s = SKILLS.find((x) => x.name === name);
if (!s) return;
selectSkill(s);
const packed = params.get('i');
if (packed) {
try {
const values = JSON.parse(decodeURIComponent(escape(atob(packed))));
const fields = [...el('inputForm').querySelectorAll('input, textarea')];
fields.forEach((f, i) => { if (values[i] != null) f.value = values[i]; });
} catch (_) { /* ignore malformed input payloads */ }
}
}
// ---------- Gallery (tiles) ----------
@@ -94,9 +181,16 @@ function renderGallery() {
const card = document.createElement('button');
card.className = 'skill-card';
card.innerHTML =
`<div class="card-tags"><span class="card-bundle"></span><span class="card-tier"></span></div>` +
`<div class="card-tags"><span class="card-bundle"></span><span class="card-eval"></span><span class="card-tier"></span></div>` +
`<h3 class="card-title"></h3><p class="card-summary"></p>`;
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);
@@ -127,12 +221,24 @@ function selectSkill(s) {
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 });
@@ -160,6 +266,58 @@ function makeField(inp, i) {
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();
@@ -171,74 +329,50 @@ async function run() {
if (missing.length) return setStatus(`Fill in: ${missing.map((f) => f.dataset.label).join(', ')}`, true);
const userMessage = buildUserMessage(fields);
const system = current.instructions +
'\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.';
const system = current.instructions + SKILL_SUFFIX;
const model = el('model').value;
const compare = el('compareToggle').checked;
const out = el('output');
out.innerHTML = '';
out.dataset.raw = '';
el('outputWrap').hidden = false;
el('runBtn').disabled = true;
el('stopBtn').hidden = false;
setStatus('Running…');
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 =
'<div class="compare-pane"><div class="compare-label">✨ With the skill</div><article class="output markdown" id="paneWith"></article></div>' +
'<div class="compare-pane"><div class="compare-label">📄 Plain prompt (no skill)</div><article class="output markdown" id="panePlain"></article></div>';
withNode = el('paneWith');
plainNode = el('panePlain');
} else {
grid.hidden = true;
out.hidden = false;
out.innerHTML = '';
out.dataset.raw = '';
}
let acc = '';
try {
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: el('model').value,
max_tokens: 8192,
stream: true,
system,
messages: [{ role: 'user', content: userMessage }],
}),
signal: controller.signal,
});
if (!res.ok) throw new Error(parseApiError(await res.text(), res.status));
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
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; // skip an unparseable / partial SSE line
}
if (evt.type === 'content_block_delta' && evt.delta && evt.delta.text) {
acc += evt.delta.text;
renderMarkdown(out, acc, true);
} else if (evt.type === 'error') {
throw new Error(evt.error ? evt.error.message : 'Stream error from the API.');
}
}
setStatus(compare ? 'Running both…' : 'Running…');
if (compare) {
// Plain = the same inputs with no skill system prompt.
[acc] = await Promise.all([
streamCompletion({ key, model, system, userMessage, node: withNode, signal: controller.signal }),
streamCompletion({ key, model, system: '', userMessage, node: plainNode, signal: controller.signal }),
]);
} else {
acc = await streamCompletion({ key, model, system, userMessage, node: out, signal: controller.signal });
}
renderMarkdown(out, acc, false);
out.dataset.raw = acc;
out.dataset.raw = acc; // copy/download use the skill output, in either mode
setStatus('Done.');
} catch (e) {
if (e.name === 'AbortError') {
out.dataset.raw = acc;
renderMarkdown(out, acc, false);
setStatus('Stopped.');
} else {
setStatus(e.message || 'Request failed.', true);