cache

Use when the user wants to view, search, add, edit, prune, archive, or clear hyperflow memory entries. CRUD interface for `.hyperflow/memory/` — never modifies source code, only memory files. Trigger with /hyperflow:cache, "show memory", "search memory for X", "clear memory", "what does hyperflow remember about Y".

8 Tools
hyperflow Plugin
ai agency Category

Allowed Tools

ReadWriteEditBash(ls:*)Bash(mv:*)Bash(rm:*)GlobGrep

Provided by Plugin

hyperflow

Fifteen specialized slash commands turn one Claude session into a structured multi-agent engineering pipeline. Thinking models orchestrate, triage, and review; worker models execute in parallel — every step is a Worker → Reviewer pair, and every non-trivial phase fans into sub-phases with their own reviewers. Auto-routing is on by default — say 'audit the diff', 'debug this test', 'large migration', or 'run a workflow' and the orchestrator routes to the right skill without the /hyperflow:* prefix. /hyperflow:workflow uses Claude Code dynamic workflows for big tasks and a portable Codex/OpenCode adapter where native workflows are unavailable; /hyperflow:spec asks the questions a senior engineer would; /hyperflow:scope decomposes into a batched task graph; /hyperflow:dispatch fans out persona-stitched workers under tiered review; /hyperflow:amplify rewrites a rough prompt into a high-quality one before you run it. 15 composable personas, 6 adaptive flow profiles, and persistent project memory compound across sessions. Works across Codex App/CLI, Claude Code, OpenCode, and Antigravity.

ai agency v4.26.1
View Plugin

Installation

This skill is included in the hyperflow plugin:

/plugin install hyperflow@claude-code-plugins-plus

Click to copy

Instructions

Cache

CRUD interface for .hyperflow/memory/. Full protocol: memory-system.md.

Storage

All operations target .hyperflow/memory/ at the project root. Never modify source code files — if asked to "remember X about file Y", add a memory entry only, never edit Y.

Subcommands

Subcommand Description
show [tag] Print index or filter entries by tag
search Full-text search across all memory files
add </code></td> <td>Append a new entry (prompts for details)</td> </tr> <tr> <td><code>edit <entry-id></code></td> <td>Find entry by date+title slug and update in place</td> </tr> <tr> <td><code>prune</code></td> <td>Remove stale, superseded, and orphaned entries</td> </tr> <tr> <td><code>archive</code></td> <td>Move entries older than 30 days to cold storage</td> </tr> <tr> <td><code>clear</code></td> <td>Wipe all memory (with confirmation, recoverable)</td> </tr> <tr> <td><code>stats</code></td> <td>Counts, tier breakdown, tag frequency, oldest/newest</td> </tr> <tr> <td><code>migrate</code></td> <td>Import entries from legacy <code>~/.claude/hyperflow-memory.md</code></td> </tr> <tr> <td><code>off</code></td> <td>Disable memory writes for this session</td> </tr> <tr> <td><code>compact</code></td> <td>Summarise aged memory entries into stubs + monthly archive sidecars</td> </tr> </tbody></table> <h2>Subcommand Details</h2> <h3><code>show [tag]</code></h3> <p>No arg → print <code>index.md</code>. With tag → filter all files for matching entries.</p> <p>Output table: <code>Date | Title | Tags | File | Tier</code></p> <h3><code>search <query></code></h3> <p>grep/ripgrep across <code>learnings.md</code>, <code>decisions.md</code>, <code>pitfalls.md</code>, <code>patterns.md</code>, <code>conventions.md</code>.</p> <p>Return <code>file:line</code> + snippet, ranked by relevance.</p> <h3><code>add <category> <title></code></h3> <p>Categories: <code>learning</code> <code>decision</code> <code>pitfall</code> <code>pattern</code> <code>convention</code></p> <p>Prompt via AskUserQuestion for: <code>what</code>, <code>why it matters</code>, <code>tags</code> (controlled vocab).</p> <p>Append to the matching file using:</p> <pre><code> ### [YYYY-MM-DD] <title> `[tag1, tag2]` **What:** ... **Why it matters:** ... **Evidence:** ... </code></pre> <p>Update <code>index.md</code> with the new row.</p> <h3><code>edit <entry-id></code></h3> <p>Locate by date+title slug. Show current value, prompt for new value, update in place.</p> <h3><code>prune</code></h3> <p>Per <a href="references/memory-system.md">memory-system.md</a> pruning protocol:</p> <ul> <li>Remove <code>[SUPERSEDED]</code> entries older than 7 days</li> <li>Remove entries whose referenced files no longer exist (<code>test -f</code>)</li> <li>Archive entries unreferenced 90+ days to <code>.hyperflow/memory/archive/YYYY-MM.md</code></li> </ul> <p>Print summary of removed/archived counts.</p> <h3><code>archive</code></h3> <p>Compress hot entries older than 30 days → <code>.hyperflow/memory/archive/YYYY-MM.md</code>.</p> <p>Leave one-line summary in original file. Update <code>index.md</code> tier column.</p> <h3><code>clear</code></h3> <p>Confirm via AskUserQuestion: "This wipes all memory for this project. Are you sure?"</p> <p>If yes → move all content to <code>.hyperflow/memory/archive/cleared-<timestamp>.md</code>, then reset files to empty stubs.</p> <h3><code>stats</code></h3> <p>Print: total entries, hot/warm/cold counts, tag frequency table, oldest and newest entry dates.</p> <h3><code>migrate</code></h3> <p>Read <code>~/.claude/hyperflow-memory.md</code>, filter entries matching current project path.</p> <p>Append matching entries to <code>learnings.md</code>. Leave legacy file untouched.</p> <p>Print count of migrated entries.</p> <h3><code>off</code></h3> <p>Print: "Memory writes disabled for this session." No files modified.</p> <h3><code>compact</code></h3> <p>User-invoked memory compaction. Summarises entries older than 7 days into stub lines and preserves the full text in monthly archive sidecars at <code>.hyperflow/memory/archive/YYYY-MM.md</code>.</p> <p>Flow:</p> <ol> <li>The compact subcommand handler reads the target memory file (default: <code>learnings.md</code>; pass a path to target another).</li> <li>The Date/tag parser splits entries into hot (≤7 days, preserved) and eligible (>7 days). Both <code>[domain, type]</code> and legacy backticked `<code> </code>[domain, type]<code> </code>` tag forms are accepted.</li> <li>The Compaction Writer is dispatched in a single batch with all eligible entries.</li> <li>The Stub formatter renders each replacement line as <code>### [YYYY-MM-DD] Short title [domain, type] — summarized, see archive/YYYY-MM.md</code>.</li> <li>The Dedup Reviewer performs source-side stub-line match and archive-side header match (date + title + tags on both sides) to prevent duplicates.</li> <li>The Archive-sidecar writer appends accepted entries to <code>archive/YYYY-MM.md</code>, grouped by each entry's calendar month.</li> <li>The source file is rewritten with stubs replacing the original entries.</li> <li>The compact subcommand handler refreshes <code>.hyperflow/memory/.checksums</code> (a memory-scoped sidecar — distinct from <code>.hyperflow/.checksums</code> which the scaffold staleness check owns) and exits with a summary.</li> </ol> <p>Output: <code>N entries compacted into archive/YYYY-MM.md · M stubs rejected as duplicates · source N→M lines</code>. Full protocol in <a href="references/compaction.md">compaction.md</a>.</p> <h2>Flow</h2> <ol> <li>Parse invocation to determine subcommand</li> <li>If subcommand missing → list subcommands table above with one-line descriptions</li> <li>Execute subcommand</li> <li>Print structured result with counts/changes summary</li> </ol> <h2>Overview</h2> <p><code>/hyperflow:cache</code> is the operator interface to project-scoped memory under <code>.hyperflow/memory/</code>. It's the only skill that mutates memory files directly (other skills append via the memory-system protocol). Subcommands cover the full lifecycle: show, search, add, edit, prune, archive, clear, stats, migrate. All operations are project-local — entries never leak across projects.</p> <h2>Prerequisites</h2> <ul> <li><code>.hyperflow/</code> initialized (run <code>/hyperflow:scaffold</code> if missing — cache creates <code>.hyperflow/memory/</code> on first write but expects the parent dir).</li> <li>Write access to <code>.hyperflow/memory/</code> and <code>.hyperflow/memory/archive/</code>.</li> <li>For <code>migrate</code> only: read access to <code>~/.claude/hyperflow-memory.md</code> (legacy global memory).</li> </ul> <h2>Instructions</h2> <p>See <a href="#subcommands">Subcommands</a> and <a href="#subcommand-details">Subcommand Details</a> above for the full operational spec. Summary:</p> <ol> <li>Parse the subcommand from the user's invocation (or list subcommands if none given).</li> <li>Validate prerequisites for the chosen subcommand (e.g. <code>clear</code> requires <code>AskUserQuestion</code> confirmation; <code>migrate</code> requires legacy file presence).</li> <li>Execute the subcommand against <code>.hyperflow/memory/</code>.</li> <li>Print structured result with counts and any file-level changes.</li> </ol> <h2>Output</h2> <p>Each subcommand prints a compact summary:</p> <ul> <li><code>show</code> — table of matching entries (Date | Title | Tags | File | Tier).</li> <li><code>search</code> — <code>file:line</code> matches with snippets, ranked by relevance.</li> <li><code>add</code> / <code>edit</code> — confirmation line with new entry id and target file.</li> <li><code>prune</code> / <code>archive</code> / <code>clear</code> — counts of removed/archived/cleared entries plus destination paths.</li> <li><code>stats</code> — totals + hot/warm/cold breakdown + top-N tags.</li> <li><code>migrate</code> — count of migrated entries + source legacy file path.</li> <li><code>off</code> — single-line <code>Memory writes disabled for this session.</code></li> </ul> <h2>Error Handling</h2> <table><thead><tr> <th>Failure</th> <th>Behavior</th> </tr></thead><tbody> <tr> <td><code>.hyperflow/memory/</code> missing</td> <td>Auto-create skeleton (index.md + 5 category files + archive/.gitkeep) on first write; for read-only subcommands, print <code>(no memory yet — invoke /hyperflow:scaffold first)</code>.</td> </tr> <tr> <td>Subcommand unknown</td> <td>Print subcommands table; suggest closest match via Levenshtein distance.</td> </tr> <tr> <td><code>add</code> with invalid category</td> <td>Reject and list valid categories: learning, decision, pitfall, pattern, convention.</td> </tr> <tr> <td><code>edit</code> entry id not found</td> <td>List 3 closest matches by title slug + date.</td> </tr> <tr> <td><code>clear</code> without confirmation (headless)</td> <td>Refuse and print <code>clear requires interactive confirmation</code>. Do not wipe.</td> </tr> <tr> <td><code>migrate</code> source file missing</td> <td>Print <code>(nothing to migrate — ~/.claude/hyperflow-memory.md not found)</code> and stop.</td> </tr> </tbody></table> <h2>Examples</h2> <h3>Show all entries</h3> <pre><code> /hyperflow:cache show Date Title Tags File Tier 2026-05-16 Bash scoping required by validator [validator, marketplace] learnings.md hot 2026-05-15 No AI attribution in commits [convention, git] conventions.md hot 2026-05-14 Per-task commits in plugin dev [convention, git] conventions.md hot 3 entries (3 hot, 0 warm, 0 cold) </code></pre> <h3>Search</h3> <pre><code> /hyperflow:cache search "validator" .hyperflow/memory/learnings.md:42 — "Jeremy's validator requires scoped Bash..." .hyperflow/memory/decisions.md:8 — "...validator score of 73 → 94 after fix" 2 matches </code></pre> <h3>Add a learning</h3> <pre><code> /hyperflow:cache add learning "Markdown frontmatter needs block scalar for colons" ? What: Block scalar (|) preserves : and backticks in YAML values ? Why it matters: prevents fatal YAML parse failures in marketplace validators ? Tags: yaml, validator, frontmatter Added — .hyperflow/memory/learnings.md (entry 2026-05-16-block-scalar-frontmatter) </code></pre> <h3>Stats</h3> <pre><code> /hyperflow:cache stats Memory entries: 47 Hot (≤7d) 12 Warm (8-30d) 23 Cold (30d+) 12 Top tags: validator (8), convention (7), git (6), yaml (4) Oldest: 2026-02-14 Newest: 2026-05-16 </code></pre> <h2>Resources</h2> <ul> <li><a href="references/memory-system.md">memory-system.md</a> — full protocol: files, tiers, tagging, pruning rules.</li> <li><a href="references/compaction.md">compaction.md</a> — <code>/hyperflow:cache compact</code> protocol: stub format, archive sidecar, idempotency.</li> <li><a href="references/output-style.md">output-style.md</a> — label and table conventions.</li> <li><a href="references/DOCTRINE.md">DOCTRINE.md</a> — orchestration rules.</li> </ul></div> </div> </section> <!-- 8. Bottom CTA --> <section class="detail-cta" data-astro-cid-qcigfm7x> <h2 class="detail-cta__headline" data-astro-cid-qcigfm7x>Ready to use hyperflow?</h2> <div class="detail-cta__actions" data-astro-cid-qcigfm7x> <button class="detail-cta__primary" data-copy-install="/plugin install hyperflow@claude-code-plugins-plus" data-astro-cid-qcigfm7x> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" data-astro-cid-qcigfm7x> <rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" stroke-width="1.5" data-astro-cid-qcigfm7x></rect> <path d="M3 11V3a1 1 0 011-1h8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" data-astro-cid-qcigfm7x></path> </svg> <span class="detail-cta__btn-text" data-astro-cid-qcigfm7x>Copy Install Command</span> </button> </div> </section> <script type="module">document.querySelectorAll("[data-copy-install]").forEach(t=>{t.addEventListener("click",async()=>{const a=t.dataset.copyInstall||"";await navigator.clipboard.writeText(a);const e=t.querySelector(".detail-cta__btn-text");if(e){const o=e.textContent;e.textContent="Copied!",t.classList.add("copied"),window.trackEvent&&window.trackEvent("install_copied",{plugin_name:a.split(" ").pop()||"",source:"detail_cta"}),setTimeout(()=>{e.textContent=o,t.classList.remove("copied")},2e3)}})});</script> <!-- 9. Related Skills (6, with tool pills) --> <section class="related-skills-section" data-astro-cid-jrlgpo3w> <h2 class="related-skills-heading" data-astro-cid-jrlgpo3w>Related Skills</h2> <div class="related-skills-grid" data-astro-cid-jrlgpo3w> <a href="/skills/agency-os/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>agency-os</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>Notion-as-source-of-truth dispatch board for running your work like an AI agency.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Write</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Edit</span> </div> </a><a href="/skills/amplify/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>amplify</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>Use when a prompt is rough, vague, or under-specified and you want it rewritten to high quality before running it.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Glob</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Grep</span> </div> </a><a href="/skills/apex/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>apex</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>Engineering lead — hand Apex any task and it routes internally.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Write</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Edit</span> </div> </a><a href="/skills/apex-plan/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>apex-plan</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>Plan and scope a project — discovery, challenge assumptions, present S/M/L options with token and cost estimates.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Write</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Edit</span> </div> </a><a href="/skills/apex-recon/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>apex-recon</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>Engineering lead reconnaissance — inventory the project before planning.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Bash</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Glob</span> </div> </a><a href="/skills/apex-review/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>apex-review</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>Cross-cutting review of recent work — catches gaps between specialists.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Write</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Edit</span> </div> </a> </div> </section> <!-- 10. Footer --> <footer class="skill-footer" data-astro-cid-jrlgpo3w> <p data-astro-cid-jrlgpo3w>Part of <a href="/plugins/hyperflow/" data-astro-cid-jrlgpo3w>hyperflow</a></p> </footer> </div> <script> document.querySelectorAll('.install-command').forEach(el => { el.addEventListener('click', function() { const text = this.dataset.copy || this.textContent; navigator.clipboard.writeText(text).then(() => { const originalText = this.textContent; this.textContent = 'Copied to clipboard!'; this.style.color = 'var(--brand-green)'; setTimeout(() => { this.textContent = originalText; this.style.color = ''; }, 2000); }); }); }); </script> </main> <!-- Footer --> <footer data-astro-cid-37fxchfa> <div class="footer-inner" data-astro-cid-37fxchfa> <div class="footer-signup-row" data-astro-cid-37fxchfa> <h4 data-astro-cid-37fxchfa>Stay in the Loop</h4> <form data-signup-form="footer" class="footer-signup-form" data-astro-cid-37fxchfa> <input type="email" name="email" placeholder="you@example.com" required data-astro-cid-37fxchfa> <input type="text" name="website" tabindex="-1" autocomplete="off" aria-hidden="true" style="position:absolute;left:-9999px;opacity:0;height:0;width:0;" data-astro-cid-37fxchfa> <button type="submit" data-astro-cid-37fxchfa>Subscribe</button> </form> <p class="footer-form-note" data-astro-cid-37fxchfa>No spam. Unsubscribe anytime.</p> </div> <div class="footer-columns" data-astro-cid-37fxchfa> <div class="footer-col" data-astro-cid-37fxchfa> <h5 data-astro-cid-37fxchfa>Product</h5> <ul data-astro-cid-37fxchfa> <li data-astro-cid-37fxchfa><a href="/explore" data-astro-cid-37fxchfa>Explore</a></li> <li data-astro-cid-37fxchfa><a href="/skills" data-astro-cid-37fxchfa>Skills</a></li> <li data-astro-cid-37fxchfa><a href="/cowork" data-astro-cid-37fxchfa>Cowork</a></li> <li data-astro-cid-37fxchfa><a href="/compare-marketplaces" data-astro-cid-37fxchfa>Compare</a></li> <li data-astro-cid-37fxchfa><a href="/tools" data-astro-cid-37fxchfa>Tools</a></li> </ul> </div> <div class="footer-col" data-astro-cid-37fxchfa> <h5 data-astro-cid-37fxchfa>Resources</h5> <ul data-astro-cid-37fxchfa> <li data-astro-cid-37fxchfa><a href="/docs" data-astro-cid-37fxchfa>Docs</a></li> <li data-astro-cid-37fxchfa><a href="/changelog" data-astro-cid-37fxchfa>Changelog</a></li> <li data-astro-cid-37fxchfa><a href="/collections" data-astro-cid-37fxchfa>Collections</a></li> <li data-astro-cid-37fxchfa><a href="/playbooks" data-astro-cid-37fxchfa>Playbooks</a></li> <li data-astro-cid-37fxchfa><a href="/research" data-astro-cid-37fxchfa>Research</a></li> <li data-astro-cid-37fxchfa><a href="/learning" data-astro-cid-37fxchfa>Learning</a></li> </ul> </div> <div class="footer-col" data-astro-cid-37fxchfa> <h5 data-astro-cid-37fxchfa>Company</h5> <ul data-astro-cid-37fxchfa> <li data-astro-cid-37fxchfa><a href="/community" data-astro-cid-37fxchfa>Community</a></li> <li data-astro-cid-37fxchfa><a href="/community#hall-of-fame" data-astro-cid-37fxchfa>Hall of Fame</a></li> <li data-astro-cid-37fxchfa><a href="https://github.com/jeremylongshore/claude-code-plugins" target="_blank" data-astro-cid-37fxchfa>GitHub</a></li> </ul> </div> <div class="footer-col" data-astro-cid-37fxchfa> <h5 data-astro-cid-37fxchfa>Legal</h5> <ul data-astro-cid-37fxchfa> <li data-astro-cid-37fxchfa><a href="/privacy" data-astro-cid-37fxchfa>Privacy</a></li> <li data-astro-cid-37fxchfa><a href="/terms" data-astro-cid-37fxchfa>Terms</a></li> <li data-astro-cid-37fxchfa><a href="/acceptable-use" data-astro-cid-37fxchfa>Acceptable Use</a></li> </ul> </div> </div> <div class="footer-bottom" data-astro-cid-37fxchfa> <p class="footer-blurb" data-astro-cid-37fxchfa>Tons of Skills by <a href="https://intentsolutions.io" target="_blank" data-astro-cid-37fxchfa>Intent Solutions</a>. Marine. Citadel Grad. 20 years ops → self-taught dev → AI architect.</p> <p class="footer-copyright" data-astro-cid-37fxchfa>© 2026 Tons of Skills | <a href="https://intentsolutions.io" target="_blank" data-astro-cid-37fxchfa>Intent Solutions</a></p> </div> </div> </footer> <!-- Light theme global overrides (must be unscoped to target body/nav/footer) --> <!-- Email signup + killer-skill nomination forms. Posts to /api/forms/{signup,nominate} on the same origin. The endpoint is a tiny Node service on the VPS (`forms-api.service`, 127.0.0.1:8090) that forwards to the Slack webhook in /etc/intentsolutions/notify.env. Anti-spam: honeypot field `website`, email/URL regex, 8 KiB body cap, per-IP rate limit on the server. See intentsolutions-vps-runbook/docs/tonsofskills-forms-api.md. --> <script type="module"> (() => { const flash = (btn, msg, ok = true, restoreMs = 3000) => { const orig = btn.dataset.origText ?? btn.textContent; if (!btn.dataset.origText) btn.dataset.origText = orig; btn.textContent = msg; btn.style.opacity = ok ? '0.85' : '1'; btn.disabled = ok; if (!ok) setTimeout(() => { btn.textContent = orig; btn.style.opacity = ''; btn.disabled = false; }, restoreMs); }; const wireSignup = (form) => form.addEventListener('submit', async (e) => { e.preventDefault(); const btn = form.querySelector('button[type="submit"]'); const emailInput = form.querySelector('input[type="email"]'); const honey = form.querySelector('input[name="website"]'); const email = (emailInput?.value || '').trim(); if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { flash(btn, 'Invalid email', false); return; } flash(btn, 'Subscribing…', true, 0); try { const r = await fetch('/api/forms/signup', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ email, source: form.dataset.signupForm || 'unknown', website: honey?.value || '' }) }); if (r.ok) { flash(btn, "You're in!", true, 0); if (emailInput) emailInput.value = ''; } else if (r.status === 429) { flash(btn, 'Slow down', false); } else { const j = await r.json().catch(() => ({})); flash(btn, j.error || 'Something went wrong', false); } } catch { flash(btn, 'Network error', false); } }); const wireNomination = (form) => form.addEventListener('submit', async (e) => { e.preventDefault(); const btn = form.querySelector('button[type="submit"]'); const repoInput = form.querySelector('input[name="repo"]'); const honey = form.querySelector('input[name="website"]'); const repoUrl = (repoInput?.value || '').trim(); if (!/^https:\/\/github\.com\/[^/\s]+\/[^/\s]+/.test(repoUrl)) { flash(btn, 'GitHub URL only', false); return; } flash(btn, 'Submitting…', true, 0); try { const r = await fetch('/api/forms/nominate', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ repoUrl, source: form.dataset.nominationForm || 'unknown', website: honey?.value || '' }) }); if (r.ok) { flash(btn, 'Submitted!', true, 0); if (repoInput) repoInput.value = ''; } else if (r.status === 429) { flash(btn, 'Slow down', false); } else { const j = await r.json().catch(() => ({})); flash(btn, j.error || 'Something went wrong', false); } } catch { flash(btn, 'Network error', false); } }); const wireContact = (form) => form.addEventListener('submit', async (e) => { e.preventDefault(); const btn = form.querySelector('button[type="submit"]'); const nameInput = form.querySelector('input[name="name"]'); const emailInput = form.querySelector('input[type="email"]'); const messageInput = form.querySelector('textarea[name="message"]'); const honey = form.querySelector('input[name="website"]'); const name = (nameInput?.value || '').trim(); const email = (emailInput?.value || '').trim(); const message = (messageInput?.value || '').trim(); if (name.length < 2) { flash(btn, 'Name required', false); return; } if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { flash(btn, 'Invalid email', false); return; } if (message.length < 10) { flash(btn, 'Tell me more', false); return; } flash(btn, 'Sending…', true, 0); try { const r = await fetch('/api/forms/contact', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name, email, message, source: form.dataset.contactForm || 'unknown', website: honey?.value || '' }) }); if (r.ok) { flash(btn, "Got it — back within 24h!", true, 0); if (nameInput) nameInput.value = ''; if (emailInput) emailInput.value = ''; if (messageInput) messageInput.value = ''; } else if (r.status === 429) { flash(btn, 'Slow down', false); } else { const j = await r.json().catch(() => ({})); flash(btn, j.error || 'Something went wrong', false); } } catch { flash(btn, 'Network error', false); } }); const init = () => { document.querySelectorAll('[data-signup-form]').forEach(wireSignup); document.querySelectorAll('[data-nomination-form]').forEach(wireNomination); document.querySelectorAll('[data-contact-form]').forEach(wireContact); }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })(); </script> <!-- Theme Toggle Script --> <script> document.addEventListener('DOMContentLoaded', function() { var themeToggle = document.querySelector('.theme-toggle'); if (themeToggle) { themeToggle.addEventListener('click', function() { var current = document.documentElement.getAttribute('data-theme') || 'dark'; var next = current === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', next); localStorage.setItem('theme', next); var meta = document.querySelector('meta[name="theme-color"]'); if (meta) meta.setAttribute('content', next === 'dark' ? '#0a0a0c' : '#fafaf9'); }); } window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', function(e) { if (!localStorage.getItem('theme')) { document.documentElement.setAttribute('data-theme', e.matches ? 'light' : 'dark'); } }); }); </script> <!-- Nav Scroll State --> <script> (function() { var nav = document.querySelector('nav'); if (!nav) return; function onScroll() { if (window.scrollY > 20) { nav.classList.add('scrolled'); } else { nav.classList.remove('scrolled'); } } window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); })(); </script> <!-- Mobile Menu Script --> <script> document.addEventListener('DOMContentLoaded', function() { var menuToggle = document.querySelector('.mobile-menu-toggle'); var navLinks = document.querySelector('.nav-links'); var menuIcon = document.querySelector('.menu-icon'); var closeIcon = document.querySelector('.close-icon'); if (menuToggle && navLinks) { menuToggle.addEventListener('click', function() { navLinks.classList.toggle('active'); document.body.classList.toggle('menu-open'); if (navLinks.classList.contains('active')) { menuIcon.style.display = 'none'; closeIcon.style.display = 'block'; } else { menuIcon.style.display = 'block'; closeIcon.style.display = 'none'; } }); navLinks.querySelectorAll('a').forEach(function(link) { link.addEventListener('click', function() { navLinks.classList.remove('active'); document.body.classList.remove('menu-open'); menuIcon.style.display = 'block'; closeIcon.style.display = 'none'; }); }); document.addEventListener('click', function(e) { if (navLinks.classList.contains('active') && !navLinks.contains(e.target) && !menuToggle.contains(e.target)) { navLinks.classList.remove('active'); document.body.classList.remove('menu-open'); menuIcon.style.display = 'block'; closeIcon.style.display = 'none'; } }); } // Track outbound link clicks (nav + footer) document.querySelectorAll('nav a[target="_blank"], footer a[target="_blank"]').forEach(function(link) { link.addEventListener('click', function() { if (window.trackEvent) { window.trackEvent('outbound_click', { url: link.href, link_text: link.textContent.trim() }); } }); }); }); </script> </body> </html>