prompts / site / gen-index.py
515 words
  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()

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/prompts.git