1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126 | #!/usr/bin/env python3
"""Builds the prompts landing page from the prompt files.
site/gen-index.py --out deploy/.site
Reads every <name>.md at the repo root (README.md excluded), extracts:
title — the "# Prompt: …" heading
summary — the first paragraph after it
needs / costs — the "> **Needs:** …" / "> **Costs:** …" lines
body — everything after the first "---" line (what gets pasted)
fillin — the fenced block right after "### FILL IN"
and writes <out>/index.html from site/index.html with the JSON embedded,
plus a copy of each prompt file at <out>/<name>.md so agents can fetch
the raw text by URL.
"""
import argparse
import json
import re
import shutil
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
TEMPLATE = ROOT / "site" / "index.html"
# curated order + group; anything else is appended alphabetically
ORDER = [
("register-domain", "domains & dns"),
("bring-domain-dns", "domains & dns"),
("dns-records", "domains & dns"),
("static-site-ipfs", "hosting"),
("share-files-ipfs", "hosting"),
("expose-local-app", "hosting"),
("publish-repos", "hosting"),
("vps-server", "servers"),
("one-click-app", "servers"),
("p2p-app", "p2p"),
("account-audit", "housekeeping"),
]
FILLIN_RE = re.compile(r"### FILL IN\s*\n```[^\n]*\n(.*?)\n```", re.S)
META_RE = re.compile(r"^> \*\*(Needs|Costs|Group):\*\* (.*)$", re.M)
GROUPS = ["domains & dns", "hosting", "servers", "p2p", "developer workflow",
"housekeeping", "more"]
def parse(path: Path) -> dict:
text = path.read_text(encoding="utf-8")
lines = text.split("\n")
m = re.match(r"# Prompt: (.+)", lines[0])
if not m:
raise SystemExit(f"{path.name}: first line must be '# Prompt: <title>'")
title = m.group(1).strip()
# summary: first paragraph after the heading
rest = "\n".join(lines[1:])
paras = [p.strip() for p in re.split(r"\n\s*\n", rest) if p.strip()]
summary = re.sub(r"\s+", " ", paras[0]) if paras else ""
summary = re.sub(r"`([^`]*)`", r"\1", summary)
meta = {k.lower(): re.sub(r"`([^`]*)`", r"\1", v.strip()) for k, v in META_RE.findall(text)}
if "\n---\n" not in text:
raise SystemExit(f"{path.name}: no '---' separator")
body = text.split("\n---\n", 1)[1].lstrip("\n")
fm = FILLIN_RE.search(body)
if not fm:
raise SystemExit(f"{path.name}: no fenced block after '### FILL IN'")
return {
"id": path.stem,
"file": path.name,
"title": title,
"summary": summary,
"needs": meta.get("needs", ""),
"costs": meta.get("costs", ""),
"group_meta": meta.get("group", ""),
"body": body,
"fillin": fm.group(1),
"words": len(body.split()),
}
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--out", required=True)
ap.add_argument("--readme-table", action="store_true",
help="print the README table rows instead of building")
args = ap.parse_args()
out = Path(args.out)
out.mkdir(parents=True, exist_ok=True)
files = {p.stem: p for p in ROOT.glob("*.md") if p.name != "README.md"}
prompts = []
for pid, group in ORDER:
if pid in files:
d = parse(files.pop(pid))
d["group"] = group
prompts.append(d)
# the rest: grouped by their "> **Group:**" line, in GROUPS order, then by id
rest = [parse(files[pid]) for pid in sorted(files)]
for d in rest:
d["group"] = d.pop("group_meta") if d.get("group_meta") in GROUPS else "more"
rest.sort(key=lambda d: (GROUPS.index(d["group"]), d["id"]))
for d in prompts:
d.pop("group_meta", None)
prompts.extend(rest)
prompts.sort(key=lambda d: GROUPS.index(d["group"])) # stable: keeps order within a group
if args.readme_table:
for d in prompts:
print(f"| [`{d['file']}`]({d['file']}) | {d['summary'][len('Have an agent '):] if d['summary'].startswith('Have an agent ') else d['summary']} |")
return
for d in prompts:
shutil.copy(ROOT / d["file"], out / d["file"])
data = json.dumps(prompts, ensure_ascii=False)
data = data.replace("</", "<\\/").replace("<!--", "<\\u0021--")
html = TEMPLATE.read_text(encoding="utf-8")
marker = "/*PROMPTS_JSON*/"
if marker not in html:
raise SystemExit("site/index.html: missing /*PROMPTS_JSON*/ marker")
html = html.replace(marker, data)
html = html.replace("<!--PROMPT_COUNT-->", str(len(prompts)))
(out / "index.html").write_text(html, encoding="utf-8")
print(f"prompts page: {len(prompts)} prompts -> {out}/index.html")
if __name__ == "__main__":
main()
|