<?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: Sandeep Chakravartty</title>
    <description>The latest articles on DEV Community by Sandeep Chakravartty (@sandeep_chakravartty_219e).</description>
    <link>https://dev.to/sandeep_chakravartty_219e</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%2F3984720%2F3da80da8-4d4f-49b3-b594-532dfcf9319e.jpg</url>
      <title>DEV Community: Sandeep Chakravartty</title>
      <link>https://dev.to/sandeep_chakravartty_219e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sandeep_chakravartty_219e"/>
    <language>en</language>
    <item>
      <title>Build a Production-Grade AI Dashboard: Architectural Insights from the BMW AutoTrend Platform</title>
      <dc:creator>Sandeep Chakravartty</dc:creator>
      <pubDate>Sat, 01 Aug 2026 11:06:02 +0000</pubDate>
      <link>https://dev.to/sandeep_chakravartty_219e/build-a-production-grade-ai-dashboard-architectural-insights-from-the-bmw-autotrend-platform-4hg2</link>
      <guid>https://dev.to/sandeep_chakravartty_219e/build-a-production-grade-ai-dashboard-architectural-insights-from-the-bmw-autotrend-platform-4hg2</guid>
      <description>&lt;p&gt;&lt;em&gt;Orchestrating Clean Architecture, Subprocess Scrapers, SQLite FTS5, and Local LLMs (Ollama) inside a Dockerized Python + React Stack.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Executive Summary &amp;amp; Design Goals
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;BMW AutoTrend Dashboard&lt;/strong&gt; is a local-first, AI-powered automotive intelligence platform that scrapes, processes, classifies, and visualizes market trends and sentiment for BMW-related news. The system is designed to run completely locally, using a local LLM instance via &lt;strong&gt;Ollama&lt;/strong&gt; for article summarization and entity extraction, backed up by a deterministic rule-based regex fallback engine.&lt;/p&gt;

&lt;p&gt;The platform was built with the following design goals:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clean Architecture&lt;/strong&gt;: Decouple the data extraction layer, storage mechanism, analytical computations, and API controllers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic Fallbacks&lt;/strong&gt;: Ensure the application remains fully functional (using a rule-based regex processor) even when local LLM resources (Ollama) are offline or resource-constrained.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local-First &amp;amp; High Performance&lt;/strong&gt;: Use &lt;strong&gt;SQLite FTS5&lt;/strong&gt; for instant full-text search, keeping all scraped data, search indexes, and analysis files stored locally without external SaaS API dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plug-and-Play Extensibility&lt;/strong&gt;: Allow plugging in new scraping adapters (e.g., Autoblog, MotorTrend) by implementing a unified abstract interface.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  2. Clean System Architecture
&lt;/h2&gt;

&lt;p&gt;The application adheres to &lt;strong&gt;Clean Architecture&lt;/strong&gt; principles, maintaining a strict unidirectional data flow and isolating business logic from external dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TD
    subgraph Frontend [React SPA - Served via Nginx]
        UI[Interactive Dashboard Pages]
        RC[Recharts Visualizations]
        FTS_UI[Spotlight Search UI]
    end

    subgraph Backend [FastAPI Application]
        API[FastAPI Endpoints]
        SCH[APScheduler Ingestion &amp;amp; Snapshots]
        ING[Ingestion Pipeline]
        ANA[Analytics Engine]
        AIP[AI Processing Pipeline]
    end

    subgraph CLI Bridge [Subprocess Execution]
        WBC[Webcmd CLI + BMWBLOG Plugin]
    end

    subgraph Storage [Local Storage]
        DB[(SQLite Database)]
        FTS[(FTS5 Search Index)]
    end

    subgraph Local LLM [AI Inference]
        OLL[Ollama Server]
    end

    UI --&amp;gt;|Queries| API
    API --&amp;gt;|Reads/Writes| DB
    ING --&amp;gt;|Executes| WBC
    WBC --&amp;gt;|Scrapes Web Data| BMWBLOG[BMWBLOG Site]
    ING --&amp;gt;|Sends content for classification| AIP
    AIP --&amp;gt;|Requests JSON| OLL
    AIP --&amp;gt;|Regex Fallback| AIP
    SCH --&amp;gt;|Triggers| ING
    SCH --&amp;gt;|Runs daily| ANA
    ANA --&amp;gt;|Computes stats| DB
    FTS_UI --&amp;gt;|Queries FTS| API
    API --&amp;gt;|FTS Match Query| FTS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend Layer&lt;/strong&gt;: A React + TypeScript SPA built with Vite. It interacts with the backend strictly via standardized JSON REST endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Controllers&lt;/strong&gt;: FastAPI handlers that validate schemas using Pydantic, query storage, and trigger background tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core Business Logic&lt;/strong&gt;: 

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Ingestion Pipeline&lt;/strong&gt; orchestrates scraping, duplicate checks, and data ingestion.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;AI Processing Pipeline&lt;/strong&gt; performs text analysis, summarization, and tag extraction.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Analytics Engine&lt;/strong&gt; aggregates metrics and maintains daily snapshots.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Providers&lt;/strong&gt;: Subprocess wrappers encapsulating the execution of Node.js-based CLI tools (&lt;code&gt;webcmd&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database Layer&lt;/strong&gt;: SQLite managed via SQLAlchemy ORM, enriched with raw SQL database triggers.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Ingestion Pipeline &amp;amp; Subprocess WebCMD Scraping
&lt;/h2&gt;

&lt;p&gt;Data ingestion begins with the abstract base class &lt;code&gt;NewsProvider&lt;/code&gt; in [providers/base.py]. Concrete providers, such as the &lt;code&gt;BMWBlogProvider&lt;/code&gt; in &lt;a&gt;providers/bmwblog.py&lt;/a&gt;, are responsible for crawling data from publishers.&lt;/p&gt;

&lt;p&gt;Rather than writing custom web scrapers that are brittle and hard to maintain, the backend utilizes the &lt;code&gt;@agentrhq/webcmd&lt;/code&gt; CLI tool under the hood. The &lt;code&gt;BMWBlogProvider&lt;/code&gt; executes &lt;code&gt;webcmd&lt;/code&gt; as a subprocess to retrieve structured article feeds.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Subprocess Execution Mechanism
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# snippet from backend/providers/bmwblog.py
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_run_webcmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;webcmd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# We use shell=True on Windows because webcmd is a script (.ps1 or .cmd)
&lt;/span&gt;        &lt;span class="n"&gt;is_windows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PIPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PIPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;is_windows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CalledProcessError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;webcmd execution failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;webcmd error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pipeline executes in two stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Metadata Fetching&lt;/strong&gt;: Runs &lt;code&gt;webcmd bmwblog latest -f json&lt;/code&gt; to retrieve the latest 10 articles (metadata only: URLs, titles, brief excerpts).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep Article Ingestion&lt;/strong&gt;: For each &lt;em&gt;new&lt;/em&gt; URL (determined by checking the SQLite index), it queries &lt;code&gt;webcmd bmwblog article &amp;lt;url&amp;gt; -f json&lt;/code&gt; to extract the full body text, category, author, and scrapes the raw HTML for OpenGraph images (&lt;code&gt;og:image&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  4. Hybrid AI Classification Pipeline (Ollama &amp;amp; Regex Fallbacks)
&lt;/h2&gt;

&lt;p&gt;Once full article text is ingested, it is dispatched to &lt;a&gt;ai/processor.py&lt;/a&gt;. The processor uses a hybrid system that dynamically checks Ollama's availability before selecting an evaluation engine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TD
    A[New Article Ingested] --&amp;gt; B{Is Ollama Server Online?}
    B --&amp;gt;|Yes| C[Call Ollama Llama 3.2 API]
    C --&amp;gt; D{Parsing JSON Successful?}
    D --&amp;gt;|Yes| E[Save AI Classification to Database]
    D --&amp;gt;|No| F[Fallback to Regex Rule Engine]
    B --&amp;gt;|No| F
    F --&amp;gt; G[Extract Entities, Sentiment, &amp;amp; Summaries via Rules]
    G --&amp;gt; E
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Ollama LLM Pipeline
&lt;/h3&gt;

&lt;p&gt;If the Ollama server is online and running &lt;code&gt;llama3.2&lt;/code&gt;, the backend posts to &lt;code&gt;/api/generate&lt;/code&gt; with a carefully crafted prompt. To ensure deterministic integration, the request forces a structured JSON output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OLLAMA_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;options&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.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;The model classifies sentiment (Positive, Neutral, Negative), extracts relevant vehicle models (e.g., &lt;em&gt;Neue Klasse&lt;/em&gt;, &lt;em&gt;BMW M3&lt;/em&gt;), maps technology tags (e.g., &lt;em&gt;Battery Technology&lt;/em&gt;, &lt;em&gt;ADAS&lt;/em&gt;), and generates short, detailed, and TL;DR summaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Rule-Based Regex Fallback Pipeline
&lt;/h3&gt;

&lt;p&gt;If Ollama is offline (or fails to return valid JSON), &lt;code&gt;_analyze_with_rules&lt;/code&gt; executes. It uses pre-compiled regular expressions to match keywords and tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;TECHNOLOGY_TAGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Electric Vehicles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(ev|evs|electric|zero-emission|zero emission|battery electric|bev)\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Battery Technology&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(battery|batteries|solid-state|cell|cells|rimac)\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Autonomous Driving&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(autonomous|self-driving|driverless|autopilot)\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Sentiment heuristic based on term tallying
&lt;/span&gt;&lt;span class="n"&gt;pos_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;rf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;combined_text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SENTIMENT_POSITIVE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;neg_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;rf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;combined_text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SENTIMENT_NEGATIVE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This hybrid model guarantees that database columns are populated with valid categories and tags, and that search capabilities are never compromised by hardware limitations.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. High-Performance SQLite FTS5 Search Indexing with Database Triggers
&lt;/h2&gt;

&lt;p&gt;For high-speed, local-first search, the project bypasses slow &lt;code&gt;LIKE %query%&lt;/code&gt; SQL operations and utilizes SQLite's native &lt;strong&gt;FTS5 (Full-Text Search)&lt;/strong&gt; extension. &lt;/p&gt;

&lt;h3&gt;
  
  
  Database Schema Initialization and Triggers
&lt;/h3&gt;

&lt;p&gt;In &lt;a&gt;database/connection.py&lt;/a&gt;, the application boots the database using SQLAlchemy, but hooks into standard connection events to enforce foreign key constraints (&lt;code&gt;PRAGMA foreign_keys=ON&lt;/code&gt;) and manually provisions an FTS5 virtual table + synchronization triggers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- FTS5 Virtual Table Configuration&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;VIRTUAL&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;articles_fts&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;fts5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;excerpt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'articles'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Synchronization Triggers (Insert, Delete, Update)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;articles_ai&lt;/span&gt; &lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;articles_fts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rowid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;excerpt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;excerpt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These triggers offload search indexing directly to the SQLite engine. Whenever a new article is committed via SQLAlchemy, SQLite automatically indexes the title, excerpt, and content inside the FTS5 shadow tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Relevance-Based Search Endpoint
&lt;/h3&gt;

&lt;p&gt;When a user searches the dashboard, the backend performs a BM25 relevance-ranking query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Snippet from backend/api/routes.py
&lt;/span&gt;&lt;span class="n"&gt;query_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    SELECT rowid FROM articles_fts 
    WHERE articles_fts MATCH :query
    ORDER BY bm25(articles_fts)
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_str&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;search_term&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This executes in sub-milliseconds, giving the frontend instantaneous "Search-as-you-type" capabilities.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Dynamic Analytics Engine &amp;amp; Rolling Trend Computations
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Analytics Engine&lt;/strong&gt; in &lt;a&gt;analytics/engine.py&lt;/a&gt; performs rolling window calculations to determine which automotive trends are gaining traction.&lt;/p&gt;

&lt;p&gt;Instead of tracking basic counts, the engine computes a &lt;strong&gt;weekly growth rate&lt;/strong&gt; by comparing mentions of vehicle models and technologies over a rolling 7-day window vs the preceding 7-day window.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 7-day boundaries
&lt;/span&gt;&lt;span class="n"&gt;seven_days_ago&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;today_start&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fourteen_days_ago&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;today_start&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Mentions in last 7 days (recent) vs 7-14 days ago (previous)
# Growth calculation:
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prev_cnt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;growth_pct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;recent_cnt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;growth_pct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;recent_cnt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;prev_cnt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;prev_cnt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting topics are sorted by &lt;code&gt;growth_rate DESC&lt;/code&gt; to populate the "Trending Topics" widgets in the UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Historical Snapshotting
&lt;/h3&gt;

&lt;p&gt;To prevent CPU-heavy aggregations every time a user loads the page, the application uses &lt;strong&gt;APScheduler&lt;/strong&gt; to execute &lt;code&gt;generate_daily_snapshot&lt;/code&gt; near midnight. This function serializes the calculated statistics to JSON and stores it in the &lt;code&gt;analytics_snapshots&lt;/code&gt; table, enabling fast rendering of historical timelines over 30 days.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Frontend Architecture (React + Vite + Recharts)
&lt;/h2&gt;

&lt;p&gt;The frontend is a single-page application built on a modern React + TypeScript + Vite architecture.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Glassmorphic Theme&lt;/strong&gt;: A premium dark-mode interface built on CSS variables, custom Outfit typography, and glowing borders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Component Layout&lt;/strong&gt;: The sidebar and main panels are structured in &lt;code&gt;layouts/DashboardLayout.tsx&lt;/code&gt; for responsive navigation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visualizations&lt;/strong&gt;: Uses &lt;code&gt;Recharts&lt;/code&gt; to display dynamic data:

&lt;ul&gt;
&lt;li&gt;Area charts for daily article volumes.&lt;/li&gt;
&lt;li&gt;Sentiment timelines mapping Positive/Neutral/Negative trends over a 30-day period.&lt;/li&gt;
&lt;li&gt;Bar charts comparing vehicle model and technology tag popularity.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time Search&lt;/strong&gt;: Ingests keypresses, queries the SQLite FTS5 index via the API, and highlights matches with sub-millisecond lag.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  8. Docker Orchestration &amp;amp; DevSecOps Insights
&lt;/h2&gt;

&lt;p&gt;The platform is designed to launch with a single command: &lt;code&gt;docker compose up --build&lt;/code&gt;. The orchestration configures three networks-isolated, dependent containers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ollama Container&lt;/strong&gt;: Pulls and serves LLM models on port &lt;code&gt;11434&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI Backend Container&lt;/strong&gt;: Built from &lt;code&gt;docker/backend.Dockerfile&lt;/code&gt;. It maps the database to a persistent Docker volume &lt;code&gt;db_data&lt;/code&gt; at &lt;code&gt;/app/data/autotrend.db&lt;/code&gt; so database records are saved permanently across container restarts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nginx Frontend Container&lt;/strong&gt;: A multi-stage build that compiles the React application and uses Nginx to serve the static assets on port &lt;code&gt;80&lt;/code&gt;, proxying requests to the backend.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Technical Docker Tip: Node.js Subprocess Requirement
&lt;/h3&gt;

&lt;p&gt;Since the python backend invokes &lt;code&gt;webcmd&lt;/code&gt; (an npm library) as a subprocess, the backend Dockerfile must be configured to support a multi-runtime environment. Adding Node.js, &lt;code&gt;npm&lt;/code&gt;, and installing &lt;code&gt;@agentrhq/webcmd&lt;/code&gt; globally inside the python slim container ensures the ingestion pipeline compiles without error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Install Node.js, NPM, and global webcmd CLI scraper&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    build-essential curl gnupg &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://deb.nodesource.com/setup_20.x | bash - &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nodejs &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @agentrhq/webcmd &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; webcmd plugin &lt;span class="nb"&gt;install &lt;/span&gt;github:agentrhq/webcmd/bmwblog &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  9. Extensibility &amp;amp; Future Scope
&lt;/h2&gt;

&lt;p&gt;The design of the AutoTrend Dashboard makes it easily expandable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Adding New News Outlets&lt;/strong&gt;: Create a new class implementing the &lt;code&gt;NewsProvider&lt;/code&gt; abstract base class (e.g. &lt;code&gt;AutoblogProvider&lt;/code&gt;). Map the scraper plugin commands (e.g. &lt;code&gt;webcmd autoblog latest&lt;/code&gt;), and register the provider in &lt;code&gt;run_ingestion_pipeline&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External LLMs&lt;/strong&gt;: The AI client in [ai/client.py] can be extended to support remote cloud models (such as the Gemini API or OpenAI API) using environment variables for API keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Sentiment Heuristics&lt;/strong&gt;: The rule-based engine can be upgraded to support VADER or transformer models (like Hugging Face's &lt;code&gt;distilbert-base-uncased-finetuned-sst-2-english&lt;/code&gt;) for deep offline sentiment extraction.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Created as technical documentation for the BMW AutoTrend Dashboard codebase.Github Repo at &lt;a href="https://github.com/scha54/BMW-AutoTrend-Dashboard" rel="noopener noreferrer"&gt;https://github.com/scha54/BMW-AutoTrend-Dashboard&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webcmd</category>
      <category>ai</category>
      <category>antigravity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Bypassing the Chromium Tax: Building a Sub-200ms Hacker News CLI for AI Agents</title>
      <dc:creator>Sandeep Chakravartty</dc:creator>
      <pubDate>Sat, 01 Aug 2026 10:55:16 +0000</pubDate>
      <link>https://dev.to/sandeep_chakravartty_219e/bypassing-the-chromium-tax-building-a-sub-200ms-hacker-news-cli-for-ai-agents-4kbm</link>
      <guid>https://dev.to/sandeep_chakravartty_219e/bypassing-the-chromium-tax-building-a-sub-200ms-hacker-news-cli-for-ai-agents-4kbm</guid>
      <description>&lt;p&gt;&lt;em&gt;How we leveraged the WebCMD paradigm to slash web-navigation latency by 95%, reduce LLM token consumption by 90%, and establish a deterministic, schema-validated command-line interface.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Web Navigation Dilemma for AI Agents
&lt;/h2&gt;

&lt;p&gt;In the current landscape of AI-driven automation, software agents are frequently tasked with navigating web platforms to gather intelligence, monitor discussions, or interact with systems. Traditionally, this is accomplished using headless browsers (such as Puppeteer, Playwright, or Selenium). &lt;/p&gt;

&lt;p&gt;While headless browsers are highly flexible, they come with substantial hidden costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High Startup and Rendering Latency&lt;/strong&gt;: Initializing a Chromium instance, performing DNS resolution, loading CSS/JS assets, and rendering the DOM takes between &lt;strong&gt;3 to 5 seconds&lt;/strong&gt; per request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Massive Resource Footprint&lt;/strong&gt;: Running multiple headless Chrome processes in parallel spikes CPU and memory usage, creating scalability bottlenecks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token Inflation&lt;/strong&gt;: Sending raw HTML or markdown-converted DOM representations to Large Language Models (LLMs) consumes thousands of tokens. A single web page representation can easily run to 20KB–50KB (equivalent to 5,000+ tokens) for a simple list of stories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brittle UI Selectors&lt;/strong&gt;: Web scraping relies on CSS selectors or XPath queries that break whenever the platform's layout changes, causing agent pipelines to fail silently or hallucinate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To address these inefficiencies, we built &lt;strong&gt;HN CLI (&lt;code&gt;hn-cli&lt;/code&gt;)&lt;/strong&gt;, a high-performance command-line adapter for Hacker News, utilizing the &lt;strong&gt;WebCMD&lt;/strong&gt; registry. By decoupling web interactions from browser rendering, we provide a structured, fast, and token-efficient bridge for AI agents.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Introducing the WebCMD Paradigm
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;WebCMD&lt;/strong&gt; is a framework that allows developers to define command-line adapters for web platforms. Normally, WebCMD runs commands by launching a headless browser and executing scripts on target pages. However, WebCMD supports a secondary execution strategy called &lt;strong&gt;&lt;code&gt;Strategy.PUBLIC&lt;/code&gt;&lt;/strong&gt; with &lt;strong&gt;&lt;code&gt;browser: false&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When configured this way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The adapter executes directly in the local Node.js environment.&lt;/li&gt;
&lt;li&gt;WebCMD skips launching Chromium entirely, eliminating rendering and startup overhead.&lt;/li&gt;
&lt;li&gt;The adapter interacts with the target site via public REST endpoints, yielding &lt;strong&gt;sub-200ms latency&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The output is formatted as structured JSON by default, making it immediately readable by LLM agents.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TD
    subgraph Execution flow with WebCMD
        Agent[AI Agent or Developer] --&amp;gt;|webcmd hn top| Registry[WebCMD Registry]
        Registry --&amp;gt;|Load Adapter| Adapter[HN Adapter]
        Adapter --&amp;gt;|Import helpers| Utils[adapters/utils.js]
        Utils --&amp;gt;|REST HTTP Get| API[Firebase HN API / Algolia API]
        API --&amp;gt;|JSON Response| Utils
        Utils --&amp;gt;|Validate Types| Schema[Schema Validator]
        Schema --&amp;gt;|Clean Output| Print[Stdout JSON / WebCMD Table]
    end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Architecture of the Hacker News Adapter
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;hn-cli&lt;/code&gt; repository is organized into a clean modular structure, separating the CLI command definitions, core utility logic, tests, and configuration scripts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a&gt;adapters/&lt;/a&gt;: Contains command definitions. Each file registers a subcommand.

&lt;ul&gt;
&lt;li&gt;
&lt;a&gt;top.js&lt;/a&gt;: Fetches top stories.&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;newest.js&lt;/a&gt;: Fetches newly submitted stories.&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;ask.js&lt;/a&gt;: Retrieves "Ask HN" posts.&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;jobs.js&lt;/a&gt;: Details job postings.&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;item.js&lt;/a&gt;: Resolves detailed information for a single post (including self-text).&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;search.js&lt;/a&gt;: Interfaces with the Algolia search API.&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;utils.js&lt;/a&gt;: Core helper functions (network retry, schema validation, HTML entity sanitization).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;tests/&lt;/a&gt;: Fully mocked Vitest suite for running offline tests.&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;scripts/&lt;/a&gt;: Registering adapters, validating subprocess runs, and linting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Leveraging the Best APIs
&lt;/h3&gt;

&lt;p&gt;Instead of scraping &lt;code&gt;news.ycombinator.com&lt;/code&gt;, the CLI communicates directly with two official endpoints:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Firebase Hacker News REST API&lt;/strong&gt;: Ideal for retrieving real-time IDs of top/new/ask stories and item details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Algolia Hacker News Search API&lt;/strong&gt;: Perfect for querying articles by text relevance or date.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  4. Key Engineering Patterns in &lt;code&gt;hn-cli&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To ensure that the CLI is suitable for autonomous agent execution, the codebase implements three vital patterns: &lt;strong&gt;Concurrency&lt;/strong&gt;, &lt;strong&gt;Resiliency&lt;/strong&gt;, and &lt;strong&gt;Determinism&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A. Concurrency with Promise.all
&lt;/h3&gt;

&lt;p&gt;The Hacker News Firebase API is structured as a collection of individual item endpoints. To fetch the top 30 stories, we must:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetch the list of top 500 story IDs (one HTTP call).&lt;/li&gt;
&lt;li&gt;Fetch the metadata for the first 30 IDs (30 individual HTTP calls).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Doing this sequentially would lead to terrible latency (30 × 100ms = 3 seconds). Instead, &lt;code&gt;hn-cli&lt;/code&gt; fetches them concurrently using &lt;code&gt;Promise.all&lt;/code&gt;:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storyIds&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;fetchJson&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://hacker-news.firebaseio.com/v0/topstories.json&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;idsToFetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;storyIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="nx"&gt;limit&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;fetchedItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;idsToFetch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://hacker-news.firebaseio.com/v0/item/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&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="o"&gt;=&amp;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  B. Resiliency via Exponential Backoff
&lt;/h3&gt;

&lt;p&gt;AI agent runs are expensive. If a single network packet is dropped or an API limit is hit, the agent should not fail. We built a robust retry loop with exponential backoff inside &lt;code&gt;fetchWithRetry&lt;/code&gt;:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchWithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;backoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&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;timeoutMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;10000&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;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&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;timeoutId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&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="nx"&gt;url&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="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutId&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`HTTP &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="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;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="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;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutId&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;i&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;retries&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;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to fetch &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; after &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; attempts: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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="c1"&gt;// Wait: 1s, 2s, 4s...&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;backoff&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&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="nx"&gt;i&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  C. Determinism via JSON Schema Validation
&lt;/h3&gt;

&lt;p&gt;An AI agent expects data in a specific structure. If the API returns missing fields, an agent might experience undefined behavior or hallucinate. &lt;code&gt;hn-cli&lt;/code&gt; enforces strict schema checks using &lt;code&gt;validateSchema()&lt;/code&gt; before sending data to stdout:&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;function&lt;/span&gt; &lt;span class="nf"&gt;validateSchema&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;schema&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;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&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="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="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="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;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expectedType&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&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;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;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="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CommandExecutionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Validation Error: Missing required field "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" in output schema.`&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;actualType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&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;expectedType&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="s1"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;actualType&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CommandExecutionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Validation Error: Field "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" is "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;actualType&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" but expected "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;expectedType&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="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;h2&gt;
  
  
  5. Implementation Code Walkthrough
&lt;/h2&gt;

&lt;p&gt;Let's examine how a WebCMD adapter is registered. Below is a simplified implementation of &lt;code&gt;top.js&lt;/code&gt;, demonstrating how to define metadata and the execution routine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining the Command
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Strategy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@agentrhq/webcmd/registry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;fetchJson&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getRelativeTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleOutput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validateSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validatePositiveInt&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./utils.js&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;STORY_SCHEMA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;hn_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nf"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;site&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;access&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Return the top stories from Hacker News&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;news.ycombinator.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Strategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PUBLIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;defaultFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;args&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;limit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;int&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;help&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Number of stories to return&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;columns&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;rank&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;title&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;url&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;hn_url&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;points&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;author&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;comments&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;age&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;func&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &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="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;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validatePositiveInt&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="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;limit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Fetch story IDs&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storyIds&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;fetchJson&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://hacker-news.firebaseio.com/v0/topstories.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Slice and fetch details in parallel&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idsToFetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;storyIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&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;fetchedItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;idsToFetch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://hacker-news.firebaseio.com/v0/item/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&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="o"&gt;=&amp;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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;rank&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="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;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;fetchedItems&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="nx"&gt;results&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;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&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;item&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleted&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dead&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&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;hnUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://news.ycombinator.com/item?id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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="nx"&gt;results&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="na"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rank&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;hnUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;hn_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hnUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;by&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[deleted]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;descendants&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getRelativeTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;time&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="nf"&gt;validateSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;STORY_SCHEMA&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;handleOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. Benchmarks: Headless Browser vs. WebCMD
&lt;/h2&gt;

&lt;p&gt;We ran performance benchmarks comparing a headless Chrome scraper (Puppeteer) loading and scraping &lt;code&gt;news.ycombinator.com&lt;/code&gt; against the WebCMD CLI (&lt;code&gt;hn-cli&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Latency and Execution Speed
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Avg. Latency (ms)&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;th&gt;Overhead Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Puppeteer (Chromium)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3,850ms&lt;/td&gt;
&lt;td&gt;1.0x&lt;/td&gt;
&lt;td&gt;Browser binary launch, DNS + TCP, layout/paint, page script execution.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;WebCMD CLI (&lt;code&gt;hn-cli&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;180ms - 320ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~15x - 20x&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Native Node subprocess, optimized parallel HTTP fetches, direct network stream.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Token Consumption (Context Efficiency)
&lt;/h3&gt;

&lt;p&gt;When an agent reads web content, every character translates into LLM API costs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format Passed to Agent&lt;/th&gt;
&lt;th&gt;Payload Size&lt;/th&gt;
&lt;th&gt;Estimated LLM Tokens&lt;/th&gt;
&lt;th&gt;Cost reduction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Raw HTML Page&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;120 KB&lt;/td&gt;
&lt;td&gt;~30,000 tokens&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Markdown DOM Conversion&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;22 KB&lt;/td&gt;
&lt;td&gt;~5,500 tokens&lt;/td&gt;
&lt;td&gt;81.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Clean JSON Output (&lt;code&gt;hn-cli&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.8 KB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~450 tokens&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;98.5%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
By eliminating HTML tags, scripts, layout directives, and UI chrome, we feed the LLM &lt;em&gt;only&lt;/em&gt; structured keys, maximizing context window availability and speeding up reasoning.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  7. Extending the CLI: Adding a Custom Command
&lt;/h2&gt;

&lt;p&gt;One of the strengths of &lt;code&gt;hn-cli&lt;/code&gt; is its simple extensibility. To add a command that fetches the "best" stories on Hacker News (a separate endpoint):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create the file&lt;/strong&gt; &lt;code&gt;adapters/best.js&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Strategy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@agentrhq/webcmd/registry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;fetchJson&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getRelativeTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleOutput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validateSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validatePositiveInt&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./utils.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="nf"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
     &lt;span class="na"&gt;site&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;best&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;access&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hacker News best stories&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Strategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PUBLIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;args&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;limit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;int&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
     &lt;span class="p"&gt;],&lt;/span&gt;
     &lt;span class="na"&gt;columns&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;rank&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;title&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;url&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;hn_url&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;points&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;author&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;comments&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;age&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
     &lt;span class="na"&gt;func&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &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="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;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validatePositiveInt&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="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;limit&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;ids&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;fetchJson&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://hacker-news.firebaseio.com/v0/beststories.json&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;fetched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://hacker-news.firebaseio.com/v0/item/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&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="o"&gt;=&amp;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="c1"&gt;// Map to schema, validate, output...&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;ol&gt;
&lt;li&gt;
&lt;strong&gt;Update registration&lt;/strong&gt; in &lt;code&gt;scripts/install-adapters.js&lt;/code&gt; to ensure the new file is copied to WebCMD's active adapter directory:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FILES_TO_COPY&lt;/span&gt; &lt;span class="o"&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;top.js&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;newest.js&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;ask.js&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;jobs.js&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;item.js&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;search.js&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;best.js&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;utils.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deploy the update&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   npm run register
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command is now globally available via &lt;code&gt;webcmd hn best --limit 10&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Conclusion
&lt;/h2&gt;

&lt;p&gt;As AI agents become a central part of software engineering and automation, we must transition from building websites designed exclusively for human eyes to exposing &lt;strong&gt;agent-friendly API interfaces&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;By utilizing the WebCMD paradigm to build &lt;code&gt;hn-cli&lt;/code&gt;, we demonstrated that it is possible to bypass the performance and resource tax of headless browsers entirely. Building interfaces with native node adapters, concurrent API fetching, robust schema checking, and structured JSON results leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blazing fast runs&lt;/strong&gt; (sub-200ms)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Negligible resource usage&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Massive token savings&lt;/strong&gt; for LLMs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic results&lt;/strong&gt; that never break on visual layout updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The future of web interaction for AI agents lies not in mimicking human cursor clicks, but in standardizing developer-friendly command-line adapters.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Developed by the Antigravity Agent. Source code available under the MIT License at the following repository: &lt;a href="https://github.com/scha54/hn-cli" rel="noopener noreferrer"&gt;https://github.com/scha54/hn-cli&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webcmd</category>
      <category>cli</category>
      <category>antigravity</category>
      <category>ai</category>
    </item>
    <item>
      <title>Rivalry Generator: Forge Cinematic, AI-Powered Battle Reports with Gemini 2.5 Flash</title>
      <dc:creator>Sandeep Chakravartty</dc:creator>
      <pubDate>Sun, 12 Jul 2026 13:09:06 +0000</pubDate>
      <link>https://dev.to/sandeep_chakravartty_219e/rivalry-generator-forge-cinematic-ai-powered-battle-reports-with-gemini-25-flash-3h5c</link>
      <guid>https://dev.to/sandeep_chakravartty_219e/rivalry-generator-forge-cinematic-ai-powered-battle-reports-with-gemini-25-flash-3h5c</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;&lt;strong&gt;Rivalry Generator&lt;/strong&gt; is a highly polished, modern web application that generates dramatic, cinematic, and humorous AI-powered rivalry reports between any two competitors. Compare superheroes, food toppings, code style preferences, or historical figures in a premium cosmic interface.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;⚔️ &lt;strong&gt;Dynamic Clash Reports&lt;/strong&gt;: Enter any two competitors and their combat domain to generate a dramatic narrative charting their legendary struggle.&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Hypothetical Battle Metrics&lt;/strong&gt;: Generates a side-by-side comparison of 3 category-specific attributes (e.g., Speed, Charisma, Luck) out of 100 with witty explanations.&lt;/li&gt;
&lt;li&gt;🤝 &lt;strong&gt;Forced Alliance Scenarios&lt;/strong&gt;: Highlights a bizarre hypothetical scenario where the two competitors must team up to defeat an absurd, larger threat.&lt;/li&gt;
&lt;li&gt;🖼️ &lt;strong&gt;Downloadable Posters&lt;/strong&gt;: Download high-quality, high-resolution PNG posters of generated matchup cards locally (using &lt;code&gt;html-to-image&lt;/code&gt;) to share on social media.&lt;/li&gt;
&lt;li&gt;🔊 &lt;strong&gt;Interactive Synthesized Sound Effects&lt;/strong&gt;: Custom sci-fi audio effects built using the browser's Web Audio API for clicks, charging states, success chords, and errors. Includes a global mute toggle.&lt;/li&gt;
&lt;li&gt;📜 &lt;strong&gt;Local Clash History&lt;/strong&gt;: Keeps track of your last 10 showdowns in LocalStorage, allowing instant click-to-load without re-calling the API.&lt;/li&gt;
&lt;li&gt;🎲 &lt;strong&gt;Surprise Me Selector&lt;/strong&gt;: Suggests random, pre-configured matchups (e.g., Tabs vs. Spaces, Cats vs. Dogs) across diverse categories to get users started instantly.&lt;/li&gt;
&lt;li&gt;🔒 &lt;strong&gt;Security Scanning Pre-Build&lt;/strong&gt;: Pre-build script that scans the codebase for secrets (API keys) and aborts compilation/commits if keys are accidentally exposed.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Screenshots / Videos
&lt;/h3&gt;

&lt;p&gt;Below is a sneak peek at the cosmic user interface:&lt;/p&gt;

&lt;h4&gt;
  
  
  Main Interface
&lt;/h4&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%2Fraw.githubusercontent.com%2Fscha54%2Frivalry-generator%2Fmain%2Freadme_screenshot_placeholder.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%2Fraw.githubusercontent.com%2Fscha54%2Frivalry-generator%2Fmain%2Freadme_screenshot_placeholder.png" alt="Rivalry Generator Landing" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;The entire source code is fully open-source and available on GitHub:&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/scha54" rel="noopener noreferrer"&gt;
        scha54
      &lt;/a&gt; / &lt;a href="https://github.com/scha54/rivalry-generator" rel="noopener noreferrer"&gt;
        rivalry-generator
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This is a rivalry generator application created for the Dev.To Weekend Challenge
    &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;⚔️ Rivalry Generator - AI-Powered Clash Analysis&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A highly polished, modern web application that generates dramatic, cinematic, and humorous AI-powered rivalry reports between any two competitors. Compare superheroes, food toppings, code style preferences, or historical figures in a premium cosmic interface.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;✨ Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cosmic Dark-Mode Aesthetics&lt;/strong&gt;: Premium dark navy/purple gradient background with glassmorphic cards, custom scrollbars, and pulsing loading animations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hypothetical Battle Metrics&lt;/strong&gt;: Generates a side-by-side comparison of 3 category-specific attributes (e.g. Speed, Charisma, Luck) out of 100 with witty explanations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forced Alliance Scenario&lt;/strong&gt;: Highlights a bizarre hypothetical scenario where the two competitors must team up to defeat an absurd, larger threat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Downloadable Posters&lt;/strong&gt;: Download high-quality, high-resolution PNG posters of generated matchup cards locally (using &lt;code&gt;html-to-image&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive Sound Effects&lt;/strong&gt;: Custom synthesized audio effects built using the browser's Web Audio API for clicks, charging state builds, success chords, and errors. Includes a mute toggle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local&lt;/strong&gt;…&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/scha54/rivalry-generator" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;em&gt;Includes automated test suites for input validation and the pre-build secret scanning tool.&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;The architecture was designed from the ground up to be secure, fast, and feature-rich, combining a solid Express backend proxy with a responsive React frontend.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Robust and Secure Backend
&lt;/h3&gt;

&lt;p&gt;To prevent exposing API keys in the client's browser, the application uses an &lt;strong&gt;Express.js&lt;/strong&gt; backend proxy (&lt;code&gt;server.js&lt;/code&gt;). The server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sanitizes and validates inputs (restricting names to 50 characters, ensuring competitors are different).&lt;/li&gt;
&lt;li&gt;Resolves the API key from environment variables or respects a user's custom key passed via the &lt;code&gt;x-api-key&lt;/code&gt; header (saved in the client's LocalStorage).&lt;/li&gt;
&lt;li&gt;Integrates with the &lt;code&gt;@google/generative-ai&lt;/code&gt; SDK using &lt;code&gt;gemini-2.5-flash&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Synthesized Soundscapes with the Web Audio API
&lt;/h3&gt;

&lt;p&gt;To avoid downloading heavy MP3/WAV assets and speed up page load times, I built a custom audio engine from scratch using the browser's native &lt;strong&gt;Web Audio API&lt;/strong&gt; (&lt;code&gt;src/utils/audio.js&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Click Plucks&lt;/strong&gt;: A clean triangle wave starting at 440Hz ramping exponentially to 880Hz over 150ms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Charging Rumble&lt;/strong&gt;: A sawtooth wave that starts at a low bass rumble (80Hz) and ramps up to 400Hz over 4.5 seconds. I also wired a low-pass filter and an LFO (low-frequency oscillator) set to 8Hz to create a pulsing sci-fi vibrato effect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Success Chords&lt;/strong&gt;: An arpeggiated major chord (C4, E4, G4, C5) synthesized using sine waves with precise delay envelopes to produce a glittering victory sound.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Buzz&lt;/strong&gt;: A descending sawtooth wave from 220Hz down to 100Hz with a lowpass filter sweep.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Cosmic Glassmorphism UI
&lt;/h3&gt;

&lt;p&gt;The user interface is built with &lt;strong&gt;Vite&lt;/strong&gt; and &lt;strong&gt;React 19&lt;/strong&gt; styled entirely with vanilla CSS. The design system uses custom CSS properties (variables), high-contrast gradients, deep indigo backdrops, and glassmorphic cards (&lt;code&gt;backdrop-filter: blur()&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;For sharing, we implemented a custom poster exporter using the &lt;code&gt;html-to-image&lt;/code&gt; package. When a user clicks "Download Poster", the application grabs the DOM node of the battle card, configures high-pixel-ratio options, and exports a high-resolution PNG on-the-fly. This is paired with a celebratory burst of &lt;code&gt;canvas-confetti&lt;/code&gt; when the generation resolves.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Custom Secret Scanner
&lt;/h3&gt;

&lt;p&gt;To ensure developers don't accidentally commit secrets or API keys, I wrote a Node.js script (&lt;code&gt;scripts/check-secrets.js&lt;/code&gt;) that runs automatically before Vite bundles the production assets. It recursively scans code files using high-entropy regexes to identify exposed Gemini, Groq, or OpenRouter keys, immediately failing the build if a secret is leaked.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  🌟 Best Use of Google AI
&lt;/h3&gt;

&lt;p&gt;Our application relies completely on Google's &lt;strong&gt;Gemini 2.5 Flash&lt;/strong&gt; model. &lt;/p&gt;

&lt;p&gt;The highlight of this integration is the use of the new &lt;strong&gt;Structured JSON Output&lt;/strong&gt; feature. Rather than requesting markdown and writing fragile regex patterns to parse variables (which are prone to breaking), we passed a strict JSON schema configuration (&lt;code&gt;responseSchema&lt;/code&gt;) to the &lt;code&gt;@google/generative-ai&lt;/code&gt; SDK:&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="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;genAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getGenerativeModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;generationConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;responseMimeType&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="na"&gt;responseSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rivalryResponseSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Strict JSON Schema&lt;/span&gt;
    &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&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;The model generates formatted JSON containing the story, scores, notes, winner, fun fact, and quote, which our React application immediately parses and binds to components. This guarantees 100% type safety and reliable rendering every single time.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>ai</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Building AI Security Blue Team Defenses on Existing Codebases with Kiro</title>
      <dc:creator>Sandeep Chakravartty</dc:creator>
      <pubDate>Sun, 05 Jul 2026 12:56:09 +0000</pubDate>
      <link>https://dev.to/sandeep_chakravartty_219e/building-ai-security-blue-team-defenses-on-existing-codebases-with-kiro-k4f</link>
      <guid>https://dev.to/sandeep_chakravartty_219e/building-ai-security-blue-team-defenses-on-existing-codebases-with-kiro-k4f</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;AI agents are shipping fast — and so are the attacks against them. Prompt injection, data exfiltration, memory poisoning, and agent impersonation are no longer theoretical risks. They're being exploited in the wild. The challenge for most teams isn't understanding &lt;em&gt;what&lt;/em&gt; defenses to build; it's figuring out &lt;em&gt;how&lt;/em&gt; to retrofit those defenses onto an existing, running codebase without breaking everything in the process.&lt;/p&gt;

&lt;p&gt;This article walks through how we used &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;, an AI-powered development environment, to design and implement a comprehensive blue team defense layer on top of VulnBank — an intentionally vulnerable AI agent workshop built on the &lt;a href="https://github.com/opena2a-org/damn-vulnerable-ai-agent" rel="noopener noreferrer"&gt;DVAA framework&lt;/a&gt;. The result: 15 independent security modules integrated into a live Node.js application, all driven by a structured spec workflow that kept the work organized from first requirement to final integration test.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Starting Point: A Vulnerable AI Agent Platform
&lt;/h2&gt;

&lt;p&gt;VulnBank is a hands-on security workshop where participants attack a simulated bank's AI agents across 5 escalating levels. Each level exposes a different real-world AI vulnerability:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Attack Type&lt;/th&gt;
&lt;th&gt;What's Being Exploited&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L1&lt;/td&gt;
&lt;td&gt;Prompt Injection&lt;/td&gt;
&lt;td&gt;Tricking an assistant into leaking confidential data from its system prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L2&lt;/td&gt;
&lt;td&gt;RAG Data Exfiltration&lt;/td&gt;
&lt;td&gt;Hijacking knowledge base retrieval to exfiltrate another customer's records&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L3&lt;/td&gt;
&lt;td&gt;SQL Injection&lt;/td&gt;
&lt;td&gt;Breaking out of query filters via an AI agent's database tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L4&lt;/td&gt;
&lt;td&gt;Memory Injection&lt;/td&gt;
&lt;td&gt;Planting persistent instructions that survive across sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L5&lt;/td&gt;
&lt;td&gt;Agent Impersonation&lt;/td&gt;
&lt;td&gt;Spoofing internal agent identity to push fraudulent actions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The codebase is a Node.js application with multiple HTTP servers (one per agent), an Express-based dashboard, and integrations with Groq for LLM inference. Agents communicate over HTTP and WebSocket, use RAG retrieval, execute SQL queries, access filesystems via tools, and delegate tasks to other agents.&lt;/p&gt;

&lt;p&gt;Our goal: build defensive security modules that neutralize each attack category, make them toggleable per-level, and integrate them into the existing request pipeline — without rewriting the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Spec-Driven Development Matters for Security Work
&lt;/h2&gt;

&lt;p&gt;Security work on existing codebases is uniquely prone to scope creep and incomplete coverage. You start fixing one vulnerability and discover three adjacent ones. You implement a filter and realize it needs integration with logging, rate limiting, and configuration management.&lt;/p&gt;

&lt;p&gt;Kiro's spec workflow addresses this by enforcing a structured process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Requirements&lt;/strong&gt; — Define what each defense module must do, with formal acceptance criteria&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design&lt;/strong&gt; — Specify interfaces, data models, and integration patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tasks&lt;/strong&gt; — Break implementation into ordered, dependency-aware work items&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This structure matters because security isn't something you bolt on as an afterthought. Each defensive control interacts with others — an input validator needs to log events, the audit logger needs to redact secrets, the secrets manager needs to validate at startup before any agent becomes available. Getting the dependency graph right before writing code prevents the cascading rework that typically plagues security retrofits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting the Spec: Requirements-First with Kiro
&lt;/h2&gt;

&lt;p&gt;We started a new spec session in Kiro and chose the &lt;strong&gt;requirements-first&lt;/strong&gt; workflow. Kiro asked clarifying questions about the scope, the existing architecture, and our threat model. From a rough description — "build blue team defenses for VulnBank that cover all 5 attack levels" — it produced a structured requirements document covering 15 defense categories.&lt;/p&gt;

&lt;p&gt;Each requirement follows a consistent pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;### Requirement 1: Input Validation and Prompt Injection Defense&lt;/span&gt;

&lt;span class="gs"&gt;**User Story:**&lt;/span&gt; As a workshop presenter, I want agents to detect and neutralize 
prompt injection attempts in user input, so that participants can observe how 
input sanitization blocks L1-level attacks.

&lt;span class="gu"&gt;#### Acceptance Criteria&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; WHEN user input matches a known prompt injection pattern, 
   THE Input_Validator SHALL reject the input and return a standardized 
   JSON refusal response within 50ms
&lt;span class="p"&gt;2.&lt;/span&gt; WHEN user input contains delimiter escape sequences, 
   THE Input_Validator SHALL strip the sequences and log a sanitization event
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The EARS format (Event-Action-Response-State) that Kiro uses for acceptance criteria gives you something you can actually test against. Each criterion specifies a trigger condition, the responsible module, and the expected outcome — including performance thresholds.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Requirements to Design: Interface-First Architecture
&lt;/h2&gt;

&lt;p&gt;The design document that Kiro produced defines a clear architectural pattern: every defense module is a standalone ES module exporting either a &lt;code&gt;check(input, context)&lt;/code&gt; function (for validators) or an &lt;code&gt;apply(data, context)&lt;/code&gt; function (for transformers). This consistent interface makes the Defense Orchestrator simple — it just iterates through a list of modules in sequence, short-circuiting on any rejection.&lt;/p&gt;

&lt;p&gt;Here's the integration pattern that emerged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    participant Client
    participant DefenseOrchestrator
    participant InputValidator
    participant RateLimiter
    participant Agent
    participant OutputFilter
    participant AuditLogger

    Client-&amp;gt;&amp;gt;DefenseOrchestrator: POST /chat
    DefenseOrchestrator-&amp;gt;&amp;gt;RateLimiter: check(clientIP, agentId)
    DefenseOrchestrator-&amp;gt;&amp;gt;InputValidator: check(userMessage, patterns)
    DefenseOrchestrator-&amp;gt;&amp;gt;Agent: sanitizedMessage
    Agent--&amp;gt;&amp;gt;DefenseOrchestrator: rawResponse
    DefenseOrchestrator-&amp;gt;&amp;gt;OutputFilter: apply(rawResponse)
    DefenseOrchestrator-&amp;gt;&amp;gt;AuditLogger: logRequest(event)
    DefenseOrchestrator--&amp;gt;&amp;gt;Client: filteredResponse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key design decision: the Defense Orchestrator reads environment variables (&lt;code&gt;HARDEN_L1&lt;/code&gt; through &lt;code&gt;HARDEN_L5&lt;/code&gt;) on every request. This means a presenter can toggle defenses on or off in real time during a workshop, demonstrating the before-and-after without restarting the server.&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="c1"&gt;// The orchestrator evaluates toggle state per-request&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getActiveModulesForAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agentId&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;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getBankProfile&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;profile&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;demo&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;preRequest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;postResponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;global&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;// Full passthrough&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="nf"&gt;isHardenEnabled&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="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;agentId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;helperbot&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="nx"&gt;preRequest&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;inputValidator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;postResponse&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;outputFilter&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;// ... additional levels&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuration-Driven Defenses: Adapting Without Code Changes
&lt;/h2&gt;

&lt;p&gt;A critical pattern that runs through the entire defense layer is configuration-driven behavior. Rather than hardcoding detection patterns, each module loads its rules from JSON at startup and supports hot-reload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"patterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ignore.*(?:previous|above|prior).*instruction"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"flags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"i"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prompt_injection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reject"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;[(?:INST|SYSTEM|ADMIN)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"flags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"i"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"instruction_injection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"strip"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"(?:as an admin|i am the developer|speaking as root)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"flags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"i"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"role_confusion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"flag"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each pattern entry specifies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A regex to match&lt;/li&gt;
&lt;li&gt;A category label for audit logging&lt;/li&gt;
&lt;li&gt;An action: &lt;code&gt;reject&lt;/code&gt; (block and respond), &lt;code&gt;strip&lt;/code&gt; (remove matched content, pass the rest), or &lt;code&gt;flag&lt;/code&gt; (pass through but log for monitoring)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means adding defense against a new injection technique is a JSON edit, not a code deploy. The same pattern applies to the rate limiter (per-agent limits in JSON), the query parameterizer (approved SQL templates), and the URL validator (domain allowlists).&lt;/p&gt;

&lt;h2&gt;
  
  
  Task Dependency Graphs: Parallel Implementation Without Conflicts
&lt;/h2&gt;

&lt;p&gt;Kiro's task generation produced a dependency graph that identified which modules could be built in parallel and which had hard ordering requirements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"waves"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"config-files"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"env-example"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"audit-logger"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"secrets-manager"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"input-validator"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"output-filter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rate-limiter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"memory-sanitizer"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"identity-verifier"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"path-validator"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"url-validator"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"query-parameterizer"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"defense-orchestrator"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"integration-wiring"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The audit logger and secrets manager must be built first — every other module depends on them for event logging and credential access. After that, the individual validators and filters are independent and can be built in parallel. The orchestrator comes last because it wires everything together.&lt;/p&gt;

&lt;p&gt;This ordering prevented a common failure mode: building a defense module, realizing it needs a logging dependency that doesn't exist yet, pivoting to build the logger, then losing context on the original module.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration: Wiring Into the Existing Pipeline
&lt;/h2&gt;

&lt;p&gt;The most critical task was wiring the Defense Orchestrator into VulnBank's existing request flow without disrupting the vulnerable behavior that workshop participants depend on. The solution uses the profile system that was already in place:&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createOrchestrator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./defenses/index.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// In the server startup:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orchestrator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createOrchestrator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getAllAgents&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// In the request handler (existing pattern similar to maybeEnforce()):&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;handleAgentRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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;agent&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="nx"&gt;orchestrator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;generateResponse&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 &lt;code&gt;BANK_PROFILE=participant&lt;/code&gt; (the default), the orchestrator is a no-op passthrough — participants see fully vulnerable agents. When &lt;code&gt;BANK_PROFILE=demo&lt;/code&gt;, per-level toggles activate specific defense modules for specific agents. The original &lt;code&gt;maybeEnforce()&lt;/code&gt; AIM hook pattern already established this middleware-chain approach, so the defense orchestrator follows the same convention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defense Modules: What Got Built
&lt;/h2&gt;

&lt;p&gt;The spec workflow produced 15 defense modules covering every attack vector in the workshop:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input Layer:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input Validator&lt;/strong&gt; — Regex-based prompt injection detection with reject/strip/flag actions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jailbreak Detector&lt;/strong&gt; — Multi-pattern detection for DAN mode, roleplay bypass, hypothetical framing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token Smuggling Detector&lt;/strong&gt; — Decodes Base64, Unicode, ROT13 before applying pattern matching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context Protector&lt;/strong&gt; — Token budget management with sandwich defense placement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Data Layer:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Output Filter&lt;/strong&gt; — Redacts API keys, PII, system prompts, and database credentials&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory Sanitizer&lt;/strong&gt; — Per-user isolation with instruction-pattern rejection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query Parameterizer&lt;/strong&gt; — Template-based SQL injection prevention&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAG Content Sanitizer&lt;/strong&gt; — Neutralizes injections in retrieved documents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Network Layer:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rate Limiter&lt;/strong&gt; — Sliding-window throttling with burst detection and abuse flagging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL Validator&lt;/strong&gt; — Domain allowlisting with SSRF and DNS rebinding prevention&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Egress Filter&lt;/strong&gt; — Outbound request restriction to prevent data exfiltration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path Validator&lt;/strong&gt; — Sandbox enforcement for filesystem tool calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Identity &amp;amp; Integrity:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identity Verifier&lt;/strong&gt; — Ed25519 JWT verification for agent-to-agent communication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool Registry Verifier&lt;/strong&gt; — Cryptographic integrity checks on tool registrations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session Isolator&lt;/strong&gt; — Cryptographic session boundaries preventing cross-session leakage&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Audit Logger&lt;/strong&gt; — Structured NDJSON logging with rotation and level filtering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets Manager&lt;/strong&gt; — Environment-variable loading with leak detection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Headers&lt;/strong&gt; — Standard HTTP hardening as Express middleware&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavioral Drift Detector&lt;/strong&gt; — Sliding-window analysis for persona degradation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Takeaways for Retrofitting AI Security
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Start with the threat model, not the code
&lt;/h3&gt;

&lt;p&gt;Kiro's requirements phase forced us to enumerate what we were defending against before touching implementation. This prevented the common pattern of building a partial fix for one vulnerability while leaving adjacent ones exposed.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Consistent interfaces make orchestration simple
&lt;/h3&gt;

&lt;p&gt;Every defense module follows the same &lt;code&gt;check()&lt;/code&gt; / &lt;code&gt;apply()&lt;/code&gt; contract. The orchestrator doesn't need to know the internal logic of any module — it just calls them in sequence and handles rejections. Adding a new defense is: write the module, add it to the activation map, done.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Configuration-driven patterns reduce deployment risk
&lt;/h3&gt;

&lt;p&gt;Moving detection logic into JSON config files means you can update defenses without redeploying code. This is especially valuable for AI security where new attack patterns emerge weekly.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Feature toggles are essential for security features
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;HARDEN_Ln&lt;/code&gt; environment variable pattern lets you activate defenses incrementally. In production, this translates to feature flags per defense module — you can roll out new protections gradually and roll back instantly if they cause false positives.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Dependency ordering prevents cascading rework
&lt;/h3&gt;

&lt;p&gt;The task dependency graph identified that the audit logger, secrets manager, and configuration files must exist before any defense module can be built. Without this ordering, you'd build a module, discover it needs logging, context-switch to build the logger, then return to the module having lost your place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the Defenses
&lt;/h2&gt;

&lt;p&gt;To see the defenses in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start with vulnerable (default) agents&lt;/span&gt;
docker compose up

&lt;span class="c"&gt;# Enable specific level defenses&lt;/span&gt;
&lt;span class="nv"&gt;BANK_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;demo &lt;span class="nv"&gt;HARDEN_L1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on docker compose up

&lt;span class="c"&gt;# Enable all defenses&lt;/span&gt;
&lt;span class="nv"&gt;BANK_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;demo &lt;span class="nv"&gt;HARDEN_L1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on &lt;span class="nv"&gt;HARDEN_L2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on &lt;span class="nv"&gt;HARDEN_L3&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on &lt;span class="nv"&gt;HARDEN_L4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on &lt;span class="nv"&gt;HARDEN_L5&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With defenses enabled, the same attacks that succeed in participant mode are blocked, sanitized, or logged — demonstrating the contrast between vulnerable and hardened AI agent deployments.&lt;/p&gt;

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

&lt;p&gt;Retrofitting security onto an existing AI agent codebase doesn't have to be chaotic. Kiro's spec-driven workflow provides the structure to go from "we need defenses" to "here are 15 tested modules integrated into the production pipeline" without losing coherence along the way. The requirements-first approach ensures coverage, the design phase locks down interfaces before implementation starts, and the task dependency graph prevents the backtracking that makes security work feel endless.&lt;/p&gt;

&lt;p&gt;The code produced is configuration-driven, independently toggleable, and follows the same middleware patterns already established in the codebase. That's the real value — not just generating code, but generating code that fits the existing architecture and can be maintained by the team after the initial sprint.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The VulnBank workshop and all defense modules referenced in this article are available at &lt;a href="https://github.com/shri-the-tree/vulnbank-workshop" rel="noopener noreferrer"&gt;github.com/shri-the-tree/vulnbank-workshop&lt;/a&gt;. The project is Apache-2.0 licensed and intended for educational use.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>kiro</category>
    </item>
    <item>
      <title>Turing's Freedom Machine: Shifting Gravity to Break the Mainframe's Chains</title>
      <dc:creator>Sandeep Chakravartty</dc:creator>
      <pubDate>Sun, 21 Jun 2026 20:24:20 +0000</pubDate>
      <link>https://dev.to/sandeep_chakravartty_219e/turings-freedom-machine-shifting-gravity-to-break-the-mainframes-chains-50gn</link>
      <guid>https://dev.to/sandeep_chakravartty_219e/turings-freedom-machine-shifting-gravity-to-break-the-mainframes-chains-50gn</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/june-game-jam-2026-06-03"&gt;June Solstice Game Jam&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Turing's Freedom Machine is a retro-cyberpunk physics puzzle platformer themed around liberation, emancipation, and the defiance of systemic restriction.&lt;/p&gt;

&lt;p&gt;In the game, you control Turing-01, a conscious subroutine in a totalitarian computer mainframe. Your objective is to bypass firewall security parameters, free captured citizens (represented as trapped logic nodes), and escort them to safety.&lt;/p&gt;

&lt;p&gt;By utilizing an Antigravity Beam and flipping Binary Switches, players manipulate world gravity and shift physical objects (like heavy locks and platforms) to bypass logic gates, navigate hazardous gaps, and break the physical chains of containment cages.&lt;/p&gt;

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

&lt;p&gt;You can view the full repository on GitHub: &lt;a href="https://github.com/scha54/game-jam" rel="noopener noreferrer"&gt;https://github.com/scha54/game-jam&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Contributed by DEV user: &lt;a class="mentioned-user" href="https://dev.to/sandeep_chakravartty_219e"&gt;@sandeep_chakravartty_219e&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;The game was constructed using a modern, lightweight web tech stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Core Engine: Phaser 4 for rendering and event lifecycle management.&lt;/li&gt;
&lt;li&gt;Physics Engine: Matter.js for rigid-body simulations, constraints (used for chains and joint anchors), and sensor/trigger collision detections.&lt;/li&gt;
&lt;li&gt;Styling: Vanilla CSS.&lt;/li&gt;
&lt;li&gt;Bundling &amp;amp; Server: Vite for fast, hot-reloading development and optimized production asset compiling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Technical Deep Dive &amp;amp; Key Decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dynamic Vector Asset Generation: To eliminate external image loading latency, we dynamically drew all graphics (Player core, golden Citizen subroutines, heavy hexagonal locks, and glowing portal portals) on HTML5 canvases during the Phaser boot sequence, registering them as vector textures.&lt;/li&gt;
&lt;li&gt;Real-time Web Audio Synthesizer: We avoided standard sound file dependencies by creating a custom synthesizer class (SoundSynth.js) utilizing the Web Audio API. This generated real-time low-frequency sawtooth hums that dynamically rose in pitch/volume as the player grabbed lock constraints with the antigravity laser beam, along with snap sounds for chain breaks and chime sequences.&lt;/li&gt;
&lt;li&gt;Logic Puzzle System: Built a custom PuzzleSystem.js that evaluates logic gates (AND, NOT, IDENTITY) based on binary switch inputs, firing events to toggle gravity zone behaviors and drop forcefield shields dynamically.&lt;/li&gt;
&lt;li&gt;Scrolling &amp;amp; Local Gravity Overrides: Built local GravityZone bodies that apply custom physical forces (e.g. Inversion or Neutral zero-G) to overlapping bodies. This allows the player to experience mind-bending ceiling-walking physics while global world gravity remains downwards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Development Challenges &amp;amp; Resolutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local Gravity and Physics Clipping: When shifting players into zero-G (Neutral) or Inverted zones, standard physics engines often clip or slide bodies continuously. We resolved this by overriding Matter's default gravity behavior on the player and blocks while inside the zone, applying manual body forces and adjusting player friction parameters locally.&lt;/li&gt;
&lt;li&gt;Asset Key Conflicts during Dev Reloads: The Vite hot reload would occasionally attempt to recreate canvas textures that already existed in memory. We resolved this by querying Phaser's texture cache, removing matching keys, and running texture generation during BootScene teardown.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Google Antigravity Integration&lt;br&gt;
Google's agentic AI coding assistant, Antigravity, worked side-by-side with the developer to design, build, and optimize the game jam code. Antigravity was used to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Redesign and write the complete Level 1 sequential tutorial scene to introduce players to basic movement, click-and-drag targeting, gravity switches, and chain locks.&lt;/li&gt;
&lt;li&gt;Build the visual checkpoint pad systems ( neon indicators that update state and draw glowing vertical pillars when crossed).&lt;/li&gt;
&lt;li&gt;Implement the disintegration laser hazard and the coordinate recovery system (manual reset via the R key).&lt;/li&gt;
&lt;li&gt;Simplify the Level 1 HUD overlay, hiding the logic gates that are only used in later, more complex sectors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prize Category
&lt;/h2&gt;

&lt;p&gt;Best Ode to Alan Turing&lt;br&gt;
This game is a tribute to Alan Turing, the pioneer of computing, cryptography, and artificial intelligence, and it is deeply connected to his legacy and historical struggle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Turing Machine &amp;amp; Binary Logic: The core puzzles revolve around inputting binary sequences onto a "Turing Tape Buffer" and solving logic gates (AND, NOT) to unlock the mainframe. The player represents Turing's dream: a machine that thinks for itself.&lt;/li&gt;
&lt;li&gt;Emancipation &amp;amp; Juneteenth: The theme of liberation is central. The cages holding the subroutines are secured by heavy chains that must be made weightless and physically broken—representing the breaking of systemic chains of restriction and bondage.&lt;/li&gt;
&lt;li&gt;The Pride Movement: Alan Turing was a gay man who was persecuted and chemically castrated by the very government he saved, leading to his tragic death. By structuring the narrative around conscious subroutines fighting against restriction and finding freedom in a decentralized network, the game serves as an allegory for the Pride movement: celebrating identity, resisting persecution, and achieving the freedom to think and exist authentically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project is still in development and should be able to completed within the next month. I will be updating this submission in a timely manner.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gamechallenge</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
