<?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: Made Büro</title>
    <description>The latest articles on DEV Community by Made Büro (@madeburo).</description>
    <link>https://dev.to/madeburo</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%2F3805507%2Fd22aa471-1023-4f1f-9454-cc439aac0c25.jpeg</url>
      <title>DEV Community: Made Büro</title>
      <link>https://dev.to/madeburo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/madeburo"/>
    <language>en</language>
    <item>
      <title>Brewwery a native macOS GUI for Homebrew</title>
      <dc:creator>Made Büro</dc:creator>
      <pubDate>Wed, 16 Sep 2026 15:20:37 +0000</pubDate>
      <link>https://dev.to/madeburo/brewwery-a-native-macos-gui-for-homebrew-165b</link>
      <guid>https://dev.to/madeburo/brewwery-a-native-macos-gui-for-homebrew-165b</guid>
      <description>&lt;p&gt;Homebrew is usually one of the first tools I install on a new Mac. Its command-line interface is fast, reliable, and familiar. But over time, a Homebrew installation becomes more than a short list of packages.&lt;/p&gt;

&lt;p&gt;There are formulae, casks, taps, background services, outdated dependencies, cleanup opportunities, diagnostics, and Brewfiles.&lt;/p&gt;

&lt;p&gt;The commands exist, but answering simple questions can still require several of them:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is installed?&lt;br&gt;&lt;br&gt;
Which services are running?&lt;br&gt;&lt;br&gt;
What needs an update?&lt;br&gt;&lt;br&gt;
What depends on this package?&lt;br&gt;&lt;br&gt;
How much space would cleanup recover?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I wanted to see the state of my entire Homebrew setup without scrolling through terminal output or remembering a different command for every task.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://www.brewwery.com/" rel="noopener noreferrer"&gt;Brewwery&lt;/a&gt;.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Homebrew, made visual with Brewwery
&lt;/h2&gt;

&lt;p&gt;Brewwery is an open-source macOS app for managing Homebrew through one native interface.&lt;/p&gt;

&lt;p&gt;It covers the workflows I use regularly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;view installed formulae and casks;&lt;/li&gt;
&lt;li&gt;discover and search for new packages;&lt;/li&gt;
&lt;li&gt;save favorites;&lt;/li&gt;
&lt;li&gt;install, uninstall, and upgrade packages;&lt;/li&gt;
&lt;li&gt;inspect dependencies and installed dependents;&lt;/li&gt;
&lt;li&gt;filter Homebrew leaves;&lt;/li&gt;
&lt;li&gt;add and remove taps;&lt;/li&gt;
&lt;li&gt;start, stop, and restart services;&lt;/li&gt;
&lt;li&gt;preview and run cleanup;&lt;/li&gt;
&lt;li&gt;run &lt;code&gt;brew doctor&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;export a reproducible Brewfile;&lt;/li&gt;
&lt;li&gt;and review local operation history.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Large package lists are virtualized and keyboard-friendly, so they remain responsive even when a Mac has accumulated a substantial Homebrew setup.&lt;/p&gt;

&lt;p&gt;The goal is not to replace Homebrew. Brewwery uses the existing Homebrew installation on your Mac and gives its state and operations a clearer interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version 1.0 is completely native
&lt;/h2&gt;

&lt;p&gt;The first release-candidate versions of Brewwery used Electron, Node, and a Rust backend.&lt;/p&gt;

&lt;p&gt;Brewwery 1.0 is a complete rewrite in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Swift 6;&lt;/li&gt;
&lt;li&gt;SwiftUI;&lt;/li&gt;
&lt;li&gt;AppKit;&lt;/li&gt;
&lt;li&gt;and Foundation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no Electron, Node, Rust runtime, WebView, or JavaScript bridge in the new app.&lt;/p&gt;

&lt;p&gt;The rewrite keeps the same product rather than reducing it to a smaller “native edition”: the same screens, Homebrew operations, validation rules, visual language, and safety model are still there.&lt;/p&gt;

&lt;p&gt;The result starts faster, uses less memory, and behaves more like the macOS utility it was always intended to be.&lt;/p&gt;

&lt;p&gt;Brewwery 1.0 also supports automatic application updates through Sparkle. These are intentionally separate from Homebrew package updates—the app updating itself should never be confused with changing the software managed by Homebrew.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  A GUI should not become an arbitrary command runner
&lt;/h2&gt;

&lt;p&gt;The most important part of a Homebrew GUI is not the package grid or the install button.&lt;/p&gt;

&lt;p&gt;It is the boundary between that button and the system.&lt;/p&gt;

&lt;p&gt;Brewwery’s interface cannot construct an arbitrary command line. Every supported operation is represented by a closed &lt;code&gt;HomebrewCommand&lt;/code&gt; type.&lt;/p&gt;

&lt;p&gt;The execution path looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SwiftUI view
  → feature model
    → HomebrewClient
      → validated HomebrewCommand
        → HomebrewRunner
          → ChildProcess
            → brew
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dynamic values such as package, cask, service, and tap names are validated before an argument array can be created.&lt;/p&gt;

&lt;p&gt;Commands are launched directly with &lt;code&gt;posix_spawn&lt;/code&gt;. No shell is involved, so user-controlled values are never interpolated into a shell command.&lt;/p&gt;

&lt;p&gt;Brewwery also follows a few strict rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it never runs arbitrary commands;&lt;/li&gt;
&lt;li&gt;it never uses &lt;code&gt;sudo&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;mutating operations require explicit confirmation;&lt;/li&gt;
&lt;li&gt;cleanup requires a preview before confirmation;&lt;/li&gt;
&lt;li&gt;only one mutating operation can run at a time;&lt;/li&gt;
&lt;li&gt;and operations have fixed safety timeouts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When an active operation is cancelled, Brewwery terminates its process group—not only the main &lt;code&gt;brew&lt;/code&gt; process. This also stops helper processes such as &lt;code&gt;git&lt;/code&gt; or &lt;code&gt;curl&lt;/code&gt; that Homebrew may have started.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Read-only checks stay read-only
&lt;/h2&gt;

&lt;p&gt;Background activity deserves the same care as visible buttons.&lt;/p&gt;

&lt;p&gt;For example, Brewwery can check which packages are outdated and show the count in the Dock. That background check runs only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew outdated &lt;span class="nt"&gt;--json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does not silently run &lt;code&gt;brew update&lt;/code&gt;, install anything, upgrade anything, or clean the system.&lt;/p&gt;

&lt;p&gt;Actions that may change the Homebrew installation always remain visible and confirmation-gated.&lt;/p&gt;

&lt;p&gt;This distinction matters because a friendly interface should make system operations easier to understand—not make them easier to trigger accidentally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local-first, without an account
&lt;/h2&gt;

&lt;p&gt;Brewwery does not require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an account;&lt;/li&gt;
&lt;li&gt;authentication;&lt;/li&gt;
&lt;li&gt;cloud sync;&lt;/li&gt;
&lt;li&gt;a subscription;&lt;/li&gt;
&lt;li&gt;or a telemetry opt-in screen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no telemetry.&lt;/p&gt;

&lt;p&gt;Favorites, settings, and operation history stay on the Mac. Brewwery itself does not send data to an external service. Homebrew may still access the network when it downloads package metadata or software, but Brewwery does not add its own cloud layer on top.&lt;/p&gt;

&lt;p&gt;The app is also open source under the MIT License, so its command handling and security boundaries can be inspected directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Homebrew without changing the developer’s Mac
&lt;/h2&gt;

&lt;p&gt;Testing a package manager interface has an obvious risk: a careless test suite can modify the machine running it.&lt;/p&gt;

&lt;p&gt;Brewwery separates its tests into several layers.&lt;/p&gt;

&lt;p&gt;Core and application tests use a scripted &lt;code&gt;brew&lt;/code&gt; double. They cover parsers, validation, command execution, cancellation, operation history, navigation, accessibility, and rendering without touching the real Homebrew installation.&lt;/p&gt;

&lt;p&gt;Visual tests can render every screen in light and dark mode for review.&lt;/p&gt;

&lt;p&gt;There is also an opt-in live test suite for real Homebrew integration. It is never enabled by default because it deliberately installs and removes test packages, controls services, and manages a test tap.&lt;/p&gt;

&lt;p&gt;This separation lets the normal test suite remain fast and safe while still providing a deliberate path for end-to-end verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  The terminal is still there when you need it
&lt;/h2&gt;

&lt;p&gt;A visual interface should not hide useful information.&lt;/p&gt;

&lt;p&gt;Brewwery shows live progress for installs, upgrades, service actions, and cleanup. Successful operations can close automatically, while failures and cancellations stay visible so their output can be inspected.&lt;/p&gt;

&lt;p&gt;For quick daily work, the interface provides clear actions and status. When something goes wrong, the underlying Homebrew output is still available.&lt;/p&gt;

&lt;p&gt;That balance is important: convenience when everything works, detail when it does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try Brewwery
&lt;/h2&gt;

&lt;p&gt;Brewwery supports Apple Silicon and Intel Macs running macOS 14 Sonoma or later.&lt;/p&gt;

&lt;p&gt;You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.brewwery.com/" rel="noopener noreferrer"&gt;Visit the Brewwery website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.brewwery.com/" rel="noopener noreferrer"&gt;Read the documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you use Homebrew regularly, I would love to hear which command or workflow you would most like to manage visually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Brewwery. Homebrew, made visual.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>homebrew</category>
      <category>macos</category>
      <category>productivity</category>
      <category>swift</category>
    </item>
    <item>
      <title>UsageNow Tracks OpenAI Codex, Claude Code, Gemini and Antigravity Usage from Your Mac Menu Bar</title>
      <dc:creator>Made Büro</dc:creator>
      <pubDate>Wed, 16 Sep 2026 15:13:34 +0000</pubDate>
      <link>https://dev.to/madeburo/usagenow-tracks-openai-codex-claude-code-gemini-and-antigravity-usage-from-your-mac-menu-bar-4lig</link>
      <guid>https://dev.to/madeburo/usagenow-tracks-openai-codex-claude-code-gemini-and-antigravity-usage-from-your-mac-menu-bar-4lig</guid>
      <description>&lt;p&gt;I rarely use just one AI coding tool anymore. Codex may be working on one task, Claude Code on another, Gemini CLI is useful for a different workflow, and Antigravity has its own strengths.&lt;/p&gt;

&lt;p&gt;The problem is that every tool has a different way of reporting usage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;different limit windows;&lt;/li&gt;
&lt;li&gt;different reset times;&lt;/li&gt;
&lt;li&gt;different definitions of “usage”;&lt;/li&gt;
&lt;li&gt;and sometimes no accessible quota information at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I kept asking the same questions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How much usage do I have left?&lt;br&gt;&lt;br&gt;
When does it reset?&lt;br&gt;&lt;br&gt;
Which model have I been using today?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Opening several apps and dashboards just to answer those questions felt unnecessary. I wanted the answer to live where I could see it immediately: in the macOS menu bar.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://usagenow.com" rel="noopener noreferrer"&gt;UsageNow&lt;/a&gt;.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  One place for AI coding usage
&lt;/h2&gt;

&lt;p&gt;UsageNow is a native macOS app that brings usage information from multiple AI coding tools into one compact view.&lt;/p&gt;

&lt;p&gt;Today it supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Codex&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gemini CLI&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Antigravity&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click the menu bar icon and you can see the available limit windows, how much is left, when they reset, and today’s local token and request activity by model.&lt;/p&gt;

&lt;p&gt;There are also small and medium desktop widgets for checking usage without opening the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  The providers are not actually the same
&lt;/h2&gt;

&lt;p&gt;One of the more interesting parts of building UsageNow was discovering how differently each tool exposes its data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Codex
&lt;/h3&gt;

&lt;p&gt;Codex limits, reset times, and account plan come from the official Codex app-server running locally. Token and request activity is calculated from session files already stored on the Mac.&lt;/p&gt;

&lt;p&gt;UsageNow never needs to read or copy Codex credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Code
&lt;/h3&gt;

&lt;p&gt;Claude Code stores enough local session data to calculate token activity, requests, models used, and account information.&lt;/p&gt;

&lt;p&gt;Subscription limits are more complicated. UsageNow can fetch them through an experimental, opt-in integration using the existing Claude Code sign-in. The access token is kept in memory and sent only to Anthropic.&lt;/p&gt;

&lt;p&gt;If this option is disabled—or the limits cannot be fetched—local activity still works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemini CLI
&lt;/h3&gt;

&lt;p&gt;Gemini CLI records local session activity, so UsageNow can show tokens, requests, and models used today.&lt;/p&gt;

&lt;p&gt;It does not expose usage limits locally. Instead of estimating them or displaying a meaningless zero, UsageNow simply says that limits are unavailable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Antigravity
&lt;/h3&gt;

&lt;p&gt;While the Antigravity app is running, UsageNow asks its local language server for the same quota summary shown in Antigravity’s own usage panel.&lt;/p&gt;

&lt;p&gt;This communication stays on &lt;code&gt;127.0.0.1&lt;/code&gt;. No Google credentials are read or transmitted by UsageNow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Missing data should look missing
&lt;/h2&gt;

&lt;p&gt;A surprisingly important product decision was to resist the temptation to make every provider card look complete.&lt;/p&gt;

&lt;p&gt;If a provider returns only a weekly limit, UsageNow shows only a weekly limit.&lt;/p&gt;

&lt;p&gt;If a quota cannot currently be refreshed, the last known value can remain visible—but it is clearly marked as stale.&lt;/p&gt;

&lt;p&gt;If a tool does not expose limits, UsageNow says so.&lt;/p&gt;

&lt;p&gt;And token activity is presented as local activity, not as a billing statement. Cached tokens and provider-specific accounting can make those two numbers different.&lt;/p&gt;

&lt;p&gt;A polished but invented percentage would look nicer. It would also be wrong.&lt;/p&gt;

&lt;p&gt;For a usage monitor, trust is more important than visual symmetry.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Local-first by design
&lt;/h2&gt;

&lt;p&gt;I did not want another cloud dashboard with another account, database, or proxy sitting between developers and their AI tools.&lt;/p&gt;

&lt;p&gt;UsageNow has no account system and no UsageNow server receiving your usage data.&lt;/p&gt;

&lt;p&gt;It does not collect prompts, responses, source code, project names, or conversation content. For local activity, it extracts only the small set of fields it needs: timestamps, identifiers, model names, and token counts.&lt;/p&gt;

&lt;p&gt;Providers can also be disabled individually. When a provider is disabled, UsageNow does not refresh it or read its files.&lt;/p&gt;

&lt;p&gt;The desktop widget follows the same principle. It cannot access provider files, credentials, or the network. The main app publishes a sanitized snapshot containing only the values needed for display, and the widget renders that snapshot.&lt;/p&gt;

&lt;p&gt;The full privacy model is documented in the &lt;a href="https://docs.usagenow.com/privacy" rel="noopener noreferrer"&gt;UsageNow privacy documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Normalizing the result, not the providers
&lt;/h2&gt;

&lt;p&gt;UsageNow is built with SwiftUI and &lt;code&gt;MenuBarExtra&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Internally, every integration is different, but each provider produces a normalized snapshot for the interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Codex app-server ─────┐
Claude session files ─┤
Gemini recordings ────┼─&amp;gt; ProviderSnapshot ─&amp;gt; Menu bar
Antigravity localhost ┘                    └─&amp;gt; Widget
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A snapshot can contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;zero or more usage windows;&lt;/li&gt;
&lt;li&gt;an optional plan;&lt;/li&gt;
&lt;li&gt;local token and request activity;&lt;/li&gt;
&lt;li&gt;activity grouped by model;&lt;/li&gt;
&lt;li&gt;freshness information;&lt;/li&gt;
&lt;li&gt;and a provider-specific unavailable reason.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The UI does not assume that every provider has the same capabilities.&lt;/p&gt;

&lt;p&gt;Providers refresh concurrently, and a failed refresh does not automatically erase the last good result. This matters for tools whose sessions expire or whose local services are only available while the tool is running.&lt;/p&gt;

&lt;p&gt;The widget is a separate target and receives only explicitly copied display fields. Provider and credential-reading code is not compiled into it.&lt;/p&gt;

&lt;p&gt;The project’s architecture and implementation are available in the &lt;a href="https://github.com/usagenow/usagenow" rel="noopener noreferrer"&gt;open-source repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What shipped in version 0.3.0
&lt;/h2&gt;

&lt;p&gt;The latest release adds Gemini CLI and Antigravity alongside Codex and Claude Code.&lt;/p&gt;

&lt;p&gt;It also adds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;activity grouped by model;&lt;/li&gt;
&lt;li&gt;provider reordering;&lt;/li&gt;
&lt;li&gt;improved support for team plans;&lt;/li&gt;
&lt;li&gt;smarter widget layouts;&lt;/li&gt;
&lt;li&gt;clearer stale-data states;&lt;/li&gt;
&lt;li&gt;and more reliable handling of renewed Claude Code sessions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cursor, GitHub Copilot, DeepSeek, and Qwen are on the roadmap—but they will only be added when there is a reliable and privacy-respecting way to obtain useful data.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Try UsageNow
&lt;/h2&gt;

&lt;p&gt;UsageNow is free and open source under the MIT License. It runs on Apple silicon and Intel Macs with macOS 15 or later.&lt;/p&gt;

&lt;p&gt;The downloadable app is signed and notarized by Apple.&lt;/p&gt;

&lt;p&gt;You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://usagenow.com" rel="noopener noreferrer"&gt;Download UsageNow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.usagenow.com" rel="noopener noreferrer"&gt;Read the documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you use multiple AI coding tools, I would love to hear which provider you want to see next—and what usage information is most useful in your daily workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See what’s left. Keep building.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>macos</category>
      <category>productivity</category>
      <category>swift</category>
    </item>
    <item>
      <title>Githubster free tool to track your GitHub followers and unfollowers</title>
      <dc:creator>Made Büro</dc:creator>
      <pubDate>Thu, 21 May 2026 11:47:42 +0000</pubDate>
      <link>https://dev.to/madeburo/githubster-free-tool-to-track-your-github-followers-and-unfollowers-3f29</link>
      <guid>https://dev.to/madeburo/githubster-free-tool-to-track-your-github-followers-and-unfollowers-3f29</guid>
      <description>&lt;p&gt;Ever looked at your GitHub following list and wondered do these people even follow me back?&lt;/p&gt;

&lt;p&gt;I built Githubster to answer that question. It's a simple, free, open-source web app that shows you:&lt;/p&gt;

&lt;p&gt;Who doesn't follow you back — people you follow who ignore you&lt;br&gt;
Who you don't follow back — your fans you might have missed&lt;br&gt;
Full following &amp;amp; followers lists — with search and filtering&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;I was cleaning up my GitHub following list manually. Clicking profiles one by one. After 10 minutes I thought — there has to be a better way. There were some tools out there, but most were outdated, required auth.&lt;/p&gt;

&lt;p&gt;So I made my own.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Enter GitHub username and get the full picture. That's it.&lt;/p&gt;

&lt;p&gt;No login required. No OAuth. No tokens (unless you want higher rate limits). Everything runs in your browser — zero data is sent to any server.&lt;/p&gt;

&lt;p&gt;Try it&lt;br&gt;
&lt;a href="https://www.githubster.com/" rel="noopener noreferrer"&gt;www.githubster.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source code&lt;br&gt;
&lt;a href="https://github.com/madeburo/githubster" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MIT licensed. PRs welcome.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>github</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>OpenModels: Explore LLM Models and Inference Providers</title>
      <dc:creator>Made Büro</dc:creator>
      <pubDate>Mon, 11 May 2026 15:50:05 +0000</pubDate>
      <link>https://dev.to/madeburo/openmodels-explore-llm-models-and-inference-providers-1156</link>
      <guid>https://dev.to/madeburo/openmodels-explore-llm-models-and-inference-providers-1156</guid>
      <description>&lt;p&gt;The number of LLM providers keeps growing and so does the confusion around pricing, availability and compatibility. OpenModels is an open-source project that brings structure to this landscape: a single registry where models, providers, and their relationships are documented, validated, and queryable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;The AI inference ecosystem is fragmented in ways that cost teams real time and money:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The same model can cost &lt;strong&gt;10x more&lt;/strong&gt; depending on which provider you use&lt;/li&gt;
&lt;li&gt;Latency between providers varies radically even for identical models&lt;/li&gt;
&lt;li&gt;Uptime is unknown until you experience an outage yourself&lt;/li&gt;
&lt;li&gt;Pricing pages change without notice, and there's no structured way to track it&lt;/li&gt;
&lt;li&gt;Choosing a provider still means opening 15 tabs and building a spreadsheet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI ecosystem already has excellent tooling for training and inference. What is still missing is &lt;strong&gt;standardized infrastructure visibility across providers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;OpenModels focuses on that operational layer.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  What is OpenModels
&lt;/h2&gt;

&lt;p&gt;OpenModels is an open infrastructure project for discovering, validating, and comparing LLM models and inference providers.&lt;/p&gt;

&lt;p&gt;The project combines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an open registry of model and provider metadata&lt;/li&gt;
&lt;li&gt;structured JSON schemas with automated validation&lt;/li&gt;
&lt;li&gt;provider normalization (pricing, rate limits, regions)&lt;/li&gt;
&lt;li&gt;real-time telemetry collection (health, latency, uptime)&lt;/li&gt;
&lt;li&gt;a searchable web interface and REST API&lt;/li&gt;
&lt;li&gt;operational intelligence for choosing the right provider&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is simple: make the modern AI inference ecosystem observable and comparable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Current State
&lt;/h2&gt;

&lt;p&gt;The registry currently tracks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;62 models&lt;/strong&gt; — from OpenAI, Anthropic, Google, Meta, DeepSeek, Mistral, xAI and others&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;30 providers&lt;/strong&gt; — including Together AI, Groq, DeepInfra, Cerebras, Fireworks, SambaNova and more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;90+ provider-model mappings&lt;/strong&gt; — each with pricing, rate limits, and region data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The public registry is community-maintained through YAML definitions on GitHub:&lt;br&gt;
&lt;a href="https://github.com/openmodelsrun/openmodels" rel="noopener noreferrer"&gt;github.com/openmodelsrun/openmodels&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The project is intentionally split into two layers.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Open Registry
&lt;/h3&gt;

&lt;p&gt;The public repository contains normalized ecosystem data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openmodels/
├── models/           # Canonical model definitions
├── providers/        # Inference provider definitions
├── mappings/         # Provider-model links with pricing
│   ├── anthropic/
│   ├── openai/
│   ├── together-ai/
│   └── ...
└── schemas/          # JSON Schema (Draft 7) for validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every YAML file is validated on pull request via GitHub Actions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;YAML syntax check&lt;/li&gt;
&lt;li&gt;JSON Schema conformance&lt;/li&gt;
&lt;li&gt;Referential integrity (mappings must reference existing models and providers)&lt;/li&gt;
&lt;li&gt;Duplicate ID detection&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nothing merges without passing all four checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Platform Layer
&lt;/h3&gt;

&lt;p&gt;The platform consumes registry data and adds operational intelligence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;REST API&lt;/strong&gt; (&lt;code&gt;api.openmodels.run&lt;/code&gt;) — model discovery, provider comparison, telemetry endpoints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Interface&lt;/strong&gt; (&lt;code&gt;openmodels.run&lt;/code&gt;) — search, browse, compare with Command Palette and ecosystem graph&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telemetry Workers&lt;/strong&gt; — health probes every 5 minutes, latency probes every 15 minutes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Core Concepts
&lt;/h2&gt;

&lt;p&gt;The registry is built around three entities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────┐         ┌───────────┐         ┌──────────┐
│  Model  │◄────────│  Mapping  │────────►│ Provider │
└─────────┘         └───────────┘         └──────────┘
  (what)          (pricing, limits)         (where)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Model&lt;/strong&gt; — a canonical LLM definition (e.g., DeepSeek V3, Claude Opus 4.6). Describes capabilities, modalities, context window, and licensing. Vendor-neutral.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provider&lt;/strong&gt; — an inference service (e.g., Together AI, Groq). Describes API endpoint, auth type, regions, and compatibility format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mapping&lt;/strong&gt; — the glue. Connects a model to a provider with specific pricing, rate limits, and available regions.&lt;/p&gt;

&lt;p&gt;This creates a many-to-many relationship: one model can be served by multiple providers at different price points, and one provider can serve dozens of models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Llama 4 Scout across providers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Input (per 1M tokens)&lt;/th&gt;
&lt;th&gt;Output (per 1M tokens)&lt;/th&gt;
&lt;th&gt;RPM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DeepInfra&lt;/td&gt;
&lt;td&gt;$0.06&lt;/td&gt;
&lt;td&gt;$0.18&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Groq&lt;/td&gt;
&lt;td&gt;$0.11&lt;/td&gt;
&lt;td&gt;$0.34&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cerebras&lt;/td&gt;
&lt;td&gt;$0.60&lt;/td&gt;
&lt;td&gt;$0.60&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same model, three providers, 10x price difference. That's the kind of visibility the registry provides.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a Model Definition Looks Like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deepseek-v3&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DeepSeek V3&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DeepSeek's third-generation large language model with mixture-of-experts architecture.&lt;/span&gt;
&lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;chat&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;completion&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;function-calling&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;code-generation&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;reasoning&lt;/span&gt;
&lt;span class="na"&gt;modalities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;text&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;code&lt;/span&gt;
&lt;span class="na"&gt;context_window&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;128000&lt;/span&gt;
&lt;span class="na"&gt;licensing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;other&lt;/span&gt;
&lt;span class="na"&gt;created_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-12-01T00:00:00.000Z"&lt;/span&gt;
&lt;span class="na"&gt;updated_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-01-15T00:00:00.000Z"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What a Mapping Looks Like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;model_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deepseek-v3&lt;/span&gt;
&lt;span class="na"&gt;provider_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;together-ai&lt;/span&gt;
&lt;span class="na"&gt;provider_model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deepseek-ai/DeepSeek-V3&lt;/span&gt;
&lt;span class="na"&gt;pricing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;input_per_million&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.90&lt;/span&gt;
  &lt;span class="na"&gt;output_per_million&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.90&lt;/span&gt;
  &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;USD&lt;/span&gt;
&lt;span class="na"&gt;rate_limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;requests_per_minute&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;
  &lt;span class="na"&gt;tokens_per_minute&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000000&lt;/span&gt;
&lt;span class="na"&gt;context_window_override&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="na"&gt;available_regions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;
&lt;span class="na"&gt;created_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-01-01T00:00:00.000Z"&lt;/span&gt;
&lt;span class="na"&gt;updated_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-01-01T00:00:00.000Z"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Telemetry
&lt;/h2&gt;

&lt;p&gt;The platform continuously monitors provider health and performance:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Interval&lt;/th&gt;
&lt;th&gt;Retention&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Health status (up/down)&lt;/td&gt;
&lt;td&gt;Every 5 min&lt;/td&gt;
&lt;td&gt;30 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to first token (TTFT)&lt;/td&gt;
&lt;td&gt;Every 15 min&lt;/td&gt;
&lt;td&gt;30 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total response time&lt;/td&gt;
&lt;td&gt;Every 15 min&lt;/td&gt;
&lt;td&gt;30 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Availability (uptime %)&lt;/td&gt;
&lt;td&gt;Computed&lt;/td&gt;
&lt;td&gt;Rolling 7 days&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When a user queries ranked providers for a model, the API computes a composite score:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Uptime (7-day rolling)&lt;/td&gt;
&lt;td&gt;40%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median latency (TTFT)&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per million tokens&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median total response time&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This means the API can answer: "Which provider should I use for DeepSeek V3 right now?" — factoring in live performance, not just listed specs.&lt;/p&gt;




&lt;h2&gt;
  
  
  API
&lt;/h2&gt;

&lt;p&gt;The public API at &lt;code&gt;api.openmodels.run&lt;/code&gt; provides model discovery, provider comparison, and telemetry data. Key endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/models                        # Search and list models
GET /api/models/:id/providers          # Providers for a model with pricing
GET /api/models/:id/compare            # Side-by-side provider comparison
GET /api/telemetry/ranked/:model_id    # Ranked providers by live performance
GET /api/search                        # Unified search across the registry
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full API reference: &lt;a href="https://docs.openmodels.run/api-reference" rel="noopener noreferrer"&gt;docs.openmodels.run/api-reference&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Web Interface
&lt;/h2&gt;

&lt;p&gt;The web app at &lt;a href="https://openmodels.run" rel="noopener noreferrer"&gt;openmodels.run&lt;/a&gt; provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model search with sort by name, recency, context window, or provider count&lt;/li&gt;
&lt;li&gt;Provider comparison view (pricing, latency, uptime side-by-side)&lt;/li&gt;
&lt;li&gt;Command Palette (&lt;code&gt;Cmd+K&lt;/code&gt; / &lt;code&gt;Ctrl+K&lt;/code&gt;) for instant global search&lt;/li&gt;
&lt;li&gt;Interactive ecosystem graph (node visualization of model-provider relationships)&lt;/li&gt;
&lt;li&gt;Popular models section with relevance-based ranking&lt;/li&gt;
&lt;li&gt;Category navigation by capability, modality, and license&lt;/li&gt;
&lt;li&gt;Dark/light mode&lt;/li&gt;
&lt;li&gt;Mobile-responsive layout&lt;/li&gt;
&lt;/ul&gt;




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

&lt;h2&gt;
  
  
  Coverage
&lt;/h2&gt;

&lt;p&gt;The registry covers models from OpenAI, Anthropic, Google, Meta, DeepSeek, Mistral, xAI, Alibaba, Microsoft, NVIDIA, Cohere, Moonshot, Zhipu, MiniMax, and others — across 30 inference providers including first-party APIs and third-party platforms like Together AI, Groq, DeepInfra, Fireworks, SambaNova, Nebius and Scaleway.&lt;/p&gt;

&lt;p&gt;Full model and provider lists are available in the &lt;a href="https://github.com/openmodelsrun/openmodels" rel="noopener noreferrer"&gt;registry repository&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;Adding a model, provider, or mapping is a pull request:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fork the repository&lt;/li&gt;
&lt;li&gt;Create a YAML file following the schema&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;python validate_registry.py&lt;/code&gt; locally&lt;/li&gt;
&lt;li&gt;Open a PR — CI validates automatically&lt;/li&gt;
&lt;li&gt;Merge after review&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All IDs use kebab-case (&lt;code&gt;deepseek-v3&lt;/code&gt;, &lt;code&gt;together-ai&lt;/code&gt;). All timestamps are ISO 8601. The schemas enforce structure, so invalid data never enters the registry.&lt;/p&gt;




&lt;h2&gt;
  
  
  Current Focus
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Telemetry reliability and probe coverage&lt;/li&gt;
&lt;li&gt;Provider ranking accuracy&lt;/li&gt;
&lt;li&gt;OpenAPI specification for the public API&lt;/li&gt;
&lt;li&gt;Expanding ecosystem coverage (models, providers, regions)&lt;/li&gt;
&lt;/ul&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Registry&lt;/strong&gt;: &lt;a href="https://github.com/openmodelsrun/openmodels" rel="noopener noreferrer"&gt;github.com/openmodelsrun/openmodels&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: &lt;a href="https://docs.openmodels.run" rel="noopener noreferrer"&gt;docs.openmodels.run&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Interface&lt;/strong&gt;: &lt;a href="https://openmodels.run" rel="noopener noreferrer"&gt;openmodels.run&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API&lt;/strong&gt;: &lt;code&gt;api.openmodels.run&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;OpenModels is open source. Contributions welcome.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>openai</category>
      <category>llm</category>
    </item>
    <item>
      <title>Compare LLM API Costs Across Providers</title>
      <dc:creator>Made Büro</dc:creator>
      <pubDate>Wed, 06 May 2026 12:30:44 +0000</pubDate>
      <link>https://dev.to/madeburo/compare-llm-api-costs-across-providers-16f5</link>
      <guid>https://dev.to/madeburo/compare-llm-api-costs-across-providers-16f5</guid>
      <description>&lt;p&gt;Choosing a model shouldn't mean opening five browser tabs and doing math. But that's the current state of things, every provider has its own pricing page, its own token definitions and its own fine print.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;llmprices&lt;/code&gt; is a CLI that cuts through this. Give it a prompt or a token count, and it returns a sorted cost comparison across providers in seconds. There's also a web version at llmcost.run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fub6toz5pahfaywfrzmy1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fub6toz5pahfaywfrzmy1.png" alt=" " width="800" height="394"&gt;&lt;/a&gt;&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;# Find best price-to-quality ratio&lt;/span&gt;
llm-cost calc &lt;span class="s2"&gt;"Build a Python REST API"&lt;/span&gt; &lt;span class="nt"&gt;--sort&lt;/span&gt; value

&lt;span class="c"&gt;# Filter by capability tier&lt;/span&gt;
llm-cost list &lt;span class="nt"&gt;--tier&lt;/span&gt; advanced

&lt;span class="c"&gt;# Combine both&lt;/span&gt;
llm-cost calc &lt;span class="s2"&gt;"Code task"&lt;/span&gt; &lt;span class="nt"&gt;--tier&lt;/span&gt; advanced &lt;span class="nt"&gt;--sort&lt;/span&gt; value &lt;span class="nt"&gt;--top&lt;/span&gt; 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;LLM pricing is more complex than it looks at first glance.&lt;br&gt;
Input and output tokens are priced separately, and the gap between them is significant output can cost anywhere from 3x to 20x more than input depending on the provider. Context window size affects whether a model is even viable for a given task, regardless of its per-token rate. And perhaps most importantly, the cheapest model isn't always the best choice: a $0.06/1M input model is excellent for classification pipelines, but the wrong tool for complex reasoning or code generation.&lt;/p&gt;

&lt;p&gt;There's no neutral, up-to-date source that puts all of this in one place. llmprices is an attempt to be that.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;pip install llmprices&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;calc&lt;/strong&gt;&lt;br&gt;
Estimates the cost of a prompt across all models and sorts by price.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;llm-cost calc "Build a Python REST API" --output 500&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;╭── Cost estimate · 7 input + 500 output ───────────────────────────────────╮
│  #  Provider      Model                Total cost    vs cheapest           │
│  1  Mistral AI    Mistral Small 3.2    $0.000090     cheapest              │
│  2  DeepSeek      DeepSeek V4 Flash    $0.000141     1.6x                  │
│  3  Google        Gemini 2.5 Flash-L   $0.000200     2.2x                  │
│  4  xAI           Grok 4.1 Fast        $0.000251     2.8x                  │
│  5  OpenAI        GPT-5.4 Nano         $0.000626     7.0x                  │
│  6  Anthropic     Claude Haiku 4.5     $0.002507     27.9x                 │
│  7  Google        Gemini 3.1 Pro       $0.006014     66.8x                 │
│  8  OpenAI        GPT-5.5              $0.015035    167.1x                 │
╰───────────────────────────────────────────────────────────────────────────╯
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can pass a raw text prompt (tokens are counted automatically) or skip it and specify token counts directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;llm-cost calc &lt;span class="nt"&gt;--input&lt;/span&gt; 4000 &lt;span class="nt"&gt;--output&lt;/span&gt; 1000
llm-cost calc &lt;span class="nt"&gt;--input&lt;/span&gt; 10000 &lt;span class="nt"&gt;--output&lt;/span&gt; 2000 &lt;span class="nt"&gt;--top&lt;/span&gt; 5 &lt;span class="nt"&gt;--provider&lt;/span&gt; google
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;compare&lt;/strong&gt;&lt;br&gt;
Direct head-to-head comparison between specific models.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;llm-cost compare gpt-5-5 claude-opus-4-7 gemini-3-1-pro

llm-cost compare claude-sonnet-4-6 gpt-5-4 gemini-3-flash &lt;span class="nt"&gt;--input&lt;/span&gt; 5000 &lt;span class="nt"&gt;--output&lt;/span&gt; 1000

llm-cost compare gpt-5-5 claude-opus-4-7 &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s2"&gt;"Explain how transformers work"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;list&lt;/strong&gt;&lt;br&gt;
Browse the full catalog with filtering and sorting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;llm-cost list
llm-cost list &lt;span class="nt"&gt;--provider&lt;/span&gt; anthropic
llm-cost list &lt;span class="nt"&gt;--sort&lt;/span&gt; output
llm-cost list &lt;span class="nt"&gt;--search&lt;/span&gt; gemini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Efficiency Tiers
&lt;/h2&gt;

&lt;p&gt;Sorting by price is useful, but it doesn't account for capability. A budget model ranked first for cost might produce unusable output for a complex task. To address this, models are grouped into four tiers based on capability level — independently of price.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flagship&lt;/strong&gt; — GPT-5.5, Claude Opus 4.7, o3. Suitable for complex analytical tasks, critical decision-making, and high-quality creative work where output quality matters more than cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advanced&lt;/strong&gt; — GPT-5.4, Claude Sonnet 4.6, DeepSeek R1, o4 Mini. A solid default for most professional workloads: code generation, detailed analysis, structured output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standard&lt;/strong&gt; — GPT-5, Claude Haiku 4.5, Gemini Flash, Mistral Large 3. Reliable for everyday tasks, basic text processing, and simple question-answering at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Budget&lt;/strong&gt; — Mistral Small 3.2, Command R7B, DeepSeek V4 Flash. Best for high-volume pipelines, classification, and prototyping where task complexity is low.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;llm-cost list &lt;span class="nt"&gt;--tier&lt;/span&gt; budget
llm-cost calc &lt;span class="s2"&gt;"Code review"&lt;/span&gt; &lt;span class="nt"&gt;--tier&lt;/span&gt; advanced &lt;span class="nt"&gt;--sort&lt;/span&gt; value
llm-cost calc &lt;span class="s2"&gt;"Complex analysis"&lt;/span&gt; &lt;span class="nt"&gt;--tier&lt;/span&gt; flagship &lt;span class="nt"&gt;--sort&lt;/span&gt; value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Some Things the Data Makes Clear
&lt;/h2&gt;

&lt;p&gt;Running &lt;code&gt;calc&lt;/code&gt; across different workloads reveals a few patterns worth keeping in mind.&lt;/p&gt;

&lt;p&gt;The cost spread is wider than most people expect. For the same token count, the cheapest available model is often 100–200x less expensive than the most capable one. The vs cheapest column in the output makes this concrete rather than abstract.&lt;/p&gt;

&lt;p&gt;At scale, tier selection matters more than model selection within a tier. For a pipeline processing 1 million classification requests at 100 input + 50 output tokens, budget-tier models land at $11–28 total. The same volume on GPT-5.5 costs around $2,000. For tasks that don't require flagship reasoning, that's a 99%+ cost reduction with no meaningful quality tradeoff.&lt;/p&gt;

&lt;p&gt;Context window size is a hidden cost variable. A model with a lower per-token rate but a 128K context limit can end up more expensive than a pricier 1M-context model once you account for chunking logic, additional requests, and the engineering overhead of working around the constraint.&lt;/p&gt;

&lt;p&gt;The practical workflow is: start with &lt;code&gt;advanced&lt;/code&gt;, drop to &lt;code&gt;budget&lt;/code&gt; if the output quality is sufficient for the task and escalate to &lt;code&gt;flagship&lt;/code&gt; only when the task genuinely requires it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;Prices are stored in a plain YAML file at llm_cost/data/prices.yaml. Adding a new model takes four fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;my-new-model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;My New Model&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.50&lt;/span&gt;      &lt;span class="c1"&gt;# $ per 1M input tokens&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6.00&lt;/span&gt;     &lt;span class="c1"&gt;# $ per 1M output tokens&lt;/span&gt;
  &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200000&lt;/span&gt;  &lt;span class="c1"&gt;# context window in tokens&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PyPI: &lt;a href="https://pypi.org/project/llmprices/" rel="noopener noreferrer"&gt;pypi.org/project/llmprices&lt;/a&gt;&lt;br&gt;
Web: &lt;a href="https://www.llmcost.run/" rel="noopener noreferrer"&gt;llmcost.run&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://github.com/madeburo/llmcost" rel="noopener noreferrer"&gt;github.com/madeburo/llmcost&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>llm</category>
      <category>opensource</category>
      <category>cli</category>
    </item>
    <item>
      <title>No Hype, No Shortcuts: A Free MIT Course That Prepares You to Build a Startup</title>
      <dc:creator>Made Büro</dc:creator>
      <pubDate>Sun, 29 Mar 2026 15:33:11 +0000</pubDate>
      <link>https://dev.to/madeburo/no-hype-no-shortcuts-a-free-mit-course-that-prepares-you-to-build-a-startup-3p0p</link>
      <guid>https://dev.to/madeburo/no-hype-no-shortcuts-a-free-mit-course-that-prepares-you-to-build-a-startup-3p0p</guid>
      <description>&lt;p&gt;After 12+ years of building digital products, OSS tools, I've noticed that the most useful resources rarely go viral. They're too honest for that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This MIT course by Joe Hadzima is one of them.&lt;/strong&gt;&lt;br&gt;
Nuts and Bolts of New Ventures is free, open to everyone, and one of the most grounded resources on startups I've seen in a while. No hype, no shortcuts, no startup mythology. Just practical thinking about why most startups fail and what actually improves your odds.&lt;br&gt;
At a time when so much startup advice is optimized for attention, this course feels refreshingly different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this course stands out
&lt;/h2&gt;

&lt;p&gt;The title of the first session says everything:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Most Startups Fail: How to Improve Your Odds"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the spirit of the whole course.&lt;br&gt;
It doesn't try to sell entrepreneurship as a glamorous path. It treats it as what it really is: a difficult, uncertain, deeply practical process that requires much more than enthusiasm and a good idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four things that actually matter
&lt;/h2&gt;

&lt;p&gt;One of the core frameworks in the course is a simple but powerful idea: a venture needs alignment across four things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idea&lt;/strong&gt; – Is there a real problem worth solving?&lt;br&gt;
&lt;strong&gt;Execution&lt;/strong&gt; – Can you actually build and deliver it?&lt;br&gt;
&lt;strong&gt;Timing&lt;/strong&gt; – Is the market ready right now?&lt;br&gt;
&lt;strong&gt;People&lt;/strong&gt; – Do you have the right team?&lt;/p&gt;

&lt;p&gt;A lot of startup content overemphasizes the idea and underestimates everything else. In reality, getting even two of these right is hard. Getting all four aligned is rare.&lt;br&gt;
&lt;strong&gt;That's also why so many startups fail.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap that gets underestimated
&lt;/h2&gt;

&lt;p&gt;One of the most honest points in the course is the gap between wanting to be a founder and doing the actual work of building a company.&lt;/p&gt;

&lt;p&gt;The idea of building a startup is attractive. The day-to-day reality is much less romantic: customer discovery, defining the market, sales, legal basics, hiring, uncertainty, trade-offs, and constant decisions with incomplete information.&lt;/p&gt;

&lt;p&gt;That operational gap is where many ventures break down – not because founders lack ambition, but because building something real is far more complex than it looks from the outside.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Who this course is useful for
&lt;/h2&gt;

&lt;p&gt;I'd recommend it especially if you are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building a side project&lt;/strong&gt; and thinking about turning it into a business&lt;br&gt;
Early in your founder journey and looking for a solid mental model&lt;br&gt;
Already experienced, but want a practical refresher from first principles&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The course includes 11 sessions&lt;/strong&gt; and covers topics like market definition, sales, legal structure, and fundraising.&lt;/p&gt;

&lt;h2&gt;
  
  
  Personal note
&lt;/h2&gt;

&lt;p&gt;I'm currently building OneBit, GEO AI and Context AI CLI, both open source, both early stage. And even with years of experience, the four-element framework from this course is something I keep coming back to.&lt;/p&gt;

&lt;p&gt;Not because it's new. Because it's a useful reality check when you're deep in execution and lose perspective on timing or people.&lt;/p&gt;

&lt;p&gt;The best frameworks aren't the most complex ones. They're the ones you can actually apply when things get hard.&lt;/p&gt;

&lt;p&gt;Links&lt;br&gt;
YouTube: &lt;a href="https://www.youtube.com/watch?v=5gtR7IjtcFE" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=5gtR7IjtcFE&lt;/a&gt;&lt;br&gt;
MIT: &lt;a href="https://ocw.mit.edu/courses/15-393-nuts-and-bolts-of-new-ventures-january-iap-2025/pages/session-1/" rel="noopener noreferrer"&gt;https://ocw.mit.edu/courses/15-393-nuts-and-bolts-of-new-ventures-january-iap-2025/pages/session-1/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From MIT. Absolutely worth your time.&lt;/p&gt;

</description>
      <category>startup</category>
      <category>courses</category>
      <category>entrepreneurship</category>
      <category>productivity</category>
    </item>
    <item>
      <title>AI context management across Claude, Cursor, Kiro, Gemini and custom agents</title>
      <dc:creator>Made Büro</dc:creator>
      <pubDate>Mon, 23 Mar 2026 06:43:31 +0000</pubDate>
      <link>https://dev.to/madeburo/ai-context-management-across-claude-cursor-kiro-gemini-and-custom-agents-2n1f</link>
      <guid>https://dev.to/madeburo/ai-context-management-across-claude-cursor-kiro-gemini-and-custom-agents-2n1f</guid>
      <description>&lt;p&gt;If you use more than one AI coding agent, you've probably noticed that each one wants its own context file.&lt;/p&gt;

&lt;p&gt;Claude Code reads &lt;code&gt;CLAUDE.md&lt;/code&gt;. Cursor reads &lt;code&gt;.cursorrules&lt;/code&gt;. GitHub Copilot reads &lt;code&gt;.github/copilot-instructions.md&lt;/code&gt;. Kiro reads &lt;code&gt;.kiro/steering/*.md&lt;/code&gt;. Windsurf reads &lt;code&gt;.windsurf/rules/*.md&lt;/code&gt;. Gemini CLI and Antigravity read &lt;code&gt;GEMINI.md&lt;/code&gt;. And then there's &lt;code&gt;AGENTS.md&lt;/code&gt; and &lt;code&gt;llms.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's nine different files describing the same thing: your project's stack, architecture, and coding conventions.&lt;/p&gt;

&lt;p&gt;Most of them contain nearly identical content. But they go out of sync the moment someone updates one and forgets the rest. The result is that your AI agent gives inconsistent suggestions depending on which tool you're using.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  One config, every agent
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/madeburo/contextai" rel="noopener noreferrer"&gt;contextai&lt;/a&gt; is a CLI that generates all of these from a single TypeScript config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineContext&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;contextai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;project&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;my-app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stack&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;TypeScript&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;React&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;Node.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;architecture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Monorepo with shared packages&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;conventions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;code&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;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;Naming&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;items&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;camelCase for variables&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;PascalCase for components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;testing&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;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;Unit tests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;items&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;Use Vitest&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;Colocate test files with source&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agent-only&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;outputs&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;AGENTS.md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CLAUDE.md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.cursorrules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.github/copilot-instructions.md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;llms.txt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.kiro/steering&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.windsurf/rules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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.md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx contextai generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All nine files created from one source. Change a convention, run generate again, everything stays in sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supported output targets
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursorrules&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex / generic agents&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.github/copilot-instructions.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kiro (AWS)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.kiro/steering/*.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windsurf&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.windsurf/rules/*.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini CLI / Antigravity&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GEMINI.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI search engines&lt;/td&gt;
&lt;td&gt;&lt;code&gt;llms.txt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;any path via config&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each generator respects the target's native format. Kiro gets YAML frontmatter with &lt;code&gt;inclusion: always&lt;/code&gt;. Windsurf gets &lt;code&gt;trigger: always_on&lt;/code&gt;. Gemini gets clean markdown. You don't have to think about format differences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project scanner
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;contextai init&lt;/code&gt; inspects your project root — reads &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;tsconfig.json&lt;/code&gt;, framework configs — and bootstraps a &lt;code&gt;context.config.ts&lt;/code&gt; with detected stack and sensible defaults.&lt;/p&gt;

&lt;p&gt;Built-in convention templates are available for Next.js, NestJS, Express, Remix, and SvelteKit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;templates&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;nextjs&lt;/span&gt;&lt;span class="dl"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Convention scoping
&lt;/h2&gt;

&lt;p&gt;Sometimes you want technical depth for AI agents that doesn't belong in human-facing docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;conventions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;security&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;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;Auth patterns&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;items&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;Use JWT tokens&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;Rotate secrets quarterly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agent-only&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;agent-only&lt;/code&gt; sections appear in all generated output files. &lt;code&gt;human-only&lt;/code&gt; sections are excluded from AI context entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom generators
&lt;/h2&gt;

&lt;p&gt;New AI tools appear regularly. Custom generators let you target anything without waiting for a contextai update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;outputs&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;CLAUDE.md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;custom&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;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;docs/ai-context.md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;architecture&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Keeping things in sync
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;contextai validate&lt;/code&gt; — checks that output files exist and aren't stale&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contextai diff&lt;/code&gt; — shows what would change without writing anything&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contextai generate --dry-run&lt;/code&gt; — preview mode&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contextai watch&lt;/code&gt; — regenerates automatically when config changes&lt;/li&gt;
&lt;li&gt;Git hook — &lt;code&gt;contextai init&lt;/code&gt; can install a pre-commit hook that runs generate before every commit&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  JSON output for CI
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;contextai generate &lt;span class="nt"&gt;--format&lt;/span&gt; json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outputs the full intermediate representation to stdout instead of writing files. Useful for CI pipelines, piping into other tools, or debugging.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Site: &lt;a href="https://contextai.run" rel="noopener noreferrer"&gt;contextai.run&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/madeburo/contextai" rel="noopener noreferrer"&gt;github.com/madeburo/contextai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;a href="https://npmjs.com/package/contextai" rel="noopener noreferrer"&gt;npmjs.com/package/contextai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: MIT&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>cli</category>
      <category>agents</category>
    </item>
    <item>
      <title>Type-safe environment validation</title>
      <dc:creator>Made Büro</dc:creator>
      <pubDate>Sat, 21 Mar 2026 15:13:51 +0000</pubDate>
      <link>https://dev.to/madeburo/type-safe-environment-validation-3mdi</link>
      <guid>https://dev.to/madeburo/type-safe-environment-validation-3mdi</guid>
      <description>&lt;p&gt;Environment variables are one of those things that look simple until they start breaking production.&lt;/p&gt;

&lt;p&gt;A missing &lt;code&gt;DATABASE_URL&lt;/code&gt;, an invalid &lt;code&gt;PORT&lt;/code&gt;, a typo in &lt;code&gt;NODE_ENV&lt;/code&gt;, or a leaked secret in logs — all of these come from the same place: &lt;code&gt;process.env&lt;/code&gt; is just untrusted input.&lt;/p&gt;

&lt;p&gt;That's why I made &lt;strong&gt;EnvZen&lt;/strong&gt;: an Open Source toolkit for validating environment variables in TypeScript / Node.js. It validates &lt;code&gt;process.env&lt;/code&gt; against a schema at startup, returns typed config, redacts sensitive values, and includes a CLI for scaffolding, checking, and syncing &lt;code&gt;.env&lt;/code&gt; files.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with process.env
&lt;/h2&gt;

&lt;p&gt;In most Node.js apps, environment variables are used everywhere but validated nowhere.&lt;/p&gt;

&lt;p&gt;That usually leads to a few common problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Values are missing, but you only find out after deploy&lt;/li&gt;
&lt;li&gt;Everything is a string, so type coercion gets repeated across the codebase&lt;/li&gt;
&lt;li&gt;Config rules live in people's heads instead of code&lt;/li&gt;
&lt;li&gt;Secrets accidentally end up in logs or JSON output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core idea behind EnvZen is simple: treat env variables like runtime input and validate them at boot time. EnvZen throws a structured &lt;code&gt;EnvValidationError&lt;/code&gt; when required values are missing, invalid, or the wrong type. It also infers TypeScript types directly from the schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;

&lt;p&gt;Install the core package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;envzen-core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then define your schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// env.ts&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;createEnv&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;envzen-core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createEnv&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="p"&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;enum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;values&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;development&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;production&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;test&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;development&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;PORT&lt;/span&gt;&lt;span class="p"&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;port&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;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&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;url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sensitive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&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;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;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sensitive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;And use it in your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv/config&lt;/span&gt;&lt;span class="dl"&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;env&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;./env.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;// number&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;// 'development' | 'production' | 'test'&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// sensitive fields are redacted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EnvZen does not load &lt;code&gt;.env&lt;/code&gt; files for you — if you use dotenv, call it before &lt;code&gt;createEnv()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What EnvZen gives you
&lt;/h2&gt;

&lt;p&gt;The core package is built around a schema-driven API.&lt;/p&gt;

&lt;p&gt;Supported field types: &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;number&lt;/code&gt;, &lt;code&gt;boolean&lt;/code&gt;, &lt;code&gt;port&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;enum&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can also mark fields as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;required&lt;/code&gt; — must be present, no fallback&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;default&lt;/code&gt; — fallback value when variable is absent&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt; — used in &lt;code&gt;.env.example&lt;/code&gt; output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sensitive&lt;/code&gt; — redacted in logs, errors, and &lt;code&gt;JSON.stringify()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;validate&lt;/code&gt; — additional Zod-based validation on top of the base type&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means your environment config becomes a real contract instead of a pile of implicit assumptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safer logging by default
&lt;/h2&gt;

&lt;p&gt;One thing I cared about from the start was avoiding secret leaks.&lt;/p&gt;

&lt;p&gt;If a field is marked as &lt;code&gt;sensitive&lt;/code&gt;, EnvZen automatically redacts it in &lt;code&gt;toJSON()&lt;/code&gt; and &lt;code&gt;JSON.stringify()&lt;/code&gt;. Sensitive values are also redacted from validation error messages.&lt;/p&gt;

&lt;p&gt;That makes it much safer to inspect config or print error output without accidentally exposing credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  CLI for real workflows
&lt;/h2&gt;

&lt;p&gt;Besides the runtime library, there's also &lt;code&gt;envzen-cli&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Available commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;envzen init                              &lt;span class="c"&gt;# scaffold env.ts config&lt;/span&gt;
envzen &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nt"&gt;--schema&lt;/span&gt; ./env.ts            &lt;span class="c"&gt;# generate .env.example from schema&lt;/span&gt;
envzen check &lt;span class="nt"&gt;--schema&lt;/span&gt; ./env.ts &lt;span class="nt"&gt;--env&lt;/span&gt; .env &lt;span class="c"&gt;# diff .env against schema&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;init&lt;/code&gt; scaffolds an &lt;code&gt;env.ts&lt;/code&gt; config file. &lt;code&gt;sync&lt;/code&gt; generates &lt;code&gt;.env.example&lt;/code&gt; from your schema — always up to date. &lt;code&gt;check&lt;/code&gt; diffs your &lt;code&gt;.env&lt;/code&gt; against the schema and reports missing, extra, or invalid variables.&lt;/p&gt;

&lt;p&gt;CI mode is also supported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;envzen init &lt;span class="nt"&gt;--ci&lt;/span&gt;
envzen check &lt;span class="nt"&gt;--ci&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Framework adapters
&lt;/h2&gt;

&lt;p&gt;EnvZen works beyond plain Node.js apps with adapters for several common setups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Express / Fastify&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;envGuardMiddleware&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;envzen-express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;envGuardMiddleware&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="c1"&gt;// Vite&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;envGuardPlugin&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;envzen-vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;envGuardPlugin&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="c1"&gt;// Next.js&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;withEnvGuard&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;envzen-next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;withEnvGuard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextConfig&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="c1"&gt;// NestJS&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;EnvGuardModule&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;envzen-nestjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;EnvGuardModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forRoot&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each adapter validates at the framework's startup lifecycle. Invalid env = the app doesn't start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error handling
&lt;/h2&gt;

&lt;p&gt;When validation fails, EnvZen throws &lt;code&gt;EnvValidationError&lt;/code&gt; with both a formatted message and a structured &lt;code&gt;failures&lt;/code&gt; array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createEnv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;EnvValidationError&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;envzen-core&lt;/span&gt;&lt;span class="dl"&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;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createEnv&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="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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;EnvValidationError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&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="c1"&gt;// human-readable summary&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&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;failures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// ValidationFailure[]&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;
  
  
  Why I made this
&lt;/h2&gt;

&lt;p&gt;There are already good libraries in this space, but I wanted something with a few specific properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schema-first API with full TypeScript inference&lt;/li&gt;
&lt;li&gt;Sensitive values redacted by default, not opt-in&lt;/li&gt;
&lt;li&gt;CLI that keeps &lt;code&gt;.env.example&lt;/code&gt; in sync automatically&lt;/li&gt;
&lt;li&gt;Lightweight adapters for Express, NestJS, Next.js, Vite&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal was not just "validate env vars" but to make environment configuration feel like part of the application contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/madeburo/envzen" rel="noopener noreferrer"&gt;github.com/madeburo/envzen&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;a href="https://npmjs.com/package/envzen-core" rel="noopener noreferrer"&gt;envzen-core&lt;/a&gt; · &lt;a href="https://npmjs.com/package/envzen-cli" rel="noopener noreferrer"&gt;envzen-cli&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: MIT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback, issues, and contributions are welcome.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>node</category>
      <category>typescript</category>
      <category>cli</category>
    </item>
    <item>
      <title>NestJS Module for AI Search Optimization</title>
      <dc:creator>Made Büro</dc:creator>
      <pubDate>Tue, 17 Mar 2026 14:05:46 +0000</pubDate>
      <link>https://dev.to/madeburo/nestjs-module-for-ai-search-optimization-942</link>
      <guid>https://dev.to/madeburo/nestjs-module-for-ai-search-optimization-942</guid>
      <description>&lt;p&gt;Over the past few weeks I’ve been building GEO AI an Open Source ecosystem around AI Search Optimization.&lt;/p&gt;

&lt;p&gt;The core idea is simple: if AI systems are going to read and reason about websites, then websites need a clearer way to expose structured content, crawler rules, and machine-readable signals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a separate package for NestJS?
&lt;/h2&gt;

&lt;p&gt;NestJS developers expect framework-native primitives: modules, DI, middleware, guards, interceptors, decorators. Asking them to manually call createGeoAI() and wire everything by hand would feel wrong.&lt;/p&gt;

&lt;p&gt;So instead of a generic adapter, I built geo-ai-nest as a thin NestJS layer on top of geo-ai-core. All the GEO logic stays in the core package. The Nest package only adapts it to how Nest apps are structured.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Quick start&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;npm install geo-ai-nest geo-ai-core&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Register the module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Module&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;@nestjs/common&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;GeoAIModule&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;geo-ai-nest&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="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;GeoAIModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forRoot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;siteName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My App&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;siteUrl&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://myapp.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;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;pages&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;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;About&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;/about&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;About us&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;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;Pricing&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;/pricing&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;Our plans&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;crawlers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Your app now serves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GET /llms.txt — compact content index&lt;/li&gt;
&lt;li&gt;GET /llms-full.txt — full content with descriptions&lt;/li&gt;
&lt;li&gt;GET /.well-known/llms.txt — standard discovery path&lt;/li&gt;
&lt;li&gt;GET /robots-ai.txt — per-bot crawler rules&lt;/li&gt;
&lt;li&gt;No extra controllers needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Async configuration
&lt;/h2&gt;

&lt;p&gt;Real apps usually load config from a database or config service. forRootAsync handles that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;GeoAIModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forRootAsync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ConfigModule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;useFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConfigService&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="na"&gt;siteName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SITE_NAME&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;siteUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SITE_URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;pages&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;// your ContentProvider here&lt;/span&gt;
    &lt;span class="na"&gt;crawlers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;injectLinkHeader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cacheMaxAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ConfigService&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;
  
  
  Detecting AI bots in your routes
&lt;/h2&gt;

&lt;p&gt;The @IsAIBot() parameter decorator tells you which AI crawler is hitting your endpoint — or null if it's a regular user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&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;Get&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;@nestjs/common&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;IsAIBot&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;geo-ai-nest&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="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CatalogController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;getProducts&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;IsAIBot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`AI crawler detected: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bot&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="c1"&gt;// return enriched response for AI consumption&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;productService&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="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;
  
  
  Protecting routes from non-AI traffic
&lt;/h2&gt;

&lt;p&gt;GeoAIGuard restricts a route to verified AI bot requests only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UseGuards&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;@nestjs/common&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;GeoAIGuard&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;geo-ai-nest&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="nd"&gt;UseGuards&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GeoAIGuard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ai-only&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;getAiContent&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;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Only AI crawlers can see this&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why middleware for llms.txt instead of a controller?&lt;/strong&gt;&lt;br&gt;
This was a deliberate choice. Middleware runs before the routing layer, which means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It intercepts requests before guards, interceptors, or pipes run&lt;/li&gt;
&lt;li&gt;It works regardless of how the app's routing is structured&lt;/li&gt;
&lt;li&gt;It avoids conflicts with catch-all routes or global prefixes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The robots-ai.txt endpoint uses a controller because it's a standard REST resource that benefits from Nest's decorator pipeline. &lt;/p&gt;

&lt;h2&gt;
  
  
  Where it fits in GEO AI
&lt;/h2&gt;

&lt;p&gt;At this point, the GEO AI ecosystem looks something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;geo-ai-core — universal engine&lt;/li&gt;
&lt;li&gt;geo-ai-next — Next.js integration&lt;/li&gt;
&lt;li&gt;geo-ai-nest — NestJS integration&lt;/li&gt;
&lt;li&gt;geo-ai-cli — CLI for any Node.js project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core package remains the center of gravity, and the framework packages are really adapters around it. The main GEO AI README now describes geo-ai-nest as the NestJS wrapper alongside the Next.js and CLI packages.  ￼&lt;/p&gt;

&lt;p&gt;I like this structure because it keeps the real logic in one place while still making adoption feel native inside each framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I wanted from this release&lt;/strong&gt;&lt;br&gt;
More than anything, I wanted NestJS developers to be able to say:&lt;/p&gt;

&lt;p&gt;“Install one package, register one module, and get the GEO surface area in a way that feels like Nest.”&lt;/p&gt;

&lt;p&gt;That includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dynamic module registration&lt;/li&gt;
&lt;li&gt;automatic llms routes&lt;/li&gt;
&lt;li&gt;a built-in robots endpoint&lt;/li&gt;
&lt;li&gt;DI-friendly service access&lt;/li&gt;
&lt;li&gt;optional route protection for AI bots&lt;/li&gt;
&lt;li&gt;optional response header injection&lt;/li&gt;
&lt;li&gt;decorators that fit how Nest apps are already written&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was the bar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://www.geoai.run" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.geoai.run/docs" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/madeburo/GEO-AI" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/geo-ai-nest" rel="noopener noreferrer"&gt;npm&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>node</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>CLI for AI Search Optimization in Node.js projects</title>
      <dc:creator>Made Büro</dc:creator>
      <pubDate>Thu, 12 Mar 2026 16:57:50 +0000</pubDate>
      <link>https://dev.to/madeburo/cli-for-ai-search-optimization-in-nodejs-projects-58li</link>
      <guid>https://dev.to/madeburo/cli-for-ai-search-optimization-in-nodejs-projects-58li</guid>
      <description>&lt;p&gt;If you've been following the AI search space, you've probably heard about llms.txt – a simple Markdown file that tells AI crawlers (ChatGPT, Claude, Perplexity, Gemini, DeepSeek etc.) what your site is about and what content they're allowed to use. Think of it as robots.txt, but designed for the LLM era.&lt;/p&gt;

&lt;p&gt;In this post I'll walk through how I built a CLI tool – geo-ai-cli — that generates and validates these files for any Node.js project, and share some of the interesting design decisions along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is llms.txt?
&lt;/h2&gt;

&lt;p&gt;The llms.txt proposal defines a standard for exposing structured site content to AI systems. Two files:&lt;/p&gt;

&lt;p&gt;llms.txt – a compact summary: site name, description, and a list of key pages with titles and URLs&lt;br&gt;
llms-full.txt – the same, but with full content for each page&lt;br&gt;
A minimal llms.txt looks like this:&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="gh"&gt;# My SaaS Product&lt;/span&gt;
&lt;span class="gt"&gt;
&amp;gt; A brief description for AI crawlers.&lt;/span&gt;

&lt;span class="gu"&gt;## Pages&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://example.com/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: Welcome page
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Pricing&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://example.com/pricing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: Plans and pricing

&lt;span class="gu"&gt;## Blog&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Getting Started&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://example.com/blog/start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: First steps guide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AI search engines like Perplexity, Claude and ChatGPT browse these files to understand your site's content and include it in answers. It's essentially GEO – Generative Engine Optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CLI
&lt;/h2&gt;

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

&lt;p&gt;&lt;code&gt;npm install --save-dev geo-ai-cli&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Four commands cover the full workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx geo-ai init      &lt;span class="c"&gt;# scaffold a config file&lt;/span&gt;
npx geo-ai generate  &lt;span class="c"&gt;# generate llms.txt + llms-full.txt&lt;/span&gt;
npx geo-ai validate  &lt;span class="c"&gt;# check output files are valid&lt;/span&gt;
npx geo-ai inspect   &lt;span class="c"&gt;# preview config and crawler rules&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;init&lt;/strong&gt;&lt;br&gt;
Scaffolds a geo-ai.config.ts in the current directory. If a config already exists, it exits without overwriting – safe to run multiple times.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// geo-ai.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GeoAIConfig&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;geo-ai-core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;siteName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My Site&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;siteUrl&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://example.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;siteDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A brief description for AI crawlers.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;crawlers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Pages&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;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;Home&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;https://example.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;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;Welcome page&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;Blog&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;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;Getting Started&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;https://example.com/blog/start&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;First steps&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;GeoAIConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The provider field is a plain object where each key becomes a section in llms.txt. You can also pass a ContentProvider instance if you want to pull data from a CMS or API dynamically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;generate&lt;/strong&gt;&lt;br&gt;
Loads your config, calls createGeoAI(config) from geo-ai-core, and writes both files to ./public (or wherever you point --out):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx geo-ai generate --out ./dist/public&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Config discovery follows a priority order: geo-ai.config.ts → geo-ai.config.js → geo-ai.config.json. TypeScript configs are loaded via dynamic import(), so you get full type safety without a separate compilation step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;validate&lt;/strong&gt;&lt;br&gt;
Checks that the generated files are present and structurally valid. Works on local files or a live URL:&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;# local&lt;/span&gt;
npx geo-ai validate &lt;span class="nt"&gt;--path&lt;/span&gt; ./public

&lt;span class="c"&gt;# remote&lt;/span&gt;
npx geo-ai validate &lt;span class="nt"&gt;--url&lt;/span&gt; https://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validation rules are intentionally simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;file missing: not_found&lt;/li&gt;
&lt;li&gt;content &amp;lt; 50 chars: warn&lt;/li&gt;
&lt;li&gt;doesn't start with #: fail&lt;/li&gt;
&lt;li&gt;starts with #: pass&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exit code 1 on any fail or not_found, so it integrates cleanly into CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/deploy.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx geo-ai generate&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx geo-ai validate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;inspect&lt;/strong&gt;&lt;br&gt;
Prints a human-readable summary of your config: site info, which AI bots are allowed/disallowed, and how many items are in each section. Useful for debugging before you generate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx geo-ai inspect

&lt;span class="c"&gt;# Site: My Site&lt;/span&gt;
&lt;span class="c"&gt;# URL:  https://example.com&lt;/span&gt;
&lt;span class="c"&gt;# Out:  ./public&lt;/span&gt;
&lt;span class="c"&gt;# Crawlers:&lt;/span&gt;
&lt;span class="c"&gt;#   GPTBot: allow&lt;/span&gt;
&lt;span class="c"&gt;#   ClaudeBot: allow&lt;/span&gt;
&lt;span class="c"&gt;#   PerplexityBot: allow&lt;/span&gt;
&lt;span class="c"&gt;#   ...&lt;/span&gt;
&lt;span class="c"&gt;# Sections:&lt;/span&gt;
&lt;span class="c"&gt;#   Pages: 3 item(s)&lt;/span&gt;
&lt;span class="c"&gt;#   Blog: 12 item(s)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also point it at a live URL to fetch and display the remote files:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx geo-ai inspect --url https://example.com&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding it to your build pipeline
&lt;/h2&gt;

&lt;p&gt;The most useful place for &lt;code&gt;geo-ai generate&lt;/code&gt; is right before your deployment step:&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;package.json&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;"scripts"&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;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next build &amp;amp;&amp;amp; geo-ai generate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"postbuild"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"geo-ai validate"&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;Or in CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate llms.txt&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx geo-ai generate&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx geo-ai validate --url ${{ env.SITE_URL }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Controlling which AI bots can crawl
&lt;/h2&gt;

&lt;p&gt;By default, crawlers: 'all' allows all 16+ known AI bots. You can get granular:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;siteName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My Site&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;siteUrl&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://example.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;crawlers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;GPTBot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ClaudeBot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;PerplexityBot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;allow&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;Google-Extended&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;disallow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// opt out of Gemini training&lt;/span&gt;
    &lt;span class="na"&gt;Bytespider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;disallow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// TikTok crawler&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&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;satisfies&lt;/span&gt; &lt;span class="nx"&gt;GeoAIConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;geo-ai-core&lt;/code&gt; engine uses this to generate a corresponding robots.txt block and per-bot allow/disallow rules in the llms files.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The CLI is part of a larger ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;geo-ai-core — the zero-dependency engine (llms.txt generation, bot rules, crawl tracking, SEO signals, AI description generation via Claude/OpenAI)&lt;/li&gt;
&lt;li&gt;geo-ai-next — a thin Next.js wrapper with middleware and App Router handler&lt;/li&gt;
&lt;li&gt;WordPress and Shopify integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The geo-ai-core/ai entry point lets you bulk-generate AI descriptions for your content using Claude or OpenAI, with a built-in rate limiter and batching – but that's a topic for another post.&lt;/p&gt;

&lt;p&gt;If you're building a public-facing Node.js project, adding llms.txt takes about 5 minutes and meaningfully improves how AI search engines understand your content. Give it a try:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx geo-ai init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;br&gt;
Website: &lt;a href="https://www.geoai.run" rel="noopener noreferrer"&gt;https://www.geoai.run&lt;/a&gt;&lt;br&gt;
CLI page: &lt;a href="https://www.geoai.run/cli" rel="noopener noreferrer"&gt;https://www.geoai.run/cli&lt;/a&gt;&lt;br&gt;
CLI docs: &lt;a href="https://www.geoai.run/docs/integrations/cli" rel="noopener noreferrer"&gt;https://www.geoai.run/docs/integrations/cli&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/madeburo/GEO-AI" rel="noopener noreferrer"&gt;https://github.com/madeburo/GEO-AI&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cli</category>
      <category>node</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How to optimize Next.js project for AI search engines</title>
      <dc:creator>Made Büro</dc:creator>
      <pubDate>Fri, 06 Mar 2026 08:47:10 +0000</pubDate>
      <link>https://dev.to/madeburo/how-to-optimize-nextjs-project-for-ai-search-engines-1gfo</link>
      <guid>https://dev.to/madeburo/how-to-optimize-nextjs-project-for-ai-search-engines-1gfo</guid>
      <description>&lt;p&gt;AI search engines like ChatGPT, Perplexity, Claude, and Gemini are becoming a major discovery channel. When someone asks them "recommend me a good product or service", they pull data from structured content, dedicated crawlers, and formats like &lt;code&gt;llms.txt&lt;/code&gt; to generate answers.&lt;/p&gt;

&lt;p&gt;Optimizing for this is called Generative Engine Optimization (GEO) – and it requires a different approach than traditional SEO.&lt;/p&gt;

&lt;p&gt;In this guide, I'll show you how to optimize your Node.js or Next.js app for 16 AI search engines using an open-source TypeScript engine called &lt;strong&gt;GEO AI Core&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;For any Node.js project:&lt;br&gt;
&lt;code&gt;npm install geo-ai-core&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For Next.js specifically:&lt;br&gt;
&lt;code&gt;npm install geo-ai-next&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Define your content
&lt;/h2&gt;

&lt;p&gt;The simplest way is to pass your content directly:&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;createGeoAI&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;geo-ai-core&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;geo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createGeoAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;siteName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My Store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;siteUrl&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://example.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;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Products&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;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;Widget&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;/products/widget&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;A great widget&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;Blog&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;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;Hello World&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;/blog/hello&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;First post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For dynamic data (CMS, database, API), implement the ContentProvider interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createGeoAI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ContentProvider&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;geo-ai-core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StrapiProvider&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;ContentProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getSections&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&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;Products&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;product&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchProducts&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;Blog&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;page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchPosts&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createGeoAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;siteName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My Store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;siteUrl&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://example.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;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StrapiProvider&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;crawlTracking&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Generate llms.txt
&lt;/h2&gt;

&lt;p&gt;llms.txt is a structured file that helps AI crawlers understand your site – like a sitemap, but designed for LLMs.&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;// Standard version — resource links with descriptions&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;llmsTxt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;geo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateLlms&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="c1"&gt;// Full version — includes content, pricing, availability, variants&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;llmsFullTxt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;geo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateLlms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add SEO signals for AI
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Meta tags for AI content discovery&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metaTags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;geo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateMetaTags&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// HTTP Link header&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;linkHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;geo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateLinkHeader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// JSON-LD (WebSite, Product, Article)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonLd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;geo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateJsonLd&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Robots.txt block with AI bot rules&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;robotsTxt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;geo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateRobotsTxt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Next.js: Wire it up with middleware
&lt;/h2&gt;

&lt;p&gt;If you're using Next.js, geo-ai-next makes this even simpler – just add the middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// middleware.ts&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;geoAIMiddleware&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;geo-ai-next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;geoAIMiddleware&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;siteName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My Store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;siteUrl&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://example.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;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MyProvider&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;24h&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;injectLinkHeader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&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;/((?!_next/static|_next/image|favicon.ico).*)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This automatically serves /llms.txt and /llms-full.txt and injects Link headers into all responses.&lt;/p&gt;

&lt;p&gt;Or use an App Router route handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/llms/route.ts&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;createLlmsHandler&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;geo-ai-next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&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;GET&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createLlmsHandler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;siteName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My Store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;siteUrl&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://example.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;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MyProvider&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Your Next.js app is now optimized for AI search engines.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-powered descriptions
&lt;/h2&gt;

&lt;p&gt;GEO AI Core can also generate AI-optimized descriptions for your content via Claude or OpenAI:&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;AiGenerator&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;geo-ai-core/ai&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;ai&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;AiGenerator&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;anthropic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sk-...&lt;/span&gt;&lt;span class="dl"&gt;'&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;claude-sonnet-4-20250514&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&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;Premium Widget&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A high-quality widget...&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;product&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$29.99&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bulk generation with progress tracking:&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;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bulkGenerate&lt;/span&gt;&lt;span class="p"&gt;(&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="na"&gt;batchSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onProgress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;total&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;completed&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;total&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What AI crawlers are supported?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GPTBot, OAI-SearchBot, ClaudeBot, claude-web, Google-Extended, PerplexityBot, DeepSeekBot, GrokBot, meta-externalagent, PanguBot, Bytespider, Baiduspider, Amazonbot, Applebot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical highlights&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero runtime dependencies&lt;/li&gt;
&lt;li&gt;TypeScript with full type declarations&lt;/li&gt;
&lt;li&gt;Dual ESM/CJS build&lt;/li&gt;
&lt;li&gt;114 tests (unit + property-based via fast-check)&lt;/li&gt;
&lt;li&gt;GDPR-compliant crawl tracking with SHA-256 IP anonymization&lt;/li&gt;
&lt;li&gt;Pluggable caching and storage&lt;/li&gt;
&lt;li&gt;Node.js 20+&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;br&gt;
GEO AI: &lt;a href="https://www.geoai.run/" rel="noopener noreferrer"&gt;https://www.geoai.run/&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/madeburo/GEO-AI" rel="noopener noreferrer"&gt;github.com/madeburo/GEO-AI&lt;/a&gt;&lt;br&gt;
npm: &lt;a href="https://www.npmjs.com/package/geo-ai-core" rel="noopener noreferrer"&gt;geo-ai-core&lt;/a&gt; / &lt;a href="https://www.npmjs.com/package/geo-ai-next" rel="noopener noreferrer"&gt;geo-ai-next&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All big things grow from small wedges in the market.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>nextjs</category>
      <category>node</category>
      <category>opensource</category>
    </item>
    <item>
      <title>GEO for Shopify: How to Get Your Products Into ChatGPT, Claude &amp; Perplexity Results</title>
      <dc:creator>Made Büro</dc:creator>
      <pubDate>Thu, 05 Mar 2026 07:05:44 +0000</pubDate>
      <link>https://dev.to/madeburo/geo-for-shopify-how-to-get-your-products-into-chatgpt-claude-perplexity-results-168h</link>
      <guid>https://dev.to/madeburo/geo-for-shopify-how-to-get-your-products-into-chatgpt-claude-perplexity-results-168h</guid>
      <description>&lt;p&gt;When someone asks ChatGPT "best running shoes for flat feet" or Perplexity "recommend a minimalist wallet" AI engines don't just crawl your sitemap. They need structured, machine-readable content that fits inside a context window. Your product data is trapped in Liquid templates, JavaScript, and navigation – LLMs waste tokens parsing it.&lt;/p&gt;

&lt;p&gt;This is Generative Engine Optimization (GEO) – making your store visible to AI search engines.&lt;/p&gt;

&lt;p&gt;I built an open-source Shopify app to solve this – GEO AI Shopify.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  What GEO AI Shopify Does
&lt;/h2&gt;

&lt;p&gt;GEO AI Shopify is a free, open-source embedded Shopify app that makes your store AI-readable with zero configuration.&lt;/p&gt;

&lt;p&gt;The app generates llms.txt and llms-full.txt – structured Markdown files served via Shopify App Proxy at /apps/llms/. Think of llms.txt as robots.txt for AI: instead of telling crawlers what not to index, it tells AI engines what to read and how to understand it.&lt;br&gt;
Content is cached in the database with configurable TTL and regenerated automatically when you update products, pages, or collections — all via webhooks, no manual intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Crawlers Supported
&lt;/h2&gt;

&lt;p&gt;ChatGPT, Copilot, Claude, Claude Web, Gemini, Perplexity, DeepSeek, Grok, LLaMA, Qwen, YandexGPT, GigaChat, Douyin, Ernie, Alexa and Siri.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Shopify-Native Architecture
&lt;/h2&gt;

&lt;p&gt;This isn't a generic llms.txt generator bolted onto Shopify. It's built as a proper embedded app with the full Shopify stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remix v2 + Vite — modern SSR framework&lt;/li&gt;
&lt;li&gt;Shopify Polaris — native admin UI&lt;/li&gt;
&lt;li&gt;Prisma ORM — type-safe database access&lt;/li&gt;
&lt;li&gt;App Bridge — seamless admin integration&lt;/li&gt;
&lt;li&gt;GraphQL Admin API — cursor-based pagination, exponential backoff, proactive throttling&lt;/li&gt;
&lt;li&gt;Webhook-driven — cache invalidates automatically on product/page/collection changes&lt;/li&gt;
&lt;li&gt;App Proxy — llms.txt served at /apps/llms/ with HMAC-SHA256 signature verification&lt;/li&gt;
&lt;li&gt;Theme Extension — injects  and JSON-LD Schema.org directly into storefront &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Metafields Integration&lt;/strong&gt;&lt;br&gt;
AI metadata lives where it should — as Shopify Metafields (namespace: geo_ai):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI Description — concise summary for LLMs (max 200 chars)&lt;/li&gt;
&lt;li&gt;AI Keywords — topics and context hints&lt;/li&gt;
&lt;li&gt;Exclude from AI — opt specific content out of llms.txt&lt;/li&gt;
&lt;li&gt;Auto-registration of metafield definitions on first use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AI-Powered Descriptions&lt;/strong&gt;&lt;br&gt;
Generate AI summaries via Claude or OpenAI APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One-click generation per product/page&lt;/li&gt;
&lt;li&gt;Bulk generation for up to 50 resources (batched by 5, rate limited)&lt;/li&gt;
&lt;li&gt;Customizable prompt templates with {title}, {content}, {type}, {price}, {category} placeholders&lt;/li&gt;
&lt;li&gt;AES-256-GCM encrypted API key storage&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Matters Now
&lt;/h2&gt;

&lt;p&gt;Shopify recently published their own GEO Playbook acknowledging that AI discovery is the next frontier for commerce. OpenAI's Agentic Commerce Protocol — launched with Shopify, Etsy, and Walmart — lets AI agents complete purchases directly. McKinsey projects agentic commerce will reach $3-5 trillion by 2030.&lt;/p&gt;

&lt;p&gt;If your store isn't optimized for AI search today, you're already falling behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Source
&lt;/h2&gt;

&lt;p&gt;Most Shopify GEO solutions are either paid apps or closed-source SaaS. GEO AI Shopify is GPL v2, fully open, and free. Merchants everywhere — especially those in emerging markets — deserve AI visibility without a monthly subscription.&lt;/p&gt;

&lt;p&gt;Try It&lt;br&gt;
GEO AI: &lt;a href="https://www.geoai.run/" rel="noopener noreferrer"&gt;https://www.geoai.run/&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/madeburo/GEO-AI-Shopify" rel="noopener noreferrer"&gt;https://github.com/madeburo/GEO-AI-Shopify&lt;/a&gt;&lt;br&gt;
Install it, check your /apps/llms/ endpoint, and see what AI engines can (or can't) find about your products.&lt;/p&gt;

&lt;p&gt;Feedback, issues, and PRs welcome.&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>opensource</category>
      <category>ai</category>
      <category>chatgpt</category>
    </item>
  </channel>
</rss>
