Files
pm-claude-skills/scripts/build-docs.mjs
T
mohitagw15856 e9bc1d0626 Security auditor, personas, orchestration, docs catalog & roadmap (#35)
Closes the remaining gaps vs alirezarezvani/claude-skills across trust, content
types, discoverability, and community.

Security (trust signal + useful):
- scripts/skill-audit.mjs scans skills/*/SKILL.md + each skill's scripts/ for
  prompt injection, exfiltration, dynamic code exec, destructive shell, secrets,
  and hidden text. HIGH fails CI (.github/workflows/skill-audit.yml) + a badge.
- New skill-security-auditor skill teaches the same review (production tier).

Content types:
- output-styles/ — 4 personas (Startup CTO, Growth Marketer, Solo Founder,
  Product Leader) as Claude Code output styles; --agent claude installs them too.
- ORCHESTRATION.md — Skill Chain / Multi-Agent Handoff / Domain Deep-Dive /
  Solo Sprint patterns.

Discoverability:
- scripts/build-docs.mjs generates a server-rendered, SEO-indexable
  web/catalog.html of all skills (built in the Pages deploy; gitignored).
  Linked from README + playground.

Community:
- ROADMAP.md (now/next/later + good-first-issues).

README badges/sections, TIERS (47 production), CHANGELOG, package.json files,
and exports/web index all updated. SkillCheck + security audit + exports verified.


Claude-Session: https://claude.ai/code/session_016JWn5jRD5tcEFKrubjQ6Px

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-18 08:09:14 +01:00

121 lines
6.4 KiB
JavaScript

#!/usr/bin/env node
// Generates web/catalog.html — a static, SEO-indexable catalog of every skill,
// grouped by bundle, from web/skills.json. Server-rendered HTML so search engines
// index each skill's name + description (the playground is client-rendered and
// isn't crawlable). Run after web/build-skills.mjs. No dependencies.
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = join(__dirname, '..');
const skillsJson = join(root, 'web', 'skills.json');
const REPO = 'https://github.com/mohitagw15856/pm-claude-skills';
if (!existsSync(skillsJson)) {
console.error('web/skills.json not found — run: node web/build-skills.mjs');
process.exit(1);
}
const { skills } = JSON.parse(readFileSync(skillsJson, 'utf8'));
const esc = (s) => String(s || '').replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
const TIER = {
production: ['🟢', 'Production-Ready'],
stable: ['🔵', 'Stable'],
experimental: ['🟡', 'Experimental'],
};
// Group by bundle, sorted; skills sorted by title within.
const byBundle = {};
for (const s of skills) (byBundle[s.plugin] ||= []).push(s);
const bundles = Object.keys(byBundle).sort();
for (const b of bundles) byBundle[b].sort((a, b2) => a.title.localeCompare(b2.title));
const cards = (list) => list.map((s) => {
const [dot, label] = TIER[s.tier] || TIER.stable;
return ` <article class="card" id="${esc(s.name)}">
<div class="row"><span class="tier tier-${s.tier}">${dot} ${label}</span><span class="bundle">${esc(s.plugin)}</span></div>
<h3>${esc(s.title)}</h3>
<p>${esc(s.description)}</p>
<div class="links">
<a href="${REPO}/blob/main/skills/${esc(s.name)}/SKILL.md">SKILL.md ↗</a>
<a href="https://mohitagw15856.github.io/pm-claude-skills/#${esc(s.name)}">Run in Playground →</a>
</div>
</article>`;
}).join('\n');
const sections = bundles.map((b) =>
` <section class="bundle-section">\n <h2 id="bundle-${esc(b)}">${esc(b)} <span class="count">${byBundle[b].length}</span></h2>\n${cards(byBundle[b])}\n </section>`
).join('\n');
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Skill Catalog — ${skills.length} Agent Skills for Claude, ChatGPT, Gemini, Cursor & more</title>
<meta name="description" content="Browse all ${skills.length} professional Agent Skills (SKILL.md) — product, engineering, customer success, marketing, design, finance, HR, sales and more. Works with Claude, ChatGPT, Gemini, Cursor, Codex, Hermes." />
<link rel="canonical" href="https://mohitagw15856.github.io/pm-claude-skills/catalog.html" />
<style>
:root{--bg:#0f1115;--panel:#161a21;--panel2:#1d222b;--border:#2a313c;--text:#e7ebf0;--muted:#95a0b0;--accent:#d97757;--accent2:#e89b82}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:15px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}
a{color:var(--accent2);text-decoration:none}a:hover{text-decoration:underline}
header{padding:28px 22px;border-bottom:1px solid var(--border);background:var(--panel)}
header h1{margin:0 0 6px;font-size:24px}header p{margin:0;color:var(--muted);font-size:14px}
.nav{margin-top:12px;display:flex;gap:14px;flex-wrap:wrap;font-size:13px}
.controls{position:sticky;top:0;z-index:5;background:var(--bg);padding:14px 22px;border-bottom:1px solid var(--border)}
.controls input{width:100%;max-width:520px;padding:10px 12px;background:var(--panel2);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:14px}
main{max-width:1100px;margin:0 auto;padding:8px 22px 60px}
.bundle-section{margin-top:30px}
.bundle-section h2{font-size:16px;border-bottom:1px solid var(--border);padding-bottom:8px;text-transform:uppercase;letter-spacing:.04em;color:var(--accent2)}
.count{color:var(--muted);font-size:12px;font-weight:400}
.card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:14px 16px;margin:12px 0}
.card h3{margin:6px 0 6px;font-size:16px}.card p{margin:0 0 10px;color:var(--muted);font-size:13.5px}
.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.tier{font-size:10px;font-weight:600;padding:2px 7px;border-radius:99px;border:1px solid transparent}
.tier-production{color:#6ee7b7;background:rgba(16,185,129,.12);border-color:rgba(16,185,129,.35)}
.tier-stable{color:#93c5fd;background:rgba(59,130,246,.12);border-color:rgba(59,130,246,.35)}
.tier-experimental{color:#fcd34d;background:rgba(245,158,11,.12);border-color:rgba(245,158,11,.35)}
.bundle{font-size:10.5px;letter-spacing:.03em;text-transform:uppercase;color:var(--accent2);font-weight:600;margin-left:auto}
.links{display:flex;gap:14px;font-size:12.5px}
.empty{color:var(--muted);padding:40px;text-align:center}
</style>
</head>
<body>
<header>
<h1>🧠 Skill Catalog — ${skills.length} professional Agent Skills</h1>
<p>Structured <code>SKILL.md</code> skills for Claude, ChatGPT, Gemini, Cursor, Codex &amp; Hermes. Install all with <code>npx pm-claude-skills add --agent &lt;tool&gt;</code>.</p>
<div class="nav">
<a href="https://mohitagw15856.github.io/pm-claude-skills/">▶ Live Playground</a>
<a href="${REPO}">GitHub</a>
<a href="${REPO}#-quick-install-2-minutes">Install</a>
<a href="${REPO}/blob/main/TIERS.md">Tiers</a>
</div>
</header>
<div class="controls"><input id="q" type="search" placeholder="Filter ${skills.length} skills…" oninput="filter()" /></div>
<main id="main">
${sections}
<p class="empty" id="empty" hidden>No skills match.</p>
</main>
<script>
function filter(){
var q=document.getElementById('q').value.toLowerCase().trim();
var any=false;
document.querySelectorAll('.bundle-section').forEach(function(sec){
var shown=0;
sec.querySelectorAll('.card').forEach(function(c){
var hit=!q||c.textContent.toLowerCase().includes(q);
c.hidden=!hit; if(hit){shown++;any=true;}
});
sec.hidden=shown===0;
});
document.getElementById('empty').hidden=any;
}
</script>
</body>
</html>
`;
writeFileSync(join(root, 'web', 'catalog.html'), html);
console.log(`Wrote web/catalog.html — ${skills.length} skills across ${bundles.length} bundles.`);