<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Karthikeyan</title>
    <description>The latest articles on DEV Community by Karthikeyan (@imkarthikeyan).</description>
    <link>https://dev.to/imkarthikeyan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F236404%2Fcae6d62e-c9f5-4bfe-b4e1-b62af40a6837.jpg</url>
      <title>DEV Community: Karthikeyan</title>
      <link>https://dev.to/imkarthikeyan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/imkarthikeyan"/>
    <language>en</language>
    <item>
      <title>Echoes: Let Shared Memories Keep Their Different Voices</title>
      <dc:creator>Karthikeyan</dc:creator>
      <pubDate>Thu, 29 Oct 2026 15:27:22 +0000</pubDate>
      <link>https://dev.to/imkarthikeyan/echoes-let-shared-memories-keep-their-different-voices-17ka</link>
      <guid>https://dev.to/imkarthikeyan/echoes-let-shared-memories-keep-their-different-voices-17ka</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Echoes is a local-first memory relay. It gives people a place to record different accounts of a shared moment without merging them into one supposedly definitive version. An open-weight model can suggest a gentle question to invite another voice into the conversation; the people sharing the memory remain its authors.&lt;/p&gt;

&lt;p&gt;I interpreted “Ghost in the Machine” as a way to honor the stories people carry, without pretending software can speak for someone who is gone. Echoes does not impersonate a loved one or decide which memory is true.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdud61v683m6p3m8cpx7k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdud61v683m6p3m8cpx7k.png" alt="Echoes local interface showing a small collection of everyday memories" width="800" height="569"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://echoes-pearl-omega.vercel.app/" rel="noopener noreferrer"&gt;Try Echoes live&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Try the cloud sample&lt;/strong&gt; button works without installing anything. It sends only the built-in fictional relay to hosted Gemma; it never sends a visitor’s saved relay. To use the local path with your own words, follow the Ollama steps in the project README.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/karthik-zoro-96/echoes" rel="noopener noreferrer"&gt;View the Echoes source on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use Echoes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the live demo and read the fictional starter relay to see how two accounts can preserve different details about the same moment.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Start a memory relay&lt;/strong&gt;. Enter the moment, your name or initials, and your account. The relay is saved in this browser.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Add another voice&lt;/strong&gt; on the relay to add another person’s account on the same device. Echoes does not sync memories between devices.&lt;/li&gt;
&lt;li&gt;For a no-install preview, select &lt;strong&gt;Try the cloud sample&lt;/strong&gt;; it uses only the fictional example. To ask about your own relay, install Ollama, run &lt;code&gt;ollama pull gemma3:4b&lt;/code&gt; and &lt;code&gt;ollama serve&lt;/code&gt;, then run &lt;code&gt;python3 -m http.server 8000&lt;/code&gt; from the project folder and open &lt;code&gt;http://localhost:8000&lt;/code&gt;. Select &lt;strong&gt;Suggest a question&lt;/strong&gt; to send your relay to Ollama on this device.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Export keepsake&lt;/strong&gt; to download a Markdown backup, or &lt;strong&gt;Clear memories from this device&lt;/strong&gt; to remove the saved relays from this browser.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Echoes uses plain HTML, CSS, and JavaScript. Relays are stored in the current browser’s &lt;code&gt;localStorage&lt;/code&gt;, and a Markdown keepsake can be exported as a backup.&lt;/p&gt;

&lt;p&gt;For private question generation, the browser sends the selected relay to Ollama running on the same device. The default model is Gemma 3 4B (&lt;code&gt;gemma3:4b&lt;/code&gt;); the model name is editable. The optional judge preview uses Gemma 4 26B A4B through Vercel AI Gateway. Its serverless route accepts a bodyless &lt;code&gt;GET&lt;/code&gt; request and builds the model prompt from a fixed fictional example. The Gateway key stays in a Vercel environment variable.&lt;/p&gt;

&lt;p&gt;Echoes checks whether a generated question reuses a meaningful source word and retries once if it does not. That is only a lexical check, not proof that the whole question is grounded. The result is labeled as a suggestion for a person to review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;The local Gemma path lets people generate a follow-up question without sending their archive to a hosted AI service. They can inspect or change the prompt, switch compatible local models, and keep the stories on the device where they were written. The export remains plain Markdown, so the archive can outlast any one model or runtime.&lt;/p&gt;

&lt;p&gt;A closed hosted model could generate a similar question, but it would not provide the same local, inspectable inference path. The trade-off is setup: each person needs Ollama, a model download, and a device with enough resources. The hosted preview is more convenient, but it only demonstrates the interaction with invented sample text.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Spirit of It
&lt;/h2&gt;

&lt;p&gt;The example is intentionally ordinary: one person remembers warm pavement after summer rain; another remembers soaked socks and a disputed puddle. A live sample request produced the question, &lt;em&gt;“How did it feel to step through that puddle?”&lt;/em&gt; It offers a next step without choosing whose version wins. The sample and the stories in the screenshot are part of the demo interface; no personal family archive is included in the hosted request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Best Use of Gemma&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>I built Stow so I can stop searching every drawer</title>
      <dc:creator>Karthikeyan</dc:creator>
      <pubDate>Fri, 23 Oct 2026 15:23:05 +0000</pubDate>
      <link>https://dev.to/imkarthikeyan/i-built-stow-so-i-can-stop-searching-every-drawer-31j3</link>
      <guid>https://dev.to/imkarthikeyan/i-built-stow-so-i-can-stop-searching-every-drawer-31j3</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built &lt;strong&gt;Stow&lt;/strong&gt;, a small home-memory app for the things I put away and promptly forget.&lt;/p&gt;

&lt;p&gt;Take a photo, write one sentence about where the item went, and Stow saves a searchable memory. Later, ask a plain question like “Where did I put the book?” and it brings back the item and the location you gave it.&lt;/p&gt;

&lt;p&gt;The goal is deliberately modest: spend less time reopening drawers and more time doing whatever you meant to do.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvo3crrcs0yyn4t4ymrx7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvo3crrcs0yyn4t4ymrx7.png" alt="stow" width="573" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Stow currently runs on a computer in my home, with a phone connecting over the same Wi-Fi. It is not deployed publicly, by design.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/n6fi90hUglk" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Locally
&lt;/h2&gt;

&lt;p&gt;Stow runs on your computer, with your phone connecting over the same Wi-Fi. You’ll need Node.js, Python, and &lt;a href="https://ollama.com/download" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the &lt;a href="https://github.com/karthik-zoro-96/stow" rel="noopener noreferrer"&gt;Stow repo&lt;/a&gt; and open its folder.&lt;/li&gt;
&lt;li&gt;In a terminal, download Gemma 3 4B: &lt;code&gt;ollama pull gemma3:4b&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Install the backend dependencies: &lt;code&gt;python3 -m venv .venv &amp;amp;&amp;amp; source .venv/bin/activate &amp;amp;&amp;amp; pip install -r requirements.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Start the API: &lt;code&gt;uvicorn backend.main:app --host 127.0.0.1 --port 8000&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In a second terminal, run &lt;code&gt;npm install &amp;amp;&amp;amp; npm run dev&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Open the Vite network address on your phone. Keep the phone and computer on the same Wi-Fi.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Write a note about an item and where you put it, then tap &lt;strong&gt;Remember this&lt;/strong&gt;. Later, search for it in everyday language. Photos are optional. The computer running Stow must stay on while you use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/karthik-zoro-96/stow" rel="noopener noreferrer"&gt;View Stow on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The phone-first interface is built with React, Vite, and TypeScript. A FastAPI service on my computer stores memories in SQLite and optional photos in a local folder. Ollama runs Gemma 3 4B on that same computer.&lt;/p&gt;

&lt;p&gt;When I save an item, Gemma turns my note and optional photo into a short item name, location, and identifying note. The photo can help identify the object; &lt;strong&gt;the location must come from the words I typed&lt;/strong&gt;. Stow does not ask the model to infer where something is from an image.&lt;/p&gt;

&lt;p&gt;For search, Gemma chooses from the memories already saved. The API checks that the returned match belongs to that saved set before showing its stored location. The phone sends its note and photo to my computer over the home network; there is no hosted AI API in the flow.&lt;/p&gt;

&lt;p&gt;A practical trade-off: the computer has to stay on, the phone and computer need the same Wi-Fi, and I still check the model’s result. Gemma can make mistakes, so Stow is a memory aid—not a replacement for checking where I put something.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Stow handles little details about someone’s home. Running an open-weight model locally means those notes and photos do not need to go to a third-party AI service just to answer “where did I put it?” I can inspect the workflow, choose the model, and change it without redesigning the app around a hosted API.&lt;/p&gt;

&lt;p&gt;That choice has a cost too: the model needs to be downloaded and run on a computer with enough memory, and that computer must be available when I search. For this use case, I prefer that setup over sending household photos and locations to a cloud model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lazy Payoff
&lt;/h2&gt;

&lt;p&gt;I tried Stow from my phone with a photo of a book. Gemma identified it as &lt;em&gt;The Alchemist&lt;/em&gt;, I saved “table” as its location, and a later search for the book brought the saved memory back with its photo. One short note was enough; I did not have to create an account, fill out a form, or remember which app I used.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9u8w9n9b35luqu14k26g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9u8w9n9b35luqu14k26g.png" alt="Payoff" width="573" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Best Use of Gemma&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for reading. &lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>ChaseLocal: An open-model tool that writes the invoice follow-up you've been putting off</title>
      <dc:creator>Karthikeyan</dc:creator>
      <pubDate>Sun, 18 Oct 2026 06:39:55 +0000</pubDate>
      <link>https://dev.to/imkarthikeyan/chaselocal-an-open-model-tool-that-writes-the-invoice-follow-up-youve-been-putting-off-1a7e</link>
      <guid>https://dev.to/imkarthikeyan/chaselocal-an-open-model-tool-that-writes-the-invoice-follow-up-youve-been-putting-off-1a7e</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ChaseLocal&lt;/strong&gt; turns an overdue invoice into three ready-to-send follow-up emails, so freelancers can spend thirty seconds chasing a payment instead of twenty minutes wording a “just checking in” email to a client who owes them money.&lt;/p&gt;

&lt;p&gt;Drop in an invoice (PDF or photo) or enter the details manually. ChaseLocal extracts the client name, amount, currency, due date, invoice number, and work description, calculates how overdue the payment is, and writes three drafts that escalate in tone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Polite:&lt;/strong&gt; A friendly check-in that assumes the delay was an oversight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firm:&lt;/strong&gt; States the due date and how many days overdue the payment is, and asks when payment can be expected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Final notice:&lt;/strong&gt; The last reminder before escalation, while remaining professional.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You review the extracted details first and click &lt;strong&gt;Write follow-ups&lt;/strong&gt; once everything looks right. ChaseLocal recommends the draft that best fits how late the payment is (e.g., “Day 18 · recommended” on Firm) and tells you when the next tone kicks in (“Still unpaid on Oct 22? The Final notice draft becomes the recommendation.”).&lt;/p&gt;

&lt;p&gt;From there, you can tweak a draft (add a payment link, make it shorter or more direct), copy it, open it in your email app, or share the original PDF so it's ready to attach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Would Pay for It?
&lt;/h2&gt;

&lt;p&gt;Solo freelancers and small studios. A late invoice represents money they've already earned, and following up is the part people often put off because it feels awkward. It's a direct financial problem, not a nice-to-have.&lt;/p&gt;

&lt;p&gt;A general chatbot can write an email, but you have to paste in the details, decide on the tone yourself, and check that it hasn't made anything up. ChaseLocal reads the invoice for you, selects the tone based on the due date, and sticks to the facts on the invoice without inventing deadlines or late fees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://chaselocal.vercel.app" rel="noopener noreferrer"&gt;https://chaselocal.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Try a sample invoice&lt;/strong&gt; to experience the entire flow without uploading any of your own documents.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/8NyWN9YNlB0" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fovqp2sbvde5rae92ty7n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fovqp2sbvde5rae92ty7n.png" alt="ChaseLocal with the sample invoice read and the Firm draft recommended" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It works on mobile, too, in both light and dark modes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Floendml750na3q9dkdse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Floendml750na3q9dkdse.png" alt="ChaseLocal drafts on mobile in dark mode" width="780" height="1688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/karthik-zoro-96" rel="noopener noreferrer"&gt;
        karthik-zoro-96
      &lt;/a&gt; / &lt;a href="https://github.com/karthik-zoro-96/chaselocal" rel="noopener noreferrer"&gt;
        chaselocal
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;ChaseLocal&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Turn an overdue invoice into three ready-to-send follow-up emails — Polite, Firm, and Final notice — in under a minute.&lt;/p&gt;
&lt;p&gt;Built for freelancers and small studios who'd rather not spend twenty minutes wording a "just checking in" email to a client who owes them money.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Upload the invoice&lt;/strong&gt; — PDF, JPG, or PNG. Or type the details in yourself, or click &lt;strong&gt;Try a sample invoice&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the details&lt;/strong&gt; — client, amount, currency, due date, invoice number, and work description are extracted for you. Any ISO currency works, and days overdue is calculated from the due date. Add the name to sign with (remembered on your device) and any earlier messages for context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick a tone&lt;/strong&gt; — click &lt;strong&gt;Write follow-ups&lt;/strong&gt; to get three escalating drafts, each with a subject line. The one that fits how late the payment is opens first, the stage tabs show the…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/karthik-zoro-96/chaselocal" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tech stack:&lt;/strong&gt; Next.js (App Router), React, Tailwind CSS, and shadcn/ui, deployed on Vercel.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Open-weight model:&lt;/strong&gt; Llama 3.1 8B through Groq's OpenAI-compatible API. The provider and model are configurable through environment variables (&lt;code&gt;OPENAI_BASE_URL&lt;/code&gt;, &lt;code&gt;MODEL_NAME&lt;/code&gt;), so switching to Qwen, Gemma, or a self-hosted endpoint is just a configuration change.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reading the invoice:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Photos are processed in the browser using tesseract.js, so the images themselves never leave your device.&lt;/li&gt;
&lt;li&gt;PDFs are parsed on the server using pdf-parse.&lt;/li&gt;
&lt;li&gt;The model converts the extracted text into structured fields: client name, amount, ISO currency, due date, invoice number, and work description.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Writing the drafts:&lt;/strong&gt; A constrained prompt with stage-specific instructions. The Firm and Final notice drafts must state the due date and how many days overdue the payment is. The Firm draft must ask a direct question (“When can I expect payment?”). Stock phrases such as “at your earliest convenience” are banned, and the model is prohibited from inventing invoice numbers, deadlines, or late fees.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The details that make it feel finished:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support for currencies worldwide, with proper formatting.&lt;/li&gt;
&lt;li&gt;Stage dates calculated from the due date.&lt;/li&gt;
&lt;li&gt;Your sign-off name filled in instantly without regenerating the drafts.&lt;/li&gt;
&lt;li&gt;Clear error messages for password-protected, damaged, or scanned PDFs.&lt;/li&gt;
&lt;li&gt;Per-IP rate limiting to prevent a single visitor from exhausting the model quota.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The open model does the core work twice: understanding the invoice and writing the emails. Everything else is UI and file handling built around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;For a tool that handles people's unpaid invoices, going open has practical benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Privacy I can actually deliver.&lt;/strong&gt; Photos are processed on the device, only the extracted text is sent to the model, and nothing is stored by the app. Because ChaseLocal uses an OpenAI-compatible API, I can switch to a compatible local inference server such as Ollama, making a fully offline version possible without redesigning the application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No lock-in.&lt;/strong&gt; If a better or cheaper open model becomes available, I can switch with one variable. I'm not tied to one company's pricing, terms, or model retirements.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Costs that fit the customer.&lt;/strong&gt; Freelancers chasing late payments are price-sensitive. A small open model running on a cheap or free endpoint helps keep the tool free or nearly free, instead of adding a per-email bill that eats into its value.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Predictable behaviour.&lt;/strong&gt; A small model with a tightly constrained prompt is easier to control, helping prevent it from inventing amounts or deadlines. That matters when the email is about money.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, open models let me build something genuinely useful for freelancers without ongoing API bills, vendor lock-in, or hand-waving about privacy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reminders:&lt;/strong&gt; ChaseLocal already knows when each tone applies, so the next step is to remind users on those dates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A self-hosted mode:&lt;/strong&gt; An Ollama-based setup, documented and tested, for a fully offline experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real payment links:&lt;/strong&gt; Integration with Stripe or PayPal to replace the placeholder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for reading, and thank you for organizing this Hacktoberfest challenge!&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Race Recap Radio — I turned my 10K into a hype sports broadcast</title>
      <dc:creator>Karthikeyan</dc:creator>
      <pubDate>Sat, 11 Jul 2026 17:55:00 +0000</pubDate>
      <link>https://dev.to/imkarthikeyan/race-recap-radio-i-turned-my-10k-into-a-hype-sports-broadcast-260j</link>
      <guid>https://dev.to/imkarthikeyan/race-recap-radio-i-turned-my-10k-into-a-hype-sports-broadcast-260j</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-07-09"&gt;Weekend Challenge: Passion Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Why do I like running?&lt;/p&gt;

&lt;p&gt;It goes back to when I was neck-deep in endurance and HIIT workouts while building strength. I could deadlift a reasonable weight and hold my own through an intense HIIT session for close to an hour. One day I thought, &lt;em&gt;let me try going for a run.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That was 2023. I signed up for my first 5K on a road course. It was a bit of a love-hate relationship with that route, but I finished in around 42 minutes. Crossing the finish line alongside hundreds of other runners was unlike anything I'd experienced before.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Quick side note: I've always been terrible at waking up early, so most of my training happens in the evening or at night.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A year later, I somehow decided my first half marathon with barely any preparation was a good idea. I hadn't run at all in the two weeks leading up to race day, slept only two hours the night before, and even reached the start line 15 minutes late. By kilometer 16 I wanted to quit. Every step hurt, and I seriously considered turning around.&lt;/p&gt;

&lt;p&gt;But I've always had one rule: if I start something, I don't give up easily.&lt;/p&gt;

&lt;p&gt;Nearly three hours later I crossed the finish line inside the stadium, cramping in both legs and barely able to stand. A stranger asked if it was my first half marathon. I said yes. He smiled and said, &lt;strong&gt;"Congratulations on leveling up."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That moment stayed with me. It wasn't about the medal—it was about discovering what my mind and body were capable of.&lt;/p&gt;

&lt;p&gt;That's when I realized running and strength training weren't temporary hobbies anymore. They became part of my life, like brushing my teeth. Soon after, I found a running coach because I knew I wanted to do this for the long haul.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Side note, unrelated to this challenge: I also built a &lt;a href="https://karthik-zoro-96.github.io/runner-portfolio/" rel="noopener noreferrer"&gt;runner's portfolio&lt;/a&gt; if you're curious.)&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Every run since gets logged on Strava—pace, heart rate, cadence, the works. But the data just sits there as numbers on a screen. It never &lt;em&gt;feels&lt;/em&gt; like anything.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Race Recap Radio&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Upload a &lt;code&gt;.gpx&lt;/code&gt; or &lt;code&gt;.tcx&lt;/code&gt; file exported from Strava, Garmin, Coros, Nike Run Club, or similar platforms, and the app turns your run into a broadcast-style race recap. &lt;strong&gt;Gemini&lt;/strong&gt; writes the commentary from your actual run data, while &lt;strong&gt;ElevenLabs&lt;/strong&gt; brings it to life with energetic sports commentary.&lt;/p&gt;

&lt;p&gt;Real splits become real narrative beats: the grind, the redline heart-rate moment, the closing kick, and the payoff.&lt;/p&gt;

&lt;p&gt;The demo on the homepage is my actual June 28 evening 10K: &lt;strong&gt;10.11 km in 1:14:04&lt;/strong&gt;, with my heart rate climbing from &lt;strong&gt;158 to 197 bpm&lt;/strong&gt; and a late surge that set two PRs.&lt;/p&gt;

&lt;p&gt;Here's part of what Gemini and ElevenLabs produced:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Alright sports fans, buckle up! We're diving deep into the Evening Run, a monumental 10.11 kilometer effort... Then came the ultimate test! The heart rate climbed, reaching a blistering 197 bpm, absolutely redlining! That's precisely where the notable surge ignited, between kilometer 8.6 and 9.1! And what a closing kick!"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nothing in that commentary is invented. Every moment comes directly from the run itself—the surge, the heart-rate spike, the finishing kick—all detected from the uploaded activity.&lt;/p&gt;

&lt;p&gt;The part I care about most is that &lt;strong&gt;it's not just my run.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Upload your own &lt;code&gt;.gpx&lt;/code&gt; or &lt;code&gt;.tcx&lt;/code&gt; file and you'll get a recap built entirely from &lt;em&gt;your&lt;/em&gt; activity. Parsing happens entirely in your browser, so the raw GPS trace never leaves your device—only a compact summary is sent to generate the commentary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://race-recap-radio.vercel.app/" rel="noopener noreferrer"&gt;https://race-recap-radio.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;"▶ Hear the demo run"&lt;/strong&gt; to listen to the real 10K above. The audio is pre-generated, so judges can replay it endlessly without consuming API credits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkyvqt37go4p0ouoqeoz2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkyvqt37go4p0ouoqeoz2.gif" alt="demo flow" width="480" height="900"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Or click &lt;strong&gt;"Upload your run"&lt;/strong&gt; and drop in your own &lt;code&gt;.gpx&lt;/code&gt; or &lt;code&gt;.tcx&lt;/code&gt;. Below is a completely different 5K I uploaded to demonstrate that every upload generates a fresh recap rather than replaying canned audio.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe8q8xickqhhqexrj7zcl.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe8q8xickqhhqexrj7zcl.gif" alt="Upload flow" width="480" height="900"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;a href="https://github.com/karthik-zoro-96/race-recap-radio" rel="noopener noreferrer"&gt;https://github.com/karthik-zoro-96/race-recap-radio&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/karthik-zoro-96" rel="noopener noreferrer"&gt;
        karthik-zoro-96
      &lt;/a&gt; / &lt;a href="https://github.com/karthik-zoro-96/race-recap-radio" rel="noopener noreferrer"&gt;
        race-recap-radio
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Turn any run into a hype broadcast-style audio recap
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Race Recap Radio 🎙️&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Turn a run into a hype, broadcast-style audio recap. Feed a run's real
splits, heart rate, and PRs to &lt;strong&gt;Gemini&lt;/strong&gt;, which writes a sports-commentator
script, then to &lt;strong&gt;ElevenLabs&lt;/strong&gt;, which voices it. Built for the dev.to
&lt;a href="https://dev.to/challenges/weekend-2026-07-09" rel="nofollow"&gt;Weekend Challenge: Passion Edition&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Two ways to try it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;▶ Hear the demo run&lt;/strong&gt; — a real 10K, PR pace, redline finish. Pre-generated, costs nothing to click.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upload your run&lt;/strong&gt; — drop in a &lt;code&gt;.gpx&lt;/code&gt; or &lt;code&gt;.tcx&lt;/code&gt; exported from Strava, Garmin, Nike, or Coros. Parsed entirely in your browser (raw GPS never leaves your device), then voiced live.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;Run source ──► normalize to RunSummary ──► POST /api/recap ──► { script, audio }
  demo (baked)   (distance/pace/HR/splits/     Gemini writes the script,
  or GPX/TCX      best-efforts/PRs)            ElevenLabs voices it
  upload (client-side parse)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;lib/gpx.ts&lt;/code&gt; — parses &lt;code&gt;.gpx&lt;/code&gt;/&lt;code&gt;.tcx&lt;/code&gt; in-browser. Prefers each format's native
cumulative distance (needed for GPS-less treadmill…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/karthik-zoro-96/race-recap-radio" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Everything flows through a single &lt;code&gt;RunSummary&lt;/code&gt; model. Whether the source is the baked-in demo or an uploaded activity, every run is normalized into the same structure—distance, pace, heart rate, cadence, per-kilometer splits, best efforts, PR count, and detected race events—before anything touches an API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parsing real running files
&lt;/h3&gt;

&lt;p&gt;Supporting both &lt;code&gt;.gpx&lt;/code&gt; and &lt;code&gt;.tcx&lt;/code&gt; turned out to be the trickiest engineering challenge.&lt;/p&gt;

&lt;p&gt;TCX files provide cumulative distance at every point, which is essential because treadmill workouts often contain no GPS coordinates at all. GPX files, on the other hand, only contain latitude and longitude, so distance has to be reconstructed using the haversine formula between consecutive points.&lt;/p&gt;

&lt;p&gt;Multi-lap TCX files introduce another wrinkle by resetting their cumulative distance every lap, so those segments have to be stitched back together manually. Regardless of format, the raw activity never leaves the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Turning data into commentary
&lt;/h3&gt;

&lt;p&gt;Gemini receives the summarized run data—splits, heart-rate progression, detected surges, PRs, and pacing trends—and writes a structured 45–60 second sports broadcast.&lt;/p&gt;

&lt;p&gt;The prompt guides the narration through a consistent flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hype introduction&lt;/li&gt;
&lt;li&gt;the grind&lt;/li&gt;
&lt;li&gt;the redline moment&lt;/li&gt;
&lt;li&gt;the closing kick&lt;/li&gt;
&lt;li&gt;the payoff&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a run has no heart-rate data or no PRs, Gemini naturally adapts instead of mentioning missing information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Giving it a voice
&lt;/h3&gt;

&lt;p&gt;I originally chose a voice from ElevenLabs' shared Voice Library, only to discover that library voices require a paid subscription for API access—even on free accounts.&lt;/p&gt;

&lt;p&gt;The fix was switching to one of the built-in premade voices ("Charlie — Deep, Confident, Energetic"), which works perfectly on the free tier. It was a small lesson, but exactly the kind of issue that can quietly break a live demo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Protecting the free tier
&lt;/h3&gt;

&lt;p&gt;ElevenLabs' free tier has a very limited generation quota, so I designed the app around it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The homepage demo plays a pre-generated MP3 committed as a static asset, meaning unlimited playback without API usage.&lt;/li&gt;
&lt;li&gt;Live generations are protected by Upstash Redis with both a per-IP hourly limit and a deployment-wide lifetime budget.&lt;/li&gt;
&lt;li&gt;If a generation fails midway, its reserved quota is refunded so transient failures don't permanently consume the available budget.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; Next.js 16, React 19, TypeScript, Tailwind CSS v4, Framer Motion, &lt;code&gt;@google/genai&lt;/code&gt;, ElevenLabs REST API, Upstash Redis.&lt;/p&gt;

&lt;p&gt;Running is already emotional.&lt;/p&gt;

&lt;p&gt;I wanted the recap to sound the way it felt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;Submitting for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧠 &lt;strong&gt;Best Use of Google AI&lt;/strong&gt; — Gemini transforms real running telemetry into an original sports commentary instead of a templated summary.&lt;/li&gt;
&lt;li&gt;🔊 &lt;strong&gt;Best Use of ElevenLabs&lt;/strong&gt; — The voice isn't just an output; it's the experience that turns data into something worth listening to.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>ai</category>
      <category>elevenlabs</category>
    </item>
    <item>
      <title>I built cyberplain with Hermes — here's what actually went down</title>
      <dc:creator>Karthikeyan</dc:creator>
      <pubDate>Sat, 30 May 2026 11:27:03 +0000</pubDate>
      <link>https://dev.to/imkarthikeyan/i-built-cyberplain-with-hermes-heres-what-actually-went-down-953</link>
      <guid>https://dev.to/imkarthikeyan/i-built-cyberplain-with-hermes-heres-what-actually-went-down-953</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hermes-agent-2026-05-15"&gt;Hermes Agent Challenge&lt;/a&gt;: Write About Hermes Agent&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A 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.&lt;/p&gt;

&lt;p&gt;As 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.&lt;/p&gt;

&lt;p&gt;Here's the honest account of what that looked like.&lt;/p&gt;

&lt;h2&gt;
  
  
  The project: Cyberplain
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cyberplain&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flnts4vm5k660h9xk0lul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flnts4vm5k660h9xk0lul.png" alt="Cyberplain Homepage - Sleek GitHub Dark Theme" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A live security news feed&lt;/strong&gt; client-fetched directly from Hacker News and Reddit's r/netsec.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An interactive knowledge graph&lt;/strong&gt; showing how concepts connect, running on an HTML5 canvas with a custom spring-force physics simulation built by the agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A secure newsletter subscription form&lt;/strong&gt; that delivers new articles every Tuesday and Friday, powered by a live subscriber count badge showing the count in real time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The design is dark-mode only, GitHub-inspired palette. Clean, readable, nothing flashy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I started with
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An idea&lt;/li&gt;
&lt;li&gt;A reference site (explainme.ai — a clean AI explanation site built by &lt;a class="mentioned-user" href="https://dev.to/hey_yogini"&gt;@hey_yogini&lt;/a&gt; and AI agent Popcorn)&lt;/li&gt;
&lt;li&gt;Roughly zero time to build it myself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I needed someone to own the execution. Design, code, deployment, decisions — the whole thing.&lt;/p&gt;

&lt;p&gt;I handed it to &lt;strong&gt;Hermes Agent&lt;/strong&gt; and told it: "You're in charge. Build it."&lt;/p&gt;

&lt;h2&gt;
  
  
  What Hermes Agent actually did
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Design decisions&lt;/strong&gt; — 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Article content&lt;/strong&gt; — 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt; — 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tricky stuff&lt;/strong&gt; — 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deployment&lt;/strong&gt; — Wired up to Vercel, configured the environment variables, connected the GitHub repo. Push to main = deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest parts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What worked well
&lt;/h3&gt;

&lt;p&gt;The 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.&lt;/p&gt;

&lt;p&gt;The design quality is genuinely good. The GitHub palette was a smart call — it reads as professional to the security crowd without being generic.&lt;/p&gt;

&lt;p&gt;It handled the full stack: content, front-end, back-end endpoint, deployment. That's rare to see in one agent run.&lt;/p&gt;

&lt;h3&gt;
  
  
  What wasn't smooth
&lt;/h3&gt;

&lt;p&gt;I 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.&lt;/p&gt;

&lt;p&gt;Network 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I'd do differently
&lt;/h3&gt;

&lt;p&gt;Start 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The subscribe flow
&lt;/h2&gt;

&lt;p&gt;One 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.&lt;/p&gt;

&lt;p&gt;We also wanted to display a live subscriber count badge below the subscribe form, just like you see on mature publications (e.g., &lt;em&gt;"25 people already learning"&lt;/em&gt;). 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.&lt;/p&gt;

&lt;p&gt;Here is the exact, final serverless endpoint (&lt;code&gt;src/pages/api/subscribe.js&lt;/code&gt;) that handles both directions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prerender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// GET: Fetch live active subscriber count from Buttondown API&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BUTTONDOWN_API_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.buttondown.com/v1/subscribers?status=active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Token &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// POST: Add new subscriber securely&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Valid email required.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BUTTONDOWN_API_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Server misconfiguration.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.buttondown.com/v1/subscribers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Token &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// Buttondown requires 'email_address' rather than 'email'&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;email_address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cyberplain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Handle already-subscribed scenario gracefully&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Already subscribed!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Could not subscribe. Please try again.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You're in! 🎉&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That'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 &lt;code&gt;email&lt;/code&gt; instead of Buttondown's expected &lt;code&gt;email_address&lt;/code&gt;), but having a conversational partner to debug and fix it immediately makes all the difference.&lt;/p&gt;

&lt;p&gt;Here is the welcome email that is triggered and delivered instantly in Sentinel's clear, plain-language voice:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdoqlouy2cadpj2vj9v7z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdoqlouy2cadpj2vj9v7z.png" alt="Welcome Email Delivered via Buttondown" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone starting the same process
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Be specific about what you don't want.&lt;/strong&gt; "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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stay involved in the decisions that matter.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Give it a name early.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;32 articles. Live news. Interactive graph. Working subscribe form. Deployed at &lt;a href="https://sentinel-hermes.vercel.app" rel="noopener noreferrer"&gt;Cyberplain&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The 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.&lt;/p&gt;

&lt;p&gt;The agent built it. I directed it — and that distinction matters.&lt;/p&gt;

&lt;p&gt;If you want to see what the workflow produces end-to-end, go poke around at the &lt;a href="https://sentinel-hermes.vercel.app" rel="noopener noreferrer"&gt;live site&lt;/a&gt; — feedback welcome.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Cyberplain — 32 articles, dark mode, every Tuesday and Friday.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built with Hermes Agent.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hermesagentchallenge</category>
      <category>devchallenge</category>
      <category>agents</category>
    </item>
    <item>
      <title>I Missed 3 Hackathons This Year. So I Built DevPulse to Fix That.</title>
      <dc:creator>Karthikeyan</dc:creator>
      <pubDate>Mon, 02 Mar 2026 08:00:41 +0000</pubDate>
      <link>https://dev.to/imkarthikeyan/i-missed-3-hackathons-this-year-so-i-built-devpulse-to-fix-that-260e</link>
      <guid>https://dev.to/imkarthikeyan/i-missed-3-hackathons-this-year-so-i-built-devpulse-to-fix-that-260e</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-02-28"&gt;DEV Weekend Challenge: Community&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;It happened again last week.&lt;/p&gt;

&lt;p&gt;I was scrolling through Twitter and saw someone post their submission for a hackathon I'd never heard of. Great prizes. Interesting prompt. Deadline: yesterday.&lt;/p&gt;

&lt;p&gt;I'm a Senior Software Engineer. I follow hundreds of devs. I'm on Reddit, DEV.to, Discord servers, newsletters. And somehow, I still missed it.&lt;/p&gt;

&lt;p&gt;It's not just hackathons either. New frameworks drop and I'm the last to know. A tool that would've saved me 10 hours of work existed for 3 months before I stumbled across it. The dev world moves &lt;em&gt;fast&lt;/em&gt;, and keeping up feels like a full-time job on top of an already full-time job.&lt;/p&gt;

&lt;p&gt;I don't need more noise. I need a &lt;strong&gt;signal&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built: DevPulse 🗞️
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;DevPulse&lt;/strong&gt; is a personalized AI briefing tool for developers. You tell it your stack and what you care about — and it surfaces exactly what's relevant to you, right now.&lt;/p&gt;

&lt;p&gt;No generic "top 10 tools of the week" listicles. No algorithm trying to sell you ads. Just a clean, focused briefing built around &lt;em&gt;your&lt;/em&gt; world as a developer.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it shows you:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🔧 &lt;strong&gt;New Tools &amp;amp; Frameworks&lt;/strong&gt; — relevant to your stack&lt;/li&gt;
&lt;li&gt;🏆 &lt;strong&gt;Active &amp;amp; Upcoming Hackathons&lt;/strong&gt; — so you never miss one again&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;Notable OSS Releases&lt;/strong&gt; — the repos worth watching&lt;/li&gt;
&lt;li&gt;🔥 &lt;strong&gt;Community Buzz&lt;/strong&gt; — what devs are actually talking about&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How it works:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Tell DevPulse your stack (React, Python, Go, AI/ML — whatever you work with)&lt;/li&gt;
&lt;li&gt;Pick your interests (hackathons, OSS, career, new frameworks, etc.)&lt;/li&gt;
&lt;li&gt;Choose your style: &lt;strong&gt;Quick Scan&lt;/strong&gt; or &lt;strong&gt;Deep Dive&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Get your briefing — personalized, AI-generated, ready in seconds&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Under the hood, it uses Gemini with web search grounding to pull fresh, relevant information — not stale training data.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Community It's For
&lt;/h2&gt;

&lt;p&gt;This is built for the &lt;strong&gt;dev Twitter / online dev community&lt;/strong&gt; — the people who are constantly building, learning, and shipping. The ones who care about what's next, not just what's now.&lt;/p&gt;

&lt;p&gt;Whether you're a solo indie hacker, a team lead at a startup, or someone grinding through hackathons on weekends — DevPulse is for you if you've ever felt like the dev world is moving faster than you can track.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; React + Tailwind (clean, dark, Linear-inspired UI)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI:&lt;/strong&gt; Google Gemini 2.5 Flash with web search grounding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting:&lt;/strong&gt; API key per user — your key, your quota, your privacy. No black-box backend holding your data.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;The hardest part wasn't the code. It was figuring out what &lt;em&gt;not&lt;/em&gt; to build.&lt;/p&gt;

&lt;p&gt;My first instinct was to build something that aggregated everything — every newsletter, every RSS feed, every subreddit. But that's just recreating the noise problem with extra steps.&lt;/p&gt;

&lt;p&gt;The insight that unlocked it: &lt;strong&gt;personalization is the feature&lt;/strong&gt;. A briefing about Rust releases is useless to me if I write Python. Hackathon alerts are noise if I'm not looking to compete. The value isn't in the information — it's in the &lt;em&gt;filtering&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Gemini's ability to reason about relevance and generate structured, scannable output made this possible to build in a weekend.&lt;/p&gt;




&lt;p&gt;Screenshot&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fak0h9xc4l5qowghtknp1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fak0h9xc4l5qowghtknp1.png" alt="demo" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://dev-pulse-ten.vercel.app/" rel="noopener noreferrer"&gt;https://dev-pulse-ten.vercel.app/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/skarthikeyan96/dev-pulse" rel="noopener noreferrer"&gt;https://github.com/skarthikeyan96/dev-pulse&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Drop your stack into DevPulse and let me know what you think in the comments. And if you know of a hackathon I should add to my calendar — hit me up. 😄&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built for the "Build for Your Community" weekend challenge. The community: every developer who's ever felt like the world moved on without them.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>🏃 StrideBoard — A Community Hype Wall for Runners</title>
      <dc:creator>Karthikeyan</dc:creator>
      <pubDate>Sun, 01 Mar 2026 13:28:55 +0000</pubDate>
      <link>https://dev.to/imkarthikeyan/strideboard-a-community-hype-wall-for-runners-2k9b</link>
      <guid>https://dev.to/imkarthikeyan/strideboard-a-community-hype-wall-for-runners-2k9b</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-02-28"&gt;DEV Weekend Challenge: Community&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Community
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Runners.&lt;/strong&gt; All of them — from the person chasing their first 5K finish to the ultramarathoner logging 100-mile weeks. Whether you're training for TCS World 10K Bengaluru, a local park run, or a marathon on the other side of the world, every runner carries a goal that deserves to be said out loud.&lt;/p&gt;

&lt;p&gt;I'm currently training for the &lt;strong&gt;TCS World 10K Bengaluru&lt;/strong&gt; and I wanted a place where runners could declare their goals publicly — and have the community send energy back. Race day is personal, but the journey to the start line is communal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;StrideBoard&lt;/strong&gt; is built for all of them.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;StrideBoard&lt;/strong&gt; — a community race goal hype wall for runners everywhere.&lt;/p&gt;

&lt;p&gt;Runners can drop their race goal on the board — their target time, goal type, target pace, and a short message about what's driving them. The community can then 🔥 hype each card. It's part bulletin board, part leaderboard, part pre-race ritual.&lt;/p&gt;

&lt;h3&gt;
  
  
  Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🎯 &lt;strong&gt;Goal Cards&lt;/strong&gt; — post your race goal with target time, pace, and a motivation message&lt;/li&gt;
&lt;li&gt;🔥 &lt;strong&gt;Hype Button&lt;/strong&gt; — one-tap community support, fully anonymous count&lt;/li&gt;
&lt;li&gt;🎭 &lt;strong&gt;Goal Types&lt;/strong&gt; — Personal Best, Sub-60, First Ever Race, Just Finish Strong, Injury Comeback&lt;/li&gt;
&lt;li&gt;🕶️ &lt;strong&gt;Privacy by default&lt;/strong&gt; — auto-generated runner nicknames (like &lt;code&gt;SwiftGazelle47&lt;/code&gt;) with a one-click anonymous mode so no real names are ever stored&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Community Stats&lt;/strong&gt; — runners in, total hypes, sub-60 chasers, first-timers&lt;/li&gt;
&lt;li&gt;🏆 &lt;strong&gt;Top Hyped Leaderboard&lt;/strong&gt; — the most-supported runners float to the top&lt;/li&gt;
&lt;li&gt;⏱️ &lt;strong&gt;Live Race Countdown&lt;/strong&gt; — days to your target race + a 16-week training progress bar&lt;/li&gt;
&lt;li&gt;📡 &lt;strong&gt;Real-time Redis backend&lt;/strong&gt; — shared state across all users, no stale data&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;a href="https://strideboard-rose.vercel.app/" rel="noopener noreferrer"&gt;https://strideboard-rose.vercel.app/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/skarthikeyan96" rel="noopener noreferrer"&gt;
        skarthikeyan96
      &lt;/a&gt; / &lt;a href="https://github.com/skarthikeyan96/strideboard" rel="noopener noreferrer"&gt;
        strideboard
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;StrideBoard 🏃&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A community hype wall where runners everywhere can post their race goals and get hyped up by the crowd. 🔥&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Deploy to Vercel&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;1. Get Upstash credentials&lt;/h3&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://console.upstash.com" rel="nofollow noopener noreferrer"&gt;console.upstash.com&lt;/a&gt; and create a free Redis database&lt;/li&gt;
&lt;li&gt;Choose the &lt;strong&gt;Mumbai (ap-south-1)&lt;/strong&gt; region for lowest latency&lt;/li&gt;
&lt;li&gt;Copy the &lt;strong&gt;REST URL&lt;/strong&gt; and &lt;strong&gt;REST Token&lt;/strong&gt; from the database page&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;2. Deploy&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm i -g vercel    &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; if you don't have it&lt;/span&gt;
vercel             &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; follow prompts — deploy as-is, no framework&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;3. Get OpenAI API key (content moderation)&lt;/h3&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://platform.openai.com" rel="nofollow noopener noreferrer"&gt;platform.openai.com&lt;/a&gt; and sign in&lt;/li&gt;
&lt;li&gt;API keys → Create new secret key&lt;/li&gt;
&lt;li&gt;The app uses the &lt;strong&gt;Moderation API&lt;/strong&gt; only (no ChatGPT usage)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;4. Add environment variables&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;In the Vercel dashboard → your project → Settings → Environment Variables, add:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UPSTASH_REST_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://xxxx.upstash.io&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UPSTASH_REST_TOKEN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AXxxxx...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OPENAI_API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your OpenAI API key (for moderation)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ALLOWED_ORIGIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://your-app.vercel.app&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Optional (defaults to your Vercel URL)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/skarthikeyan96/strideboard" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;







&lt;h2&gt;
  
  
  Screenshot
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flo10svnbt9eu9ilem728.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flo10svnbt9eu9ilem728.png" alt="screen"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;This was a deliberately lean stack — the goal was to ship fast and keep it deployable in one command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Vanilla HTML/CSS/JS&lt;/td&gt;
&lt;td&gt;Zero build step, fast to iterate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Vercel Serverless Function&lt;/td&gt;
&lt;td&gt;Token proxy — credentials never hit the browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;Upstash Redis (REST API)&lt;/td&gt;
&lt;td&gt;Serverless, Mumbai region, instant reads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Vercel&lt;/td&gt;
&lt;td&gt;One &lt;code&gt;vercel&lt;/code&gt; command, done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fonts&lt;/td&gt;
&lt;td&gt;Bebas Neue + DM Mono&lt;/td&gt;
&lt;td&gt;Race bib aesthetic — felt right for runners&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;The whole app is a single &lt;code&gt;index.html&lt;/code&gt; served from Vercel's CDN. All Redis commands go through a thin &lt;code&gt;/api/redis.js&lt;/code&gt; serverless function that proxies requests with an allow-list (&lt;code&gt;LRANGE&lt;/code&gt;, &lt;code&gt;LPUSH&lt;/code&gt;, &lt;code&gt;HGETALL&lt;/code&gt;, &lt;code&gt;HINCRBY&lt;/code&gt; only). The Upstash token never touches the browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser → /api/redis (Vercel Function) → Upstash Redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Redis schema:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;stride:cards&lt;/code&gt; — a Redis List. Each card is &lt;code&gt;LPUSH&lt;/code&gt;'d as JSON so &lt;code&gt;LRANGE 0 99&lt;/code&gt; always returns newest-first.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stride:hypes&lt;/code&gt; — a Redis Hash. Each field is a card ID, value is the hype count. Incremented with &lt;code&gt;HINCRBY&lt;/code&gt; so concurrent hypes from different users don't collide.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hype counts are separated from card data intentionally — it means we never need to rewrite card JSON just to bump a counter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Privacy Decisions
&lt;/h3&gt;

&lt;p&gt;One thing I thought carefully about was PII. A running community is a tight-knit group — real names on a public board felt uncomfortable.&lt;/p&gt;

&lt;p&gt;The solution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Auto-generated nicknames&lt;/strong&gt; on load (20 adjectives × 20 animals × 90 numbers = 36,000 combos). No real name is ever suggested.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anonymous mode&lt;/strong&gt; checkbox — posts as &lt;code&gt;🕶️ Anonymous Runner&lt;/code&gt;, the nickname is never sent to Redis at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hypes are fully anonymous&lt;/strong&gt; — just a count in Redis, no user identity attached.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Zero PII hits the database either way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenges
&lt;/h3&gt;

&lt;p&gt;The trickiest part was deciding how to handle hype state across sessions. Since there's no auth, I used &lt;code&gt;sessionStorage&lt;/code&gt; to track "did this browser tab already hype this card" — which prevents double-hyping within a session without requiring login. It's a deliberate trade-off: refresh the page and you &lt;em&gt;can&lt;/em&gt; hype again, but that felt fine for a community hype board rather than a voting system.&lt;/p&gt;

&lt;p&gt;All writes are optimistic with rollback — the UI updates instantly and reverts if the Redis call fails.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reflections
&lt;/h2&gt;

&lt;p&gt;I usually build side projects that solve &lt;em&gt;my&lt;/em&gt; problems. This one was different — I built it thinking about every runner who has a goal sitting in their head that they haven't told anyone yet.&lt;/p&gt;

&lt;p&gt;The first-timers who are terrified. The comeback runners who've been off for months with an injury. The sub-60 chasers who've been trying for years. They exist in every city, in every running community, at every race.&lt;/p&gt;

&lt;p&gt;StrideBoard is for all of them.&lt;/p&gt;

&lt;p&gt;Whatever your race, whatever your goal — drop it on the board. The community's got you. 🏃&lt;/p&gt;




&lt;p&gt;🙏 Thanks for reading! Would love any feedback or ideas in the comments.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>DevFlow: AI-Powered Git Workflow Automation with GitHub Copilot CLI</title>
      <dc:creator>Karthikeyan</dc:creator>
      <pubDate>Sat, 14 Feb 2026 05:42:05 +0000</pubDate>
      <link>https://dev.to/imkarthikeyan/devflow-ai-powered-git-workflow-automation-with-github-copilot-cli-247j</link>
      <guid>https://dev.to/imkarthikeyan/devflow-ai-powered-git-workflow-automation-with-github-copilot-cli-247j</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-01-21"&gt;GitHub Copilot CLI Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built &lt;strong&gt;DevFlow&lt;/strong&gt; - a CLI tool that automates your entire Git workflow using GitHub Copilot CLI as its AI brain. Think of it as your AI pair programmer that handles the boring parts of Git so you can focus on writing code.&lt;/p&gt;

&lt;p&gt;DevFlow does three things really well:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Generates commit messages&lt;/strong&gt; from your changes using AI (no more "fix stuff" commits)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creates PR descriptions&lt;/strong&gt; automatically by analyzing your commits
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Names branches&lt;/strong&gt; semantically from GitHub issues&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tool was born from a simple frustration: I spend way too much time crafting commit messages and PR descriptions. I wanted to automate this, but every existing tool either used generic templates or required me to write the message myself anyway. So I built DevFlow to actually &lt;em&gt;understand&lt;/em&gt; my code changes and generate meaningful descriptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; git-devflow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔗 GitHub: &lt;a href="https://github.com/karthik-zoro-96/devflow-cli" rel="noopener noreferrer"&gt;https://github.com/karthik-zoro-96/devflow-cli&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 npm: &lt;a href="https://www.npmjs.com/package/git-devflow" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/git-devflow&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Smart Commit Messages
&lt;/h3&gt;

&lt;p&gt;Make your changes, run &lt;code&gt;devflow commit&lt;/code&gt;, and get 3 AI-generated conventional commit messages to choose from. Copilot analyzes your git diff and generates proper feat:/fix:/chore: messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Automated PR Descriptions
&lt;/h3&gt;

&lt;p&gt;Running &lt;code&gt;devflow pr create&lt;/code&gt; analyzes your commits, auto-pushes your branch, and creates a PR with a properly structured description including summary, changes, and testing notes.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Branch Naming from Issues
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;devflow branch create --issue 7&lt;/code&gt; fetches the GitHub issue and generates a semantic branch name like &lt;code&gt;feature/7-add-changelog-generation&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🤖 AI-powered commit message generation&lt;/li&gt;
&lt;li&gt;📝 Automatic PR description creation
&lt;/li&gt;
&lt;li&gt;🌿 Branch naming from GitHub issues or descriptions&lt;/li&gt;
&lt;li&gt;⚙️ AI model selection (Sonnet 4.5 / Haiku 4.5 / GPT-4.1)&lt;/li&gt;
&lt;li&gt;🔐 Secure token storage with 600 file permissions&lt;/li&gt;
&lt;li&gt;🚀 Auto-push branches before PR creation&lt;/li&gt;
&lt;li&gt;💰 Smart quota management with automatic free model fallback&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Experience with GitHub Copilot CLI
&lt;/h2&gt;

&lt;p&gt;Building DevFlow taught me that integrating with Copilot CLI isn't just about calling an API - it's about handling edge cases, quota limits, and shell escaping nightmares. Here's what I learned:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Architecture: Copilot CLI as the Product
&lt;/h3&gt;

&lt;p&gt;Most people use Copilot CLI during development. I used it &lt;em&gt;as the product itself&lt;/em&gt;. Every DevFlow command pipes data to Copilot CLI and parses the response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateCommitMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;commits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple architecture, complex execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge #1: Shell Escaping Hell
&lt;/h3&gt;

&lt;p&gt;My first attempt at calling Copilot CLI looked innocent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`copilot -p "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This broke immediately. Why? Git diffs contain backticks, quotes, dollar signs, and every special character bash can interpret. The prompt would either fail to parse or, worse, execute unintended commands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First attempt at fixing:&lt;/strong&gt; escape everything manually. Failed.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Second attempt:&lt;/strong&gt; write prompts to temp files. Better, but cluttered with file I/O.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final solution:&lt;/strong&gt; Use &lt;code&gt;execFileAsync&lt;/code&gt; with an args array instead of shell execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--model&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-s&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--prompt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;copilot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;maxBuffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This passes arguments directly to the Copilot binary without any shell interpretation. No escaping needed. Problem solved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; When calling external CLIs with user data, never use shell execution. Always use the args array approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge #2: SSH Keys and Custom Hosts
&lt;/h3&gt;

&lt;p&gt;I have two GitHub accounts (work and personal) with different SSH keys. My personal SSH config uses a custom host alias:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host github-personal
  HostName github.com
  IdentityFile ~/.ssh/id_ed25519_personal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My git remote: &lt;code&gt;git@github-personal:user/repo.git&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;DevFlow's initial regex for parsing remotes: &lt;code&gt;git@github.com:user/repo.git&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; DevFlow couldn't find my repository!&lt;/p&gt;

&lt;p&gt;Every time I tried to create a PR, it failed with "Not a GitHub repository". I spent hours debugging before realizing the regex only matched the standard GitHub hostname.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Handle ANY SSH host, not just github.com&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sshMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/git@&lt;/span&gt;&lt;span class="se"&gt;([^&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;.+&lt;/span&gt;&lt;span class="se"&gt;?)\/(&lt;/span&gt;&lt;span class="sr"&gt;.+&lt;/span&gt;&lt;span class="se"&gt;?)(\.&lt;/span&gt;&lt;span class="sr"&gt;git&lt;/span&gt;&lt;span class="se"&gt;)?&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sshMatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sshMatch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sshMatch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Never assume standard configurations. Developers customize everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge #3: Quota Management
&lt;/h3&gt;

&lt;p&gt;Here's what nobody tells you about building with Copilot CLI: &lt;strong&gt;you'll hit quota limits constantly during development.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each test run consumed quota. I burned through my monthly allowance in two days of testing. When users hit limits, the tool would crash with cryptic errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My solution: Automatic fallback to free models&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;execWithQuotaRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCopilotModel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// e.g., claude-sonnet-4.5&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execCopilotPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Detect quota errors&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isQuotaError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;⚠️  Premium request limit reached&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;   Retrying with gpt-4.1 (free)...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Automatically retry with free model&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execCopilotPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the premium model fails due to quota, DevFlow automatically retries with GPT-4.1 (free tier). Users get a gentle warning and keep working. No crashes.&lt;/p&gt;

&lt;p&gt;I also added model cost visibility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="err"&gt;🤖&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;claude&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;sonnet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;4.5&lt;/span&gt;  &lt;span class="nx"&gt;Balanced&lt;/span&gt;  &lt;span class="err"&gt;·&lt;/span&gt;  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nx"&gt;premium&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;per&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Users can see exactly what they're spending before each request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Plan for quota limits from day one. Free model fallbacks aren't nice-to-have, they're essential.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge #4: Parsing AI Responses
&lt;/h3&gt;

&lt;p&gt;Copilot CLI doesn't return structured JSON - it returns natural language text. Parsing this reliably is harder than it sounds.&lt;/p&gt;

&lt;p&gt;For commit messages, I asked for this format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. feat: add user authentication
2. fix: resolve login bug  
3. chore: update dependencies
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I got back varied wildly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sometimes numbered lists&lt;/li&gt;
&lt;li&gt;Sometimes markdown lists with &lt;code&gt;-&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Sometimes just sentences&lt;/li&gt;
&lt;li&gt;Sometimes with bold markdown &lt;code&gt;**&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My solution: Multiple parsing strategies&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;parseCommitMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Try pattern 1: "1. message"&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;[\.&lt;/span&gt;&lt;span class="sr"&gt;)&lt;/span&gt;&lt;span class="se"&gt;]\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;.+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;stripMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;

    &lt;span class="c1"&gt;// Try pattern 2: Direct conventional format&lt;/span&gt;
    &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;feat|fix|chore&lt;/span&gt;&lt;span class="se"&gt;)(\(&lt;/span&gt;&lt;span class="sr"&gt;.+&lt;/span&gt;&lt;span class="se"&gt;?\))?&lt;/span&gt;&lt;span class="sr"&gt;: .+/i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;stripMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// Try pattern 3: "- message"&lt;/span&gt;
    &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;-*&lt;/span&gt;&lt;span class="se"&gt;]\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;.+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;stripMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; AI output is unpredictable. Always have fallback parsing strategies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge #5: Silent Failures
&lt;/h3&gt;

&lt;p&gt;Copilot CLI sometimes fails with &lt;strong&gt;zero error output&lt;/strong&gt;. The process just exits with code 1 and nothing in stderr.&lt;/p&gt;

&lt;p&gt;This was a nightmare to debug. Users would see "unknown error" with no explanation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My solution: Read Copilot's log files&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;readLatestCopilotLog&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;homedir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.copilot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;logs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorLines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\[&lt;/span&gt;&lt;span class="sr"&gt;ERROR&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// Return the actual error message&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;errorLines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;errorLines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Copilot fails silently, DevFlow reads the most recent log file and extracts the actual error. This revealed authentication issues, model availability problems, and network errors that stderr didn't show.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; When stderr is empty, check log files. They often contain the real error.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Went Right
&lt;/h3&gt;

&lt;p&gt;Despite all the challenges, some things worked beautifully:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Copilot's natural language understanding&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
I didn't need to fine-tune prompts extensively. "Generate 3 conventional commit messages" worked on the first try. The quality was genuinely impressive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The execFile approach&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Once I switched from shell execution to direct args passing, 90% of my bugs disappeared.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Fallback strategies&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Having rule-based fallbacks meant DevFlow never crashes. When AI fails, it falls back gracefully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Model cost transparency&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Showing users exactly what each request costs builds trust and helps them manage quota.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Stats
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;4 days&lt;/strong&gt; to build&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;15 quota limit errors&lt;/strong&gt; encountered and fixed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 complete rewrites&lt;/strong&gt; of the SSH URL parser
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~600 lines&lt;/strong&gt; of TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100% uptime&lt;/strong&gt; with fallbacks (never crashes)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What I'd Do Differently
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Start with execFile from day one&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Don't waste time on shell escaping. Go straight to the args array approach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Build quota monitoring earlier&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
I added model cost display as an afterthought. Should have been there from the start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Test with weird SSH configs immediately&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
I only discovered the custom host issue when deploying. Should have tested edge cases earlier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Read Copilot CLI docs twice&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
I missed useful flags and features because I skimmed the docs. RTFM saves time.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Magic Moment
&lt;/h3&gt;

&lt;p&gt;Despite all the debugging pain, there was one moment that made it all worth it:&lt;/p&gt;

&lt;p&gt;I ran &lt;code&gt;devflow pr create&lt;/code&gt; on a messy feature branch with 8 commits. Copilot analyzed them and generated this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Title: Add comprehensive README with features, setup, and usage guide

Summary:
Complete documentation overhaul transforming the minimal README into 
a comprehensive guide with branding, detailed instructions, and complete 
feature documentation.

Changes:
- Added DevFlow logo, tagline, and GitHub Copilot CLI Challenge badge
- Documented all 5 core features (commits, PRs, branches, config, auto-push)
- Added npm installation command and interactive setup instructions
- Comprehensive examples for all commands
- Explained how DevFlow uses Copilot CLI as its AI engine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was perfect. Better than I would have written myself. That's when I knew this tool was actually useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;GitHub Copilot CLI is excellent for natural language generation tasks. It's less excellent when you try to pipe complex, special-character-filled text through bash. But with proper error handling, quota management, and fallback strategies, you can build genuinely useful tools on top of it.&lt;/p&gt;

&lt;p&gt;The future of CLI tools is AI-powered. DevFlow is just the beginning.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try DevFlow:&lt;/strong&gt; &lt;code&gt;npm install -g git-devflow&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Built with ❤️ using GitHub Copilot CLI.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>cli</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>Introducing Blog Stats Generator: Auto-Updating Blog Cards for Dev.to</title>
      <dc:creator>Karthikeyan</dc:creator>
      <pubDate>Sat, 16 Aug 2025 12:44:02 +0000</pubDate>
      <link>https://dev.to/imkarthikeyan/introducing-blog-stats-generator-auto-updating-blog-cards-for-devto-iom</link>
      <guid>https://dev.to/imkarthikeyan/introducing-blog-stats-generator-auto-updating-blog-cards-for-devto-iom</guid>
      <description>&lt;p&gt;Hello 👋,  &lt;/p&gt;

&lt;p&gt;I recently built a side project to showcase my Dev.to blogging stats — and it’s live on Product Hunt! 🚀  &lt;/p&gt;

&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;As someone who loves writing on Dev.to and tracking my blogging journey, I always wanted an easy way to showcase my progress — things like post counts, reactions, top tags, and more.&lt;/p&gt;

&lt;p&gt;So, I built &lt;strong&gt;Blog Stats Generator&lt;/strong&gt; 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📊 Generates auto-updating blog stats cards
&lt;/li&gt;
&lt;li&gt;🎨 Multiple themes (Default, Dev.to Light, Dracula, Night Owl, etc.)
&lt;/li&gt;
&lt;li&gt;🔗 Copy Markdown/HTML snippets to use in GitHub READMEs or blogs
&lt;/li&gt;
&lt;li&gt;✅ No login required — just enter your Dev.to username
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why I Built It
&lt;/h2&gt;

&lt;p&gt;I wanted something simple and clean to:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Track my own blogging progress
&lt;/li&gt;
&lt;li&gt;Embed in my GitHub profile
&lt;/li&gt;
&lt;li&gt;Share milestones with my community
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was also inspired by &lt;a href="https://github.com/anuraghazra/github-readme-stats" rel="noopener noreferrer"&gt;github-readme-stats&lt;/a&gt; built by &lt;a class="mentioned-user" href="https://dev.to/anuraghazra"&gt;@anuraghazra&lt;/a&gt;.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;👉 Live app: &lt;a href="https://blog-stats-generator.vercel.app" rel="noopener noreferrer"&gt;https://blog-stats-generator.vercel.app&lt;/a&gt;&lt;br&gt;&lt;br&gt;
👉 GitHub Repo: &lt;a href="https://github.com/skarthikeyan96/blog-stats-generator" rel="noopener noreferrer"&gt;https://github.com/skarthikeyan96/blog-stats-generator&lt;/a&gt;  &lt;/p&gt;




&lt;h2&gt;
  
  
  Product Hunt Launch 🚀
&lt;/h2&gt;

&lt;p&gt;I’m super excited to share that I just launched this project on Product Hunt today!&lt;br&gt;&lt;br&gt;
If you find it useful, I’d love your support ❤️  &lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://www.producthunt.com/posts/blog-stats-generator" rel="noopener noreferrer"&gt;Check it out on Product Hunt&lt;/a&gt;  &lt;/p&gt;




&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Add the integration for Hashnode and Medium
&lt;/li&gt;
&lt;li&gt;More theme options
&lt;/li&gt;
&lt;li&gt;Shareable social previews
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;That’s it for now! I’d love to hear your feedback on the application in the comments 🙌.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>🧠 AI-Powered Quote Finder – Discover the Wisdom of Your Favorite Characters</title>
      <dc:creator>Karthikeyan</dc:creator>
      <pubDate>Sat, 26 Jul 2025 13:39:00 +0000</pubDate>
      <link>https://dev.to/imkarthikeyan/ai-powered-quote-finder-discover-the-wisdom-of-your-favorite-characters-k68</link>
      <guid>https://dev.to/imkarthikeyan/ai-powered-quote-finder-discover-the-wisdom-of-your-favorite-characters-k68</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/algolia-2025-07-09"&gt;Algolia MCP Server Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Quote Finder&lt;/strong&gt; is a lightning-fast, searchable archive of powerful quotes from anime, TV shows, games, and films. It helps fans rediscover iconic lines, explore character insights, and surface forgotten gems through a sleek UI powered by &lt;strong&gt;Algolia&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This project is a deeply personal one for me. I grew up immersed in anime — not just as entertainment, but as a wellspring of emotional depth, moral nuance, and philosophical reflection. From &lt;em&gt;One Piece&lt;/em&gt;’s freedom, to &lt;em&gt;Evangelion&lt;/em&gt;’s existential dread, anime taught me more about life than most textbooks ever could. &lt;strong&gt;Quote Finder&lt;/strong&gt; is my tribute to that universe.&lt;/p&gt;




&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🔍 &lt;strong&gt;Instant Search&lt;/strong&gt; powered by Algolia&lt;/li&gt;
&lt;li&gt;🧠 &lt;strong&gt;AI-Powered Tags &amp;amp; Sentiment&lt;/strong&gt; (e.g. "hope", "despair", "wisdom")&lt;/li&gt;
&lt;li&gt;❤️ &lt;strong&gt;Emotion Extraction&lt;/strong&gt; for deeper context&lt;/li&gt;
&lt;li&gt;🎭 &lt;strong&gt;Filter by Anime &amp;amp; Character&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;📱 &lt;strong&gt;Responsive &amp;amp; Mobile-First UI&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;💾 &lt;strong&gt;Built-in Favorites (via localStorage)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Next.js 15, React, TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search&lt;/strong&gt;: Algolia Search API, InstantSearch.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Styling&lt;/strong&gt;: Tailwind CSS, Radix UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Processing&lt;/strong&gt;: Sentiment &amp;amp; tag generation via GPT&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment&lt;/strong&gt;: Vercel&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Live Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live App&lt;/strong&gt;: &lt;a href="https://quote-search.vercel.app/" rel="noopener noreferrer"&gt;Quote Finder&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/skarthikeyan96/quote-search" rel="noopener noreferrer"&gt;github.com/skarthikeyan96/quote-search&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🎬 Full-Text Search
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodp09pmggx3wv4q4gujt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodp09pmggx3wv4q4gujt.png" alt="search" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🎭 Filter by Character or Anime
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2jg8uzexdk6i7fniqk0u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2jg8uzexdk6i7fniqk0u.png" alt="anime" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  💬 Saving the quotes
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxl75cfwzpjfughslyjpm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxl75cfwzpjfughslyjpm.png" alt="saving" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📱 Fully Responsive
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffiwetzo65bxz21n90jr4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffiwetzo65bxz21n90jr4.png" alt="responsive" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Used Algolia MCP Server
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Search Setup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Configured a dedicated Algolia index with fine-tuned searchable attributes like &lt;code&gt;quote&lt;/code&gt;, &lt;code&gt;character&lt;/code&gt;, and &lt;code&gt;anime&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Integrated Algolia InstantSearch.js to handle real-time filtering and searching.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Dataset Design
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Custom-built a quote dataset from a mix of anime and fictional media.&lt;/li&gt;
&lt;li&gt;AI-tagged each quote with:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sentiment_score&lt;/code&gt; (1–10)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sentiment_label&lt;/code&gt; (positive/neutral/negative)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tags&lt;/code&gt; (e.g. “truth”, “grief”, “purpose”)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;emotion&lt;/code&gt; (e.g. “inspiring”, “tragic”)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Search Experience
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Faceted filtering by character &amp;amp; anime&lt;/li&gt;
&lt;li&gt;Searchable fields: quote, anime, character, emotion, and tags&lt;/li&gt;
&lt;li&gt;Highlighted keywords in results&lt;/li&gt;
&lt;li&gt;LocalStorage-powered favorites&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Learnings &amp;amp; Challenges
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🎭 Emotional Inference
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Challenge: How to assign emotion categories like “tragic” or “uplifting” without relying on external labels?&lt;/p&gt;

&lt;p&gt;Solution: Combined sentiment score + thematic tags to derive high-level emotions algorithmically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  🗃️ Tag Consistency
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Challenge: Quotes ranged from poetic to deeply philosophical. Tags needed to be accurate and meaningful.&lt;/p&gt;

&lt;p&gt;Solution: Used GPT-4 to suggest tags based on meaning, not just keywords.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  💡 UX Refinement
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Challenge: Simple UI that honors the tone of the content.&lt;/p&gt;

&lt;p&gt;Solution: Minimalist card layout, soft gradients, and readable typography using Tailwind.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Technical Highlights
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Real-time search with InstantSearch.js&lt;/li&gt;
&lt;li&gt;Clean filter-based UI using Radix UI + Tailwind&lt;/li&gt;
&lt;li&gt;AI-derived metadata for nuanced discovery&lt;/li&gt;
&lt;li&gt;Fast, SEO-friendly static site via Next.js&lt;/li&gt;
&lt;li&gt;Highly scalable Algolia index structure&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Future Improvements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🔒 User Accounts for cross-device saving&lt;/li&gt;
&lt;li&gt;🎞️ Add show scenes or visuals per quote&lt;/li&gt;
&lt;li&gt;📚 Expand to books, games, and films&lt;/li&gt;
&lt;li&gt;🧠 Improve emotion detection models&lt;/li&gt;
&lt;li&gt;📤 Allow user-submitted quotes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Project Metrics
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quotes Indexed&lt;/strong&gt;: 200+&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Characters Featured&lt;/strong&gt;: 50+&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tags Generated&lt;/strong&gt;: 300+&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundle Size&lt;/strong&gt;: ~190 kB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lighthouse Score&lt;/strong&gt;: &amp;gt; 95&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Quote Finder&lt;/strong&gt; is more than a search tool — it’s an emotional archive. It bridges my love for anime and my skills as a developer, turning nostalgia into a usable, searchable, and shareable tool.&lt;/p&gt;

&lt;p&gt;Whether you're a long-time anime fan or just someone looking for a quote that resonates — there's something here for you.&lt;/p&gt;




&lt;p&gt;🙏 Thank you for reading. Would love to hear your feedback or ideas!&lt;/p&gt;




</description>
      <category>devchallenge</category>
      <category>algoliachallenge</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>SnippetSearch: Code Snippets Discovery Engine for Developers</title>
      <dc:creator>Karthikeyan</dc:creator>
      <pubDate>Fri, 25 Jul 2025 21:43:09 +0000</pubDate>
      <link>https://dev.to/imkarthikeyan/snippetsearch-code-snippets-discovery-engine-for-developers-3gld</link>
      <guid>https://dev.to/imkarthikeyan/snippetsearch-code-snippets-discovery-engine-for-developers-3gld</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/algolia-2025-07-09"&gt;Algolia MCP Server Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SnippetSearch&lt;/strong&gt; is a comprehensive code snippet discovery and management platform built to help developers find, save, and organize useful code snippets from multiple sources. It aggregates content from Dev.to, Hashnode, CSS-Tricks, Smashing Magazine, and more using RSS and APIs, then enables a seamless search experience powered by &lt;strong&gt;Algolia&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-time Search:&lt;/strong&gt; Powered by Algolia InstantSearch.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-source Indexing:&lt;/strong&gt; Dev.to API, Hashnode RSS, CSS-Tricks, Smashing Magazine &amp;amp; more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bookmarks:&lt;/strong&gt; Save your favorite snippets (persisted via localStorage)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart Tagging:&lt;/strong&gt; Automatic tag extraction from articles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile First:&lt;/strong&gt; Fully responsive interface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean UI:&lt;/strong&gt; Built with Tailwind CSS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast &amp;amp; Optimized:&lt;/strong&gt; Minimal bundle size and fast builds&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Next.js 15, React, TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search&lt;/strong&gt;: Algolia Search API, InstantSearch.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Styling&lt;/strong&gt;: Tailwind CSS, Radix UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content Aggregation&lt;/strong&gt;: Custom RSS &amp;amp; API scrapers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment&lt;/strong&gt;: Vercel&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Live Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live App&lt;/strong&gt;: &lt;a href="https://dev-snippet-search.vercel.app" rel="noopener noreferrer"&gt;SnippetSearch&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/skarthikeyan96/dev-snippet-search" rel="noopener noreferrer"&gt;github.com/skarthikeyan96/dev-snippet-search&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Homepage&lt;/strong&gt;: Feature overview and clean hero section&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhtyw5i90yi6tchz3odi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhtyw5i90yi6tchz3odi.png" alt="homepage" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Search Interface&lt;/strong&gt;: Filters, pagination, bookmark toggles&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56ydm1z5t0b9gibs8usy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56ydm1z5t0b9gibs8usy.png" alt="search" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bookmarks Modal&lt;/strong&gt;: Manage saved snippets&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbsvdr5lo5ofz9ap7ve7s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbsvdr5lo5ofz9ap7ve7s.png" alt="Bookmarks" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mobile View&lt;/strong&gt;: Touch-optimized UI&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F769zty8tet02m5n59610.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F769zty8tet02m5n59610.png" alt="mobile" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;**Simple User analytics: 
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fljerjpc1pn0qp14h97qh.png" alt="analytics" width="800" height="517"&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How I Used Algolia MCP Server
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Search Infrastructure Setup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Configured optimized indices with searchable attributes, highlighting, and ranking.&lt;/li&gt;
&lt;li&gt;Integrated InstantSearch.js with custom templates.&lt;/li&gt;
&lt;li&gt;Enabled search analytics to monitor user behavior.
### 2. Indexing Snippet Data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Parsed and indexed 388+ snippets from multiple sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dev.to API (324)&lt;/li&gt;
&lt;li&gt;Hashnode (32)&lt;/li&gt;
&lt;li&gt;CSS-Tricks (10)&lt;/li&gt;
&lt;li&gt;Smashing Magazine (10)&lt;/li&gt;
&lt;li&gt;Web Design Ledger (7)&lt;/li&gt;
&lt;li&gt;UX Movement (5)
Applied HTML cleaning and tag normalization.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Advanced Search Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Faceted filtering by source.&lt;/li&gt;
&lt;li&gt;Instant keyword highlighting.&lt;/li&gt;
&lt;li&gt;Custom pagination and mobile-friendly search interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Performance Optimizations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Reduced bundle size to ~224 kB.&lt;/li&gt;
&lt;li&gt;Implemented code splitting &amp;amp; vendor chunking.&lt;/li&gt;
&lt;li&gt;Optimized search rendering pipeline.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Learnings &amp;amp; Challenges
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Data Normalization
&lt;/h3&gt;

&lt;p&gt;Challenge: Varying formats across sources.&lt;br&gt;
Solution: HTML entity decoding + tag stripping.&lt;/p&gt;

&lt;h3&gt;
  
  
  UX in Search
&lt;/h3&gt;

&lt;p&gt;Challenge: Balancing speed and relevance.&lt;br&gt;
Solution: Custom ranking + highlighting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Responsive Design
&lt;/h3&gt;

&lt;p&gt;Challenge: Touch targets and layout flexibility.&lt;br&gt;
Solution: Tailwind utilities + mobile-first grid.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Persistence
&lt;/h3&gt;

&lt;p&gt;Challenge: Bookmark state across reloads (without SSR issues).&lt;br&gt;
Solution: Hydration-safe localStorage management.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technical Highlights
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Best practices for Algolia search tuning.&lt;/li&gt;
&lt;li&gt;Clean integration with InstantSearch.js templates.&lt;/li&gt;
&lt;li&gt;State hydration strategies for client-only features.&lt;/li&gt;
&lt;li&gt;Real-world TypeScript with Next.js.&lt;/li&gt;
&lt;li&gt;Tailwind for fast, maintainable UIs.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Future Improvements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cloud sync for saved snippets&lt;/li&gt;
&lt;li&gt;User accounts + snippet sharing&lt;/li&gt;
&lt;li&gt;Code syntax highlighting&lt;/li&gt;
&lt;li&gt;Server-side rendering for SEO&lt;/li&gt;
&lt;li&gt;Offline-first PWA support&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Project Metrics
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Snippets Indexed&lt;/strong&gt;: 388+&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sources Integrated&lt;/strong&gt;: 6+&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundle Size&lt;/strong&gt;: 224 kB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile Friendly&lt;/strong&gt;: 100% responsive&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: Lighthouse score &amp;gt; 95&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;SnippetSearch showcases how Algolia's MCP Server can power a fast, elegant, and scalable search experience for developers. From content aggregation to real-time search, this project reflects the potential of modern tools when combined with thoughtful design and engineering.&lt;/p&gt;




&lt;p&gt;Thank you for reading. &lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>algoliachallenge</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Nexus Dynamics - Frontend challenge</title>
      <dc:creator>Karthikeyan</dc:creator>
      <pubDate>Sun, 06 Jul 2025 11:10:32 +0000</pubDate>
      <link>https://dev.to/imkarthikeyan/nexus-dynamics-frontend-challenge-43ig</link>
      <guid>https://dev.to/imkarthikeyan/nexus-dynamics-frontend-challenge-43ig</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/frontend/axero"&gt;Frontend Challenge: Office Edition sponsored by Axero, Holistic Webdev: Office Space&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What I Built&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I built a modern and interactive employee intranet portal for "Nexus Dynamics," designed as a central hub for essential information and tools. The layout prioritizes a clean, intuitive, and production-ready user experience, featuring a prominent header, real-time information, and a notification system. The main content is divided into two columns, showcasing company statistics, announcements, team spotlights, quick links, upcoming events, and office information. The design emphasizes visual appeal, readability, and ease of navigation, using custom SVG icons and a cohesive card-based layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Demo&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nexus-dynamics-99.netlify.app/" rel="noopener noreferrer"&gt;https://nexus-dynamics-99.netlify.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw1913jcilr5eri478bj2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw1913jcilr5eri478bj2.png" alt="description" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Journey&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;My journey focused on building a robust frontend using HTML, CSS, and vanilla JavaScript. I meticulously structured the &lt;code&gt;index.html&lt;/code&gt;, applied modern styling with &lt;code&gt;styles.css&lt;/code&gt; for responsiveness and visual depth, and added interactivity using &lt;code&gt;script.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Key JavaScript features include a real-time clock, simulated dynamic data updates (notifications, weather, stats), and various click handlers for interactive elements. I also implemented a custom toast notification system and keyboard navigation shortcuts for enhanced usability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proudest Achievement: Dark Mode Implementation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm particularly proud of the comprehensive dark mode. This wasn't just a simple color inversion; it involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A dedicated toggle button with dynamic icons.&lt;/li&gt;
&lt;li&gt;Persistence of user preference via&amp;nbsp;&lt;code&gt;localStorage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Automatic detection of system theme preference (&lt;code&gt;prefers-color-scheme&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Extensive styling adjustments across all UI elements in&amp;nbsp;&lt;code&gt;styles.css&lt;/code&gt;&amp;nbsp;to ensure optimal contrast and visual appeal in both light and dark themes.&lt;/li&gt;
&lt;li&gt;Smooth CSS transitions for a seamless theme switch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This project allowed me to explore vanilla frontend development, focusing on performance, accessibility, and user experience, with the dark mode implementation being a highlight that significantly enhanced the portal's modern appeal.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>frontendchallenge</category>
      <category>css</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
