Skip to content

Commit e2fceca

Browse files
automate site builds from README + add release notes workflow
- build.js parses README.md and generates dist/index.html - deploy.yml: lint → build → deploy to Pages on every push to main - release-notes.yml: auto-creates GitHub release when plugins are added to README - removed static site/ folder (now generated at build time) - new plugins added to README automatically appear on the site after merge
1 parent 92334c4 commit e2fceca

5 files changed

Lines changed: 145 additions & 86 deletions

File tree

.github/workflows/ci.yml

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Deploy to Pages
1+
name: Build & Deploy
22

33
on:
44
push:
@@ -15,13 +15,26 @@ concurrency:
1515
cancel-in-progress: false
1616

1717
jobs:
18+
lint:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
22+
- uses: gaurav-nelson/github-action-markdown-link-check@v1
23+
with:
24+
config-file: .mlc_config.json
25+
1826
build:
1927
runs-on: ubuntu-latest
28+
needs: lint
2029
steps:
2130
- uses: actions/checkout@v4
31+
- uses: actions/setup-node@v4
32+
with:
33+
node-version: '20'
34+
- run: node build.js
2235
- uses: actions/upload-pages-artifact@v3
2336
with:
24-
path: ./site
37+
path: ./dist
2538

2639
deploy:
2740
environment:
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Release Notes
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'README.md'
8+
9+
permissions:
10+
contents: write
11+
12+
jobs:
13+
release:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
- name: Generate release notes
20+
env:
21+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22+
run: |
23+
# Get previous README.md content and compare
24+
PREV=$(git show HEAD~1:README.md 2>/dev/null || echo "")
25+
CURR=$(cat README.md)
26+
27+
if [ -z "$PREV" ]; then
28+
echo "No previous README, skipping release notes"
29+
exit 0
30+
fi
31+
32+
# Extract new plugin lines (lines that exist in current but not previous)
33+
NEW_PLUGINS=$(diff <(echo "$PREV") <(echo "$CURR") | grep "^>" | grep "^>- \[" | sed 's/^> //' || true)
34+
35+
if [ -z "$NEW_PLUGINS" ]; then
36+
echo "No new plugins detected"
37+
exit 0
38+
fi
39+
40+
echo "New plugins found:"
41+
echo "$NEW_PLUGINS"
42+
43+
# Extract plugin names for tag
44+
TAG_DATE=$(date +%Y.%m.%d)
45+
TAG="plugins-${TAG_DATE}"
46+
47+
# Check if tag exists
48+
if git rev-parse "$TAG" >/dev/null 2>&1; then
49+
TAG="plugins-${TAG_DATE}.$(date +%H%M%S)"
50+
fi
51+
52+
# Build release body
53+
BODY=$(cat <<EOF
54+
## New Plugins
55+
56+
$NEW_PLUGINS
57+
58+
---
59+
Auto-generated from README.md changes on $(date -u +%Y-%m-%d)
60+
EOF
61+
)
62+
63+
# Create GitHub release
64+
gh release create "$TAG" \
65+
--title "Plugin Update - $(date -u +%Y-%m-%d)" \
66+
--notes "$BODY" \
67+
--target main

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules/
22
.next/
33
web/
4+
dist/

site/index.html renamed to build.js

Lines changed: 62 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,47 @@
1-
<!DOCTYPE html>
1+
const fs = require('fs');
2+
3+
const readme = fs.readFileSync('README.md', 'utf8');
4+
5+
// Parse official plugins
6+
const officialMatch = readme.match(/## Official Plugins\n\n[\s\S]*?\n((?:- \S.*\n)+)/);
7+
// Parse community plugins - split by ### subcategories
8+
const communityMatch = readme.match(/## Community Plugins\n\n[\s\S]*?(?=\n## |\n---)/);
9+
10+
function parseLine(line) {
11+
// Match: - [Name](url) - Description (linked)
12+
let m = line.match(/^- \[([^\]]+)\]\(([^)]+)\)\s*[-]\s*(.+)/);
13+
if (m) return { name: m[1], url: m[2], desc: m[3].trim() };
14+
// Match: - Name - Description (unlinked, official)
15+
m = line.match(/^- ([A-Z][A-Za-z0-9\s]+?)\s*[-]\s*(.+)/);
16+
if (m) return { name: m[1].trim(), url: 'https://developers.openai.com/codex/plugins', desc: m[2].trim() };
17+
return null;
18+
}
19+
20+
function parseSection(text) {
21+
if (!text) return [];
22+
const lines = text.split('\n').filter(l => l.startsWith('- '));
23+
return lines.map(parseLine).filter(Boolean);
24+
}
25+
26+
const official = parseSection(officialMatch?.[1] || '');
27+
28+
// Parse community with subcategories
29+
const community = [];
30+
const catLines = (communityMatch?.[0] || '').split('\n');
31+
let currentCat = 'General';
32+
for (const line of catLines) {
33+
const catMatch = line.match(/^### (.+)/);
34+
if (catMatch) { currentCat = catMatch[1]; continue; }
35+
const plugin = parseLine(line);
36+
if (plugin) community.push({ ...plugin, cat: currentCat });
37+
}
38+
39+
const total = official.length + community.length;
40+
41+
// Count unique categories
42+
const cats = [...new Set(community.map(p => p.cat))];
43+
44+
const siteHtml = `<!DOCTYPE html>
245
<html lang="en">
346
<head>
447
<meta charset="UTF-8">
@@ -54,7 +97,6 @@
5497
</head>
5598
<body class="bg-white text-gray-900 antialiased">
5699
57-
<!-- Nav -->
58100
<nav class="border-b border-gray-200">
59101
<div class="max-w-5xl mx-auto px-6 h-14 flex items-center justify-between">
60102
<a href="/" class="font-mono text-sm text-gray-500 hover:text-gray-900 transition-colors">
@@ -69,7 +111,6 @@
69111
70112
<div class="max-w-5xl mx-auto px-6">
71113
72-
<!-- Hero -->
73114
<header class="pt-16 pb-12 border-b border-gray-200">
74115
<p class="font-mono text-xs uppercase tracking-widest text-hol-purple mb-4">curated directory</p>
75116
<h1 class="font-mono text-4xl font-bold leading-tight text-gray-900 max-w-lg">
@@ -84,116 +125,79 @@ <h1 class="font-mono text-4xl font-bold leading-tight text-gray-900 max-w-lg">
84125
</div>
85126
</header>
86127
87-
<!-- Search -->
88128
<div class="py-5 border-b border-gray-200">
89129
<input type="text" id="q" placeholder="filter plugins..." autocomplete="off"
90130
class="w-full max-w-xs font-mono text-sm px-3 py-2 bg-surface-50 border border-gray-200 rounded-lg outline-none focus:border-hol-purple/50 transition-colors placeholder:text-gray-300">
91131
</div>
92132
93-
<!-- Official -->
94133
<section id="sec-official" class="pt-8 pb-4">
95134
<div class="flex items-center gap-3 mb-5">
96135
<h2 class="font-mono text-xs font-medium uppercase tracking-widest text-gray-400">Official</h2>
97-
<span class="font-mono text-[10px] text-gray-300 bg-surface-100 px-1.5 py-0.5 rounded" id="official-count">12</span>
136+
<span class="font-mono text-[10px] text-gray-300 bg-surface-100 px-1.5 py-0.5 rounded">${official.length}</span>
98137
<div class="flex-1 h-px bg-gray-200"></div>
99138
</div>
100139
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3" id="list-official"></div>
101140
</section>
102141
103-
<!-- Community -->
104142
<section id="sec-community" class="pt-8 pb-4">
105143
<div class="flex items-center gap-3 mb-5">
106144
<h2 class="font-mono text-xs font-medium uppercase tracking-widest text-gray-400">Community</h2>
107-
<span class="font-mono text-[10px] text-gray-300 bg-surface-100 px-1.5 py-0.5 rounded" id="community-count">14</span>
145+
<span class="font-mono text-[10px] text-gray-300 bg-surface-100 px-1.5 py-0.5 rounded">${community.length}</span>
108146
<div class="flex-1 h-px bg-gray-200"></div>
109147
</div>
110148
<div id="cats"></div>
111149
</section>
112150
113151
<div id="empty" class="hidden py-16 text-center font-mono text-sm text-gray-300">No results.</div>
114152
115-
<!-- Footer -->
116153
<footer class="py-10 mt-4 border-t border-gray-200">
117154
<p class="font-mono text-xs text-gray-300">
118-
Apache 2.0 &middot; <a href="https://hol.org" target="_blank" rel="noopener" class="text-gray-400 hover:text-gray-900 transition-colors">HOL</a> &middot; <span id="total">26</span> plugins
155+
Apache 2.0 &middot; <a href="https://hol.org" target="_blank" rel="noopener" class="text-gray-400 hover:text-gray-900 transition-colors">HOL</a> &middot; ${total} plugins
119156
</p>
120157
</footer>
121158
</div>
122159
123160
<script>
124-
const O = [
125-
["Box","Access and manage files","https://developers.openai.com/codex/plugins"],
126-
["Cloudflare","Manage Workers, Pages, DNS, and infrastructure","https://developers.openai.com/codex/plugins"],
127-
["Figma","Inspect designs, extract specs, document components","https://developers.openai.com/codex/plugins"],
128-
["GitHub","Review changes, manage issues, interact with repos","https://developers.openai.com/codex/plugins"],
129-
["Gmail","Read, search, compose emails","https://developers.openai.com/codex/plugins"],
130-
["Google Drive","Edit and manage files","https://developers.openai.com/codex/plugins"],
131-
["Hugging Face","Browse models, datasets, spaces","https://developers.openai.com/codex/plugins"],
132-
["Linear","Issues, projects, workflows","https://developers.openai.com/codex/plugins"],
133-
["Notion","Pages, databases, content","https://developers.openai.com/codex/plugins"],
134-
["Sentry","Error monitoring, triage, performance","https://developers.openai.com/codex/plugins"],
135-
["Slack","Messages, channels, conversations","https://developers.openai.com/codex/plugins"],
136-
["Vercel","Deploy, preview, manage projects","https://developers.openai.com/codex/plugins"]
137-
];
138-
139-
const C = [
140-
["Registry Broker","Delegate tasks to specialist agents via HOL Registry","https://github.com/hashgraph-online/registry-broker-codex-plugin","Development"],
141-
["Project Autopilot","Structured project workflow: planning, execution, handoff","https://github.com/AlexMi64/codex-project-autopilot","Development"],
142-
["Codex Reviewer","Second-pass review of Claude-driven plans","https://github.com/schuettc/codex-reviewer","Development"],
143-
["HOTL Plugin","Human-on-the-Loop coding workflow plugin for Codex, Claude Code, and Cline with structured planning, review, and verification guardrails","https://github.com/yimwoo/hotl-plugin","Development"],
144-
["AgentOps","DevOps for coding agents with persistent memory","https://github.com/boshu2/agentops","Development"],
145-
["Codex Be Serious","Enforce formal written register across output","https://github.com/lulucatdev/codex-be-serious","Development"],
146-
["Chrome DevTools","Wrapper for chrome-devtools-mcp","https://github.com/win4r/chrome-devtools-codex-plugin","Integrations"],
147-
["Launch Fast","Rapid SaaS deployment adapter","https://github.com/BlockchainHB/launchfast_codex_plugin","Integrations"],
148-
["PapersFlow","Paper discovery, citation verification, DeepScan","https://github.com/papersflow-ai/papersflow-codex-plugin","Integrations"],
149-
["Apple Productivity","Calendar and Reminders for macOS","https://github.com/matk0shub/apple-productivity-mcp","Integrations"],
150-
["Yandex Direct","Yandex Direct, Wordstat, Metrika, Roistat","https://github.com/nebelov/yandex-direct-for-all","Integrations"],
151-
["OpenProject","Team collaboration via OpenProject","https://github.com/varaprasadreddy9676/team-codex-plugins","Integrations"],
152-
["OrgX","MCP access and initiative-aware skills","https://github.com/useorgx/orgx-codex-plugin","Integrations"],
153-
["Codex Mem","Capture and inject session context","https://github.com/2kDarki/codex-mem","Integrations"]
154-
];
155-
156-
const card = ([name, desc, url], official) => `
157-
<a href="${url}" target="_blank" rel="noopener"
161+
const O = ${JSON.stringify(official)};
162+
const C = ${JSON.stringify(community.map(p => [p.name, p.desc, p.url, p.cat]))};
163+
164+
const card = ([name, desc, url], official) => \`
165+
<a href="\${url}" target="_blank" rel="noopener"
158166
class="group block p-4 rounded-xl border border-gray-200 bg-surface-50 hover:border-hol-purple/30 hover:bg-white hover:shadow-sm transition-all">
159167
<div class="flex items-start justify-between gap-2">
160-
<h3 class="font-mono text-sm font-medium text-gray-900">${name}</h3>
161-
<span class="font-mono text-[10px] font-medium uppercase tracking-wide px-2 py-0.5 rounded shrink-0 ${official ? 'bg-purple-50 text-hol-purple' : 'bg-emerald-50 text-emerald-600'}">${official ? 'Official' : 'Verified'}</span>
168+
<h3 class="font-mono text-sm font-medium text-gray-900">\${name}</h3>
169+
<span class="font-mono text-[10px] font-medium uppercase tracking-wide px-2 py-0.5 rounded shrink-0 \${official ? 'bg-purple-50 text-hol-purple' : 'bg-emerald-50 text-emerald-600'}">\${official ? 'Official' : 'Verified'}</span>
162170
</div>
163-
<p class="mt-2 text-sm text-gray-400 leading-relaxed line-clamp-2">${desc}</p>
164-
</a>`;
171+
<p class="mt-2 text-sm text-gray-400 leading-relaxed line-clamp-2">\${desc}</p>
172+
</a>\`;
165173
166174
const q = document.getElementById('q');
167175
const lo = document.getElementById('list-official');
168176
const catsEl = document.getElementById('cats');
169177
const empty = document.getElementById('empty');
170178
const secO = document.getElementById('sec-official');
171179
const secC = document.getElementById('sec-community');
172-
const oCount = document.getElementById('official-count');
173-
const cCount = document.getElementById('community-count');
174180
175181
function render() {
176182
const s = q.value.toLowerCase();
177183
const fo = s ? O.filter(([n,d]) => n.toLowerCase().includes(s) || d.toLowerCase().includes(s)) : O;
178184
const fc = s ? C.filter(([n,d]) => n.toLowerCase().includes(s) || d.toLowerCase().includes(s)) : C;
179-
180185
secO.style.display = fo.length ? '' : 'none';
181186
secC.style.display = fc.length ? '' : 'none';
182187
empty.classList.toggle('hidden', fo.length || fc.length);
183-
oCount.textContent = fo.length;
184-
cCount.textContent = fc.length;
185-
186188
lo.innerHTML = fo.map(p => card(p, true)).join('');
187-
188189
const groups = {};
189190
fc.forEach(p => { (groups[p[3]] = groups[p[3]] || []).push(p); });
190191
catsEl.innerHTML = Object.entries(groups).map(([cat, items]) =>
191-
`<p class="font-mono text-[10px] uppercase tracking-widest text-gray-300 font-medium mt-8 mb-3">${cat}</p><div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">${items.map(p => card(p, false)).join('')}</div>`
192+
\`<p class="font-mono text-[10px] uppercase tracking-widest text-gray-300 font-medium mt-8 mb-3">\${cat}</p><div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">\${items.map(p => card(p, false)).join('')}</div>\`
192193
).join('');
193194
}
194-
195195
render();
196196
q.addEventListener('input', render);
197197
</script>
198198
</body>
199-
</html>
199+
</html>`;
200+
201+
fs.mkdirSync('dist', { recursive: true });
202+
fs.writeFileSync('dist/index.html', siteHtml);
203+
console.log(`Built site: ${total} plugins (${official.length} official, ${community.length} community)`);

0 commit comments

Comments
 (0)