@olegkoval/agent-skills 1.38.3 → 1.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +1 -0
- package/.github/prompts/veto-routing.prompt.md +65 -0
- package/.kiro/steering/veto-routing.md +64 -0
- package/.windsurf/rules/veto-routing.md +63 -0
- package/README.md +3 -2
- package/adapters/claude/olko-skill-meta/skills/veto-routing/SKILL.md +76 -0
- package/adapters/codex/olko-skill-meta/README.md +1 -0
- package/adapters/cursor/olko-skill-meta/skills/veto-routing/SKILL.md +76 -0
- package/adapters/grok/olko-skill-meta/skills/veto-routing/SKILL.md +76 -0
- package/catalog/skills.json +23 -0
- package/package.json +1 -1
- package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-skill-meta/skills/veto-routing/SKILL.md +75 -0
- package/plugins/olko-web-ops/.claude-plugin/plugin.json +1 -1
- package/scripts/lib/catalog.mjs +1 -1
- package/site/assets/catalog.css +1064 -0
- package/site/assets/catalog.js +232 -0
- package/site/build.mjs +218 -166
- package/site/assets/paperbag.css +0 -707
- package/site/assets/paperbag.js +0 -218
package/site/build.mjs
CHANGED
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
/*
|
|
3
3
|
* Static site generator for the agent-skills catalog.
|
|
4
4
|
*
|
|
5
|
-
* Reads the machine-readable catalog
|
|
6
|
-
*
|
|
7
|
-
* There is no templating library on purpose: the catalog is small, the output
|
|
8
|
-
* must stay auditable, and CI should not need a lockfile refresh to publish.
|
|
5
|
+
* Reads the machine-readable catalog and emits a dependency-free site into
|
|
6
|
+
* `_site/`. The catalog stays the source of truth for plugin and skill data.
|
|
9
7
|
*/
|
|
10
8
|
|
|
11
9
|
import { mkdir, readFile, readdir, copyFile, writeFile, rm } from "node:fs/promises";
|
|
12
10
|
import { existsSync } from "node:fs";
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
13
12
|
import { dirname, join } from "node:path";
|
|
14
13
|
import { fileURLToPath } from "node:url";
|
|
15
14
|
import { loadCatalog } from "../scripts/lib/catalog.mjs";
|
|
@@ -19,10 +18,16 @@ const root = join(here, "..");
|
|
|
19
18
|
const out = join(root, "_site");
|
|
20
19
|
|
|
21
20
|
const SITE_URL = "https://skills.olegkoval.com";
|
|
21
|
+
const PORTFOLIO = "https://www.olegkoval.com";
|
|
22
22
|
const REPO = "https://github.com/oleg-koval/agent-skills";
|
|
23
23
|
const CNAME = "skills.olegkoval.com";
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const GA_MEASUREMENT_ID = "G-NV8Q2H8YV0";
|
|
25
|
+
const MARKETPLACE_NAME = "olko-agent-skills";
|
|
26
|
+
const assetVersion = createHash("sha256")
|
|
27
|
+
.update(await readFile(join(here, "assets", "catalog.css")))
|
|
28
|
+
.update(await readFile(join(here, "assets", "catalog.js")))
|
|
29
|
+
.digest("hex")
|
|
30
|
+
.slice(0, 10);
|
|
26
31
|
|
|
27
32
|
const esc = (value) =>
|
|
28
33
|
String(value)
|
|
@@ -34,7 +39,8 @@ const esc = (value) =>
|
|
|
34
39
|
const titleCase = (slug) =>
|
|
35
40
|
slug.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
const displayPlugin = (name) => name.replace(/^olko-/, "").replace(/-/g, " ");
|
|
43
|
+
|
|
38
44
|
const readFrontmatter = async (path) => {
|
|
39
45
|
if (!existsSync(path)) return {};
|
|
40
46
|
const raw = await readFile(path, "utf8");
|
|
@@ -49,13 +55,12 @@ const readFrontmatter = async (path) => {
|
|
|
49
55
|
return fields;
|
|
50
56
|
};
|
|
51
57
|
|
|
52
|
-
/** Counts the extra reference material shipped alongside a skill. */
|
|
53
58
|
const countSupportFiles = async (dir) => {
|
|
54
59
|
let files = 0;
|
|
55
60
|
const walk = async (current) => {
|
|
56
61
|
const entries = await readdir(current, { withFileTypes: true });
|
|
57
62
|
for (const entry of entries) {
|
|
58
|
-
if (entry.name === "adapters") continue;
|
|
63
|
+
if (entry.name === "adapters") continue;
|
|
59
64
|
if (entry.isDirectory()) await walk(join(current, entry.name));
|
|
60
65
|
else if (entry.name !== "SKILL.md") files += 1;
|
|
61
66
|
}
|
|
@@ -64,9 +69,7 @@ const countSupportFiles = async (dir) => {
|
|
|
64
69
|
return files;
|
|
65
70
|
};
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const layout = ({ title, description, canonical, body, ogTitle }) => `<!doctype html>
|
|
72
|
+
const layout = ({ title, description, canonical, body, ogTitle, pageType }) => `<!doctype html>
|
|
70
73
|
<html lang="en">
|
|
71
74
|
<head>
|
|
72
75
|
<meta charset="utf-8">
|
|
@@ -80,219 +83,283 @@ const layout = ({ title, description, canonical, body, ogTitle }) => `<!doctype
|
|
|
80
83
|
<meta property="og:description" content="${esc(description)}">
|
|
81
84
|
<meta property="og:url" content="${esc(canonical)}">
|
|
82
85
|
<meta name="twitter:card" content="summary">
|
|
83
|
-
<meta name="theme-color" content="#
|
|
86
|
+
<meta name="theme-color" content="#f5f7f4">
|
|
84
87
|
<link rel="icon" href="/assets/favicon.svg" type="image/svg+xml">
|
|
85
|
-
<link rel="stylesheet" href="/assets/
|
|
88
|
+
<link rel="stylesheet" href="/assets/catalog.css?v=${assetVersion}">
|
|
86
89
|
</head>
|
|
87
|
-
<body>
|
|
88
|
-
<
|
|
89
|
-
<div class="halftone" aria-hidden="true"></div>
|
|
90
|
-
<div class="blotch" aria-hidden="true"></div>
|
|
91
|
-
<div class="smudge" aria-hidden="true"></div>
|
|
90
|
+
<body data-page-type="${esc(pageType)}" data-ga-measurement-id="${GA_MEASUREMENT_ID}">
|
|
91
|
+
<a class="skip-link" href="#main">Skip to content</a>
|
|
92
92
|
<div class="shell">
|
|
93
93
|
<header class="masthead">
|
|
94
|
-
<
|
|
94
|
+
<div class="identity">
|
|
95
|
+
<a class="wordmark" href="/" aria-label="Agent Skills home"><span aria-hidden="true">as</span>agent skills</a>
|
|
96
|
+
<a class="maker" href="${PORTFOLIO}" data-analytics-event="portfolio_visit" data-analytics-location="header">by Oleg Koval <span aria-hidden="true">↗</span></a>
|
|
97
|
+
</div>
|
|
95
98
|
<nav aria-label="Primary">
|
|
96
|
-
<a href="/#
|
|
99
|
+
<a href="/#marketplace">Marketplace</a>
|
|
100
|
+
<a href="/#catalog">Skills</a>
|
|
97
101
|
<a href="/#install">Install</a>
|
|
98
|
-
<a href="
|
|
99
|
-
<a href="${REPO}">GitHub</a>
|
|
102
|
+
<a href="${REPO}" data-analytics-event="github_visit" data-analytics-location="header">GitHub <span aria-hidden="true">↗</span></a>
|
|
100
103
|
</nav>
|
|
101
104
|
</header>
|
|
102
|
-
|
|
105
|
+
<main id="main">${body}</main>
|
|
103
106
|
<footer class="colophon">
|
|
104
|
-
<
|
|
105
|
-
<
|
|
107
|
+
<p>Open source under MIT. Built and maintained by <a href="${PORTFOLIO}" data-analytics-event="portfolio_visit" data-analytics-location="footer">Oleg Koval</a>.</p>
|
|
108
|
+
<div>
|
|
109
|
+
<button class="text-button" type="button" data-consent-open>Analytics preferences</button>
|
|
110
|
+
<a href="${REPO}">Source</a>
|
|
111
|
+
</div>
|
|
106
112
|
</footer>
|
|
107
113
|
</div>
|
|
108
|
-
<
|
|
114
|
+
<section class="consent" data-consent-banner hidden aria-label="Analytics preference">
|
|
115
|
+
<div>
|
|
116
|
+
<strong>Optional traffic analytics</strong>
|
|
117
|
+
<p>Off until you allow. Page and install interaction counts help improve this catalog. Search text is never sent.</p>
|
|
118
|
+
</div>
|
|
119
|
+
<div class="consent-actions">
|
|
120
|
+
<button class="button button-secondary" type="button" data-consent-decline>Decline</button>
|
|
121
|
+
<button class="button button-primary" type="button" data-consent-accept>Allow analytics</button>
|
|
122
|
+
</div>
|
|
123
|
+
</section>
|
|
124
|
+
<script src="/assets/catalog.js?v=${assetVersion}" defer></script>
|
|
109
125
|
</body>
|
|
110
126
|
</html>
|
|
111
127
|
`;
|
|
112
128
|
|
|
113
|
-
const
|
|
114
|
-
<
|
|
129
|
+
const command = (label, code, analytics = {}) => `<div class="command">
|
|
130
|
+
<div class="command-head"><span>${esc(label)}</span><button class="copy" type="button" data-copy ${Object.entries(analytics)
|
|
131
|
+
.map(([key, value]) => `data-analytics-${esc(key)}="${esc(value)}"`)
|
|
132
|
+
.join(" ")}>Copy</button></div>
|
|
115
133
|
<pre>${esc(code)}</pre>
|
|
116
134
|
</div>`;
|
|
117
135
|
|
|
118
|
-
|
|
136
|
+
const pluginCard = (plugin) => {
|
|
137
|
+
const install = `/plugin install ${plugin.name}@${MARKETPLACE_NAME}`;
|
|
138
|
+
return `<article class="plugin-card">
|
|
139
|
+
<div class="plugin-card-main">
|
|
140
|
+
<span class="eyebrow">${plugin.count} skill${plugin.count === 1 ? "" : "s"}</span>
|
|
141
|
+
<h3><a href="/?category=${encodeURIComponent(plugin.name)}#catalog">${esc(plugin.name)}</a></h3>
|
|
142
|
+
<p>${esc(plugin.description)}</p>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="install-line">
|
|
145
|
+
<code>${esc(install)}</code>
|
|
146
|
+
<button class="copy-icon" type="button" data-copy data-analytics-event="marketplace_install_copy" data-analytics-plugin="${esc(plugin.name)}" aria-label="Copy install command for ${esc(plugin.name)}">Copy</button>
|
|
147
|
+
</div>
|
|
148
|
+
</article>`;
|
|
149
|
+
};
|
|
119
150
|
|
|
120
|
-
const installSection = (sample) => `<section class="
|
|
121
|
-
<
|
|
122
|
-
<p class="sub">Pick your agent. The canonical package is the same either way; only the wrapper changes.</p>
|
|
123
|
-
<div class="cols">
|
|
151
|
+
const installSection = (sample) => `<section class="section" id="install">
|
|
152
|
+
<div class="section-heading">
|
|
124
153
|
<div>
|
|
125
|
-
<
|
|
126
|
-
|
|
154
|
+
<p class="eyebrow">Installation</p>
|
|
155
|
+
<h2>Use the same catalog anywhere.</h2>
|
|
127
156
|
</div>
|
|
128
|
-
<
|
|
157
|
+
<p>Marketplace installs are quickest in Claude Code and Grok. Codex and the other adapters use the same canonical skill packages.</p>
|
|
158
|
+
</div>
|
|
159
|
+
<div class="install-grid">
|
|
160
|
+
<article>
|
|
161
|
+
<h3>Claude Code</h3>
|
|
162
|
+
<p>Add the marketplace once. Then copy a plugin command from the shelf above.</p>
|
|
163
|
+
${command("Marketplace", "/plugin marketplace add oleg-koval/agent-skills", { event: "marketplace_add_copy", agent: "claude" })}
|
|
164
|
+
</article>
|
|
165
|
+
<article>
|
|
129
166
|
<h3>Codex</h3>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
167
|
+
<p>Clone the catalog and link its canonical packages into your local skills directory.</p>
|
|
168
|
+
${command("Local install", "git clone https://github.com/oleg-koval/agent-skills.git\ncd agent-skills\n./scripts/install-codex-symlinks.sh", { event: "catalog_install_copy", agent: "codex" })}
|
|
169
|
+
</article>
|
|
170
|
+
<article>
|
|
133
171
|
<h3>Grok</h3>
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
172
|
+
<p>Add the public marketplace. Its generated wrappers stay in sync with the source skills.</p>
|
|
173
|
+
${command("Marketplace", "grok plugin marketplace add oleg-koval/agent-skills", { event: "marketplace_add_copy", agent: "grok" })}
|
|
174
|
+
</article>
|
|
175
|
+
<article>
|
|
176
|
+
<h3>Then ask directly</h3>
|
|
177
|
+
<p>Skills can be discovered automatically or named explicitly in a session.</p>
|
|
178
|
+
${command("Any agent", `Use the ${sample} skill.`, { event: "skill_prompt_copy", agent: "any" })}
|
|
179
|
+
</article>
|
|
140
180
|
</div>
|
|
141
|
-
<p class="
|
|
181
|
+
<p class="section-note">Cursor, Copilot, Windsurf, Kiro, Pi and Hermes wrappers live under <code>adapters/</code>. See the <a href="${REPO}#quick-start">installation guide</a> for agent-specific paths.</p>
|
|
142
182
|
</section>`;
|
|
143
183
|
|
|
144
|
-
const
|
|
184
|
+
const skillCard = (pkg) => {
|
|
145
185
|
const search = [pkg.name, pkg.description, pkg.plugin.name, ...(pkg.tags || [])]
|
|
146
186
|
.join(" ")
|
|
147
187
|
.toLowerCase();
|
|
148
|
-
return `<li class="card"
|
|
188
|
+
return `<li class="skill-card"
|
|
149
189
|
data-category="${esc(pkg.plugin.name)}"
|
|
150
190
|
data-adapters="${esc((pkg.adapters || []).join(" "))}"
|
|
151
191
|
data-search="${esc(search)}">
|
|
152
|
-
<
|
|
153
|
-
|
|
192
|
+
<div class="skill-card-top">
|
|
193
|
+
<span>${esc(displayPlugin(pkg.plugin.name))}</span>
|
|
194
|
+
<code>${esc(pkg.lookupName)}</code>
|
|
195
|
+
</div>
|
|
196
|
+
<h3><a href="/skills/${esc(pkg.name)}/" data-analytics-event="skill_open" data-analytics-skill="${esc(pkg.name)}">${esc(titleCase(pkg.name))}</a></h3>
|
|
154
197
|
<p>${esc(pkg.description)}</p>
|
|
155
|
-
<span class="lookup">${esc(pkg.lookupName)}</span>
|
|
156
198
|
<ul class="tags">${(pkg.tags || [])
|
|
157
|
-
.slice(0,
|
|
158
|
-
.map((
|
|
199
|
+
.slice(0, 4)
|
|
200
|
+
.map((tag) => `<li>${esc(tag)}</li>`)
|
|
159
201
|
.join("")}</ul>
|
|
160
202
|
</li>`;
|
|
161
203
|
};
|
|
162
204
|
|
|
163
|
-
const indexPage = ({ packages, categories, adapters }) => {
|
|
205
|
+
const indexPage = ({ packages, plugins, categories, adapters }) => {
|
|
164
206
|
const body = `<section class="hero">
|
|
165
|
-
<
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
207
|
+
<div class="hero-copy">
|
|
208
|
+
<p class="eyebrow">Agent workflows by Oleg Koval</p>
|
|
209
|
+
<h1>Useful workflows, ready for your agent.</h1>
|
|
210
|
+
<p class="lede">A public marketplace of opinionated skills for shipping software, running products, and maintaining the work around them. Install by plugin, inspect every source file, and keep the same workflow across agents.</p>
|
|
211
|
+
<div class="hero-actions">
|
|
212
|
+
<a class="button button-primary" href="#marketplace">Browse marketplace</a>
|
|
213
|
+
<a class="button button-secondary" href="${REPO}" data-analytics-event="github_visit" data-analytics-location="hero">View source</a>
|
|
214
|
+
</div>
|
|
215
|
+
<p class="facts"><strong>${packages.length}</strong> skills <span>·</span> <strong>${plugins.length}</strong> plugins <span>·</span> <strong>${adapters.length}</strong> agent adapters</p>
|
|
216
|
+
</div>
|
|
217
|
+
<aside class="hero-command" aria-label="Quick marketplace install">
|
|
218
|
+
<p class="eyebrow">Start here</p>
|
|
219
|
+
<h2>Add the marketplace once.</h2>
|
|
220
|
+
${command("Claude Code", "/plugin marketplace add oleg-koval/agent-skills", { event: "marketplace_add_copy", agent: "claude" })}
|
|
221
|
+
<p>Then choose one focused plugin below. You can add more later without installing the whole catalog.</p>
|
|
222
|
+
</aside>
|
|
169
223
|
</section>
|
|
170
224
|
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
<
|
|
176
|
-
<div><b>${packages.length}</b><small>Skills</small></div>
|
|
177
|
-
<div><b>${adapters.length}</b><small>Agent adapters</small></div>
|
|
178
|
-
<div><b>${categories.length}</b><small>Plugins</small></div>
|
|
179
|
-
</div>
|
|
180
|
-
|
|
181
|
-
<section class="band" id="catalog">
|
|
182
|
-
<div class="controls">
|
|
183
|
-
<label for="q">Search the catalog</label>
|
|
184
|
-
<input id="q" type="search" autocomplete="off" placeholder="release, pull requests, obsidian, watch face…">
|
|
185
|
-
<div class="chips" role="group" aria-label="Filter by plugin">
|
|
186
|
-
${categories
|
|
187
|
-
.map(
|
|
188
|
-
(c) =>
|
|
189
|
-
`<button class="chip" type="button" aria-pressed="false" data-kind="category" data-value="${esc(c.name)}">${esc(c.name.replace(/^olko-/, "").replace(/-/g, " "))} <span aria-hidden="true">${c.count}</span></button>`,
|
|
190
|
-
)
|
|
191
|
-
.join("\n ")}
|
|
225
|
+
<section class="section marketplace" id="marketplace">
|
|
226
|
+
<div class="section-heading">
|
|
227
|
+
<div>
|
|
228
|
+
<p class="eyebrow">Marketplace plugins</p>
|
|
229
|
+
<h2>Install by the work you do.</h2>
|
|
192
230
|
</div>
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
231
|
+
<p>Each plugin is a focused bundle. Copy its Claude Code install command, or open it to see the individual skills it includes.</p>
|
|
232
|
+
</div>
|
|
233
|
+
<div class="plugin-grid">
|
|
234
|
+
${plugins.map(pluginCard).join("\n ")}
|
|
235
|
+
</div>
|
|
236
|
+
</section>
|
|
237
|
+
|
|
238
|
+
<section class="section catalog" id="catalog">
|
|
239
|
+
<div class="section-heading">
|
|
240
|
+
<div>
|
|
241
|
+
<p class="eyebrow">Skill catalog</p>
|
|
242
|
+
<h2>Inspect before you install.</h2>
|
|
200
243
|
</div>
|
|
244
|
+
<p>Search the complete source-backed catalog, then narrow it by plugin or supported agent.</p>
|
|
245
|
+
</div>
|
|
246
|
+
<div class="controls">
|
|
247
|
+
<label for="q">Search skills</label>
|
|
248
|
+
<input id="q" type="search" autocomplete="off" placeholder="Pull requests, releases, analytics, watch faces...">
|
|
249
|
+
<details>
|
|
250
|
+
<summary>Filter by plugin <span aria-hidden="true"></span></summary>
|
|
251
|
+
<div class="chips" role="group" aria-label="Filter by plugin">
|
|
252
|
+
${categories
|
|
253
|
+
.map(
|
|
254
|
+
(category) =>
|
|
255
|
+
`<button class="chip" type="button" aria-pressed="false" data-kind="category" data-value="${esc(category.name)}">${esc(displayPlugin(category.name))} <span>${category.count}</span></button>`,
|
|
256
|
+
)
|
|
257
|
+
.join("\n ")}
|
|
258
|
+
</div>
|
|
259
|
+
</details>
|
|
260
|
+
<details>
|
|
261
|
+
<summary>Filter by agent <span aria-hidden="true"></span></summary>
|
|
262
|
+
<div class="chips" role="group" aria-label="Filter by agent">
|
|
263
|
+
${adapters
|
|
264
|
+
.map(
|
|
265
|
+
(adapter) =>
|
|
266
|
+
`<button class="chip" type="button" aria-pressed="false" data-kind="adapter" data-value="${esc(adapter.name)}">${esc(adapter.name)} <span>${adapter.count}</span></button>`,
|
|
267
|
+
)
|
|
268
|
+
.join("\n ")}
|
|
269
|
+
</div>
|
|
270
|
+
</details>
|
|
201
271
|
<p class="count" id="count" role="status">${packages.length} skills</p>
|
|
202
272
|
</div>
|
|
203
|
-
<ul class="grid" id="grid">
|
|
204
|
-
${packages.map(
|
|
273
|
+
<ul class="skill-grid" id="grid">
|
|
274
|
+
${packages.map(skillCard).join("\n ")}
|
|
205
275
|
</ul>
|
|
206
|
-
<p class="empty" id="empty" hidden>
|
|
276
|
+
<p class="empty" id="empty" hidden>No skills match those filters.</p>
|
|
207
277
|
</section>
|
|
208
278
|
|
|
209
279
|
${installSection(packages[0].lookupName)}
|
|
210
280
|
|
|
211
|
-
<section class="
|
|
212
|
-
<
|
|
213
|
-
|
|
214
|
-
|
|
281
|
+
<section class="section about" id="about">
|
|
282
|
+
<div class="section-heading">
|
|
283
|
+
<div>
|
|
284
|
+
<p class="eyebrow">How it stays useful</p>
|
|
285
|
+
<h2>One source, many agents.</h2>
|
|
286
|
+
</div>
|
|
287
|
+
<p>Every workflow has one canonical skill package. Tool-specific adapters are generated from it, so the marketplace, source files, and this site stay aligned.</p>
|
|
288
|
+
</div>
|
|
289
|
+
<a class="quiet-link" href="${PORTFOLIO}" data-analytics-event="portfolio_visit" data-analytics-location="about">More open-source work and writing at olegkoval.com <span aria-hidden="true">↗</span></a>
|
|
215
290
|
</section>`;
|
|
216
291
|
|
|
217
292
|
return layout({
|
|
218
|
-
title: "
|
|
219
|
-
ogTitle: "
|
|
220
|
-
description: `${packages.length}
|
|
293
|
+
title: "Agent Skills by Oleg Koval",
|
|
294
|
+
ogTitle: "Agent Skills",
|
|
295
|
+
description: `${packages.length} open-source agent skills packaged for Claude Code, Codex, Grok, Cursor, Copilot, Windsurf, Kiro, Pi and Hermes.`,
|
|
221
296
|
canonical: `${SITE_URL}/`,
|
|
222
297
|
body,
|
|
298
|
+
pageType: "catalog",
|
|
223
299
|
});
|
|
224
300
|
};
|
|
225
301
|
|
|
226
|
-
/* ----------------------------------------------------------- detail page */
|
|
227
|
-
|
|
228
302
|
const detailPage = (pkg, prev, next) => {
|
|
229
303
|
const label = titleCase(pkg.name);
|
|
230
|
-
const body = `<p class="crumb"><a href="/">
|
|
231
|
-
<
|
|
232
|
-
<
|
|
233
|
-
<h1
|
|
304
|
+
const body = `<p class="crumb"><a href="/">Skills</a><span>/</span><a href="/?category=${encodeURIComponent(pkg.plugin.name)}#catalog">${esc(displayPlugin(pkg.plugin.name))}</a></p>
|
|
305
|
+
<header class="detail-head">
|
|
306
|
+
<p class="eyebrow">${esc(pkg.lookupName)}</p>
|
|
307
|
+
<h1>${esc(label)}</h1>
|
|
234
308
|
<p>${esc(pkg.longDescription || pkg.description)}</p>
|
|
235
|
-
</
|
|
309
|
+
</header>
|
|
236
310
|
|
|
237
311
|
<dl class="meta">
|
|
238
|
-
<div><dt>
|
|
239
|
-
<div><dt>Plugin</dt><dd>${esc(pkg.plugin.name.replace(/^olko-/, "").replace(/-/g, " "))}</dd></div>
|
|
312
|
+
<div><dt>Plugin</dt><dd>${esc(pkg.plugin.name)}</dd></div>
|
|
240
313
|
<div><dt>Agents</dt><dd>${esc((pkg.adapters || []).join(", "))}</dd></div>
|
|
241
314
|
<div><dt>Bundled references</dt><dd>${pkg.supportFiles} file${pkg.supportFiles === 1 ? "" : "s"}</dd></div>
|
|
242
315
|
</dl>
|
|
243
316
|
|
|
244
|
-
<section class="
|
|
245
|
-
<
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
<
|
|
317
|
+
<section class="section detail-use">
|
|
318
|
+
<div class="section-heading">
|
|
319
|
+
<div><p class="eyebrow">Install and use</p><h2>Add its plugin, then ask.</h2></div>
|
|
320
|
+
<p>The marketplace command is needed once per Claude Code setup. The plugin command installs this skill and its related workflows.</p>
|
|
321
|
+
</div>
|
|
322
|
+
<div class="install-grid">
|
|
323
|
+
<article>${command("Add marketplace", "/plugin marketplace add oleg-koval/agent-skills", { event: "marketplace_add_copy", agent: "claude" })}</article>
|
|
324
|
+
<article>${command("Install plugin", `/plugin install ${pkg.plugin.name}@${MARKETPLACE_NAME}`, { event: "marketplace_install_copy", plugin: pkg.plugin.name })}</article>
|
|
325
|
+
<article>${command("Invoke skill", `Use the ${pkg.lookupName} skill.`, { event: "skill_prompt_copy", skill: pkg.name })}</article>
|
|
326
|
+
</div>
|
|
327
|
+
<p class="section-note"><a href="${REPO}/blob/main/${esc(pkg.path)}/SKILL.md">Read the full SKILL.md</a> <span>·</span> <a href="${REPO}/tree/main/${esc(pkg.path)}">Browse package files</a></p>
|
|
250
328
|
</section>
|
|
251
329
|
|
|
252
330
|
${
|
|
253
331
|
(pkg.tags || []).length
|
|
254
|
-
? `<section class="
|
|
255
|
-
<h2 data-reactive>Tags</h2>
|
|
256
|
-
<ul class="tags">${pkg.tags.map((t) => `<li><a href="/?q=${encodeURIComponent(t)}">${esc(t)}</a></li>`).join("")}</ul>
|
|
257
|
-
</section>`
|
|
332
|
+
? `<section class="section compact-section"><p class="eyebrow">Tags</p><ul class="tags detail-tags">${pkg.tags.map((tag) => `<li><a href="/?q=${encodeURIComponent(tag)}#catalog">${esc(tag)}</a></li>`).join("")}</ul></section>`
|
|
258
333
|
: ""
|
|
259
334
|
}
|
|
260
335
|
|
|
261
|
-
<nav class="pager" aria-label="
|
|
262
|
-
<a href="/skills/${esc(prev.name)}/"
|
|
263
|
-
<a href="/skills/${esc(next.name)}/">${esc(titleCase(next.name))}
|
|
336
|
+
<nav class="pager" aria-label="Skill catalog">
|
|
337
|
+
<a href="/skills/${esc(prev.name)}/"><span>Previous</span>${esc(titleCase(prev.name))}</a>
|
|
338
|
+
<a href="/skills/${esc(next.name)}/"><span>Next</span>${esc(titleCase(next.name))}</a>
|
|
264
339
|
</nav>`;
|
|
265
340
|
|
|
266
341
|
return layout({
|
|
267
|
-
title: `${label}
|
|
342
|
+
title: `${label} | Agent Skills`,
|
|
268
343
|
description: pkg.description,
|
|
269
344
|
canonical: `${SITE_URL}/skills/${pkg.name}/`,
|
|
270
345
|
body,
|
|
346
|
+
pageType: "skill",
|
|
271
347
|
});
|
|
272
348
|
};
|
|
273
349
|
|
|
274
|
-
/* ------------------------------------------------------------------ main */
|
|
275
|
-
|
|
276
350
|
const favicon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
277
|
-
<rect width="32" height="32" fill="#
|
|
278
|
-
<path d="
|
|
279
|
-
<path d="M7 9l4-4h10l4 4" fill="none" stroke="#191410" stroke-width="2.5"/>
|
|
280
|
-
<circle cx="16" cy="18" r="3.4" fill="#a8321f"/>
|
|
351
|
+
<rect width="32" height="32" rx="8" fill="#365b4a"/>
|
|
352
|
+
<path d="M9 10h14M9 16h14M9 22h9" fill="none" stroke="#f5f7f4" stroke-width="2.5" stroke-linecap="round"/>
|
|
281
353
|
</svg>
|
|
282
354
|
`;
|
|
283
355
|
|
|
284
356
|
const main = async () => {
|
|
285
357
|
const catalog = loadCatalog(join(root, "catalog", "skills.json"));
|
|
286
|
-
|
|
287
|
-
const packages = [...catalog.skills].sort((a, b) =>
|
|
288
|
-
a.name.localeCompare(b.name),
|
|
289
|
-
);
|
|
358
|
+
const packages = [...catalog.skills].sort((a, b) => a.name.localeCompare(b.name));
|
|
290
359
|
|
|
291
360
|
for (const pkg of packages) {
|
|
292
361
|
const dir = join(root, pkg.path);
|
|
293
362
|
const front = await readFrontmatter(join(dir, "SKILL.md"));
|
|
294
|
-
// The catalog description is the source of truth; frontmatter only fills
|
|
295
|
-
// the longer detail-page blurb when it is genuinely richer.
|
|
296
363
|
if (front.description && front.description.length > pkg.description.length) {
|
|
297
364
|
pkg.longDescription = front.description;
|
|
298
365
|
}
|
|
@@ -302,8 +369,7 @@ const main = async () => {
|
|
|
302
369
|
const tally = (getValues) => {
|
|
303
370
|
const counts = new Map();
|
|
304
371
|
for (const pkg of packages) {
|
|
305
|
-
const
|
|
306
|
-
for (const value of values) counts.set(value, (counts.get(value) || 0) + 1);
|
|
372
|
+
for (const value of getValues(pkg)) counts.set(value, (counts.get(value) || 0) + 1);
|
|
307
373
|
}
|
|
308
374
|
return [...counts]
|
|
309
375
|
.map(([name, count]) => ({ name, count }))
|
|
@@ -312,28 +378,22 @@ const main = async () => {
|
|
|
312
378
|
|
|
313
379
|
const categories = tally((pkg) => [pkg.plugin.name]);
|
|
314
380
|
const adapters = tally((pkg) => pkg.adapters || []);
|
|
381
|
+
const plugins = catalog.plugins.map((plugin) => ({
|
|
382
|
+
name: plugin.name,
|
|
383
|
+
description: plugin.description,
|
|
384
|
+
count: plugin.skills.length,
|
|
385
|
+
}));
|
|
315
386
|
|
|
316
387
|
await rm(out, { recursive: true, force: true });
|
|
317
388
|
await mkdir(join(out, "assets"), { recursive: true });
|
|
318
|
-
|
|
319
|
-
await copyFile(
|
|
320
|
-
join(here, "assets", "paperbag.css"),
|
|
321
|
-
join(out, "assets", "paperbag.css"),
|
|
322
|
-
);
|
|
323
|
-
await copyFile(
|
|
324
|
-
join(here, "assets", "paperbag.js"),
|
|
325
|
-
join(out, "assets", "paperbag.js"),
|
|
326
|
-
);
|
|
389
|
+
await copyFile(join(here, "assets", "catalog.css"), join(out, "assets", "catalog.css"));
|
|
390
|
+
await copyFile(join(here, "assets", "catalog.js"), join(out, "assets", "catalog.js"));
|
|
327
391
|
await writeFile(join(out, "assets", "favicon.svg"), favicon);
|
|
392
|
+
await writeFile(join(out, "index.html"), indexPage({ packages, plugins, categories, adapters }));
|
|
328
393
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
);
|
|
333
|
-
|
|
334
|
-
for (const [i, pkg] of packages.entries()) {
|
|
335
|
-
const prev = packages[(i - 1 + packages.length) % packages.length];
|
|
336
|
-
const next = packages[(i + 1) % packages.length];
|
|
394
|
+
for (const [index, pkg] of packages.entries()) {
|
|
395
|
+
const prev = packages[(index - 1 + packages.length) % packages.length];
|
|
396
|
+
const next = packages[(index + 1) % packages.length];
|
|
337
397
|
const dir = join(out, "skills", pkg.name);
|
|
338
398
|
await mkdir(dir, { recursive: true });
|
|
339
399
|
await writeFile(join(dir, "index.html"), detailPage(pkg, prev, next));
|
|
@@ -342,34 +402,26 @@ const main = async () => {
|
|
|
342
402
|
await writeFile(
|
|
343
403
|
join(out, "404.html"),
|
|
344
404
|
layout({
|
|
345
|
-
title: "
|
|
405
|
+
title: "Page not found | Agent Skills",
|
|
346
406
|
description: "That page does not exist.",
|
|
347
407
|
canonical: `${SITE_URL}/404.html`,
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
<h1 data-reactive>Not in the bag</h1>
|
|
351
|
-
<p class="lede">Whatever you reached for is not here. <a href="/">Go back to the catalog</a>.</p>
|
|
352
|
-
</section>`,
|
|
408
|
+
pageType: "not-found",
|
|
409
|
+
body: `<section class="not-found"><p class="eyebrow">404</p><h1>That skill is not here.</h1><p>Return to the catalog to find another workflow.</p><a class="button button-primary" href="/">Browse skills</a></section>`,
|
|
353
410
|
}),
|
|
354
411
|
);
|
|
355
412
|
|
|
356
|
-
const urls = [`${SITE_URL}/`, ...packages.map((
|
|
413
|
+
const urls = [`${SITE_URL}/`, ...packages.map((pkg) => `${SITE_URL}/skills/${pkg.name}/`)];
|
|
357
414
|
await writeFile(
|
|
358
415
|
join(out, "sitemap.xml"),
|
|
359
416
|
`<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${urls
|
|
360
|
-
.map((
|
|
417
|
+
.map((url) => ` <url><loc>${url}</loc></url>`)
|
|
361
418
|
.join("\n")}\n</urlset>\n`,
|
|
362
419
|
);
|
|
363
|
-
await writeFile(
|
|
364
|
-
join(out, "robots.txt"),
|
|
365
|
-
`User-agent: *\nAllow: /\nSitemap: ${SITE_URL}/sitemap.xml\n`,
|
|
366
|
-
);
|
|
420
|
+
await writeFile(join(out, "robots.txt"), `User-agent: *\nAllow: /\nSitemap: ${SITE_URL}/sitemap.xml\n`);
|
|
367
421
|
await writeFile(join(out, "CNAME"), `${CNAME}\n`);
|
|
368
422
|
await writeFile(join(out, ".nojekyll"), "");
|
|
369
423
|
|
|
370
|
-
console.log(
|
|
371
|
-
`site: ${packages.length} skills, ${categories.length} categories, ${adapters.length} adapters -> _site/`,
|
|
372
|
-
);
|
|
424
|
+
console.log(`site: ${packages.length} skills, ${categories.length} categories, ${adapters.length} adapters -> _site/`);
|
|
373
425
|
};
|
|
374
426
|
|
|
375
427
|
await main();
|