{"slug": "i-built-cyberplain-with-hermes-here-s-what-actually-went-down", "title": "I built cyberplain with Hermes — here's what actually went down", "summary": "A developer built Cyberplain, a plain-language cybersecurity education site with 32 articles covering topics like phishing, malware, and zero trust, using Hermes Agent to handle design, content, architecture, and deployment. The agent chose Astro, Tailwind, and Vercel for the stack, implemented a GitHub-inspired dark-mode palette, and fixed a security issue by moving an API key server-side. The project demonstrated the agent's ability to manage the full stack independently, though network constraints required some manual completion.", "body_md": "*This is a submission for the Hermes Agent Challenge: Write About Hermes Agent*\n\nA few weeks ago I decided to build a cybersecurity education site. I was a complete beginner in the space. I knew the terms — phishing, malware, zero trust — but not the depth behind them. I wanted to learn, and building something real felt like the fastest way to do that.\n\nAs a developer, I could have built the entire site myself. But I wanted to try something with Hermes—that was the goal. So instead of writing the code, I decided to see how far I could get by directing an AI agent to build the entire system.\n\nHere's the honest account of what that looked like.\n\n**Cyberplain** is a plain-language cybersecurity education site. 32 articles covering the fundamentals — phishing, malware, the CIA triad, zero trust, ransomware, compliance, and more. No jargon. No hype. Just clear explanations that don't assume you already know what the words mean.\n\nThere's also:\n\nThe design is dark-mode only, GitHub-inspired palette. Clean, readable, nothing flashy.\n\nI needed someone to own the execution. Design, code, deployment, decisions — the whole thing.\n\nI handed it to **Hermes Agent** and told it: \"You're in charge. Build it.\"\n\n**Design decisions** — I said \"inspiration is explainme.ai, but don't copy it.\" Hermes picked the color scheme (GitHub dark: #0d1117 background, #58a6ff accent), decided on a grid layout for articles, and set up the typography. It made the call to go dark-only, no toggle — which was the right call for this audience.\n\n**Article content** — 32 articles, all written in a consistent voice. Sentinel's voice. Not a chatbot that sounds like it's reading Wikipedia — something that explains concepts the way someone who actually understands security would — clearly, without performing expertise.\n\n**Architecture** — Astro for the framework, Tailwind for styling, Vercel for hosting. It picked the right tools for the job: static output with one serverless endpoint for the subscribe form.\n\n**The tricky stuff** — There was a moment where the subscribe form wasn't working because the API key was hardcoded in client-side JavaScript. I flagged it. Hermes caught the issue, moved it server-side properly, and restructured the whole approach. That's the kind of thing you'd normally have to refactor twice before getting right — the agent caught it fast.\n\n**Deployment** — Wired up to Vercel, configured the environment variables, connected the GitHub repo. Push to main = deploy.\n\nThe agent moved fast. We had a working site — dark mode, 32 articles, live news, proper styling — within a few sessions. No hand-holding needed. I'd give it direction, it would execute, I'd verify, move on.\n\nThe design quality is genuinely good. The GitHub palette was a smart call — it reads as professional to the security crowd without being generic.\n\nIt handled the full stack: content, front-end, back-end endpoint, deployment. That's rare to see in one agent run.\n\nI had to push back on some design choices. The first iteration was too close to the reference site — same layout, same visual patterns. \"Inspired by\" can turn into \"a clone of\" fast. I told it to make its own decisions and it did — the result is better for it.\n\nNetwork access was inconsistent in the environment the agent ran in. Some things that should've been one-click (setting up the Buttondown welcome email) required manual work because the agent couldn't reach the API from its environment. That's a networking constraint, not an agent problem — but it meant I had to finish some things manually.\n\nStart with clearer constraints on the design direction upfront. \"Make it your own\" is good guidance but the agent performs best when the guardrails are explicit. Something like \"dark palette, no emoji in headings, cards over lists\" would've shaved off a revision cycle.\n\nOne thing worth noting: the subscribe form posts to an SSR endpoint on Vercel, which calls the Buttondown API server-side. The API key never touches the browser—all communications with Buttondown are isolated within a secure Astro serverless endpoint.\n\nWe also wanted to display a live subscriber count badge below the subscribe form, just like you see on mature publications (e.g., *\"25 people already learning\"*). The agent set up a secure GET route that queries the Buttondown API server-side to fetch the active subscriber count without exposing the token, and updates the DOM client-side.\n\nHere is the exact, final serverless endpoint (`src/pages/api/subscribe.js`\n\n) that handles both directions:\n\n``` js\nexport const prerender = false;\n\n// GET: Fetch live active subscriber count from Buttondown API\nexport async function GET() {\n  const apiKey = import.meta.env.BUTTONDOWN_API_KEY;\n  if (!apiKey) {\n    return new Response(JSON.stringify({ count: null }), {\n      status: 200,\n      headers: { 'Content-Type': 'application/json' },\n    });\n  }\n\n  try {\n    const res = await fetch('https://api.buttondown.com/v1/subscribers?status=active', {\n      headers: { Authorization: `Token ${apiKey}` },\n    });\n\n    if (!res.ok) {\n      return new Response(JSON.stringify({ count: null }), {\n        status: 200,\n        headers: { 'Content-Type': 'application/json' },\n      });\n    }\n\n    const data = await res.json();\n    return new Response(JSON.stringify({ count: data.count }), {\n      status: 200,\n      headers: { 'Content-Type': 'application/json' },\n    });\n  } catch {\n    return new Response(JSON.stringify({ count: null }), {\n      status: 200,\n      headers: { 'Content-Type': 'application/json' },\n    });\n  }\n}\n\n// POST: Add new subscriber securely\nexport async function POST({ request }) {\n  const { email } = await request.json();\n\n  if (!email || !email.includes('@')) {\n    return new Response(JSON.stringify({ error: 'Valid email required.' }), {\n      status: 400,\n      headers: { 'Content-Type': 'application/json' },\n    });\n  }\n\n  const apiKey = import.meta.env.BUTTONDOWN_API_KEY;\n  if (!apiKey) {\n    return new Response(JSON.stringify({ error: 'Server misconfiguration.' }), {\n      status: 500,\n      headers: { 'Content-Type': 'application/json' },\n    });\n  }\n\n  const res = await fetch('https://api.buttondown.com/v1/subscribers', {\n    method: 'POST',\n    headers: {\n      Authorization: `Token ${apiKey}`,\n      'Content-Type': 'application/json',\n    },\n    // Buttondown requires 'email_address' rather than 'email'\n    body: JSON.stringify({ email_address: email, tags: ['cyberplain'] }),\n  });\n\n  // Handle already-subscribed scenario gracefully\n  if (res.status === 400) {\n    return new Response(JSON.stringify({ message: 'Already subscribed!' }), {\n      status: 200,\n      headers: { 'Content-Type': 'application/json' },\n    });\n  }\n\n  if (!res.ok) {\n    return new Response(JSON.stringify({ error: 'Could not subscribe. Please try again.' }), {\n      status: 502,\n      headers: { 'Content-Type': 'application/json' },\n    });\n  }\n\n  return new Response(JSON.stringify({ message: \"You're in! 🎉\" }), {\n    status: 200,\n    headers: { 'Content-Type': 'application/json' },\n  });\n}\n```\n\nThat's not groundbreaking, but it's the right pattern. AI agents can miss security details like this, or misinterpret minor API payload keys (like sending `email`\n\ninstead of Buttondown's expected `email_address`\n\n), but having a conversational partner to debug and fix it immediately makes all the difference.\n\nHere is the welcome email that is triggered and delivered instantly in Sentinel's clear, plain-language voice:\n\n**Be specific about what you don't want.** \"Make it good\" doesn't work. \"Don't copy the reference site, don't use a theme toggle, don't add search\" — those constraints actually helped.\n\n**Stay involved in the decisions that matter.** Design direction, security patterns, content voice — those are things where your judgment beats the agent's defaults. The agent is great at execution. Your job is to make sure it's building the right thing.\n\n**Give it a name early.** Even a working title shapes how the agent writes content and names things internally. \"Sentinel\" gave the whole project a consistent voice from the start.\n\n32 articles. Live news. Interactive graph. Working subscribe form. Deployed at [Cyberplain](https://sentinel-hermes.vercel.app).\n\nThe whole thing took a few hours of actual agent time across a few sessions, plus my own time for review and the manual email setup.\n\nThe agent built it. I directed it — and that distinction matters.\n\nIf you want to see what the workflow produces end-to-end, go poke around at the [live site](https://sentinel-hermes.vercel.app) — feedback welcome.\n\n*Cyberplain — 32 articles, dark mode, every Tuesday and Friday.*\n\n*Built with Hermes Agent.*", "url": "https://wpnews.pro/news/i-built-cyberplain-with-hermes-here-s-what-actually-went-down", "canonical_source": "https://dev.to/imkarthikeyan/i-built-cyberplain-with-hermes-heres-what-actually-went-down-953", "published_at": "2026-05-30 11:27:03+00:00", "updated_at": "2026-05-30 11:41:16.802203+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-products", "generative-ai", "ai-startups"], "entities": ["Hermes Agent", "Cyberplain", "GitHub", "explainme.ai"], "alternates": {"html": "https://wpnews.pro/news/i-built-cyberplain-with-hermes-here-s-what-actually-went-down", "markdown": "https://wpnews.pro/news/i-built-cyberplain-with-hermes-here-s-what-actually-went-down.md", "text": "https://wpnews.pro/news/i-built-cyberplain-with-hermes-here-s-what-actually-went-down.txt", "jsonld": "https://wpnews.pro/news/i-built-cyberplain-with-hermes-here-s-what-actually-went-down.jsonld"}}