agent-templates 0.1.0 → 0.2.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/ADOPTING.md +57 -57
- package/CLAUDE.md +128 -125
- package/LICENSE +21 -21
- package/README.md +32 -32
- package/package.json +32 -32
- package/patterns/three-agent-architect-builder-reviewer/README.md +161 -153
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/agents/architect.md +31 -31
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/agents/builder.md +25 -25
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/agents/reviewer.md +33 -33
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/agents/triage.md +31 -31
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/breakdown-prd.md +18 -18
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/nightly-issues.md +14 -14
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/review-ticket.md +14 -14
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/start-all.md +17 -0
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/start-milestone.md +15 -15
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/verify-delivery.md +20 -20
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/hooks/guard-main-session-writes.mjs +53 -53
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/scripts/milestone-dag.mjs +94 -0
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/scripts/publish-tickets.mjs +263 -263
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/settings.json +15 -15
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/workflows/nightly-issues.js +139 -139
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/workflows/run-milestone.js +223 -223
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/workflows/start-all.js +118 -0
- package/patterns/three-agent-architect-builder-reviewer/scaffold/INSTALL.md +65 -65
- package/patterns/three-agent-architect-builder-reviewer/scaffold/claude-md-snippet.md +38 -37
- package/scripts/adopt.mjs +187 -177
- package/scripts/build-site.mjs +264 -264
- package/scripts/cli.mjs +52 -52
- package/templates/pattern-README.template.md +65 -65
- package/templates/ticket.template.md +104 -104
- package/templates/tracker/github/ISSUE_TEMPLATE/bug-report.md +28 -28
- package/templates/tracker/github/ISSUE_TEMPLATE/decision-record.md +25 -25
- package/templates/tracker/github/ISSUE_TEMPLATE/task.md +36 -36
- package/templates/tracker/github/PULL_REQUEST_TEMPLATE.md +54 -54
- package/templates/tracker/gitlab/issue_templates/bug-report.md +23 -23
- package/templates/tracker/gitlab/issue_templates/decision-record.md +20 -20
- package/templates/tracker/gitlab/issue_templates/task.md +31 -31
- package/templates/tracker/gitlab/merge_request_templates/default.md +55 -55
package/scripts/build-site.mjs
CHANGED
|
@@ -1,264 +1,264 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// build-site.mjs — generates the catalog's GitHub Pages site from the catalog's own
|
|
3
|
-
// data (patterns/*/README.md + package.json). Never hand-edit the output: the page
|
|
4
|
-
// must not be able to drift from the pattern metadata.
|
|
5
|
-
//
|
|
6
|
-
// Usage: node scripts/build-site.mjs [--out <dir>] (default: site/)
|
|
7
|
-
// Output: <out>/index.html (self-contained) + <out>/.nojekyll
|
|
8
|
-
//
|
|
9
|
-
// Style: claymorphism per catalog issue #17 — cream background, pastel clay palette,
|
|
10
|
-
// puffy cards, soft extruded shadows, rounded chunky type, emoji as clay icons.
|
|
11
|
-
|
|
12
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
13
|
-
import { join } from 'node:path'
|
|
14
|
-
import { fileURLToPath } from 'node:url'
|
|
15
|
-
|
|
16
|
-
const ROOT = fileURLToPath(new URL('..', import.meta.url))
|
|
17
|
-
const argv = process.argv.slice(2)
|
|
18
|
-
const outIx = argv.indexOf('--out')
|
|
19
|
-
const OUT = outIx !== -1 && argv[outIx + 1] ? argv[outIx + 1] : join(ROOT, 'site')
|
|
20
|
-
|
|
21
|
-
const GITHUB = 'https://github.com/Ruihang2017/agent-templates'
|
|
22
|
-
const NPM = 'https://www.npmjs.com/package/agent-templates'
|
|
23
|
-
const QUICKSTART = 'npx agent-templates@latest adopt three-agent-architect-builder-reviewer .'
|
|
24
|
-
|
|
25
|
-
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
|
26
|
-
const strip = (s) => String(s).replace(/\*\*/g, '').replace(/`/g, '').trim()
|
|
27
|
-
|
|
28
|
-
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'))
|
|
29
|
-
|
|
30
|
-
function parsePattern(dir) {
|
|
31
|
-
const path = join(ROOT, 'patterns', dir, 'README.md')
|
|
32
|
-
if (!existsSync(path)) return null
|
|
33
|
-
const md = readFileSync(path, 'utf8')
|
|
34
|
-
const pick = (re) => (md.match(re) || [])[1] || ''
|
|
35
|
-
|
|
36
|
-
const title = strip(pick(/^# Pattern: (.+)$/m)) || dir
|
|
37
|
-
const statusRaw = strip(pick(/\|\s*\*\*Status\*\*\s*\|\s*([^|]+)\|/))
|
|
38
|
-
const status = (statusRaw.match(/[a-z]+/) || ['proposed'])[0]
|
|
39
|
-
const asOf = strip(pick(/\|\s*\*\*As-of date\*\*\s*\|\s*([^|]+)\|/))
|
|
40
|
-
|
|
41
|
-
// the one-line topology summary: first plain paragraph after the metadata table
|
|
42
|
-
let summary = ''
|
|
43
|
-
const lines = md.split('\n')
|
|
44
|
-
for (let i = 0; i < lines.length; i++) {
|
|
45
|
-
const l = lines[i].trim()
|
|
46
|
-
if (!l || l.startsWith('#') || l.startsWith('|') || l.startsWith('<!--')) continue
|
|
47
|
-
summary = strip(l)
|
|
48
|
-
break
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// model/effort rows from §3
|
|
52
|
-
const roles = []
|
|
53
|
-
const sec3 = md.split(/^## 3\. Model \+ effort[^\n]*$/m)[1]
|
|
54
|
-
if (sec3) {
|
|
55
|
-
for (const row of sec3.split(/^## /m)[0].split('\n')) {
|
|
56
|
-
const m = row.match(/^\|\s*([A-Z][^|]*?)\s*\|\s*([^|]+?)\s*\|\s*`([^`]+)`\s*\|/)
|
|
57
|
-
if (m && !/^Role/.test(m[1])) roles.push({ role: strip(m[1]), model: strip(m[2]), effort: m[3] })
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// first three "Use when" bullets from §1
|
|
62
|
-
const useWhen = []
|
|
63
|
-
const sec1 = md.split(/\*\*Use when:\*\*/)[1]
|
|
64
|
-
if (sec1) {
|
|
65
|
-
for (const l of sec1.split(/\*\*Do not use when:\*\*/)[0].split('\n')) {
|
|
66
|
-
const m = l.match(/^- (.+)$/)
|
|
67
|
-
if (m && useWhen.length < 3) useWhen.push(strip(m[1]))
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return { dir, title, status, asOf, summary, roles, useWhen }
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const patterns = readdirSync(join(ROOT, 'patterns'), { withFileTypes: true })
|
|
75
|
-
.filter((d) => d.isDirectory() && existsSync(join(ROOT, 'patterns', d.name, 'scaffold')))
|
|
76
|
-
.map((d) => parsePattern(d.name))
|
|
77
|
-
.filter(Boolean)
|
|
78
|
-
|
|
79
|
-
const STATUS_STYLE = {
|
|
80
|
-
proposed: ['var(--blue)', 'var(--blue-d)', '🧪'],
|
|
81
|
-
trialed: ['var(--butter)', 'var(--butter-d)', '🌱'],
|
|
82
|
-
adopted: ['var(--sage)', 'var(--sage-d)', '✅'],
|
|
83
|
-
deprecated: ['var(--pink)', 'var(--pink-d)', '🗄️'],
|
|
84
|
-
}
|
|
85
|
-
const ROLE_EMOJI = { Architect: '📐', Builder: '🔨', Reviewer: '🔍' }
|
|
86
|
-
const roleEmoji = (r) => ROLE_EMOJI[Object.keys(ROLE_EMOJI).find((k) => r.startsWith(k))] || '🌙'
|
|
87
|
-
|
|
88
|
-
const patternCards = patterns
|
|
89
|
-
.map((p) => {
|
|
90
|
-
const [bg, fg, ico] = STATUS_STYLE[p.status] || STATUS_STYLE.proposed
|
|
91
|
-
return `
|
|
92
|
-
<article class="card pattern">
|
|
93
|
-
<div class="pattern-head">
|
|
94
|
-
<h3>${esc(p.title)}</h3>
|
|
95
|
-
<span class="pill status" style="background:${bg};color:${fg}">${ico} ${esc(p.status)} · as of ${esc(p.asOf)}</span>
|
|
96
|
-
</div>
|
|
97
|
-
<p class="summary">${esc(p.summary)}</p>
|
|
98
|
-
<div class="roles">
|
|
99
|
-
${p.roles.map((r) => `<span class="pill role">${roleEmoji(r.role)} <b>${esc(r.role)}</b> · ${esc(r.model)} <code>@${esc(r.effort)}</code></span>`).join('\n ')}
|
|
100
|
-
</div>
|
|
101
|
-
${p.useWhen.length ? `<ul class="usewhen">${p.useWhen.map((u) => `<li>${esc(u)}</li>`).join('')}</ul>` : ''}
|
|
102
|
-
<div class="links">
|
|
103
|
-
<a class="btn small sage" href="${GITHUB}/tree/main/patterns/${esc(p.dir)}">📖 Pattern write-up</a>
|
|
104
|
-
<a class="btn small lav" href="${GITHUB}/tree/main/patterns/${esc(p.dir)}/scaffold">🧰 Scaffold</a>
|
|
105
|
-
</div>
|
|
106
|
-
</article>`
|
|
107
|
-
})
|
|
108
|
-
.join('\n')
|
|
109
|
-
|
|
110
|
-
const STEPS = [
|
|
111
|
-
['📦', 'Adopt', `<code>${esc(QUICKSTART)}</code> — scaffold, templates, docs skeleton, CLAUDE.md, in one idempotent command.`],
|
|
112
|
-
['🗺️', 'Break down', '<code>/breakdown-prd</code> — the Architect turns your PRD into sub-PRDs and cold-startable tickets, then stops.'],
|
|
113
|
-
['🚦', 'Gate 1 — you decide', 'Review the breakdown, then <code>/start-milestone</code>: tickets become tracker issues and the pipeline starts.'],
|
|
114
|
-
['🤖', 'Autonomous middle', 'Plan → build → fresh-context review (bounce-capped in code) → merge on CLEAR → issue closed → delivery verified.'],
|
|
115
|
-
['🔎', 'Gate 2 — smoke test', 'Agents own unit/integration/E2E all along; you test once, when the PRD is done. A nightly sweep fixes issues while you sleep.'],
|
|
116
|
-
]
|
|
117
|
-
|
|
118
|
-
const html = `<!doctype html>
|
|
119
|
-
<html lang="en">
|
|
120
|
-
<head>
|
|
121
|
-
<meta charset="utf-8">
|
|
122
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
123
|
-
<title>agent-templates — multi-agent patterns, ready to drop in</title>
|
|
124
|
-
<meta name="description" content="A catalog of multi-agent development architecture patterns: design write-ups plus drop-in scaffolding, E2E-tested, published on npm.">
|
|
125
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
126
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
127
|
-
<link href="https://fonts.googleapis.com/css2?family=Baloo+2:wght@600;700;800&family=Nunito:ital,wght@0,400;0,600;0,700;1,400&display=swap" rel="stylesheet">
|
|
128
|
-
<style>
|
|
129
|
-
:root{
|
|
130
|
-
--bg:#f7f0e6; --card:#fdf8f2; --ink:#5b4a3f; --muted:#96826f;
|
|
131
|
-
--sage:#cfe3d0; --sage-d:#5f9c6c; --peach:#f9d3b5; --peach-d:#d97f45;
|
|
132
|
-
--pink:#f7c6d0; --pink-d:#cf6d87; --butter:#f7e0a8; --butter-d:#a97e1c;
|
|
133
|
-
--blue:#c5d7ee; --blue-d:#5c82b6; --lav:#d9cdf0; --lav-d:#8266bd;
|
|
134
|
-
--clay:0 10px 24px rgba(91,74,63,.13), inset 0 3px 8px rgba(255,255,255,.95), inset 0 -4px 8px rgba(91,74,63,.07);
|
|
135
|
-
--clay-sm:0 6px 14px rgba(91,74,63,.12), inset 0 2px 5px rgba(255,255,255,.9), inset 0 -2px 5px rgba(91,74,63,.06);
|
|
136
|
-
}
|
|
137
|
-
*{box-sizing:border-box}
|
|
138
|
-
body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.6 "Nunito",system-ui,sans-serif;
|
|
139
|
-
background-image:radial-gradient(600px 300px at 85% -50px, rgba(247,198,208,.35), transparent 70%),
|
|
140
|
-
radial-gradient(500px 260px at -60px 30%, rgba(207,227,208,.4), transparent 70%),
|
|
141
|
-
radial-gradient(520px 300px at 110% 75%, rgba(217,205,240,.32), transparent 70%);}
|
|
142
|
-
h1,h2,h3{font-family:"Baloo 2","Nunito",sans-serif;line-height:1.15;margin:0}
|
|
143
|
-
a{color:inherit;text-decoration:none}
|
|
144
|
-
.wrap{max-width:1060px;margin:0 auto;padding:28px 20px 60px}
|
|
145
|
-
header.nav{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:26px}
|
|
146
|
-
.logo{font-family:"Baloo 2",sans-serif;font-weight:800;font-size:1.35rem;background:var(--card);
|
|
147
|
-
padding:10px 20px;border-radius:999px;box-shadow:var(--clay-sm);border:1.5px solid #fff}
|
|
148
|
-
.nav .spacer{flex:1}
|
|
149
|
-
.btn{display:inline-flex;align-items:center;gap:8px;font-weight:700;padding:10px 20px;border-radius:999px;
|
|
150
|
-
box-shadow:var(--clay-sm);border:1.5px solid #fff;transition:transform .15s ease, box-shadow .15s ease;cursor:pointer}
|
|
151
|
-
.btn:hover{transform:translateY(-2px)}
|
|
152
|
-
.btn.small{padding:8px 14px;font-size:.9rem}
|
|
153
|
-
.btn.sage{background:var(--sage);color:var(--sage-d)} .btn.peach{background:var(--peach);color:var(--peach-d)}
|
|
154
|
-
.btn.blue{background:var(--blue);color:var(--blue-d)} .btn.lav{background:var(--lav);color:var(--lav-d)}
|
|
155
|
-
.card{background:var(--card);border-radius:28px;box-shadow:var(--clay);border:1.5px solid #fff;padding:26px 28px}
|
|
156
|
-
.hero{display:grid;grid-template-columns:1.5fr 1fr;gap:22px;align-items:stretch;margin-bottom:22px}
|
|
157
|
-
.hero .main{background:linear-gradient(145deg,#fbe3cd, #f9d3b5);}
|
|
158
|
-
.hero h1{font-size:clamp(1.9rem,4.5vw,2.9rem);color:#7a4b28;margin-bottom:10px}
|
|
159
|
-
.hero p.lede{color:#8a5a35;font-size:1.06rem;max-width:34em;margin:0 0 18px}
|
|
160
|
-
.cta{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:18px}
|
|
161
|
-
.quick{background:rgba(255,255,255,.75);border-radius:18px;box-shadow:var(--clay-sm);border:1.5px solid #fff;
|
|
162
|
-
padding:12px 16px;display:flex;gap:10px;align-items:center;flex-wrap:wrap}
|
|
163
|
-
.quick code{font:600 .85rem/1.4 ui-monospace,Consolas,monospace;color:#7a4b28;word-break:break-all}
|
|
164
|
-
.hero .side{display:flex;flex-direction:column;gap:14px;justify-content:center}
|
|
165
|
-
.side .fact{display:flex;gap:12px;align-items:center;background:var(--card);border-radius:20px;
|
|
166
|
-
box-shadow:var(--clay-sm);border:1.5px solid #fff;padding:12px 16px;font-weight:600}
|
|
167
|
-
.side .fact .ico{font-size:1.5rem}
|
|
168
|
-
.chips{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:16px;margin:22px 0}
|
|
169
|
-
.chip{border-radius:24px;padding:18px 20px;box-shadow:var(--clay);border:1.5px solid #fff}
|
|
170
|
-
.chip .big{font-family:"Baloo 2",sans-serif;font-weight:800;font-size:1.7rem;display:block}
|
|
171
|
-
.chip small{font-weight:700;opacity:.75}
|
|
172
|
-
.chip.c1{background:var(--sage);color:var(--sage-d)} .chip.c2{background:var(--peach);color:var(--peach-d)}
|
|
173
|
-
.chip.c3{background:var(--butter);color:var(--butter-d)} .chip.c4{background:var(--blue);color:var(--blue-d)}
|
|
174
|
-
section{margin-top:34px}
|
|
175
|
-
section > h2{font-size:1.6rem;margin-bottom:14px}
|
|
176
|
-
.pattern{margin-bottom:18px}
|
|
177
|
-
.pattern-head{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;align-items:center;margin-bottom:8px}
|
|
178
|
-
.pattern h3{font-size:1.3rem}
|
|
179
|
-
.pill{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:999px;font-weight:700;
|
|
180
|
-
font-size:.85rem;box-shadow:var(--clay-sm);border:1.5px solid #fff}
|
|
181
|
-
.summary{color:var(--muted);margin:6px 0 14px;font-weight:600}
|
|
182
|
-
.roles{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:12px}
|
|
183
|
-
.pill.role{background:#f3ece1;color:var(--ink)} .pill.role code{color:var(--lav-d)}
|
|
184
|
-
.usewhen{margin:0 0 14px;padding-left:22px;color:var(--muted)} .usewhen li{margin:4px 0}
|
|
185
|
-
.links{display:flex;gap:10px;flex-wrap:wrap}
|
|
186
|
-
.steps{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:16px}
|
|
187
|
-
.step{border-radius:24px;padding:20px;box-shadow:var(--clay);border:1.5px solid #fff;background:var(--card)}
|
|
188
|
-
.step .n{display:inline-flex;width:44px;height:44px;align-items:center;justify-content:center;font-size:1.4rem;
|
|
189
|
-
border-radius:16px;box-shadow:var(--clay-sm);border:1.5px solid #fff;margin-bottom:10px}
|
|
190
|
-
.step:nth-child(1) .n{background:var(--sage)} .step:nth-child(2) .n{background:var(--butter)}
|
|
191
|
-
.step:nth-child(3) .n{background:var(--peach)} .step:nth-child(4) .n{background:var(--lav)}
|
|
192
|
-
.step:nth-child(5) .n{background:var(--pink)}
|
|
193
|
-
.step h3{font-size:1.05rem;margin-bottom:6px} .step p{margin:0;font-size:.9rem;color:var(--muted)}
|
|
194
|
-
.step code{font-size:.8rem;color:var(--lav-d);word-break:break-all}
|
|
195
|
-
footer{margin-top:44px;text-align:center;color:var(--muted);font-weight:600;font-size:.9rem}
|
|
196
|
-
footer a{color:var(--lav-d)}
|
|
197
|
-
@media (max-width:760px){.hero{grid-template-columns:1fr}}
|
|
198
|
-
</style>
|
|
199
|
-
</head>
|
|
200
|
-
<body>
|
|
201
|
-
<div class="wrap">
|
|
202
|
-
<header class="nav">
|
|
203
|
-
<span class="logo">🏗️ agent-templates</span>
|
|
204
|
-
<span class="spacer"></span>
|
|
205
|
-
<a class="btn sage" href="${GITHUB}">⭐ GitHub</a>
|
|
206
|
-
<a class="btn peach" href="${NPM}">📦 npm <span data-npm-version>v${esc(pkg.version)}</span></a>
|
|
207
|
-
</header>
|
|
208
|
-
|
|
209
|
-
<div class="hero">
|
|
210
|
-
<div class="card main">
|
|
211
|
-
<h1>Multi-agent patterns,<br>ready to drop in.</h1>
|
|
212
|
-
<p class="lede">Field-proven architectures for AI-agent development — each one a design write-up <b>plus</b> working scaffolding. Humans decide at two gates; the agents do the rest.</p>
|
|
213
|
-
<div class="cta">
|
|
214
|
-
<a class="btn blue" href="${GITHUB}/blob/main/ADOPTING.md">🚀 Adoption guide</a>
|
|
215
|
-
<a class="btn lav" href="${GITHUB}/blob/main/CLAUDE.md">📜 Operating manual</a>
|
|
216
|
-
</div>
|
|
217
|
-
<div class="quick"><code id="qs">${esc(QUICKSTART)}</code><button class="btn small butter" style="background:var(--butter);color:var(--butter-d)" onclick="navigator.clipboard.writeText(document.getElementById('qs').textContent).then(()=>{this.textContent='Copied ✓'})">Copy</button></div>
|
|
218
|
-
</div>
|
|
219
|
-
<div class="side">
|
|
220
|
-
<div class="fact"><span class="ico">🧾</span> Every model/effort claim carries a source label and an expiry date</div>
|
|
221
|
-
<div class="fact"><span class="ico">🛡️</span> Role boundaries enforced by hooks, not prose</div>
|
|
222
|
-
<div class="fact"><span class="ico">🌙</span> Nightly sweep triages and fixes issues while you sleep</div>
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
|
|
226
|
-
<div class="chips">
|
|
227
|
-
<div class="chip c1"><span class="big">${patterns.length}</span><small>pattern${patterns.length === 1 ? '' : 's'} in the catalog</small></div>
|
|
228
|
-
<div class="chip c2"><span class="big" data-npm-version>v${esc(pkg.version)}</span><small>on npm · MIT</small></div>
|
|
229
|
-
<div class="chip c3"><span class="big">E2E</span><small>gated merges — deterministic, zero-token tests</small></div>
|
|
230
|
-
<div class="chip c4"><span class="big">2</span><small>human gates: sign-off & smoke test</small></div>
|
|
231
|
-
</div>
|
|
232
|
-
|
|
233
|
-
<section>
|
|
234
|
-
<h2>🧩 Patterns</h2>
|
|
235
|
-
${patternCards}
|
|
236
|
-
</section>
|
|
237
|
-
|
|
238
|
-
<section>
|
|
239
|
-
<h2>🛤️ From a bare PRD.md to shipped</h2>
|
|
240
|
-
<div class="steps">
|
|
241
|
-
${STEPS.map(([ico, t, d], i) => `<div class="step"><span class="n">${ico}</span><h3>${i + 1}. ${t}</h3><p>${d}</p></div>`).join('\n ')}
|
|
242
|
-
</div>
|
|
243
|
-
</section>
|
|
244
|
-
|
|
245
|
-
<footer>
|
|
246
|
-
Generated from the pattern catalog by <a href="${GITHUB}/blob/main/scripts/build-site.mjs">scripts/build-site.mjs</a>
|
|
247
|
-
· ${new Date().toISOString().slice(0, 10)} · <a href="${GITHUB}/blob/main/LICENSE">MIT</a>
|
|
248
|
-
· <a href="${GITHUB}/issues/new/choose">Feedback → issues</a>
|
|
249
|
-
</footer>
|
|
250
|
-
</div>
|
|
251
|
-
<script>
|
|
252
|
-
fetch('https://registry.npmjs.org/agent-templates').then(r=>r.json()).then(d=>{
|
|
253
|
-
var v=d['dist-tags']&&d['dist-tags'].latest;
|
|
254
|
-
if(v){document.querySelectorAll('[data-npm-version]').forEach(function(e){e.textContent='v'+v})}
|
|
255
|
-
}).catch(function(){})
|
|
256
|
-
</script>
|
|
257
|
-
</body>
|
|
258
|
-
</html>
|
|
259
|
-
`
|
|
260
|
-
|
|
261
|
-
mkdirSync(OUT, { recursive: true })
|
|
262
|
-
writeFileSync(join(OUT, 'index.html'), html)
|
|
263
|
-
writeFileSync(join(OUT, '.nojekyll'), '')
|
|
264
|
-
console.log(`site: ${join(OUT, 'index.html')} (${patterns.length} pattern(s), pkg v${pkg.version})`)
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// build-site.mjs — generates the catalog's GitHub Pages site from the catalog's own
|
|
3
|
+
// data (patterns/*/README.md + package.json). Never hand-edit the output: the page
|
|
4
|
+
// must not be able to drift from the pattern metadata.
|
|
5
|
+
//
|
|
6
|
+
// Usage: node scripts/build-site.mjs [--out <dir>] (default: site/)
|
|
7
|
+
// Output: <out>/index.html (self-contained) + <out>/.nojekyll
|
|
8
|
+
//
|
|
9
|
+
// Style: claymorphism per catalog issue #17 — cream background, pastel clay palette,
|
|
10
|
+
// puffy cards, soft extruded shadows, rounded chunky type, emoji as clay icons.
|
|
11
|
+
|
|
12
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
13
|
+
import { join } from 'node:path'
|
|
14
|
+
import { fileURLToPath } from 'node:url'
|
|
15
|
+
|
|
16
|
+
const ROOT = fileURLToPath(new URL('..', import.meta.url))
|
|
17
|
+
const argv = process.argv.slice(2)
|
|
18
|
+
const outIx = argv.indexOf('--out')
|
|
19
|
+
const OUT = outIx !== -1 && argv[outIx + 1] ? argv[outIx + 1] : join(ROOT, 'site')
|
|
20
|
+
|
|
21
|
+
const GITHUB = 'https://github.com/Ruihang2017/agent-templates'
|
|
22
|
+
const NPM = 'https://www.npmjs.com/package/agent-templates'
|
|
23
|
+
const QUICKSTART = 'npx agent-templates@latest adopt three-agent-architect-builder-reviewer .'
|
|
24
|
+
|
|
25
|
+
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
|
26
|
+
const strip = (s) => String(s).replace(/\*\*/g, '').replace(/`/g, '').trim()
|
|
27
|
+
|
|
28
|
+
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'))
|
|
29
|
+
|
|
30
|
+
function parsePattern(dir) {
|
|
31
|
+
const path = join(ROOT, 'patterns', dir, 'README.md')
|
|
32
|
+
if (!existsSync(path)) return null
|
|
33
|
+
const md = readFileSync(path, 'utf8')
|
|
34
|
+
const pick = (re) => (md.match(re) || [])[1] || ''
|
|
35
|
+
|
|
36
|
+
const title = strip(pick(/^# Pattern: (.+)$/m)) || dir
|
|
37
|
+
const statusRaw = strip(pick(/\|\s*\*\*Status\*\*\s*\|\s*([^|]+)\|/))
|
|
38
|
+
const status = (statusRaw.match(/[a-z]+/) || ['proposed'])[0]
|
|
39
|
+
const asOf = strip(pick(/\|\s*\*\*As-of date\*\*\s*\|\s*([^|]+)\|/))
|
|
40
|
+
|
|
41
|
+
// the one-line topology summary: first plain paragraph after the metadata table
|
|
42
|
+
let summary = ''
|
|
43
|
+
const lines = md.split('\n')
|
|
44
|
+
for (let i = 0; i < lines.length; i++) {
|
|
45
|
+
const l = lines[i].trim()
|
|
46
|
+
if (!l || l.startsWith('#') || l.startsWith('|') || l.startsWith('<!--')) continue
|
|
47
|
+
summary = strip(l)
|
|
48
|
+
break
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// model/effort rows from §3
|
|
52
|
+
const roles = []
|
|
53
|
+
const sec3 = md.split(/^## 3\. Model \+ effort[^\n]*$/m)[1]
|
|
54
|
+
if (sec3) {
|
|
55
|
+
for (const row of sec3.split(/^## /m)[0].split('\n')) {
|
|
56
|
+
const m = row.match(/^\|\s*([A-Z][^|]*?)\s*\|\s*([^|]+?)\s*\|\s*`([^`]+)`\s*\|/)
|
|
57
|
+
if (m && !/^Role/.test(m[1])) roles.push({ role: strip(m[1]), model: strip(m[2]), effort: m[3] })
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// first three "Use when" bullets from §1
|
|
62
|
+
const useWhen = []
|
|
63
|
+
const sec1 = md.split(/\*\*Use when:\*\*/)[1]
|
|
64
|
+
if (sec1) {
|
|
65
|
+
for (const l of sec1.split(/\*\*Do not use when:\*\*/)[0].split('\n')) {
|
|
66
|
+
const m = l.match(/^- (.+)$/)
|
|
67
|
+
if (m && useWhen.length < 3) useWhen.push(strip(m[1]))
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { dir, title, status, asOf, summary, roles, useWhen }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const patterns = readdirSync(join(ROOT, 'patterns'), { withFileTypes: true })
|
|
75
|
+
.filter((d) => d.isDirectory() && existsSync(join(ROOT, 'patterns', d.name, 'scaffold')))
|
|
76
|
+
.map((d) => parsePattern(d.name))
|
|
77
|
+
.filter(Boolean)
|
|
78
|
+
|
|
79
|
+
const STATUS_STYLE = {
|
|
80
|
+
proposed: ['var(--blue)', 'var(--blue-d)', '🧪'],
|
|
81
|
+
trialed: ['var(--butter)', 'var(--butter-d)', '🌱'],
|
|
82
|
+
adopted: ['var(--sage)', 'var(--sage-d)', '✅'],
|
|
83
|
+
deprecated: ['var(--pink)', 'var(--pink-d)', '🗄️'],
|
|
84
|
+
}
|
|
85
|
+
const ROLE_EMOJI = { Architect: '📐', Builder: '🔨', Reviewer: '🔍' }
|
|
86
|
+
const roleEmoji = (r) => ROLE_EMOJI[Object.keys(ROLE_EMOJI).find((k) => r.startsWith(k))] || '🌙'
|
|
87
|
+
|
|
88
|
+
const patternCards = patterns
|
|
89
|
+
.map((p) => {
|
|
90
|
+
const [bg, fg, ico] = STATUS_STYLE[p.status] || STATUS_STYLE.proposed
|
|
91
|
+
return `
|
|
92
|
+
<article class="card pattern">
|
|
93
|
+
<div class="pattern-head">
|
|
94
|
+
<h3>${esc(p.title)}</h3>
|
|
95
|
+
<span class="pill status" style="background:${bg};color:${fg}">${ico} ${esc(p.status)} · as of ${esc(p.asOf)}</span>
|
|
96
|
+
</div>
|
|
97
|
+
<p class="summary">${esc(p.summary)}</p>
|
|
98
|
+
<div class="roles">
|
|
99
|
+
${p.roles.map((r) => `<span class="pill role">${roleEmoji(r.role)} <b>${esc(r.role)}</b> · ${esc(r.model)} <code>@${esc(r.effort)}</code></span>`).join('\n ')}
|
|
100
|
+
</div>
|
|
101
|
+
${p.useWhen.length ? `<ul class="usewhen">${p.useWhen.map((u) => `<li>${esc(u)}</li>`).join('')}</ul>` : ''}
|
|
102
|
+
<div class="links">
|
|
103
|
+
<a class="btn small sage" href="${GITHUB}/tree/main/patterns/${esc(p.dir)}">📖 Pattern write-up</a>
|
|
104
|
+
<a class="btn small lav" href="${GITHUB}/tree/main/patterns/${esc(p.dir)}/scaffold">🧰 Scaffold</a>
|
|
105
|
+
</div>
|
|
106
|
+
</article>`
|
|
107
|
+
})
|
|
108
|
+
.join('\n')
|
|
109
|
+
|
|
110
|
+
const STEPS = [
|
|
111
|
+
['📦', 'Adopt', `<code>${esc(QUICKSTART)}</code> — scaffold, templates, docs skeleton, CLAUDE.md, in one idempotent command.`],
|
|
112
|
+
['🗺️', 'Break down', '<code>/breakdown-prd</code> — the Architect turns your PRD into sub-PRDs and cold-startable tickets, then stops.'],
|
|
113
|
+
['🚦', 'Gate 1 — you decide', 'Review the breakdown, then <code>/start-milestone</code>: tickets become tracker issues and the pipeline starts.'],
|
|
114
|
+
['🤖', 'Autonomous middle', 'Plan → build → fresh-context review (bounce-capped in code) → merge on CLEAR → issue closed → delivery verified.'],
|
|
115
|
+
['🔎', 'Gate 2 — smoke test', 'Agents own unit/integration/E2E all along; you test once, when the PRD is done. A nightly sweep fixes issues while you sleep.'],
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
const html = `<!doctype html>
|
|
119
|
+
<html lang="en">
|
|
120
|
+
<head>
|
|
121
|
+
<meta charset="utf-8">
|
|
122
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
123
|
+
<title>agent-templates — multi-agent patterns, ready to drop in</title>
|
|
124
|
+
<meta name="description" content="A catalog of multi-agent development architecture patterns: design write-ups plus drop-in scaffolding, E2E-tested, published on npm.">
|
|
125
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
126
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
127
|
+
<link href="https://fonts.googleapis.com/css2?family=Baloo+2:wght@600;700;800&family=Nunito:ital,wght@0,400;0,600;0,700;1,400&display=swap" rel="stylesheet">
|
|
128
|
+
<style>
|
|
129
|
+
:root{
|
|
130
|
+
--bg:#f7f0e6; --card:#fdf8f2; --ink:#5b4a3f; --muted:#96826f;
|
|
131
|
+
--sage:#cfe3d0; --sage-d:#5f9c6c; --peach:#f9d3b5; --peach-d:#d97f45;
|
|
132
|
+
--pink:#f7c6d0; --pink-d:#cf6d87; --butter:#f7e0a8; --butter-d:#a97e1c;
|
|
133
|
+
--blue:#c5d7ee; --blue-d:#5c82b6; --lav:#d9cdf0; --lav-d:#8266bd;
|
|
134
|
+
--clay:0 10px 24px rgba(91,74,63,.13), inset 0 3px 8px rgba(255,255,255,.95), inset 0 -4px 8px rgba(91,74,63,.07);
|
|
135
|
+
--clay-sm:0 6px 14px rgba(91,74,63,.12), inset 0 2px 5px rgba(255,255,255,.9), inset 0 -2px 5px rgba(91,74,63,.06);
|
|
136
|
+
}
|
|
137
|
+
*{box-sizing:border-box}
|
|
138
|
+
body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.6 "Nunito",system-ui,sans-serif;
|
|
139
|
+
background-image:radial-gradient(600px 300px at 85% -50px, rgba(247,198,208,.35), transparent 70%),
|
|
140
|
+
radial-gradient(500px 260px at -60px 30%, rgba(207,227,208,.4), transparent 70%),
|
|
141
|
+
radial-gradient(520px 300px at 110% 75%, rgba(217,205,240,.32), transparent 70%);}
|
|
142
|
+
h1,h2,h3{font-family:"Baloo 2","Nunito",sans-serif;line-height:1.15;margin:0}
|
|
143
|
+
a{color:inherit;text-decoration:none}
|
|
144
|
+
.wrap{max-width:1060px;margin:0 auto;padding:28px 20px 60px}
|
|
145
|
+
header.nav{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:26px}
|
|
146
|
+
.logo{font-family:"Baloo 2",sans-serif;font-weight:800;font-size:1.35rem;background:var(--card);
|
|
147
|
+
padding:10px 20px;border-radius:999px;box-shadow:var(--clay-sm);border:1.5px solid #fff}
|
|
148
|
+
.nav .spacer{flex:1}
|
|
149
|
+
.btn{display:inline-flex;align-items:center;gap:8px;font-weight:700;padding:10px 20px;border-radius:999px;
|
|
150
|
+
box-shadow:var(--clay-sm);border:1.5px solid #fff;transition:transform .15s ease, box-shadow .15s ease;cursor:pointer}
|
|
151
|
+
.btn:hover{transform:translateY(-2px)}
|
|
152
|
+
.btn.small{padding:8px 14px;font-size:.9rem}
|
|
153
|
+
.btn.sage{background:var(--sage);color:var(--sage-d)} .btn.peach{background:var(--peach);color:var(--peach-d)}
|
|
154
|
+
.btn.blue{background:var(--blue);color:var(--blue-d)} .btn.lav{background:var(--lav);color:var(--lav-d)}
|
|
155
|
+
.card{background:var(--card);border-radius:28px;box-shadow:var(--clay);border:1.5px solid #fff;padding:26px 28px}
|
|
156
|
+
.hero{display:grid;grid-template-columns:1.5fr 1fr;gap:22px;align-items:stretch;margin-bottom:22px}
|
|
157
|
+
.hero .main{background:linear-gradient(145deg,#fbe3cd, #f9d3b5);}
|
|
158
|
+
.hero h1{font-size:clamp(1.9rem,4.5vw,2.9rem);color:#7a4b28;margin-bottom:10px}
|
|
159
|
+
.hero p.lede{color:#8a5a35;font-size:1.06rem;max-width:34em;margin:0 0 18px}
|
|
160
|
+
.cta{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:18px}
|
|
161
|
+
.quick{background:rgba(255,255,255,.75);border-radius:18px;box-shadow:var(--clay-sm);border:1.5px solid #fff;
|
|
162
|
+
padding:12px 16px;display:flex;gap:10px;align-items:center;flex-wrap:wrap}
|
|
163
|
+
.quick code{font:600 .85rem/1.4 ui-monospace,Consolas,monospace;color:#7a4b28;word-break:break-all}
|
|
164
|
+
.hero .side{display:flex;flex-direction:column;gap:14px;justify-content:center}
|
|
165
|
+
.side .fact{display:flex;gap:12px;align-items:center;background:var(--card);border-radius:20px;
|
|
166
|
+
box-shadow:var(--clay-sm);border:1.5px solid #fff;padding:12px 16px;font-weight:600}
|
|
167
|
+
.side .fact .ico{font-size:1.5rem}
|
|
168
|
+
.chips{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:16px;margin:22px 0}
|
|
169
|
+
.chip{border-radius:24px;padding:18px 20px;box-shadow:var(--clay);border:1.5px solid #fff}
|
|
170
|
+
.chip .big{font-family:"Baloo 2",sans-serif;font-weight:800;font-size:1.7rem;display:block}
|
|
171
|
+
.chip small{font-weight:700;opacity:.75}
|
|
172
|
+
.chip.c1{background:var(--sage);color:var(--sage-d)} .chip.c2{background:var(--peach);color:var(--peach-d)}
|
|
173
|
+
.chip.c3{background:var(--butter);color:var(--butter-d)} .chip.c4{background:var(--blue);color:var(--blue-d)}
|
|
174
|
+
section{margin-top:34px}
|
|
175
|
+
section > h2{font-size:1.6rem;margin-bottom:14px}
|
|
176
|
+
.pattern{margin-bottom:18px}
|
|
177
|
+
.pattern-head{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;align-items:center;margin-bottom:8px}
|
|
178
|
+
.pattern h3{font-size:1.3rem}
|
|
179
|
+
.pill{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:999px;font-weight:700;
|
|
180
|
+
font-size:.85rem;box-shadow:var(--clay-sm);border:1.5px solid #fff}
|
|
181
|
+
.summary{color:var(--muted);margin:6px 0 14px;font-weight:600}
|
|
182
|
+
.roles{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:12px}
|
|
183
|
+
.pill.role{background:#f3ece1;color:var(--ink)} .pill.role code{color:var(--lav-d)}
|
|
184
|
+
.usewhen{margin:0 0 14px;padding-left:22px;color:var(--muted)} .usewhen li{margin:4px 0}
|
|
185
|
+
.links{display:flex;gap:10px;flex-wrap:wrap}
|
|
186
|
+
.steps{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:16px}
|
|
187
|
+
.step{border-radius:24px;padding:20px;box-shadow:var(--clay);border:1.5px solid #fff;background:var(--card)}
|
|
188
|
+
.step .n{display:inline-flex;width:44px;height:44px;align-items:center;justify-content:center;font-size:1.4rem;
|
|
189
|
+
border-radius:16px;box-shadow:var(--clay-sm);border:1.5px solid #fff;margin-bottom:10px}
|
|
190
|
+
.step:nth-child(1) .n{background:var(--sage)} .step:nth-child(2) .n{background:var(--butter)}
|
|
191
|
+
.step:nth-child(3) .n{background:var(--peach)} .step:nth-child(4) .n{background:var(--lav)}
|
|
192
|
+
.step:nth-child(5) .n{background:var(--pink)}
|
|
193
|
+
.step h3{font-size:1.05rem;margin-bottom:6px} .step p{margin:0;font-size:.9rem;color:var(--muted)}
|
|
194
|
+
.step code{font-size:.8rem;color:var(--lav-d);word-break:break-all}
|
|
195
|
+
footer{margin-top:44px;text-align:center;color:var(--muted);font-weight:600;font-size:.9rem}
|
|
196
|
+
footer a{color:var(--lav-d)}
|
|
197
|
+
@media (max-width:760px){.hero{grid-template-columns:1fr}}
|
|
198
|
+
</style>
|
|
199
|
+
</head>
|
|
200
|
+
<body>
|
|
201
|
+
<div class="wrap">
|
|
202
|
+
<header class="nav">
|
|
203
|
+
<span class="logo">🏗️ agent-templates</span>
|
|
204
|
+
<span class="spacer"></span>
|
|
205
|
+
<a class="btn sage" href="${GITHUB}">⭐ GitHub</a>
|
|
206
|
+
<a class="btn peach" href="${NPM}">📦 npm <span data-npm-version>v${esc(pkg.version)}</span></a>
|
|
207
|
+
</header>
|
|
208
|
+
|
|
209
|
+
<div class="hero">
|
|
210
|
+
<div class="card main">
|
|
211
|
+
<h1>Multi-agent patterns,<br>ready to drop in.</h1>
|
|
212
|
+
<p class="lede">Field-proven architectures for AI-agent development — each one a design write-up <b>plus</b> working scaffolding. Humans decide at two gates; the agents do the rest.</p>
|
|
213
|
+
<div class="cta">
|
|
214
|
+
<a class="btn blue" href="${GITHUB}/blob/main/ADOPTING.md">🚀 Adoption guide</a>
|
|
215
|
+
<a class="btn lav" href="${GITHUB}/blob/main/CLAUDE.md">📜 Operating manual</a>
|
|
216
|
+
</div>
|
|
217
|
+
<div class="quick"><code id="qs">${esc(QUICKSTART)}</code><button class="btn small butter" style="background:var(--butter);color:var(--butter-d)" onclick="navigator.clipboard.writeText(document.getElementById('qs').textContent).then(()=>{this.textContent='Copied ✓'})">Copy</button></div>
|
|
218
|
+
</div>
|
|
219
|
+
<div class="side">
|
|
220
|
+
<div class="fact"><span class="ico">🧾</span> Every model/effort claim carries a source label and an expiry date</div>
|
|
221
|
+
<div class="fact"><span class="ico">🛡️</span> Role boundaries enforced by hooks, not prose</div>
|
|
222
|
+
<div class="fact"><span class="ico">🌙</span> Nightly sweep triages and fixes issues while you sleep</div>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<div class="chips">
|
|
227
|
+
<div class="chip c1"><span class="big">${patterns.length}</span><small>pattern${patterns.length === 1 ? '' : 's'} in the catalog</small></div>
|
|
228
|
+
<div class="chip c2"><span class="big" data-npm-version>v${esc(pkg.version)}</span><small>on npm · MIT</small></div>
|
|
229
|
+
<div class="chip c3"><span class="big">E2E</span><small>gated merges — deterministic, zero-token tests</small></div>
|
|
230
|
+
<div class="chip c4"><span class="big">2</span><small>human gates: sign-off & smoke test</small></div>
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
<section>
|
|
234
|
+
<h2>🧩 Patterns</h2>
|
|
235
|
+
${patternCards}
|
|
236
|
+
</section>
|
|
237
|
+
|
|
238
|
+
<section>
|
|
239
|
+
<h2>🛤️ From a bare PRD.md to shipped</h2>
|
|
240
|
+
<div class="steps">
|
|
241
|
+
${STEPS.map(([ico, t, d], i) => `<div class="step"><span class="n">${ico}</span><h3>${i + 1}. ${t}</h3><p>${d}</p></div>`).join('\n ')}
|
|
242
|
+
</div>
|
|
243
|
+
</section>
|
|
244
|
+
|
|
245
|
+
<footer>
|
|
246
|
+
Generated from the pattern catalog by <a href="${GITHUB}/blob/main/scripts/build-site.mjs">scripts/build-site.mjs</a>
|
|
247
|
+
· ${new Date().toISOString().slice(0, 10)} · <a href="${GITHUB}/blob/main/LICENSE">MIT</a>
|
|
248
|
+
· <a href="${GITHUB}/issues/new/choose">Feedback → issues</a>
|
|
249
|
+
</footer>
|
|
250
|
+
</div>
|
|
251
|
+
<script>
|
|
252
|
+
fetch('https://registry.npmjs.org/agent-templates').then(r=>r.json()).then(d=>{
|
|
253
|
+
var v=d['dist-tags']&&d['dist-tags'].latest;
|
|
254
|
+
if(v){document.querySelectorAll('[data-npm-version]').forEach(function(e){e.textContent='v'+v})}
|
|
255
|
+
}).catch(function(){})
|
|
256
|
+
</script>
|
|
257
|
+
</body>
|
|
258
|
+
</html>
|
|
259
|
+
`
|
|
260
|
+
|
|
261
|
+
mkdirSync(OUT, { recursive: true })
|
|
262
|
+
writeFileSync(join(OUT, 'index.html'), html)
|
|
263
|
+
writeFileSync(join(OUT, '.nojekyll'), '')
|
|
264
|
+
console.log(`site: ${join(OUT, 'index.html')} (${patterns.length} pattern(s), pkg v${pkg.version})`)
|