<?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: Nick Taylor</title>
    <description>The latest articles on DEV Community by Nick Taylor (@nickytonline).</description>
    <link>https://dev.to/nickytonline</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F9597%2F8f1ed696-3e87-4bd4-809a-0ee9bb905c3c.jpg</url>
      <title>DEV Community: Nick Taylor</title>
      <link>https://dev.to/nickytonline</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nickytonline"/>
    <language>en</language>
    <item>
      <title>Stop Shipping Broken Env Config</title>
      <dc:creator>Nick Taylor</dc:creator>
      <pubDate>Tue, 10 Mar 2026 14:28:56 +0000</pubDate>
      <link>https://dev.to/nickytonline/stop-shipping-broken-env-config-25m</link>
      <guid>https://dev.to/nickytonline/stop-shipping-broken-env-config-25m</guid>
      <description>&lt;p&gt;I first hung out with the Varlock team back in late August 2025, so this is not brand new to me. But while wiring it into an Astro project recently, I had the same reaction I always have after cleaning up env handling: I should have done this sooner.&lt;/p&gt;

&lt;p&gt;Most teams start with &lt;code&gt;.env&lt;/code&gt; files and a few runtime checks. That works for a while, until it does not. Someone forgets a key in CI. A value is malformed in production. A secret accidentally gets logged. Then you are debugging config problems instead of shipping.&lt;/p&gt;

&lt;p&gt;Varlock gives you a schema-first workflow for environment variables, with validation and safer defaults. In Astro projects, the integration is clean and built on top of the Vite plugin.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/dmno-dev" rel="noopener noreferrer"&gt;
        dmno-dev
      &lt;/a&gt; / &lt;a href="https://github.com/dmno-dev/varlock" rel="noopener noreferrer"&gt;
        varlock
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      AI-safe .env files: Schemas for agents, Secrets for humans.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;
  &lt;a href="https://varlock.dev" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fpackages%2Fvarlock-website%2Fpublic%2Fgithub-readme-banner.png" alt="Varlock banner"&gt;
  &lt;/a&gt;
&lt;/p&gt;



&lt;p&gt;
  &lt;a href="https://npmjs.com/package/varlock" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/15db39ef8b32160b176ecadd11bc3084e63cca29d4e49121b43ac55e297df709/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f7661726c6f636b2e737667" alt="npm package"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/LICENSE.md" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/eb783425d7ae4e270f00091c6889b8710c3e198a974b3ff9e7bf1f95fd6d2988/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f6c2f7661726c6f636b2e737667" alt="license"&gt;&lt;/a&gt;
  &lt;a href="https://nodejs.org/en/about/previous-releases" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/82da0e80e6d1157841b711dbba22bbfc9053c6a382e30d98ee2de107cb8042af/68747470733a2f2f696d672e736869656c64732e696f2f6e6f64652f762f7661726c6f636b2e737667" alt="node compatibility"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/dmno-dev/varlock/actions/workflows/test.yaml" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a019877e536dd84e4671aa63bc37fc66cd36e9a6a1242ff62959fe83e202285b/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f646d6e6f2d6465762f7661726c6f636b2f746573742e79616d6c3f7374796c653d666c6174266c6f676f3d676974687562266c6162656c3d4349" alt="build status"&gt;&lt;/a&gt;
  &lt;a href="https://chat.dmno.dev" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/dfb592142e7ee5a1305ba5faaae8befaffa33eb3b18ee36b86ff8b2416ddb424/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636861742d646973636f72642d3538363546323f7374796c653d666c6174266c6f676f3d646973636f7264" alt="discord chat"&gt;&lt;/a&gt;
&lt;/p&gt;



&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Varlock&lt;/h2&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;AI-safe .env files: Schemas for agents, Secrets for humans.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;🤖 AI-safe config — agents read your schema, never your secrets&lt;/li&gt;
&lt;li&gt;🔍 proactive leak scanning via &lt;code&gt;varlock scan&lt;/code&gt; + git hooks&lt;/li&gt;
&lt;li&gt;🔏 runtime protection — log redaction and leak prevention&lt;/li&gt;
&lt;li&gt;🛡️ validation, coercion, type safety w/ IntelliSense&lt;/li&gt;
&lt;li&gt;🌐 flexible multi-environment management — auto .env.* loading and explicit import&lt;/li&gt;
&lt;li&gt;🔌 &lt;a href="https://varlock.dev/plugins/overview/" rel="nofollow noopener noreferrer"&gt;plugins&lt;/a&gt; to pull data from various backends (1Password, Infisical, AWS, Azure, GCP, HCP Vault, more!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike .env.example, &lt;strong&gt;your .env.schema is a single source of truth&lt;/strong&gt;, built for collaboration, that will never be out of sync.&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; @defaultSensitive=false @defaultRequired=infer @currentEnv=$APP_ENV&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; ---&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; our environment flag, will control automatic loading of `.env.xxx` files&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; @type=enum(development, preview, production, test)&lt;/span&gt;
APP_ENV=development &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; default value, can override&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; @type=port&lt;/span&gt;
API_PORT=8080 &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; non-sensitive values can be set directly&lt;/span&gt;

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; API url including _expansion_ referencing another env var&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; @type=url&lt;/span&gt;
API_URL=http://localhost:&lt;span class="pl-smi"&gt;${API_PORT}&lt;/span&gt;

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; sensitive api key,&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/dmno-dev/varlock" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use Varlock to define your environment contract in &lt;code&gt;.env.schema&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add the Astro integration with &lt;a href="https://www.npmjs.com/package/@varlock/astro-integration" rel="noopener noreferrer"&gt;&lt;code&gt;@varlock/astro-integration&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Validate locally and in CI with &lt;code&gt;npx varlock load&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If you work with other Vite-based apps, &lt;a href="https://www.npmjs.com/package/@varlock/vite-integration" rel="noopener noreferrer"&gt;&lt;code&gt;@varlock/vite-integration&lt;/code&gt;&lt;/a&gt; is the foundation.&lt;/li&gt;
&lt;li&gt;You catch config mistakes earlier and avoid expensive deploy churn.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where &lt;code&gt;.env&lt;/code&gt; workflows usually break
&lt;/h2&gt;

&lt;p&gt;The usual pattern is familiar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep secrets in &lt;code&gt;.env.local&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Keep a partial &lt;code&gt;.env.example&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add manual checks in app startup&lt;/li&gt;
&lt;li&gt;Hope &lt;code&gt;.env.example&lt;/code&gt; stays in sync&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cracks show up quickly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Drift between docs and reality&lt;br&gt;
&lt;code&gt;.env.example&lt;/code&gt; gets stale the minute someone adds a new variable and forgets to update it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Validation is scattered&lt;br&gt;
One variable is checked in server startup, another in a route handler, another not at all.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bad feedback timing&lt;br&gt;
You often learn about missing config at runtime, not at build/startup when you can fix it fast.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Risk of secret leaks&lt;br&gt;
People print env values while debugging. It happens.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What got better right away
&lt;/h2&gt;

&lt;p&gt;Varlock centers everything around a schema so your config has a single source of truth. Instead of treating env as untyped key-value noise, you define expectations up front.&lt;/p&gt;

&lt;p&gt;In practice, that means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Required values are explicit&lt;/li&gt;
&lt;li&gt;Types and rules are explicit&lt;/li&gt;
&lt;li&gt;Validation happens before your app gets far enough to do damage&lt;/li&gt;
&lt;li&gt;Sensitive values can be redacted from logs/output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the part I like most: it moves config errors from "mystery bug in prod" to "clear error in dev/CI."&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting it up in Astro
&lt;/h2&gt;

&lt;p&gt;If you're in an Astro project, this is the fastest path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx astro add @varlock/astro-integration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That wires in the integration and updates your Astro config.&lt;/p&gt;

&lt;p&gt;Then initialize Varlock:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx varlock init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you already have existing env files, &lt;code&gt;init&lt;/code&gt; can help bootstrap your schema from what is there.&lt;/p&gt;

&lt;p&gt;If required values are missing, you get actionable errors immediately. Here is a real fail-fast example from my build logs for my personal site when a required env var was missing:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🚨 🚨 🚨 Configuration is currently invalid 🚨 🚨 🚨
Invalid items:
❓ TURSO_AUTH_TOKEN* 🔐sensitive
└ undefined
- Value is required but is currently empty
💥 Resolved config/env did not pass validation 💥
🚨 initVarlockEnv failed 🚨
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This was the biggest win for me. I hit missing env vars during setup, and Varlock made those failures obvious early instead of letting them turn into runtime bugs. It also let me delete a bunch of defensive "does this env var exist" checks in app code because the schema and validation now handle that upfront.&lt;/p&gt;

&lt;p&gt;I also opened a PR on my personal site while doing this migration. If you want a concrete implementation reference, here it is:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/nickytonline/nickyt.live/pull/22" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        chore: add environment configuration and integrate varlock
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#22&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/nickytonline" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F833231%3Fv%3D4" alt="nickytonline avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/nickytonline" rel="noopener noreferrer"&gt;nickytonline&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/nickytonline/nickyt.live/pull/22" rel="noopener noreferrer"&gt;&lt;time&gt;Aug 21, 2025&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;This pull request introduces environment variable management using the &lt;a href="https://varlock.dev" rel="nofollow noopener noreferrer"&gt;Varlock library&lt;/a&gt; and its Astro integration. It standardizes how environment variables are accessed throughout the codebase, improves type safety, and updates configuration files and dependencies accordingly.&lt;/p&gt;
&lt;p&gt;Related Live Stream:&lt;/p&gt;
&lt;a href="https://www.youtube.com/watch?v=AG9rTCZwokw" title="Levelling up Astro env with Varlock" rel="nofollow noopener noreferrer"&gt;
&lt;img width="1280" height="720" alt="image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F8f7252ad-21ae-4158-8a92-aa98ccab19f7"&gt;
&lt;/a&gt;
&lt;p&gt;&lt;strong&gt;Environment variable management and configuration:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Added &lt;code&gt;.env.schema&lt;/code&gt; file with Varlock annotations to define and document required environment variables, their types, and defaults. This enables type generation and validation for environment variables.&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;.env.development&lt;/code&gt; with a &lt;code&gt;URL&lt;/code&gt; variable referencing the &lt;code&gt;PORT&lt;/code&gt; variable for local development.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Dependency and integration updates:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Added &lt;code&gt;@varlock/astro-integration&lt;/code&gt; and &lt;code&gt;varlock&lt;/code&gt; to &lt;code&gt;package.json&lt;/code&gt; dependencies to enable Varlock environment management.&lt;/li&gt;
&lt;li&gt;Updated &lt;code&gt;astro.config.mjs&lt;/code&gt; to use the Varlock Astro integration and set the server port from the &lt;code&gt;ENV.PORT&lt;/code&gt; variable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Codebase refactoring for environment variable usage:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replaced direct usage of &lt;code&gt;import.meta.env&lt;/code&gt; with &lt;code&gt;ENV&lt;/code&gt; from &lt;code&gt;varlock/env&lt;/code&gt; in both &lt;code&gt;src/pages/feed.ts&lt;/code&gt; and &lt;code&gt;src/pages/index.astro&lt;/code&gt;, ensuring consistent and type-safe access to environment variables. [1] [2]
&lt;/li&gt;
&lt;/ul&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nickytonline/nickyt.live/pull/22" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;





&lt;p&gt;That is exactly the behavior I want. Stop immediately, show me what is wrong, and do not let a bad config sneak into a deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick note on Astro + Vite
&lt;/h2&gt;

&lt;p&gt;Astro uses Vite under the hood, and Varlock leans on that.&lt;/p&gt;

&lt;p&gt;I like this approach. &lt;a href="https://www.npmjs.com/package/@varlock/astro-integration" rel="noopener noreferrer"&gt;&lt;code&gt;@varlock/astro-integration&lt;/code&gt;&lt;/a&gt; stays thin, most of the logic lives in &lt;a href="https://www.npmjs.com/package/@varlock/vite-integration" rel="noopener noreferrer"&gt;&lt;code&gt;@varlock/vite-integration&lt;/code&gt;&lt;/a&gt;, and that usually means fewer weird bugs and less maintenance.&lt;/p&gt;

&lt;p&gt;So even if you start with Astro, you are learning a model that transfers well.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup flow I recommend
&lt;/h2&gt;

&lt;p&gt;Here is a workflow I would recommend for personal projects and teams.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define your schema first. Treat &lt;code&gt;.env.schema&lt;/code&gt; like API contract documentation for your app config.

&lt;ul&gt;
&lt;li&gt;Mark required values as required&lt;/li&gt;
&lt;li&gt;Add validation constraints where obvious&lt;/li&gt;
&lt;li&gt;Keep non-sensitive defaults where appropriate&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Keep local secrets local. Be explicit about your current setup. Right now, I still keep local secrets in a plaintext &lt;code&gt;.env&lt;/code&gt; file 🙈 (on my TODO list to move to 1Password), and in deploys those values come from Netlify environment configuration.&lt;/li&gt;
&lt;li&gt;Run validation early. Run &lt;code&gt;npx varlock load&lt;/code&gt; locally.&lt;/li&gt;
&lt;li&gt;Fail fast in CI. If config is broken, the build should fail before deploy.&lt;/li&gt;
&lt;li&gt;Use typed env access when available. Where it fits your codebase, use Varlock’s typed env access instead of reading raw &lt;code&gt;process.env&lt;/code&gt; values everywhere.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Example CI step
&lt;/h2&gt;

&lt;p&gt;If your build/check already initializes Varlock (like Astro config loading does), you do not need a separate &lt;code&gt;npx varlock load&lt;/code&gt; in that same job.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;npx varlock load&lt;/code&gt; as an explicit precheck when you want to fail fast before heavier steps, or when another job/step needs validated env without running your full app init path.&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;# Option A: rely on your normal build if it already initializes Varlock&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;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="c1"&gt;# Option B: explicit precheck before build (optional)&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 environment with Varlock&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 varlock load&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  One Astro config gotcha this fixes
&lt;/h2&gt;

&lt;p&gt;In many setups, using env values inside config files is awkward because loading order can get weird. With Varlock integrated, that experience is much cleaner because env loading and validation are already part of the flow.&lt;/p&gt;

&lt;p&gt;That can reduce the number of one-off workarounds in &lt;code&gt;astro.config.*&lt;/code&gt; and keep configuration logic more predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is not just a convenience thing
&lt;/h2&gt;

&lt;p&gt;Varlock is a productivity win, but it is also a security and reliability win.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It reduces accidental secret exposure during debugging&lt;/li&gt;
&lt;li&gt;It encourages explicit handling of sensitive values&lt;/li&gt;
&lt;li&gt;It lowers the chance of deploying with bad or partial config&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think a lot of env tooling gets treated like "nice to have." I disagree. Configuration bugs can take down production just as effectively as code bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Astro integration or Vite plugin?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;a href="https://www.npmjs.com/package/@varlock/astro-integration" rel="noopener noreferrer"&gt;&lt;code&gt;@varlock/astro-integration&lt;/code&gt;&lt;/a&gt; when you are in an Astro app and want the smoothest native setup.&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://www.npmjs.com/package/@varlock/vite-integration" rel="noopener noreferrer"&gt;&lt;code&gt;@varlock/vite-integration&lt;/code&gt;&lt;/a&gt; for non-Astro Vite projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your stack includes multiple frameworks, it is good to know they can share the same core model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;If your current env strategy is "it mostly works," Varlock is worth trying. It feels like a small upgrade until it prevents the next deployment headache.&lt;/p&gt;

&lt;p&gt;In my opinion, schema-first env management should be the default for modern JavaScript projects, especially where AI tools and automation are involved and you want strong guardrails around secrets.&lt;/p&gt;

&lt;p&gt;When would I skip this? Tiny throwaway scripts with no real deploy surface. For anything that ships, this is worth it.&lt;/p&gt;

&lt;p&gt;If you want to see the walkthrough that motivated this post, here is the video:&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/AG9rTCZwokw"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;Also give them some love over on the Syntax YouTube channel, they were on there recently too!&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/M5IkdUunf8g"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;One thing worth calling out from their broader direction is that they are working on better support for other languages, including automatic type generation from your schema for various languages. That is a big deal if your stack is not purely JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Varlock: &lt;a href="https://varlock.dev" rel="noopener noreferrer"&gt;https://varlock.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@varlock/astro-integration&lt;/code&gt;: &lt;a href="https://www.npmjs.com/package/@varlock/astro-integration" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@varlock/astro-integration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@varlock/vite-integration&lt;/code&gt;: &lt;a href="https://www.npmjs.com/package/@varlock/vite-integration" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@varlock/vite-integration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Other language support roadmap: &lt;a href="https://varlock.dev/integrations/other-languages/" rel="noopener noreferrer"&gt;https://varlock.dev/integrations/other-languages/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to stay in touch, all my socials are on &lt;a href="https://nickyt.online" rel="noopener noreferrer"&gt;nickyt.online&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Until the next one!&lt;/p&gt;

&lt;p&gt;Want to stay updated with tech tips? Check out my newsletter:&lt;br&gt;
&lt;a href="https://onetipaweek.com" rel="noopener noreferrer"&gt;OneTipAWeek.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>vite</category>
      <category>astro</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Clawspace: A Browser-Based File Explorer for OpenClaw</title>
      <dc:creator>Nick Taylor</dc:creator>
      <pubDate>Mon, 09 Mar 2026 00:09:36 +0000</pubDate>
      <link>https://dev.to/nickytonline/clawspace-a-browser-based-file-explorer-for-openclaw-2ef</link>
      <guid>https://dev.to/nickytonline/clawspace-a-browser-based-file-explorer-for-openclaw-2ef</guid>
      <description>&lt;p&gt;I've been working with &lt;a href="https://docs.openclaw.ai/" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; for a while now. If you're not familiar, it's a self-hosted personal AI assistant that answers you on the channels you already use: WhatsApp, Telegram, Discord, Slack, iMessage, and a lot more. Local, fast, and always-on. One thing that kept coming up for me personally was the need to inspect and edit workspace files without jumping into an SSH session or opening a terminal. It's my own friction point, but if you're an OpenClaw user I suspect I'm not alone.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/nickytonline/clawspace" rel="noopener noreferrer"&gt;Clawspace&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nickytonline" rel="noopener noreferrer"&gt;
        nickytonline
      &lt;/a&gt; / &lt;a href="https://github.com/nickytonline/clawspace" rel="noopener noreferrer"&gt;
        clawspace
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Clawspace is a browser-based file explorer/editor for an OpenClaw workspace.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Clawspace&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/nickytonline/clawspace/./public/assets/nano-banana-lobster.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fnickytonline%2Fclawspace%2F.%2Fpublic%2Fassets%2Fnano-banana-lobster.png" alt="Nano banana lobster at a desk"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Clawspace is a browser-based file explorer/editor for an OpenClaw workspace.&lt;/p&gt;
&lt;p&gt;It gives you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;File and directory browsing (&lt;code&gt;/workspace&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Timeline view (&lt;code&gt;/timeline&lt;/code&gt;) with folder filters and pagination&lt;/li&gt;
&lt;li&gt;Configurable default home view for &lt;code&gt;/&lt;/code&gt; (Files or Timeline)&lt;/li&gt;
&lt;li&gt;Monaco editor for text files&lt;/li&gt;
&lt;li&gt;Save/revert/copy actions&lt;/li&gt;
&lt;li&gt;Auto-format on blur (supported file types)&lt;/li&gt;
&lt;li&gt;Basic hardening for writes (path checks, blocked files, audit log)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why this exists&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;OpenClaw users often want a fast, authenticated UI to inspect and edit workspace files without opening SSH/terminal sessions.&lt;/p&gt;
&lt;p&gt;Clawspace is designed to run on your LAN, or behind a trusted auth proxy (for example Pomerium + OpenClaw trusted-proxy mode).&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Install&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/nickytonline/clawspace
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; clawspace
npm install&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick start&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run build
npm run clawspace:serve&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Default port is &lt;code&gt;6789&lt;/code&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Development&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run dev&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Configuration&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Clawspace uses the parent of the app directory as the workspace root by default
If you install it elsewhere,…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nickytonline/clawspace" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;Clawspace is a browser-based file explorer and editor for an OpenClaw workspace.&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%2Fbn0x1zxzlxqoptnzshq8.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%2Fbn0x1zxzlxqoptnzshq8.png" alt="Clawspace main page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It runs as a lightweight server, gives you a Monaco editor (the same editor that powers VS Code) for text files, and handles the basics you'd want: browsing directories, saving, reverting, deleting, and copying files, and auto-formatting on blur for supported file types. Internal OpenClaw files like &lt;code&gt;SOUL.md&lt;/code&gt; are protected and can't be deleted or modified.&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%2Fq2qf985wep2ce71oyv87.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%2Fq2qf985wep2ce71oyv87.png" alt="Editing a file in Clawspace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use SSH?
&lt;/h2&gt;

&lt;p&gt;You could. But if you're already running OpenClaw and you want to make a quick edit to a config file or peek at a log, having a UI that loads in your browser is faster than reaching for a terminal. It also fits nicely into situations where the person using the workspace isn't a developer who wants to context-switch into a shell. I find this super handy on my phone.&lt;/p&gt;

&lt;p&gt;For my setup, I run it with &lt;a href="https://usepom.link/claw-guide" rel="noopener noreferrer"&gt;Pomerium&lt;/a&gt; in front of it. Pomerium is an open core identity-aware proxy that handles the authentication layer, so Clawspace never has to think about it. I actually implemented &lt;a href="https://docs.openclaw.ai/gateway/trusted-proxy-auth" rel="noopener noreferrer"&gt;Trusted Proxy Auth mode&lt;/a&gt; in OpenClaw to make this work cleanly. (&lt;a href="https://usepom.link/claw-guide" rel="noopener noreferrer"&gt;The hardening guide&lt;/a&gt; was written before Trusted Proxy Auth mode existed, so I'm in the process of updating it.)&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/openclaw/openclaw/pull/15940" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        feat(gateway): add trusted-proxy auth mode
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#15940&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/nickytonline" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F833231%3Fv%3D4" alt="nickytonline avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/nickytonline" rel="noopener noreferrer"&gt;nickytonline&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/openclaw/openclaw/pull/15940" rel="noopener noreferrer"&gt;&lt;time&gt;Feb 14, 2026&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Summary&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;Adds a new &lt;code&gt;trusted-proxy&lt;/code&gt; auth mode that delegates authentication to a reverse proxy (Pomerium, Caddy, nginx + OAuth, etc.). This allows Clawdbot to run behind identity-aware proxies or reverse proxies without requiring token auth in WebSocket payloads.&lt;/p&gt;
&lt;p&gt;Closes #1560
Relates to #1710&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Changes&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Types &amp;amp; Schema&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;trusted-proxy&lt;/code&gt; to &lt;code&gt;GatewayAuthMode&lt;/code&gt; union&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;GatewayTrustedProxyConfig&lt;/code&gt; type with &lt;code&gt;userHeader&lt;/code&gt;, &lt;code&gt;requiredHeaders&lt;/code&gt;, &lt;code&gt;allowUsers&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Update zod schema with validation&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Auth Logic&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;authorizeTrustedProxy()&lt;/code&gt; helper function&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;authorizeGatewayConnect()&lt;/code&gt; to handle trusted-proxy mode&lt;/li&gt;
&lt;li&gt;Validate proxy source IP against &lt;code&gt;gateway.trustedProxies&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Support required headers and user allowlist&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Runtime Guards&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Allow non-loopback bind with trusted-proxy mode&lt;/li&gt;
&lt;li&gt;Reject trusted-proxy + loopback combination&lt;/li&gt;
&lt;li&gt;Require &lt;code&gt;trustedProxies&lt;/code&gt; to be configured&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Security Audit&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Add critical finding when trusted-proxy auth is enabled&lt;/li&gt;
&lt;li&gt;Flag missing &lt;code&gt;trustedProxies&lt;/code&gt; or &lt;code&gt;userHeader&lt;/code&gt; configuration&lt;/li&gt;
&lt;li&gt;Warn when &lt;code&gt;allowUsers&lt;/code&gt; is empty&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Tests&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;10 new auth tests covering all trusted-proxy scenarios&lt;/li&gt;
&lt;li&gt;4 new security audit tests&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Documentation&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;New doc page: &lt;code&gt;/gateway/trusted-proxy-auth&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Examples for Pomerium, Caddy, nginx, Traefik&lt;/li&gt;
&lt;li&gt;Security checklist and troubleshooting guide&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Example Config&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-js js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-c1"&gt;gateway&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-c1"&gt;bind&lt;/span&gt;: &lt;span class="pl-s"&gt;"lan"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-c1"&gt;trustedProxies&lt;/span&gt;: &lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-s"&gt;"10.0.0.1"&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-c1"&gt;auth&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
      &lt;span class="pl-c1"&gt;mode&lt;/span&gt;: &lt;span class="pl-s"&gt;"trusted-proxy"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;trustedProxy&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
        &lt;span class="pl-c1"&gt;userHeader&lt;/span&gt;: &lt;span class="pl-s"&gt;"x-pomerium-email"&lt;/span&gt;
      &lt;span class="pl-kos"&gt;}&lt;/span&gt;
    &lt;span class="pl-kos"&gt;}&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Screenshots and Recordings&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;CLI in Action&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/user-attachments/assets/e500cef8-988c-459e-8e9e-16af8a33dc9e" rel="noopener noreferrer"&gt;https://github.com/user-attachments/assets/e500cef8-988c-459e-8e9e-16af8a33dc9e&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Overview page when trusted proxy mode is enabled&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Removed the Gateway Token field entirely when trusted proxy mode is active&lt;/li&gt;
&lt;li&gt;Updated the helper text next to the Connect/Refresh buttons - when in trusted proxy mode it now shows "Authenticated via trusted
proxy."&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/user-attachments/assets/537794b7-10d2-4060-aa0a-c5e45c6523a6"&gt;&lt;img width="1220" height="928" alt="CleanShot 2026-02-14 at 11 31 22@2x" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F537794b7-10d2-4060-aa0a-c5e45c6523a6"&gt;&lt;/a&gt;&lt;/p&gt;                                              
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Security Considerations&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;Per maintainer guidance, this is an explicit opt-in feature with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Strict trust boundary (only accepts headers from configured &lt;code&gt;trustedProxies&lt;/code&gt; IPs)&lt;/li&gt;
&lt;li&gt;No silent fallback (rejects if proxy headers missing)&lt;/li&gt;
&lt;li&gt;Audit warnings to ensure users understand the security implications&lt;/li&gt;
&lt;li&gt;Clear documentation about when to use and when NOT to use&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Testing&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell js-code-highlight"&gt;
&lt;pre&gt;npm &lt;span class="pl-c1"&gt;test&lt;/span&gt; -- src/gateway/auth.test.ts
npm &lt;span class="pl-c1"&gt;test&lt;/span&gt; -- src/security/audit.test.ts&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;All 20 auth tests and 40 security audit tests pass.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Greptile Overview&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Greptile Summary&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;Adds &lt;code&gt;trusted-proxy&lt;/code&gt; authentication mode that delegates authentication to reverse proxies (Pomerium, Caddy, nginx + OAuth). The implementation correctly handles the security boundaries with IP-based trust validation, required header checks, and user allowlists. All auth flows validate that requests originate from configured &lt;code&gt;trustedProxies&lt;/code&gt; before trusting proxy headers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key changes:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Added &lt;code&gt;trusted-proxy&lt;/code&gt; to &lt;code&gt;GatewayAuthMode&lt;/code&gt; union with comprehensive type definitions and zod validation&lt;/li&gt;
&lt;li&gt;Implemented &lt;code&gt;authorizeTrustedProxy()&lt;/code&gt; helper with multi-layered validation (source IP, required headers, user allowlist)&lt;/li&gt;
&lt;li&gt;Added runtime guards preventing dangerous configurations (rejects loopback binding, requires &lt;code&gt;trustedProxies&lt;/code&gt; config)&lt;/li&gt;
&lt;li&gt;Implemented CIDR notation support in &lt;code&gt;isTrustedProxyAddress()&lt;/code&gt; for flexible subnet matching&lt;/li&gt;
&lt;li&gt;Added comprehensive security audit checks (critical severity for trusted-proxy mode with detailed remediation)&lt;/li&gt;
&lt;li&gt;Updated UI to hide token/password fields when trusted-proxy mode is active&lt;/li&gt;
&lt;li&gt;Extensive test coverage (10 auth tests, 4 audit tests, net.ts CIDR tests, runtime config tests)&lt;/li&gt;
&lt;li&gt;Well-documented with examples for Pomerium, Caddy, nginx, and Traefik&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Security posture:&lt;/strong&gt;
The implementation follows defense-in-depth principles with strict validation at multiple layers. The trusted-proxy auth bypasses rate limiting (returns early at src/gateway/auth.ts:314-332), which is appropriate since the proxy handles auth. All validation happens before processing requests, and failure modes are explicit with clear error reasons.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Confidence Score: 5/5&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;This PR is safe to merge with minimal risk.&lt;/li&gt;
&lt;li&gt;The implementation demonstrates excellent security engineering with defense-in-depth validation, comprehensive test coverage (20+ tests across auth, net, audit, and runtime config), proper error handling, and clear documentation. The trusted-proxy auth logic correctly validates source IPs before trusting headers, preventing header injection attacks. Runtime guards prevent dangerous misconfigurations. The CIDR implementation has edge case validation. All changes follow the repository's coding standards and include appropriate security audit warnings.&lt;/li&gt;
&lt;li&gt;No files require special attention. The implementation is production-ready.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Last reviewed commit: a1e1c19&lt;/p&gt;



    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/openclaw/openclaw/pull/15940" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;




&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;The only real requirement is that Clawspace has access to the root of your OpenClaw workspace. How you get there is up to you: npm scripts or the Docker image both work fine.&lt;/p&gt;

&lt;p&gt;Via npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/nickytonline/clawspace
&lt;span class="nb"&gt;cd &lt;/span&gt;clawspace
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run build
npm run clawspace:serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Default port is &lt;code&gt;6789&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Or via Docker, mounting your workspace volume:&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;clawspace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/nickytonline/clawspace:latest&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CLAWSPACE_ROOT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/claw/workspace&lt;/span&gt;
    &lt;span class="na"&gt;CLAWSPACE_IGNORE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.pnpm,dist,logs"&lt;/span&gt;
    &lt;span class="na"&gt;SHOW_INTERNAL_CLAW_FILES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./openclaw-data/workspace:/claw/workspace&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6789:6789"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I currently run Clawspace inside my workspace rather than as a separate container, mostly because it lets me iterate on it in real time while pairing with OpenClaw. Since it's built with Astro, running &lt;code&gt;npm run dev&lt;/code&gt; gives you instant updates via Vite, so I can make changes and see them immediately without an editor, just me and OpenClaw going back and forth. For most people though, the container approach is probably cleaner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;By default, Clawspace uses the parent of the app directory as the workspace root. You can override that with an environment variable.&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;# .env (see .env.example)&lt;/span&gt;
&lt;span class="nv"&gt;CLAWSPACE_ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/absolute/path/to/workspace
&lt;span class="nv"&gt;CLAWSPACE_IGNORE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;".pnpm,dist,logs"&lt;/span&gt;
&lt;span class="nv"&gt;SHOW_INTERNAL_CLAW_FILES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CLAWSPACE_IGNORE&lt;/code&gt; variable takes comma-separated patterns, and those get merged with hardcoded defaults (&lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;, etc.), your &lt;code&gt;.gitignore&lt;/code&gt;, and a &lt;code&gt;.clawspace-ignore&lt;/code&gt; file if you have one at the workspace root.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SHOW_INTERNAL_CLAW_FILES&lt;/code&gt; controls whether things like &lt;code&gt;SOUL.md&lt;/code&gt;, &lt;code&gt;MEMORY.md&lt;/code&gt;, and &lt;code&gt;.env&lt;/code&gt; show up in the file browser. Default is &lt;code&gt;false&lt;/code&gt;, which is what you want most of the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;p&gt;Clawspace assumes network-level auth is handled externally. It's not trying to be a multi-user app with roles and admin checks. File writes are restricted to the workspace root, internal and sensitive files are blocked, and all writes get audited to &lt;code&gt;/claw/workspace/logs/clawspace-edit-audit.log&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you're exposing it beyond your LAN, put it behind a proxy you trust. I expose mine to the internet using Trusted Proxy Auth mode, with &lt;a href="https://usepom.link/claw-guide" rel="noopener noreferrer"&gt;Pomerium&lt;/a&gt; as the identity-aware proxy in front of it, so authentication is handled before a request ever reaches Clawspace.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's meant to be tweaked
&lt;/h2&gt;

&lt;p&gt;Clawspace is intentionally hackable. The README says it plainly: clone it, edit the UI and guardrails, make it yours. It's a starting point for the kind of workspace tooling that fits how you work, not a finished product trying to cover every case.&lt;/p&gt;

&lt;p&gt;Fun fact: the look and feel is based on &lt;a href="https://nickyt.co" rel="noopener noreferrer"&gt;nickyt.co&lt;/a&gt;, my personal site. I paired with OpenClaw to build it, which felt like a nice proof of the thing I was building the tool for in the first place.&lt;/p&gt;

&lt;p&gt;If you give it a try or have ideas for it, I'd love to hear what you think.&lt;/p&gt;

&lt;p&gt;If you want to stay in touch, all my socials are on &lt;a href="https://nickyt.online" rel="noopener noreferrer"&gt;nickyt.online&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Until the next one!&lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>aiagents</category>
      <category>selfhosted</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Let Dependabot Merge Its Own PRs</title>
      <dc:creator>Nick Taylor</dc:creator>
      <pubDate>Sun, 08 Mar 2026 18:16:29 +0000</pubDate>
      <link>https://dev.to/nickytonline/let-dependabot-merge-its-own-prs-27pc</link>
      <guid>https://dev.to/nickytonline/let-dependabot-merge-its-own-prs-27pc</guid>
      <description>&lt;p&gt;Dependabot opens PRs automatically. That part most people have set up. But then those PRs just sit there until you get around to reviewing and merging them. I had 6 open across one of my repos recently. None of them were risky. I just didn't feel like giving a review and approving, then merging.&lt;/p&gt;

&lt;p&gt;If your CI passes and the update is a patch or minor version bump, there's not much to review. You're going to merge it. So why not let it happen automatically?&lt;/p&gt;

&lt;p&gt;I've added this to two repos now and it's one of those small things that quietly removes friction from your day.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, enable auto-merge on your repo
&lt;/h2&gt;

&lt;p&gt;Before the workflow can do anything, you need to allow auto-merge in your repository settings. Go to e.g. &lt;a href="https://github.com/yourorg-username/your-repo/settings/actions" rel="noopener noreferrer"&gt;https://github.com/yourorg-username/your-repo/settings/actions&lt;/a&gt; and scroll down to the Pull Requests section, and check Allow auto-merge.&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%2Fojecpo5catdgb6r48qdf.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%2Fojecpo5catdgb6r48qdf.png" alt="allow auto-merge in your repository settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This isn't Dependabot-specific, but it is required for this to work. Without it, the &lt;code&gt;gh pr merge --auto&lt;/code&gt; command in the workflow will fail. In fact this is what I do to automate using dev.to as a headless CMS for my blog!&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nickytonline/automate-and-merge-pull-requests-using-github-actions-and-the-github-cli-4lo6" class="crayons-story__hidden-navigation-link"&gt;Automate and Auto-Merge Pull Requests using GitHub Actions and the GitHub CLI&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nickytonline" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F9597%2F8f1ed696-3e87-4bd4-809a-0ee9bb905c3c.jpg" alt="nickytonline profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nickytonline" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Nick Taylor
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Nick Taylor
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-1245107" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nickytonline" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F9597%2F8f1ed696-3e87-4bd4-809a-0ee9bb905c3c.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Nick Taylor&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nickytonline/automate-and-merge-pull-requests-using-github-actions-and-the-github-cli-4lo6" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 6 '22&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nickytonline/automate-and-merge-pull-requests-using-github-actions-and-the-github-cli-4lo6" id="article-link-1245107"&gt;
          Automate and Auto-Merge Pull Requests using GitHub Actions and the GitHub CLI
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/github"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;github&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/githubcli"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;githubcli&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/githubactions"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;githubactions&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/git"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;git&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nickytonline/automate-and-merge-pull-requests-using-github-actions-and-the-github-cli-4lo6" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;73&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nickytonline/automate-and-merge-pull-requests-using-github-actions-and-the-github-cli-4lo6#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  The workflow
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;.github/workflows/auto-merge-dependabot.yml&lt;/code&gt; in your repo:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Auto-merge Dependabot PRs&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pull_request&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;auto-merge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.actor == 'dependabot[bot]'&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&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;Approve PR&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;gh pr review --approve "$PR_URL"&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;PR_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.pull_request.html_url }}&lt;/span&gt;
          &lt;span class="na"&gt;GH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&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;Enable auto-merge&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;gh pr merge --auto --squash "$PR_URL"&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;PR_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.pull_request.html_url }}&lt;/span&gt;
          &lt;span class="na"&gt;GH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;if: github.actor == 'dependabot[bot]'&lt;/code&gt; condition makes sure this only runs on Dependabot PRs, not every PR that comes in.&lt;/p&gt;

&lt;p&gt;The two steps do exactly what they say: approve the PR, then enable auto-merge with squash. GitHub handles the actual merge once all your required checks pass.&lt;/p&gt;

&lt;p&gt;Here's an example of it not auto-merging after auto-approval because checks failed.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/nickytonline/nickytdotco/pull/809" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        chore(deps-dev): bump eslint from 9.39.2 to 10.0.3
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#809&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/apps/dependabot" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fin%2F29110%3Fv%3D4" alt="dependabot[bot] avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/apps/dependabot" rel="noopener noreferrer"&gt;dependabot[bot]&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/nickytonline/nickytdotco/pull/809" rel="noopener noreferrer"&gt;&lt;time&gt;Mar 08, 2026&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;Bumps &lt;a href="https://github.com/eslint/eslint" rel="noopener noreferrer"&gt;eslint&lt;/a&gt; from 9.39.2 to 10.0.3.&lt;/p&gt;

Release notes
&lt;p&gt;&lt;em&gt;Sourced from &lt;a href="https://github.com/eslint/eslint/releases" rel="noopener noreferrer"&gt;eslint's releases&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;v10.0.3&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Bug Fixes&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/e511b58d5ecd63a232b87743614867f4eaadbba4" rel="noopener noreferrer"&gt;&lt;code&gt;e511b58&lt;/code&gt;&lt;/a&gt; fix: update eslint (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20595" rel="noopener noreferrer"&gt;#20595&lt;/a&gt;) (renovate[bot])&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/f4c9cf9b8dc5642de555a09295933464080d722a" rel="noopener noreferrer"&gt;&lt;code&gt;f4c9cf9&lt;/code&gt;&lt;/a&gt; fix: include variable name in &lt;code&gt;no-useless-assignment&lt;/code&gt; message (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20581" rel="noopener noreferrer"&gt;#20581&lt;/a&gt;) (sethamus)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/ee9ff31cee13712d2be2a6b5c0a4a54449fe9fe1" rel="noopener noreferrer"&gt;&lt;code&gt;ee9ff31&lt;/code&gt;&lt;/a&gt; fix: update dependency minimatch to ^10.2.4 (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20562" rel="noopener noreferrer"&gt;#20562&lt;/a&gt;) (Milos Djermanovic)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Documentation&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/9fc31b03ef05abfc4f0f449b22947029d51a72f6" rel="noopener noreferrer"&gt;&lt;code&gt;9fc31b0&lt;/code&gt;&lt;/a&gt; docs: Update README (GitHub Actions Bot)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/4efaa367c62d5a45dd21e246e4a506e11dd51758" rel="noopener noreferrer"&gt;&lt;code&gt;4efaa36&lt;/code&gt;&lt;/a&gt; docs: add info box for &lt;code&gt;eslint-plugin-eslint-comments&lt;/code&gt; (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20570" rel="noopener noreferrer"&gt;#20570&lt;/a&gt;) (DesselBane)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/23b2759dd5cd70976ab2e8f4a1cf86ffe4b9f65d" rel="noopener noreferrer"&gt;&lt;code&gt;23b2759&lt;/code&gt;&lt;/a&gt; docs: add v10 migration guide link to Use docs index (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20577" rel="noopener noreferrer"&gt;#20577&lt;/a&gt;) (Pixel998)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/80259a9b0d9e29596a5ef0e1e5269031636cacdb" rel="noopener noreferrer"&gt;&lt;code&gt;80259a9&lt;/code&gt;&lt;/a&gt; docs: Remove deprecated eslintrc documentation files (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20472" rel="noopener noreferrer"&gt;#20472&lt;/a&gt;) (Copilot)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/9b9b4baf7f0515d28290464ea754d7e7dc350395" rel="noopener noreferrer"&gt;&lt;code&gt;9b9b4ba&lt;/code&gt;&lt;/a&gt; docs: fix typo in no-await-in-loop documentation (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20575" rel="noopener noreferrer"&gt;#20575&lt;/a&gt;) (Pixel998)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/e7d72a77e5e1277690a505160137aebd5985909a" rel="noopener noreferrer"&gt;&lt;code&gt;e7d72a7&lt;/code&gt;&lt;/a&gt; docs: document TypeScript 5.3 minimum supported version (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20547" rel="noopener noreferrer"&gt;#20547&lt;/a&gt;) (sethamus)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Chores&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/ef8fb924bfabc2e239b46b2d7b3c37319b03084e" rel="noopener noreferrer"&gt;&lt;code&gt;ef8fb92&lt;/code&gt;&lt;/a&gt; chore: package.json update for eslint-config-eslint release (Jenkins)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/e8f21040f675753e92df8e04f2dbd03addb92985" rel="noopener noreferrer"&gt;&lt;code&gt;e8f2104&lt;/code&gt;&lt;/a&gt; chore: updates for v9.39.4 release (Jenkins)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/5cd1604cea5734bc235155a1a1add9f08ae83370" rel="noopener noreferrer"&gt;&lt;code&gt;5cd1604&lt;/code&gt;&lt;/a&gt; refactor: simplify isCombiningCharacter helper (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20524" rel="noopener noreferrer"&gt;#20524&lt;/a&gt;) (Huáng Jùnliàng)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/70ff1d07a8e7eba9e70b67ea55fcf2e47cdc9b2d" rel="noopener noreferrer"&gt;&lt;code&gt;70ff1d0&lt;/code&gt;&lt;/a&gt; chore: eslint-config-eslint require Node &lt;code&gt;^20.19.0 || ^22.13.0 || &amp;gt;=24&lt;/code&gt; (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20586" rel="noopener noreferrer"&gt;#20586&lt;/a&gt;) (Milos Djermanovic)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/e32df71a569d5f4aca13079dedd4ae76ea05168a" rel="noopener noreferrer"&gt;&lt;code&gt;e32df71&lt;/code&gt;&lt;/a&gt; chore: update eslint-plugin-eslint-comments, remove legacy-peer-deps (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20576" rel="noopener noreferrer"&gt;#20576&lt;/a&gt;) (Milos Djermanovic)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/53ca6eeed87262ebddd20636107f486badabcc1f" rel="noopener noreferrer"&gt;&lt;code&gt;53ca6ee&lt;/code&gt;&lt;/a&gt; chore: disable &lt;code&gt;eslint-comments/no-unused-disable&lt;/code&gt; rule (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20578" rel="noopener noreferrer"&gt;#20578&lt;/a&gt;) (Milos Djermanovic)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/e1218957452e223af27ace1f9d031ab421aec08f" rel="noopener noreferrer"&gt;&lt;code&gt;e121895&lt;/code&gt;&lt;/a&gt; ci: pin Node.js 25.6.1 (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20559" rel="noopener noreferrer"&gt;#20559&lt;/a&gt;) (Milos Djermanovic)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/efc5aef2f9a05f01d5cad53dcb91e7f2c575e295" rel="noopener noreferrer"&gt;&lt;code&gt;efc5aef&lt;/code&gt;&lt;/a&gt; chore: update &lt;code&gt;tsconfig.json&lt;/code&gt; in &lt;code&gt;eslint-config-eslint&lt;/code&gt; (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20551" rel="noopener noreferrer"&gt;#20551&lt;/a&gt;) (Francesco Trotta)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;v10.0.2&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Bug Fixes&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/2b723616a4daeacd4605f11b4d087d4a7cae5c74" rel="noopener noreferrer"&gt;&lt;code&gt;2b72361&lt;/code&gt;&lt;/a&gt; fix: update &lt;code&gt;ajv&lt;/code&gt; to &lt;code&gt;6.14.0&lt;/code&gt; to address security vulnerabilities (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20537" rel="noopener noreferrer"&gt;#20537&lt;/a&gt;) (루밀LuMir)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Documentation&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/13eeedbbd16218b0da1425b78cb284937fd964ca" rel="noopener noreferrer"&gt;&lt;code&gt;13eeedb&lt;/code&gt;&lt;/a&gt; docs: link rule type explanation to CLI option --fix-type (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20548" rel="noopener noreferrer"&gt;#20548&lt;/a&gt;) (Mike McCready)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/98cbf6ba53a1fb2028d25078c7049a538d0e392c" rel="noopener noreferrer"&gt;&lt;code&gt;98cbf6b&lt;/code&gt;&lt;/a&gt; docs: update migration guide per Program range change (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20534" rel="noopener noreferrer"&gt;#20534&lt;/a&gt;) (Huáng Jùnliàng)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/61a24054411fa56ce74bef554846caa9d8cb01f5" rel="noopener noreferrer"&gt;&lt;code&gt;61a2405&lt;/code&gt;&lt;/a&gt; docs: add missing semicolon in vars-on-top rule example (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20533" rel="noopener noreferrer"&gt;#20533&lt;/a&gt;) (Abilash)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Chores&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/951223b29669885643f7854d7c824288ba962d7e" rel="noopener noreferrer"&gt;&lt;code&gt;951223b&lt;/code&gt;&lt;/a&gt; chore: update dependency &lt;code&gt;@​eslint/eslintrc&lt;/code&gt; to ^3.3.4 (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20553" rel="noopener noreferrer"&gt;#20553&lt;/a&gt;) (renovate[bot])&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/6aa1afe6694f3fd7f82116109a5ef2ad18ece074" rel="noopener noreferrer"&gt;&lt;code&gt;6aa1afe&lt;/code&gt;&lt;/a&gt; chore: update dependency eslint-plugin-jsdoc to ^62.7.0 (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20536" rel="noopener noreferrer"&gt;#20536&lt;/a&gt;) (Milos Djermanovic)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;v10.0.1&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Bug Fixes&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/c87d5bded54c5cf491eb04c24c9d09bbbd42c23e" rel="noopener noreferrer"&gt;&lt;code&gt;c87d5bd&lt;/code&gt;&lt;/a&gt; fix: update eslint (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20531" rel="noopener noreferrer"&gt;#20531&lt;/a&gt;) (renovate[bot])&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/d84100115c14691691058f00779c94e74fca946a" rel="noopener noreferrer"&gt;&lt;code&gt;d841001&lt;/code&gt;&lt;/a&gt; fix: update &lt;code&gt;minimatch&lt;/code&gt; to &lt;code&gt;10.2.1&lt;/code&gt; to address security vulnerabilities (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20519" rel="noopener noreferrer"&gt;#20519&lt;/a&gt;) (루밀LuMir)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/04c21475b3004904948f02049f2888b401d82c78" rel="noopener noreferrer"&gt;&lt;code&gt;04c2147&lt;/code&gt;&lt;/a&gt; fix: update error message for unused suppressions (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20496" rel="noopener noreferrer"&gt;#20496&lt;/a&gt;) (fnx)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/38b089c1726feac0e31a31d47941bd99e29ce003" rel="noopener noreferrer"&gt;&lt;code&gt;38b089c&lt;/code&gt;&lt;/a&gt; fix: update dependency &lt;code&gt;@​eslint/config-array&lt;/code&gt; to ^0.23.1 (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20484" rel="noopener noreferrer"&gt;#20484&lt;/a&gt;) (renovate[bot])&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Documentation&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/5b3dbce50a1404a9f118afe810cefeee79388a2a" rel="noopener noreferrer"&gt;&lt;code&gt;5b3dbce&lt;/code&gt;&lt;/a&gt; docs: add AI acknowledgement section to templates (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20431" rel="noopener noreferrer"&gt;#20431&lt;/a&gt;) (루밀LuMir)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/6f23076037d5879f20fb3be2ef094293b1e8d38c" rel="noopener noreferrer"&gt;&lt;code&gt;6f23076&lt;/code&gt;&lt;/a&gt; docs: toggle nav in no-JS mode (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20476" rel="noopener noreferrer"&gt;#20476&lt;/a&gt;) (Tanuj Kanti)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/b69cfb32a16c5d5e9986390d484fae1d21e406f9" rel="noopener noreferrer"&gt;&lt;code&gt;b69cfb3&lt;/code&gt;&lt;/a&gt; docs: Update README (GitHub Actions Bot)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Chores&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;

&lt;/blockquote&gt;
&lt;p&gt;... (truncated)&lt;/p&gt;


Commits
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/bfce7eaa0ec5d6591fd247b7ff57b51e45fb88a1" rel="noopener noreferrer"&gt;&lt;code&gt;bfce7ea&lt;/code&gt;&lt;/a&gt; 10.0.3&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/d44ced84bb00b1df3c616255f28d036089703ed8" rel="noopener noreferrer"&gt;&lt;code&gt;d44ced8&lt;/code&gt;&lt;/a&gt; Build: changelog update for 10.0.3&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/e511b58d5ecd63a232b87743614867f4eaadbba4" rel="noopener noreferrer"&gt;&lt;code&gt;e511b58&lt;/code&gt;&lt;/a&gt; fix: update eslint (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20595" rel="noopener noreferrer"&gt;#20595&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/ef8fb924bfabc2e239b46b2d7b3c37319b03084e" rel="noopener noreferrer"&gt;&lt;code&gt;ef8fb92&lt;/code&gt;&lt;/a&gt; chore: package.json update for eslint-config-eslint release&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/e8f21040f675753e92df8e04f2dbd03addb92985" rel="noopener noreferrer"&gt;&lt;code&gt;e8f2104&lt;/code&gt;&lt;/a&gt; chore: updates for v9.39.4 release&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/5cd1604cea5734bc235155a1a1add9f08ae83370" rel="noopener noreferrer"&gt;&lt;code&gt;5cd1604&lt;/code&gt;&lt;/a&gt; refactor: simplify isCombiningCharacter helper (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20524" rel="noopener noreferrer"&gt;#20524&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/9fc31b03ef05abfc4f0f449b22947029d51a72f6" rel="noopener noreferrer"&gt;&lt;code&gt;9fc31b0&lt;/code&gt;&lt;/a&gt; docs: Update README&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/70ff1d07a8e7eba9e70b67ea55fcf2e47cdc9b2d" rel="noopener noreferrer"&gt;&lt;code&gt;70ff1d0&lt;/code&gt;&lt;/a&gt; chore: eslint-config-eslint require Node &lt;code&gt;^20.19.0 || ^22.13.0 || &amp;gt;=24&lt;/code&gt; (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20586" rel="noopener noreferrer"&gt;#20586&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/f4c9cf9b8dc5642de555a09295933464080d722a" rel="noopener noreferrer"&gt;&lt;code&gt;f4c9cf9&lt;/code&gt;&lt;/a&gt; fix: include variable name in &lt;code&gt;no-useless-assignment&lt;/code&gt; message (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20581" rel="noopener noreferrer"&gt;#20581&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/eslint/eslint/commit/4efaa367c62d5a45dd21e246e4a506e11dd51758" rel="noopener noreferrer"&gt;&lt;code&gt;4efaa36&lt;/code&gt;&lt;/a&gt; docs: add info box for &lt;code&gt;eslint-plugin-eslint-comments&lt;/code&gt; (&lt;a href="https://redirect.github.com/eslint/eslint/issues/20570" rel="noopener noreferrer"&gt;#20570&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Additional commits viewable in &lt;a href="https://github.com/eslint/eslint/compare/v9.39.2...v10.0.3" rel="noopener noreferrer"&gt;compare view&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;br&gt;
&lt;p&gt;&lt;a href="https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/24bad0da35e2a1197813fbacc1c8aa3bfebaf937450da2aa9f81d830641594a7/68747470733a2f2f646570656e6461626f742d6261646765732e6769746875626170702e636f6d2f6261646765732f636f6d7061746962696c6974795f73636f72653f646570656e64656e63792d6e616d653d65736c696e74267061636b6167652d6d616e616765723d6e706d5f616e645f7961726e2670726576696f75732d76657273696f6e3d392e33392e32266e65772d76657273696f6e3d31302e302e33" alt="Dependabot compatibility score"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting &lt;code&gt;@dependabot rebase&lt;/code&gt;.&lt;/p&gt;


Dependabot commands and options
&lt;br&gt;
&lt;p&gt;You can trigger Dependabot actions by commenting on this PR:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@dependabot rebase&lt;/code&gt; will rebase this PR&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@dependabot recreate&lt;/code&gt; will recreate this PR, overwriting any edits that have been made to it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@dependabot show &amp;lt;dependency name&amp;gt; ignore conditions&lt;/code&gt; will show all of the ignore conditions of the specified dependency&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@dependabot ignore this major version&lt;/code&gt; will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@dependabot ignore this minor version&lt;/code&gt; will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@dependabot ignore this dependency&lt;/code&gt; will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)&lt;/li&gt;
&lt;/ul&gt;


    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nickytonline/nickytdotco/pull/809" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;





&lt;p&gt;Note: &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; is automatically available in every GitHub Actions workflow, no setup needed on your end.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;p&gt;Once it's set up and a Dependabot PR comes in, you'll see the github-actions bot approve the PR and enable auto-merge. The PR then waits for your required checks to complete and merges itself when everything is green.&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%2Faiv3ozw0xf2andqrxvrz.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%2Faiv3ozw0xf2andqrxvrz.png" alt="github-actions bot approving a PR"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on safety
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This setup is only as safe as your CI.&lt;/strong&gt; If you don't have required checks configured, the PR can auto-merge the moment the workflow approves it. At a minimum you want a build check required, tests if you have them. Branch protection rules still apply. If a required check fails, the PR won't merge. The workflow isn't bypassing anything, it's just handling the approval and queuing up the merge for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Being more selective
&lt;/h2&gt;

&lt;p&gt;This workflow approves and enables auto-merge on every Dependabot PR regardless of whether it's a patch, minor, or major update. If you want to be more selective, you can use the &lt;a href="https://github.com/dependabot/fetch-metadata" rel="noopener noreferrer"&gt;dependabot/fetch-metadata action&lt;/a&gt; to check the update type and only proceed for patch and minor updates. The &lt;a href="https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions" rel="noopener noreferrer"&gt;GitHub docs on automating Dependabot&lt;/a&gt; cover that in more detail.&lt;/p&gt;

&lt;p&gt;If you want to see a PR that went through this whole flow check out the PR below.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/nickytonline/nickytdotco/pull/790" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        chore(deps): bump rollup from 4.54.0 to 4.59.0
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#790&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/apps/dependabot" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fin%2F29110%3Fv%3D4" alt="dependabot[bot] avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/apps/dependabot" rel="noopener noreferrer"&gt;dependabot[bot]&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/nickytonline/nickytdotco/pull/790" rel="noopener noreferrer"&gt;&lt;time&gt;Feb 28, 2026&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;Bumps &lt;a href="https://github.com/rollup/rollup" rel="noopener noreferrer"&gt;rollup&lt;/a&gt; from 4.54.0 to 4.59.0.&lt;/p&gt;

Release notes
&lt;p&gt;&lt;em&gt;Sourced from &lt;a href="https://github.com/rollup/rollup/releases" rel="noopener noreferrer"&gt;rollup's releases&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;v4.59.0&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;4.59.0&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;2026-02-22&lt;/em&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Features&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Throw when the generated bundle contains paths that would leave the output directory (&lt;a href="https://redirect.github.com/rollup/rollup/issues/6276" rel="noopener noreferrer"&gt;#6276&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Pull Requests&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6275" rel="noopener noreferrer"&gt;#6275&lt;/a&gt;: Validate bundle stays within output dir (&lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;v4.58.0&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;4.58.0&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;2026-02-20&lt;/em&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Features&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Also support &lt;code&gt;__NO_SIDE_EFFECTS__&lt;/code&gt; annotation before variable declarations declaring function expressions (&lt;a href="https://redirect.github.com/rollup/rollup/issues/6272" rel="noopener noreferrer"&gt;#6272&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Pull Requests&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6256" rel="noopener noreferrer"&gt;#6256&lt;/a&gt;: docs: document PreRenderedChunk properties including isDynamicEntry and isImplicitEntry (&lt;a href="https://github.com/njg7194" rel="noopener noreferrer"&gt;&lt;code&gt;@​njg7194&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6259" rel="noopener noreferrer"&gt;#6259&lt;/a&gt;: docs: Correct typo and improve sentence structure in docs for &lt;code&gt;output.experimentalMinChunkSize&lt;/code&gt; (&lt;a href="https://github.com/millerick" rel="noopener noreferrer"&gt;&lt;code&gt;@​millerick&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6260" rel="noopener noreferrer"&gt;#6260&lt;/a&gt;: fix(deps): update rust crate swc_compiler_base to v47 (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot], &lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6261" rel="noopener noreferrer"&gt;#6261&lt;/a&gt;: fix(deps): lock file maintenance minor/patch updates (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot], &lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6262" rel="noopener noreferrer"&gt;#6262&lt;/a&gt;: Avoid unnecessary cloning of the code string (&lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6263" rel="noopener noreferrer"&gt;#6263&lt;/a&gt;: fix(deps): update minor/patch updates (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot], &lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6265" rel="noopener noreferrer"&gt;#6265&lt;/a&gt;: chore(deps): lock file maintenance (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot])&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6267" rel="noopener noreferrer"&gt;#6267&lt;/a&gt;: fix(deps): update minor/patch updates (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot])&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6268" rel="noopener noreferrer"&gt;#6268&lt;/a&gt;: chore(deps): update dependency eslint-plugin-unicorn to v63 (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot], &lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6269" rel="noopener noreferrer"&gt;#6269&lt;/a&gt;: chore(deps): update dependency lru-cache to v11 (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot])&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6270" rel="noopener noreferrer"&gt;#6270&lt;/a&gt;: chore(deps): lock file maintenance (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot])&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6272" rel="noopener noreferrer"&gt;#6272&lt;/a&gt;: forward NO_SIDE_EFFECTS annotations to function expressions in variable declarations (&lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;v4.57.1&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;4.57.1&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;2026-01-30&lt;/em&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Bug Fixes&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Fix heap corruption issue in Windows (&lt;a href="https://redirect.github.com/rollup/rollup/issues/6251" rel="noopener noreferrer"&gt;#6251&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Ensure exports of a dynamic import are fully included when called from a try...catch (&lt;a href="https://redirect.github.com/rollup/rollup/issues/6254" rel="noopener noreferrer"&gt;#6254&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Pull Requests&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6251" rel="noopener noreferrer"&gt;#6251&lt;/a&gt;: fix: Isolate and cache &lt;code&gt;process.report.getReport()&lt;/code&gt; calls in a child process for robust environment detection (&lt;a href="https://github.com/alan-agius4" rel="noopener noreferrer"&gt;&lt;code&gt;@​alan-agius4&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;/blockquote&gt;
&lt;p&gt;... (truncated)&lt;/p&gt;


Changelog
&lt;p&gt;&lt;em&gt;Sourced from &lt;a href="https://github.com/rollup/rollup/blob/master/CHANGELOG.md" rel="noopener noreferrer"&gt;rollup's changelog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;4.59.0&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;2026-02-22&lt;/em&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Features&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Throw when the generated bundle contains paths that would leave the output directory (&lt;a href="https://redirect.github.com/rollup/rollup/issues/6276" rel="noopener noreferrer"&gt;#6276&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Pull Requests&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6275" rel="noopener noreferrer"&gt;#6275&lt;/a&gt;: Validate bundle stays within output dir (&lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;4.58.0&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;2026-02-20&lt;/em&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Features&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Also support &lt;code&gt;__NO_SIDE_EFFECTS__&lt;/code&gt; annotation before variable declarations declaring function expressions (&lt;a href="https://redirect.github.com/rollup/rollup/issues/6272" rel="noopener noreferrer"&gt;#6272&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Pull Requests&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6256" rel="noopener noreferrer"&gt;#6256&lt;/a&gt;: docs: document PreRenderedChunk properties including isDynamicEntry and isImplicitEntry (&lt;a href="https://github.com/njg7194" rel="noopener noreferrer"&gt;&lt;code&gt;@​njg7194&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6259" rel="noopener noreferrer"&gt;#6259&lt;/a&gt;: docs: Correct typo and improve sentence structure in docs for &lt;code&gt;output.experimentalMinChunkSize&lt;/code&gt; (&lt;a href="https://github.com/millerick" rel="noopener noreferrer"&gt;&lt;code&gt;@​millerick&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6260" rel="noopener noreferrer"&gt;#6260&lt;/a&gt;: fix(deps): update rust crate swc_compiler_base to v47 (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot], &lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6261" rel="noopener noreferrer"&gt;#6261&lt;/a&gt;: fix(deps): lock file maintenance minor/patch updates (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot], &lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6262" rel="noopener noreferrer"&gt;#6262&lt;/a&gt;: Avoid unnecessary cloning of the code string (&lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6263" rel="noopener noreferrer"&gt;#6263&lt;/a&gt;: fix(deps): update minor/patch updates (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot], &lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6265" rel="noopener noreferrer"&gt;#6265&lt;/a&gt;: chore(deps): lock file maintenance (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot])&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6267" rel="noopener noreferrer"&gt;#6267&lt;/a&gt;: fix(deps): update minor/patch updates (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot])&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6268" rel="noopener noreferrer"&gt;#6268&lt;/a&gt;: chore(deps): update dependency eslint-plugin-unicorn to v63 (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot], &lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6269" rel="noopener noreferrer"&gt;#6269&lt;/a&gt;: chore(deps): update dependency lru-cache to v11 (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot])&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6270" rel="noopener noreferrer"&gt;#6270&lt;/a&gt;: chore(deps): lock file maintenance (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot])&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6272" rel="noopener noreferrer"&gt;#6272&lt;/a&gt;: forward NO_SIDE_EFFECTS annotations to function expressions in variable declarations (&lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;4.57.1&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;2026-01-30&lt;/em&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Bug Fixes&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Fix heap corruption issue in Windows (&lt;a href="https://redirect.github.com/rollup/rollup/issues/6251" rel="noopener noreferrer"&gt;#6251&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Ensure exports of a dynamic import are fully included when called from a try...catch (&lt;a href="https://redirect.github.com/rollup/rollup/issues/6254" rel="noopener noreferrer"&gt;#6254&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Pull Requests&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6251" rel="noopener noreferrer"&gt;#6251&lt;/a&gt;: fix: Isolate and cache &lt;code&gt;process.report.getReport()&lt;/code&gt; calls in a child process for robust environment detection (&lt;a href="https://github.com/alan-agius4" rel="noopener noreferrer"&gt;&lt;code&gt;@​alan-agius4&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6252" rel="noopener noreferrer"&gt;#6252&lt;/a&gt;: chore(deps): update dependency lru-cache to v11 (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot])&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6253" rel="noopener noreferrer"&gt;#6253&lt;/a&gt;: chore(deps): lock file maintenance minor/patch updates (&lt;a href="https://github.com/renovate" rel="noopener noreferrer"&gt;&lt;code&gt;@​renovate&lt;/code&gt;&lt;/a&gt;[bot], &lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redirect.github.com/rollup/rollup/pull/6254" rel="noopener noreferrer"&gt;#6254&lt;/a&gt;: Fully include dynamic imports in a try-catch (&lt;a href="https://github.com/lukastaegert" rel="noopener noreferrer"&gt;&lt;code&gt;@​lukastaegert&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;/blockquote&gt;
&lt;p&gt;... (truncated)&lt;/p&gt;


Commits
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/rollup/rollup/commit/ae846957f109690a866cc3e4c073613c338d3476" rel="noopener noreferrer"&gt;&lt;code&gt;ae84695&lt;/code&gt;&lt;/a&gt; 4.59.0&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rollup/rollup/commit/b39616e9175b3d9fc3977c99153174c490805a93" rel="noopener noreferrer"&gt;&lt;code&gt;b39616e&lt;/code&gt;&lt;/a&gt; Update audit-resolve&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rollup/rollup/commit/c60770d7aaf750e512c1b2774989ea4596e660b2" rel="noopener noreferrer"&gt;&lt;code&gt;c60770d&lt;/code&gt;&lt;/a&gt; Validate bundle stays within output dir (&lt;a href="https://redirect.github.com/rollup/rollup/issues/6275" rel="noopener noreferrer"&gt;#6275&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rollup/rollup/commit/33f39c1f205ea2eadaf4b589e493453e2baa3662" rel="noopener noreferrer"&gt;&lt;code&gt;33f39c1&lt;/code&gt;&lt;/a&gt; 4.58.0&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rollup/rollup/commit/b61c40803b717854c1c28937e8098e5ad3c7b8ca" rel="noopener noreferrer"&gt;&lt;code&gt;b61c408&lt;/code&gt;&lt;/a&gt; forward NO_SIDE_EFFECTS annotations to function expressions in variable decla...&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rollup/rollup/commit/7f00689ec90e2cafb11c26eefbcac62343c936f6" rel="noopener noreferrer"&gt;&lt;code&gt;7f00689&lt;/code&gt;&lt;/a&gt; Extend agent instructions&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rollup/rollup/commit/e7b2b85af0901244ecc141b9d792c6db6b527ea4" rel="noopener noreferrer"&gt;&lt;code&gt;e7b2b85&lt;/code&gt;&lt;/a&gt; chore(deps): lock file maintenance (&lt;a href="https://redirect.github.com/rollup/rollup/issues/6270" rel="noopener noreferrer"&gt;#6270&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rollup/rollup/commit/2aa5da9baf82211b8207d268c8751630cb766970" rel="noopener noreferrer"&gt;&lt;code&gt;2aa5da9&lt;/code&gt;&lt;/a&gt; fix(deps): update minor/patch updates (&lt;a href="https://redirect.github.com/rollup/rollup/issues/6267" rel="noopener noreferrer"&gt;#6267&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rollup/rollup/commit/4319837c5448d0c10d89e9ded118888deec2eeec" rel="noopener noreferrer"&gt;&lt;code&gt;4319837&lt;/code&gt;&lt;/a&gt; chore(deps): update dependency lru-cache to v11 (&lt;a href="https://redirect.github.com/rollup/rollup/issues/6269" rel="noopener noreferrer"&gt;#6269&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rollup/rollup/commit/c3b6b4bdc4f2ed978fa233132a526957e6513233" rel="noopener noreferrer"&gt;&lt;code&gt;c3b6b4b&lt;/code&gt;&lt;/a&gt; chore(deps): update dependency eslint-plugin-unicorn to v63 (&lt;a href="https://redirect.github.com/rollup/rollup/issues/6268" rel="noopener noreferrer"&gt;#6268&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Additional commits viewable in &lt;a href="https://github.com/rollup/rollup/compare/v4.54.0...v4.59.0" rel="noopener noreferrer"&gt;compare view&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;br&gt;
&lt;p&gt;&lt;a href="https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a03e54e2737a5b39e385af4f1e6f963fdc86465e6de7f29ba7820a0465fa52b7/68747470733a2f2f646570656e6461626f742d6261646765732e6769746875626170702e636f6d2f6261646765732f636f6d7061746962696c6974795f73636f72653f646570656e64656e63792d6e616d653d726f6c6c7570267061636b6167652d6d616e616765723d6e706d5f616e645f7961726e2670726576696f75732d76657273696f6e3d342e35342e30266e65772d76657273696f6e3d342e35392e30" alt="Dependabot compatibility score"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting &lt;code&gt;@dependabot rebase&lt;/code&gt;.&lt;/p&gt;


Dependabot commands and options
&lt;br&gt;
&lt;p&gt;You can trigger Dependabot actions by commenting on this PR:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@dependabot rebase&lt;/code&gt; will rebase this PR&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@dependabot recreate&lt;/code&gt; will recreate this PR, overwriting any edits that have been made to it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@dependabot show &amp;lt;dependency name&amp;gt; ignore conditions&lt;/code&gt; will show all of the ignore conditions of the specified dependency&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@dependabot ignore this major version&lt;/code&gt; will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@dependabot ignore this minor version&lt;/code&gt; will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@dependabot ignore this dependency&lt;/code&gt; will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the &lt;a href="https://github.com/nickytonline/nickytdotco/network/alerts" rel="noopener noreferrer"&gt;Security Alerts page&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nickytonline/nickytdotco/pull/790" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;This has taken a whole category of busywork off my plate for my personal site and my Clawspace project.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nickytonline" rel="noopener noreferrer"&gt;
        nickytonline
      &lt;/a&gt; / &lt;a href="https://github.com/nickytonline/nickytdotco" rel="noopener noreferrer"&gt;
        nickytdotco
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Source code for my web site nickyt.co
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a href="https://app.netlify.com/sites/iamdeveloperdotcom/deploys" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/f7192d68138516a15ca52465522ce91922bef5665291edfc1d09328e8018f35c/68747470733a2f2f6170692e6e65746c6966792e636f6d2f6170692f76312f6261646765732f63326330386136642d303937642d343964662d623332642d3237666133643766633866382f6465706c6f792d737461747573" alt="Netlify Status"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Nick Taylor's Personal Website&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;This is the source code for &lt;a href="https://www.nickyt.co" rel="nofollow noopener noreferrer"&gt;nickyt.co&lt;/a&gt;, Nick Taylor's personal website and blog.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tech Stack&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://astro.build/" rel="nofollow noopener noreferrer"&gt;Astro&lt;/a&gt;&lt;/strong&gt; - Modern web framework for building fast, content-focused websites&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://react.dev/" rel="nofollow noopener noreferrer"&gt;React&lt;/a&gt;&lt;/strong&gt; - For interactive UI components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://docs.astro.build/en/guides/integrations-guide/mdx/" rel="nofollow noopener noreferrer"&gt;MDX&lt;/a&gt;&lt;/strong&gt; - For blog posts and content with embedded components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://tailwindcss.com/" rel="nofollow noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt;&lt;/strong&gt; - Utility-first CSS framework&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.typescriptlang.org/" rel="nofollow noopener noreferrer"&gt;TypeScript&lt;/a&gt;&lt;/strong&gt; - Type-safe JavaScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://expressive-code.com/" rel="nofollow noopener noreferrer"&gt;Expressive Code&lt;/a&gt;&lt;/strong&gt; - Syntax highlighting for code blocks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://netlify.com" rel="nofollow noopener noreferrer"&gt;Netlify&lt;/a&gt;&lt;/strong&gt; - Hosting and deployment platform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js 22+&lt;/strong&gt; - Runtime environment&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Terminal commands&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Install the dependencies first&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Run in dev mode&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run dev&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Build a production version of the site&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run build&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;Test the production site locally&lt;/h4&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run preview&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Styling&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Tailwind v4 is configured in &lt;code&gt;tailwind.config.cjs&lt;/code&gt; and &lt;code&gt;postcss.config.cjs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Global styles are loaded from &lt;code&gt;src/styles/tailwind.css&lt;/code&gt;, which imports &lt;code&gt;src/styles/legacy.css&lt;/code&gt; for bespoke rules.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Licensing&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;This project contains two separate licenses:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Code License&lt;/strong&gt;: The website's source code (in the project root and…&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nickytonline/nickytdotco" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nickytonline" rel="noopener noreferrer"&gt;
        nickytonline
      &lt;/a&gt; / &lt;a href="https://github.com/nickytonline/clawspace" rel="noopener noreferrer"&gt;
        clawspace
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Clawspace is a browser-based file explorer/editor for an OpenClaw workspace.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Clawspace&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/nickytonline/clawspace/./public/assets/nano-banana-lobster.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fnickytonline%2Fclawspace%2F.%2Fpublic%2Fassets%2Fnano-banana-lobster.png" alt="Nano banana lobster at a desk"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Clawspace is a browser-based file explorer/editor for an OpenClaw workspace.&lt;/p&gt;
&lt;p&gt;It gives you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;File and directory browsing (&lt;code&gt;/workspace&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Timeline view (&lt;code&gt;/timeline&lt;/code&gt;) with folder filters and pagination&lt;/li&gt;
&lt;li&gt;Configurable default home view for &lt;code&gt;/&lt;/code&gt; (Files or Timeline)&lt;/li&gt;
&lt;li&gt;Monaco editor for text files&lt;/li&gt;
&lt;li&gt;Save/revert/copy actions&lt;/li&gt;
&lt;li&gt;Auto-format on blur (supported file types)&lt;/li&gt;
&lt;li&gt;Basic hardening for writes (path checks, blocked files, audit log)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why this exists&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;OpenClaw users often want a fast, authenticated UI to inspect and edit workspace files without opening SSH/terminal sessions.&lt;/p&gt;
&lt;p&gt;Clawspace is designed to run on your LAN, or behind a trusted auth proxy (for example Pomerium + OpenClaw trusted-proxy mode).&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Install&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/nickytonline/clawspace
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; clawspace
npm install&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick start&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run build
npm run clawspace:serve&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Default port is &lt;code&gt;6789&lt;/code&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Development&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run dev&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Configuration&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Clawspace uses the parent of the app directory as the workspace root by default
If you install it elsewhere,…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nickytonline/clawspace" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;For work projects there would probably be some push back on this potentially, but if you have a really great CI/CD pipeline with checks, definitely consider doing this or at least having a discussion with your team.&lt;/p&gt;

&lt;p&gt;If you want to stay in touch, all my socials are on &lt;a href="https://nickyt.online" rel="noopener noreferrer"&gt;nickyt.online&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Until the next one!&lt;/p&gt;

</description>
      <category>github</category>
      <category>cicd</category>
      <category>githubactions</category>
      <category>devops</category>
    </item>
    <item>
      <title>Stop using cat</title>
      <dc:creator>Nick Taylor</dc:creator>
      <pubDate>Mon, 02 Mar 2026 02:08:25 +0000</pubDate>
      <link>https://dev.to/nickytonline/stop-using-cat-35gd</link>
      <guid>https://dev.to/nickytonline/stop-using-cat-35gd</guid>
      <description>&lt;p&gt;If you use &lt;code&gt;cat&lt;/code&gt; in your daily workflow, this is a tiny upgrade with lots of upsides and honestly, no downsides aside from you need to install it as it’s not native.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is bat?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sharkdp/bat" rel="noopener noreferrer"&gt;bat&lt;/a&gt; is a &lt;code&gt;cat&lt;/code&gt; alternative with syntax highlighting and line numbering to name a few features while being a drop in replacement to workflows you have that use regular &lt;code&gt;cat&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;bat

&lt;span class="c"&gt;# Ubuntu / Debian&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;bat

&lt;span class="c"&gt;# via installation script&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://sh.rustup.rs | bat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just FYI, once it’s installed, on some Linux distros, the binary is named &lt;code&gt;batcat&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six practical ways to use &lt;code&gt;bat&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;By default you get all the &lt;code&gt;bat&lt;/code&gt; goodness when you don’t specify any flags. Syntax highlighting, line numbering etc. Here’s some common use cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read config files quickly
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bat ./astro.config.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;cat&lt;/code&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%2Frk86lix3uo49i477zqia.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%2Frk86lix3uo49i477zqia.png" alt="cat outputting an Astro configuration file" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;bat&lt;/code&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%2Fahshr9qzj83glfe5gwha.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%2Fahshr9qzj83glfe5gwha.png" alt="bat outputting an Astro configuration file" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You get visual structure without opening an editor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Show line numbers while debugging
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bat &lt;span class="nt"&gt;-n&lt;/span&gt; src/server.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Line numbers make it much easier to point teammates to exact spots in a file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use plain mode for logs or scripts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bat &lt;span class="nt"&gt;-p&lt;/span&gt; logs/app.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-p&lt;/code&gt; strips the decorations when you want cleaner output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Focus on a line range
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bat &lt;span class="nt"&gt;--line-range&lt;/span&gt; 40:120 src/index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great when you only need one section of a file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quickly review changed files
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; | xargs bat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful for a quick scan of touched files before a commit or review.&lt;/p&gt;

&lt;h3&gt;
  
  
  Navigate large files with a pager
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bat large-file.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bat&lt;/code&gt; automatically pipes output through a pager like &lt;a href="https://www.man7.org/linux/man-pages/man1/less.1.html" rel="noopener noreferrer"&gt;less&lt;/a&gt; when the file is longer than your terminal window. No need to manually pipe to &lt;code&gt;less&lt;/code&gt; like you would with &lt;code&gt;cat&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you alias &lt;code&gt;cat&lt;/code&gt; to &lt;code&gt;bat&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;I alias it, because YOLO, but if you do run into issues, just alias it to something other than &lt;code&gt;cat&lt;/code&gt;, or not at all.&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="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;c&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'bat'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;bat&lt;/code&gt; is one of the easiest terminal upgrades you can make. If you read code, logs, or config files in the terminal every day, switching from &lt;code&gt;cat&lt;/code&gt; to &lt;code&gt;bat&lt;/code&gt; is a no brainer.&lt;/p&gt;

&lt;p&gt;If you want to stay in touch, all my socials are on &lt;a href="https://nickyt.online" rel="noopener noreferrer"&gt;nickyt.online&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Until the next one!&lt;/p&gt;

</description>
      <category>terminal</category>
      <category>shell</category>
    </item>
    <item>
      <title>Five Git Config Settings Every Dev Needs</title>
      <dc:creator>Nick Taylor</dc:creator>
      <pubDate>Tue, 10 Feb 2026 02:19:13 +0000</pubDate>
      <link>https://dev.to/nickytonline/five-git-config-settings-every-dev-needs-3e55</link>
      <guid>https://dev.to/nickytonline/five-git-config-settings-every-dev-needs-3e55</guid>
      <description>&lt;p&gt;You've probably added some settings to your &lt;a href="https://git-scm.com/docs/git-config" rel="noopener noreferrer"&gt;Git Configuration&lt;/a&gt;, but here are some you might not have configured. If you haven't set these up yet, you're doing more manual work than you need to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rebase on pull instead of merge
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; pull.rebase &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time you pull without this, Git creates a merge commit. Do that a few times a day across a team and your git log turns into a mess of "Merge branch 'main' into main" entries that tell you nothing. With rebase, your commits stay on top of the latest changes and your history actually reads like a coherent timeline.&lt;/p&gt;

&lt;p&gt;Bonus: I do this a lot, &lt;code&gt;g pull -r origin main&lt;/code&gt; (&lt;code&gt;g&lt;/code&gt; is my shell alias for &lt;code&gt;git&lt;/code&gt;) to keep my branch up to date with main. You can also add&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; branch.main.rebase &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and now I just do &lt;code&gt;g pull origin main&lt;/code&gt;. Sure it's only two less characters, but one less thing for me to think about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto set upstream on push
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; push.autoSetupRemote &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You create a new branch, do your work, push, and Git hits you with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fatal: The current branch my-branch has no upstream branch.
To push the current branch and &lt;span class="nb"&gt;set &lt;/span&gt;the remote as upstream, use

    git push &lt;span class="nt"&gt;--set-upstream&lt;/span&gt; origin my-branch

To have this happen automatically &lt;span class="k"&gt;for &lt;/span&gt;branches without a tracking
upstream, see &lt;span class="s1"&gt;'push.autoSetupRemote'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s1"&gt;'git help config'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every single time. This setting makes that whole thing go away. Git sets the upstream automatically on your first push to a new branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto prune on fetch
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; fetch.prune &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stale remote branches pile up silently. Someone merged and deleted their branch weeks ago, but your local still shows it when you run &lt;code&gt;git branch -r&lt;/code&gt;. This cleans out those dead references every time you fetch so your branch list reflects what actually exists on the remote.&lt;/p&gt;

&lt;p&gt;I had this alias in my shell to prune local and remote branches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rmmerged&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  git branch &lt;span class="nt"&gt;--merged&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ev&lt;/span&gt; &lt;span class="s2"&gt;"(&lt;/span&gt;&lt;span class="se"&gt;\*&lt;/span&gt;&lt;span class="s2"&gt;|master|main)"&lt;/span&gt; | xargs &lt;span class="nt"&gt;-n&lt;/span&gt; 1 git branch &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git remote prune origin
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, it's simplified to only pruning local branches since the fetch prune setting handles remote ones.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rmmerged&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  git branch &lt;span class="nt"&gt;--merged&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ev&lt;/span&gt; &lt;span class="s2"&gt;"(&lt;/span&gt;&lt;span class="se"&gt;\*&lt;/span&gt;&lt;span class="s2"&gt;|master|main)"&lt;/span&gt; | xargs &lt;span class="nt"&gt;-n&lt;/span&gt; 1 git branch &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A better diff algorithm
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; diff.algorithm histogram
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default diff algorithm works, but histogram produces cleaner diffs when there are lots of similarly structured lines, think repeated return statements, closing braces, or blank lines. The default algorithm can get confused about which identical lines to match and produces diffs that interleave additions and deletions in ways that are hard to follow. Histogram handles that better. The bigger the file and the more repetitive the structure, the more noticeable the improvement. It's a drop-in upgrade with no downside.&lt;/p&gt;

&lt;p&gt;Here's a fictitious example since I had a hard time finding a good example in my own recent commits showing the difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  Myers Algorithm (Default)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/tmp/example_before.js b/tmp/example_after.js
index 30d9ab3c..8ec95ef5 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/tmp/example_before.js
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/tmp/example_after.js
&lt;/span&gt;&lt;span class="p"&gt;@@ -8,15 +8,10 @@&lt;/span&gt; function validateUser(user) {
    if (!user.name) {
      return { error: 'Name is required' };
    }
&lt;span class="gd"&gt;-  return { valid: true };
-}
-
-function processData(data) {
-  const result = transform(data);
-  if (!result) {
-    return { error: 'Transform failed' };
&lt;/span&gt;&lt;span class="gi"&gt;+  if (!user.id) {
+    return { error: 'ID is required' };
&lt;/span&gt;    }
&lt;span class="gd"&gt;-  return result;
&lt;/span&gt;&lt;span class="gi"&gt;+  return { valid: true };
&lt;/span&gt;  }
&lt;span class="err"&gt;
&lt;/span&gt;  function validateProduct(product) {
&lt;span class="p"&gt;@@ -26,13 +21,28 @@&lt;/span&gt; function validateProduct(product) {
    if (!product.price) {
      return { error: 'Price is required' };
    }
&lt;span class="gi"&gt;+  if (!product.name) {
+    return { error: 'Name is required' };
+  }
&lt;/span&gt;    return { valid: true };
  }
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+function processData(data) {
+  const result = transform(data);
+  if (!result) {
+    return { error: 'Transform failed' };
+  }
+  return result;
+}
+
&lt;/span&gt;  function saveToDatabase(item) {
    const connection = getConnection();
    if (!connection) {
      return { error: 'Database connection failed' };
    }
&lt;span class="gi"&gt;+  const validated = validateItem(item);
+  if (!validated) {
+    return { error: 'Validation failed' };
+  }
&lt;/span&gt;    return connection.save(item);
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Histogram Algorithm
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/tmp/example_before.js b/tmp/example_after.js
index 30d9ab3c..8ec95ef5 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/tmp/example_before.js
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/tmp/example_after.js
&lt;/span&gt;&lt;span class="p"&gt;@@ -8,6 +8,22 @@&lt;/span&gt; function validateUser(user) {
    if (!user.name) {
      return { error: 'Name is required' };
    }
&lt;span class="gi"&gt;+  if (!user.id) {
+    return { error: 'ID is required' };
+  }
+  return { valid: true };
+}
+
+function validateProduct(product) {
+  if (!product) {
+    return { error: 'Product is required' };
+  }
+  if (!product.price) {
+    return { error: 'Price is required' };
+  }
+  if (!product.name) {
+    return { error: 'Name is required' };
+  }
&lt;/span&gt;    return { valid: true };
  }
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;@@ -19,20 +35,14 @@&lt;/span&gt; function processData(data) {
    return result;
  }
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-function validateProduct(product) {
-  if (!product) {
-    return { error: 'Product is required' };
-  }
-  if (!product.price) {
-    return { error: 'Price is required' };
-  }
-  return { valid: true };
-}
-
&lt;/span&gt;  function saveToDatabase(item) {
    const connection = getConnection();
    if (!connection) {
      return { error: 'Database connection failed' };
    }
&lt;span class="gi"&gt;+  const validated = validateItem(item);
+  if (!validated) {
+    return { error: 'Validation failed' };
+  }
&lt;/span&gt;    return connection.save(item);
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Rerere
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; rerere.enabled &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://onetipaweek.com/p/one-tip-a-week-rerere" rel="noopener noreferrer"&gt;Rerere&lt;/a&gt; stands for "reuse recorded resolution." When you resolve a merge conflict, Git remembers how you resolved it. The next time the same conflict comes up, Git applies your previous resolution automatically. If you've ever rebased a long-lived branch and had to resolve the same conflict over and over, this is the fix. It won't silently merge things for you in a way you can't review. It records your resolutions and replays them so you don't have to redo the same work.&lt;/p&gt;

&lt;p&gt;Want to see what you currently have set? Run &lt;code&gt;git config --global --list&lt;/code&gt; and see what's missing.&lt;/p&gt;

&lt;p&gt;If you enjoy tips like this, I have a newsletter, &lt;a href="https://OneTipAWeek.com" rel="noopener noreferrer"&gt;OneTipAWeek.com&lt;/a&gt;. One developer tip a week. Short &amp;amp; valuable. That's it!&lt;/p&gt;

&lt;p&gt;If you want to stay in touch, all my socials are on &lt;a href="https://nickyt.online" rel="noopener noreferrer"&gt;nickyt.online&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Until the next one!&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@yancymin?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Yancy Min&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-close-up-of-a-text-description-on-a-computer-screen-842ofHC6MaI?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>git</category>
    </item>
    <item>
      <title>BenQ RD280UG Review: 28-Inch 4K Programming Monitor for Developers</title>
      <dc:creator>Nick Taylor</dc:creator>
      <pubDate>Tue, 20 Jan 2026 12:48:00 +0000</pubDate>
      <link>https://dev.to/nickytonline/benq-rd280ug-review-28-inch-4k-programming-monitor-for-developers-2iko</link>
      <guid>https://dev.to/nickytonline/benq-rd280ug-review-28-inch-4k-programming-monitor-for-developers-2iko</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a sponsored post, but it's an honest review.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;BenQ sent me their new &lt;a href="https://benqurl.biz/4a2wR0n" rel="noopener noreferrer"&gt;RD280UG monitor&lt;/a&gt; to review. They've sent me monitors in the past, and I've been a fan of their programming-focused lineup.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nickytonline/benq-rd280u-review-a-28-4k-monitor-built-for-developers-20d2" class="crayons-story__hidden-navigation-link"&gt;BenQ RD280U Review: A 28" 4K Monitor Built for Developers&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nickytonline" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F9597%2F8f1ed696-3e87-4bd4-809a-0ee9bb905c3c.jpg" alt="nickytonline profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nickytonline" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Nick Taylor
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Nick Taylor
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-2403437" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nickytonline" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F9597%2F8f1ed696-3e87-4bd4-809a-0ee9bb905c3c.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Nick Taylor&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nickytonline/benq-rd280u-review-a-28-4k-monitor-built-for-developers-20d2" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 13 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nickytonline/benq-rd280u-review-a-28-4k-monitor-built-for-developers-20d2" id="article-link-2403437"&gt;
          BenQ RD280U Review: A 28" 4K Monitor Built for Developers
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/review"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;review&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/benq"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;benq&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programmingmonitor"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programmingmonitor&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/officesetup"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;officesetup&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nickytonline/benq-rd280u-review-a-28-4k-monitor-built-for-developers-20d2" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;12&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nickytonline/benq-rd280u-review-a-28-4k-monitor-built-for-developers-20d2#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  The 3:2 Aspect Ratio Thing
&lt;/h2&gt;

&lt;p&gt;These monitors use a 3:2 aspect ratio instead of the standard widescreen format. When I got my first BenQ programming monitor about a year ago, the 28-inch 3:2 dimensions seemed odd. The aspect ratio was a little weird at first, but honestly, after a few days of using it, I was already used to it. I keep mine in the normal horizontal position, though you can rotate these monitors vertically if that's your thing.&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%2Fea2uoyf64jkwpcyijymt.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%2Fea2uoyf64jkwpcyijymt.jpg" alt="BenQ RD280UG monitor"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;The RD280UG is a 28-inch 4K monitor with a 3:2 aspect ratio, running at 3840 x 2560 pixels with a 120Hz refresh rate, up from the &lt;a href="https://benqurl.biz/4kZ3mRx" rel="noopener noreferrer"&gt;RD280U model's&lt;/a&gt; 60Hz. It works with both Mac and PC. I'm using it with my Mac for development and live streaming, and the aspect ratio hasn't caused any issues with streaming setups.&lt;/p&gt;

&lt;p&gt;The new monitor keeps the features I liked from previous models. The moon halo lighting on the back is still there, and you can quickly switch display settings using the front button. Dark mode, light mode, and custom user settings are all available. You can also adjust blue light levels.&lt;/p&gt;

&lt;p&gt;The big addition is the paper color mode. I've been testing it out and it's solid. It's designed to reduce eye strain and blue light. One caveat though: if you're doing UI development, you'll want to switch back to your regular settings since the colors won't be as vibrant. When I'm working on UI stuff, I just flip back to my normal dark theme.&lt;/p&gt;

&lt;p&gt;I haven't noticed any eye strain while using this monitor, and the paper color mode seems to be doing its job well.&lt;/p&gt;

&lt;p&gt;Assembly is straightforward, just like the previous model. No screwdriver needed. You flip open a thing, turn to tighten the base to the stand, and the monitor clicks into place.&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%2Fr01b26astx6p9kgdn0f2.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%2Fr01b26astx6p9kgdn0f2.jpg" alt="Bottom of monitor base"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's a leather cable management piece that snaps together on the back to keep things tidy.&lt;/p&gt;

&lt;p&gt;Small thing, but they include a cleaning cloth with the monitor. I've always been paranoid about what to use on monitors. I usually grab my glasses cloth and hope for the best, so having a dedicated one is appreciated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Port Upgrades
&lt;/h2&gt;

&lt;p&gt;This is where I discovered something cool. The ports are located to the right of the monitor settings button, just under the screen. You get a USB-C port, two USB-A ports, and a headphone jack if you're still into the wired world. &lt;/p&gt;

&lt;p&gt;Funny thing is, I didn't even know my old monitor had ports there too. Turns out it did - just 3 USB-A ports, no USB-C. The RD280UG swaps one of those USB-A ports for USB-C. Right now I've got my watch charging via USB-C, my mic in one USB port, and my USB hub in the other.&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%2Fxzn7nw00964gr77npemn.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%2Fxzn7nw00964gr77npemn.png" alt="USB ports and headphone jack"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr4s4c92gealhkvtmt3yc.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%2Fr4s4c92gealhkvtmt3yc.jpg" alt="devices plugged in to USB ports on monitor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have the monitor connected via USB-C, though you can also use HDMI or DisplayPort if that's your preference. The port placement on the back is way more accessible than before and you can daisy chain monitors if you want multiple displays connected to your laptop or desktop.&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%2Fs4jhfj4bf8dc1svkoby5.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%2Fs4jhfj4bf8dc1svkoby5.png" alt="monitor port types including KVM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The monitor also has built-in KVM functionality like previous models. I don't use it normally, but it's there if you need to switch between multiple computers with one set of peripherals. I'll probably use it when I connect my mini PC if I'm not SSHing into it or booting into Windows on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Verdict
&lt;/h2&gt;

&lt;p&gt;I've been using this for a week now. The 28-inch 3:2 aspect ratio continues to be great for programming work. The improved port accessibility, new paper color mode, and 120Hz refresh rate (up from 60Hz on the &lt;a href="https://benqurl.biz/4kZ3mRx" rel="noopener noreferrer"&gt;RD280U&lt;/a&gt;) are solid upgrades. The higher refresh rate makes scrolling through code noticeably smoother and the text is super clear on this monitor as well. A picture probably can't do that justice though. 😅&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="instagram-position"&gt;
  &lt;iframe id="instagram-liquid-tag" src="https://www.instagram.com/p/DTvl0MZgNtx/embed/captioned/"&gt;
  &lt;/iframe&gt;
  
&lt;/div&gt;




&lt;p&gt;The RD280UG is priced at $759.99 (MSRP). You can check it out on &lt;a href="https://benqurl.biz/3LPNrs3" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt; or learn more on the &lt;a href="https://benqurl.biz/4a2wR0n" rel="noopener noreferrer"&gt;BenQ website&lt;/a&gt;. If you're a dev and in the market for a new monitor, BenQ has got you set.&lt;/p&gt;

&lt;p&gt;Thanks again BenQ for the monitor!&lt;/p&gt;

&lt;p&gt;If you want to stay in touch, all my socials are on &lt;a href="https://nickyt.online" rel="noopener noreferrer"&gt;nickyt.online&lt;/a&gt;&lt;/p&gt;

</description>
      <category>officesetup</category>
      <category>benq</category>
      <category>programmingmonitor</category>
      <category>benqmonitor</category>
    </item>
    <item>
      <title>From Fresh Mac to Productive in 30 Minutes</title>
      <dc:creator>Nick Taylor</dc:creator>
      <pubDate>Sat, 03 Jan 2026 16:20:20 +0000</pubDate>
      <link>https://dev.to/nickytonline/from-fresh-mac-to-productive-in-30-minutes-21db</link>
      <guid>https://dev.to/nickytonline/from-fresh-mac-to-productive-in-30-minutes-21db</guid>
      <description>&lt;p&gt;I've been hearing developers talk about their dotfiles for years. "Oh yeah, I just clone my dotfiles repo and I'm set up in minutes." I had some &lt;a href="https://gist.github.com/nickytonline/729fc106a0146345c0b90f3356a41e4d" rel="noopener noreferrer"&gt;semblance of a setup script&lt;/a&gt; at one point, but I kinda just went with manual mode for fresh Mac setups.&lt;/p&gt;

&lt;p&gt;Even if I had most apps installed with a script, weeks later when I needed a tool, I realized I forgot to install it. Also, macOS settings that I had are missing and I forget where to find them. One big hot mess of a fresh install.&lt;/p&gt;

&lt;p&gt;I recently decided to wipe my Mac for a clean slate. I didn't want to spend days piecing my setup back together app by app, so I finally committed to creating a proper dotfiles repo. With a dash of AI, I was on my way to making a great install setup.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nickytonline" rel="noopener noreferrer"&gt;
        nickytonline
      &lt;/a&gt; / &lt;a href="https://github.com/nickytonline/dotfiles" rel="noopener noreferrer"&gt;
        dotfiles
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Dotfiles&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;My personal dotfiles and system configuration for macOS.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contents&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Brewfile&lt;/code&gt; - Homebrew packages, casks, and Mac App Store apps&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shell/&lt;/code&gt; - Shell configuration files (.zshrc, .zshenv, .zprofile, .bashrc, .profile)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git/&lt;/code&gt; - Git configuration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ssh/&lt;/code&gt; - SSH configuration (not keys!)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;config/&lt;/code&gt; - Application configs (starship, gh, atuin, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.npmrc&lt;/code&gt; - npm configuration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;install.sh&lt;/code&gt; - Bootstrap script to set up a new machine&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;macos-defaults.sh&lt;/code&gt; - macOS system preferences and settings&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Fresh macOS Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Prerequisites&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Before starting, make sure you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Signed into your Apple ID&lt;/li&gt;
&lt;li&gt;Installed pending macOS updates&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Installation Steps&lt;/h3&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Clone this repository:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/nickytonline/dotfiles.git &lt;span class="pl-k"&gt;~&lt;/span&gt;/dotfiles
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; &lt;span class="pl-k"&gt;~&lt;/span&gt;/dotfiles&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Run the install script:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;chmod +x install.sh
./install.sh&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The install script will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install Xcode Command Line Tools&lt;/li&gt;
&lt;li&gt;Install Homebrew&lt;/li&gt;
&lt;li&gt;Create symlinks for all dotfiles&lt;/li&gt;
&lt;li&gt;Install Rust via rustup&lt;/li&gt;
&lt;li&gt;Install all packages from Brewfile (brew, casks, Mac App Store apps)&lt;/li&gt;
&lt;li&gt;Install Node.js LTS via fnm&lt;/li&gt;
&lt;li&gt;Install OpenAI Codex CLI via…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nickytonline/dotfiles" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;h2&gt;
  
  
  Dotfiles to the Rescue
&lt;/h2&gt;

&lt;p&gt;The concept is simple. All those configuration files that start with a dot (&lt;code&gt;.zshrc&lt;/code&gt;, &lt;code&gt;.gitconfig&lt;/code&gt;, etc.) can be version controlled and shared across machines.&lt;/p&gt;

&lt;p&gt;A dotfiles repository is not macOS specific, but I wanted to go further, my whole Mac setup or most of it.&lt;/p&gt;

&lt;p&gt;Here's what I ended up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/dotfiles/
├── Brewfile              &lt;span class="c"&gt;# Every app I use&lt;/span&gt;
├── install.sh            &lt;span class="c"&gt;# One script to rule them all&lt;/span&gt;
├── macos-defaults.sh     &lt;span class="c"&gt;# macOS system preferences&lt;/span&gt;
├── shell/                &lt;span class="c"&gt;# Shell configs (.zshrc, etc.)&lt;/span&gt;
├── git/                  &lt;span class="c"&gt;# Git configuration&lt;/span&gt;
├── ssh/                  &lt;span class="c"&gt;# SSH config (no keys!)&lt;/span&gt;
└── config/               &lt;span class="c"&gt;# App configs (starship, gh, atuin, etc.)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Brewfile's Got Your Apps' Backs
&lt;/h2&gt;

&lt;p&gt;If you're on macOS and not using &lt;a href="https://brew.sh" rel="noopener noreferrer"&gt;Homebrew&lt;/a&gt;, you're missing out. It's basically &lt;code&gt;apt&lt;/code&gt; or &lt;code&gt;yum&lt;/code&gt; for macOS.&lt;/p&gt;

&lt;p&gt;What I didn't fully appreciate until this project was Homebrew's Brewfile feature. I knew it existed but never bothered to create one. If you already have Homebrew installed, you can generate a Brewfile from everything you currently have installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew bundle dump
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;code&gt;Brewfile&lt;/code&gt; in your current directory with all your brew packages, casks, Mac App Store apps. &lt;/p&gt;

&lt;p&gt;Instead of running &lt;code&gt;brew install&lt;/code&gt; commands one by one, you declare everything in a single file:&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;# CLI tools&lt;/span&gt;
brew &lt;span class="s2"&gt;"git"&lt;/span&gt;
brew &lt;span class="s2"&gt;"ripgrep"&lt;/span&gt;
brew &lt;span class="s2"&gt;"fnm"&lt;/span&gt;  &lt;span class="c"&gt;# Node version manager - https://github.com/Schniz/fnm&lt;/span&gt;
brew &lt;span class="s2"&gt;"starship"&lt;/span&gt;  &lt;span class="c"&gt;# Beautiful shell prompt - https://starship.rs&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;

&lt;span class="c"&gt;# Desktop apps (casks)&lt;/span&gt;
cask &lt;span class="s2"&gt;"visual-studio-code@insiders"&lt;/span&gt;
cask &lt;span class="s2"&gt;"raycast"&lt;/span&gt;
cask &lt;span class="s2"&gt;"1password"&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;

&lt;span class="c"&gt;# Rust crates&lt;/span&gt;
cargo &lt;span class="s2"&gt;"oha"&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have your Brewfile, you can use it to install all your apps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew bundle &lt;span class="nt"&gt;--file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Brewfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's &lt;a href="https://github.com/nickytonline/dotfiles/blob/main/Brewfile" rel="noopener noreferrer"&gt;my Brewfile&lt;/a&gt; if you wanna check it out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Look mas!
&lt;/h2&gt;

&lt;p&gt;I always thought Mac App Store apps couldn't be automated. Turns out, there's a tool called &lt;a href="https://github.com/mas-cli/mas" rel="noopener noreferrer"&gt;mas&lt;/a&gt; (Mac App Store CLI) that allows you to automate installation of Mac App Store apps.&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;# In your Brewfile&lt;/span&gt;
brew &lt;span class="s2"&gt;"mas"&lt;/span&gt;

&lt;span class="c"&gt;# Then add Mac App Store apps&lt;/span&gt;
mas &lt;span class="s2"&gt;"Dato"&lt;/span&gt;, &lt;span class="nb"&gt;id&lt;/span&gt;: 1470584107
mas &lt;span class="s2"&gt;"Keynote"&lt;/span&gt;, &lt;span class="nb"&gt;id&lt;/span&gt;: 409183694
mas &lt;span class="s2"&gt;"Numbers"&lt;/span&gt;, &lt;span class="nb"&gt;id&lt;/span&gt;: 409203825
mas &lt;span class="s2"&gt;"TestFlight"&lt;/span&gt;, &lt;span class="nb"&gt;id&lt;/span&gt;: 899247664
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run &lt;code&gt;brew bundle&lt;/code&gt;, it installs &lt;code&gt;mas&lt;/code&gt; first, then uses it to download and install Mac App Store apps automatically.&lt;/p&gt;

&lt;p&gt;To find app IDs, just search on the Mac App Store website and grab the ID from the URL, e.g. &lt;code&gt;1470584107&lt;/code&gt; is the app ID for &lt;a href="https://onetipaweek..com/p/one-tip-a-week-dato" rel="noopener noreferrer"&gt;Dato&lt;/a&gt;, &lt;a href="https://apps.apple.com/us/app/dato/1470584107" rel="noopener noreferrer"&gt;https://apps.apple.com/us/app/dato/1470584107&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Or just run &lt;code&gt;mas list&lt;/code&gt; to see all the apps you currently have installed and snatch the app IDs from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Install Script
&lt;/h2&gt;

&lt;p&gt;Here's my &lt;a href="https://github.com/nickytonline/dotfiles/blob/main/install.sh" rel="noopener noreferrer"&gt;install.sh&lt;/a&gt; that I orchestrates the whole setup. The script is idempotent and prompts for each major step.&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;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c"&gt;# Install Xcode Command Line Tools first&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; xcode-select &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;xcode-select &lt;span class="nt"&gt;--install&lt;/span&gt;
    &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;  &lt;span class="c"&gt;# Wait for completion&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Install Homebrew&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; brew &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    /bin/bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Symlink all dotfiles&lt;/span&gt;
create_symlink &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOTFILES_DIR&lt;/span&gt;&lt;span class="s2"&gt;/shell/.zshrc"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.zshrc"&lt;/span&gt;
create_symlink &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOTFILES_DIR&lt;/span&gt;&lt;span class="s2"&gt;/git/.gitconfig"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.gitconfig"&lt;/span&gt;
&lt;span class="c"&gt;# ... and more&lt;/span&gt;

&lt;span class="c"&gt;# Install Rust (before brew bundle, so cargo packages work)&lt;/span&gt;
curl &lt;span class="nt"&gt;--proto&lt;/span&gt; &lt;span class="s1"&gt;'=https'&lt;/span&gt; &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 &lt;span class="nt"&gt;-sSf&lt;/span&gt; https://sh.rustup.rs | sh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="c"&gt;# Install everything from Brewfile&lt;/span&gt;
brew bundle &lt;span class="nt"&gt;--file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOTFILES_DIR&lt;/span&gt;&lt;span class="s2"&gt;/Brewfile"&lt;/span&gt;

&lt;span class="c"&gt;# Install Node.js LTS via fnm&lt;/span&gt;
fnm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--lts&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @openai/codex

&lt;span class="c"&gt;# Apply macOS system preferences&lt;/span&gt;
./macos-defaults.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automating macOS Preferences
&lt;/h2&gt;

&lt;p&gt;Like I mentioned in the beginnging, this is more than a dotfiles repository. It also covers macOS settings like Dock position and size, Finder settings, keyboard repeat rate, menu bar clock settings etc.&lt;/p&gt;

&lt;p&gt;All of these can be automated with &lt;code&gt;defaults write&lt;/code&gt; commands:&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;# Position Dock on the right&lt;/span&gt;
defaults write com.apple.dock orientation &lt;span class="nt"&gt;-string&lt;/span&gt; &lt;span class="s2"&gt;"right"&lt;/span&gt;

&lt;span class="c"&gt;# Show hidden files in Finder&lt;/span&gt;
defaults write com.apple.finder AppleShowAllFiles &lt;span class="nt"&gt;-bool&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# Fast keyboard repeat&lt;/span&gt;
defaults write NSGlobalDomain KeyRepeat &lt;span class="nt"&gt;-int&lt;/span&gt; 2

&lt;span class="c"&gt;# Show analog clock with day of week&lt;/span&gt;
defaults write com.apple.menuextra.clock IsAnalog &lt;span class="nt"&gt;-bool&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;defaults write com.apple.menuextra.clock ShowDayOfWeek &lt;span class="nt"&gt;-bool&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Here's &lt;a href="https://github.com/nickytonline/dotfiles/blob/main/macos-defaults.sh" rel="noopener noreferrer"&gt;my macOS settings&lt;/a&gt; with both recommended defaults for devs and my personal preferences, commented so I can pick and choose.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Not to Commit to Your Dotfiles Repo
&lt;/h2&gt;

&lt;p&gt;A lot of devs showcase their dotfiles as a public repository on GitHub, so do not add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API tokens or credentials&lt;/li&gt;
&lt;li&gt;SSH private keys&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.npmrc&lt;/code&gt; files with auth tokens&lt;/li&gt;
&lt;li&gt;Work-related hostnames or company names&lt;/li&gt;
&lt;li&gt;Personal email addresses (if you care about that)&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To prevent this, use your &lt;code&gt;.gitignore&lt;/code&gt; file to prevent certain files from being committed to the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;.npmrc
&lt;span class="k"&gt;**&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;_token
&lt;span class="k"&gt;**&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;_key
&lt;span class="k"&gt;*&lt;/span&gt;.local
.ssh/config_local
.env
.env.&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;.env
.env.&lt;span class="k"&gt;*&lt;/span&gt;.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Git Setup
&lt;/h2&gt;

&lt;p&gt;My install script creates a symlink for my Git config, and from there I just need to set up my signing key and access key&lt;br&gt;
If not already configured.&lt;/p&gt;
&lt;h3&gt;
  
  
  Modern Git Signing with SSH
&lt;/h3&gt;

&lt;p&gt;While setting this up, I learned you can &lt;a href="https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#ssh-commit-signature-verification" rel="noopener noreferrer"&gt;sign git commits with SSH keys&lt;/a&gt; instead of GPG, which is &lt;a href="https://dev.to/nickytonline/anyone-can-commit-code-as-you-on-github-heres-how-to-stop-them-2in7"&gt;what I was previously doing&lt;/a&gt;. So the SSH key you use for getting git access via SSH can also be used for signing your commits. No more dealing with GPG key expiration or complex setup:&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;# Tell git to use SSH for signing&lt;/span&gt;
git config &lt;span class="nt"&gt;--global&lt;/span&gt; gpg.format ssh
git config &lt;span class="nt"&gt;--global&lt;/span&gt; user.signingkey ~/.ssh/id_ed25519.pub
git config &lt;span class="nt"&gt;--global&lt;/span&gt; commit.gpgsign &lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# Add your SSH key as a "Signing Key" on GitHub&lt;/span&gt;
&lt;span class="c"&gt;# (not just an authentication key!)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub shows commits as "Verified" just like with GPG, but it's way simpler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Productive in 30 Minutes
&lt;/h2&gt;

&lt;p&gt;Now when I get a new Mac or when I need to refresh my existing one (that's what prompted me to automate this), the process is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repo:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   git clone https://github.com/nickytonline/dotfiles.git ~/dotfiles
   &lt;span class="nb"&gt;cd&lt;/span&gt; ~/dotfiles
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Run the install script:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   ./install.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Say "yes" to the prompts and watch the magic happen:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Xcode Command Line Tools install&lt;/li&gt;
&lt;li&gt;Homebrew installs&lt;/li&gt;
&lt;li&gt;My apps download and install automatically&lt;/li&gt;
&lt;li&gt;All dotfiles symlink&lt;/li&gt;
&lt;li&gt;Rust, Node.js, and CLI tools set up&lt;/li&gt;
&lt;li&gt;macOS preferences apply&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;5 minutes of manual work:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Generate/restore SSH keys&lt;/li&gt;
&lt;li&gt;Sign into 1Password&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;gh auth login&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Sign into a few apps (Slack, Raycast, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Boom! All set up.&lt;/p&gt;

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

&lt;p&gt;Setting up dotfiles is easier than you think. Start small:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;~/dotfiles&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;Move your &lt;code&gt;.zshrc&lt;/code&gt; there and symlink it: &lt;code&gt;ln -s ~/dotfiles/.zshrc ~/.zshrc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Initialize a git repo: &lt;code&gt;git init&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create a Brewfile: &lt;code&gt;brew bundle dump&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Build from there&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your future self (and your next Mac) will thank you.&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://github.com/nickytonline/dotfiles" rel="noopener noreferrer"&gt;my dotfiles repo&lt;/a&gt; if you want to see the full setup or steal parts of it. If you've got your own dotfiles setup or discovered other tools that make Mac automation easier, I'd love to hear about it.&lt;/p&gt;

&lt;p&gt;If you want to stay in touch, all my socials are on &lt;a href="https://nickyt.online" rel="noopener noreferrer"&gt;nickyt.online&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@alesnesetril?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Ales Nesetril&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/gray-and-black-laptop-computer-on-surface-Im7lZjxeLhg?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>macos</category>
      <category>productivity</category>
      <category>automation</category>
      <category>devtools</category>
    </item>
    <item>
      <title>My 2025 Year in Review</title>
      <dc:creator>Nick Taylor</dc:creator>
      <pubDate>Tue, 30 Dec 2025 18:21:13 +0000</pubDate>
      <link>https://dev.to/nickytonline/my-2025-year-in-review-3nom</link>
      <guid>https://dev.to/nickytonline/my-2025-year-in-review-3nom</guid>
      <description>&lt;p&gt;2025 was wild. I wrapped up 2024 with OpenSauced getting acquired by the Linux Foundation, which meant I was job hunting in late fall/early winter. My network came through for me and I landed at Pomerium in January.&lt;/p&gt;

&lt;h2&gt;
  
  
  From App Dev to Infra and Security
&lt;/h2&gt;

&lt;p&gt;January 13th marked my first day at Pomerium as a Developer Advocate. I'd had DevRel tendencies since my days at dev.to, but this was my first official title as a DevRel. They always say you're doing the thing before you're doing the thing and I think that's spot on. People over the past five years have always assumed I was DevRel until I let them know that I was actually full-time on engineering.&lt;/p&gt;

&lt;p&gt;Anyway, I went from being an app dev to working at Pomerium, a company whose main product is an identity-aware proxy (IAP). Completely out of my wheelhouse, but I was up for the challenge.&lt;/p&gt;

&lt;p&gt;I'd talked to some people who said getting into security was a solid move right now, and they were right. I think the main thing that had me a bit nervous was, what if no one wants to hear a talk from me since this was new territory for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hello MCP
&lt;/h2&gt;

&lt;p&gt;Then in May, everything shifted. I had heard about the Model Context Protocol (MCP) and then I saw Angie Jones mention on LinkedIn that she was speaking at the inaugural MCP Dev Summit in San Francisco. I brought it up internally, saying we should probably attend and I even submitted a talk as the CFP was still open. That conversation kicked off everything that came after.&lt;/p&gt;

&lt;p&gt;I think my CEO had also brought up MCPs and then all of a sudden we were securing MCPs as well. For a TLDR: an IAP operates at layer seven in the network stack, commonly known as the application layer, and for the most part that is HTTP, where remote MCP servers live. There were too many talks they wanted to do so they spilt over onto The Context (the MCP Dev Summit's online stream) which is where I gave my talk.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/KY1kCZkqUh0"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;I also suggested we attend the AI World Fair, so we ended up grabbing a booth there and met all kinds of people interested in AI including MCP. I also got to attend a couple workshops including one from the WorkOS people (Nick and Zack), where we dove into Mastra.&lt;/p&gt;

&lt;p&gt;And of course it was great to see old co-workers and friends.&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%2Fcu6aqyci1py0sugw0k6p.jpeg" 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%2Fcu6aqyci1py0sugw0k6p.jpeg" alt="AI world fair friends"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulz7terbhj3hcxo8hb6e.jpeg" 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%2Fulz7terbhj3hcxo8hb6e.jpeg" alt="More AI world fair friends"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjmpt53kbj59v39jgj8xa.jpeg" 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%2Fjmpt53kbj59v39jgj8xa.jpeg" alt="Even more AI world fair friends"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conference Circuit
&lt;/h2&gt;

&lt;p&gt;2025 was my biggest year for talks. I gave &lt;a href="https://nickyt.live/talks/" rel="noopener noreferrer"&gt;somewhere between 15 and 20 talks&lt;/a&gt; across different conferences. BlackHat USA was cool as I'd never been to Vegas before. I was a little stressed before my talk as the venue was massive and I was having trouble finding the location for my talk, but in the end, I made it. Maybe a bit sweaty lol.&lt;/p&gt;

&lt;p&gt;I also got to meet a friend of mine, Carmen, irl finally!&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%2Fa2drkqnv70sfp9kpy1ae.jpeg" 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%2Fa2drkqnv70sfp9kpy1ae.jpeg" alt="me and Carmen in Vegas"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I had multiple appearances at SREday, wrapping it up at the Microsoft Reactor campus where I think I gave my best version of that talk.&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%2Flljwubrfc12tgq295rls.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%2Flljwubrfc12tgq295rls.png" alt="Me and the organizers of SREDay"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My main talk evolved throughout the year: "Agentic Access: OAuth Gets You In, Zero Trust Keeps You Safe." Different versions for different audiences, but the core message stayed consistent.&lt;/p&gt;

&lt;p&gt;One talk that I was a little nervous for was on Zero Trust and Kubernetes (K8s). I'm still very new to K8s, so I wasn't sure I was going to be able to pull it off. I got my demo working at the last minute which definitely helped with stress levels. At the beginning of my talk, I asked if there were any K8s experts in the room to which I think two thirds of the audience raised their hands.&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%2Fw5kvohzt5ium0412x4lp.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5kvohzt5ium0412x4lp.gif" alt="forker shirtballs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The talk ended up going really well and I asked some of the audience if I explained the concepts well and they said I did, so I felt really good about that. This was at All Things Open and I got roped into co-hosting the Whiskey Web and Whatnot podcast for a couple episodes as well as being a guest myself. Thanks Robbie for having me!&lt;/p&gt;

&lt;p&gt;There's no point going through the whole list of talks, but my last in-person talk was the MCP Dev Summit in London which was really cool being there for the second MCP Dev Summit.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/_wtJzDf068w"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;Again it was great hanging with some awesome people.&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%2Frvmspmd095mg1vwwbhv6.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%2Frvmspmd095mg1vwwbhv6.png" alt="In London at the MCP Dev Summit with friends at the wrap up social"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprzz7u8iu8hiaasm5eb5.jpeg" 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%2Fprzz7u8iu8hiaasm5eb5.jpeg" alt="me and Marlene"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frt4ycr74gambxbrk1fkh.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%2Frt4ycr74gambxbrk1fkh.png" alt="Me and Rachel grabbing tea"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;People always say this, but it's 100% true. Getting to meet people in person at all these conferences is the real motivation. Obviously there's speaking obligations, but it's a cheat code to hang with friends as well as people you may have never had the chance to chat with irl.&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%2Fzks5jznriozy2ibtfr22.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%2Fzks5jznriozy2ibtfr22.png" alt="Me, bdougie, Tyler and Mark at ATO 2025"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7tkur5mhf0haeczrzm1.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%2Fz7tkur5mhf0haeczrzm1.png" alt="Me, Lawrence and Diana at ATO 2025"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyykaudf5twkafmnlko74.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%2Fyykaudf5twkafmnlko74.png" alt="Me and Shruti at ATO 2025"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafsu1w2p93q7lmsk3jyw.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%2Fafsu1w2p93q7lmsk3jyw.png" alt="Me and friends at a social at ATO 2025"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2kmie83fmilcab860x5l.jpeg" 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%2F2kmie83fmilcab860x5l.jpeg" alt="the Commit Your Code crew"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;In early June, I became a GitHub Star. The community nominates people for this, and honestly, I didn't realize how few there were. As of writing this, there are only 88 stars on the planet. Pretty cool.&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%2Fxg12i6wm4krcvbbwht8x.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%2Fxg12i6wm4krcvbbwht8x.png" alt="My GitHub Star at GitHub Universe 2025"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the best perks? GitHub flew me out to GitHub Universe in October. I got to hang with other stars including people I'd known for years but never met in person, like Santosh Yadav and Corbin Crutchley. We grabbed dinner together and it was great finally meeting them face to face.&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%2Fspgkztbnbowvadk950pj.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%2Fspgkztbnbowvadk950pj.png" alt="Me, Wes and Kevin"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F42ce9vigve0bpyojwb84.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%2F42ce9vigve0bpyojwb84.png" alt="Me and fellow GitHub Star Jess Temporal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4rfhnxhxgsw18kicpk17.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%2F4rfhnxhxgsw18kicpk17.png" alt="Ruben, Santosh, Peli and me"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo630ogvnfck0yj8jabxc.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%2Fo630ogvnfck0yj8jabxc.png" alt="Me, Ruben and Corbin"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also became a Microsoft MVP, which was really cool. It definitely feels great to be recognized.&lt;/p&gt;

&lt;p&gt;Aside from that I became a mentor in the &lt;a href="https://discord.gg/aAzCA7fT" rel="noopener noreferrer"&gt;Let's Get Technical community&lt;/a&gt; where I'm one of the mentors for people new to the tech industry working through a real world project. We wrapped up our Secret Santa Exchange. Great work team!&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/LetsGetTechnical" rel="noopener noreferrer"&gt;
        LetsGetTechnical
      &lt;/a&gt; / &lt;a href="https://github.com/LetsGetTechnical/elecretanta" rel="noopener noreferrer"&gt;
        elecretanta
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An AI-enabled secret santa app to make managing gift exchanges and finding the perfect gift easy
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Secret Santa Exchange 🎅&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Secret Santa Exchange is an AI-powered Secret Santa platform that makes gift exchanges delightful and effortless. Perfect for organizing gift exchanges between colleagues, friends, and family, Secret Santa Exchange takes the guesswork out of gift-giving with personalized AI suggestions.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🎄 Hackathon Project&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;This project was created for the Let's Get Technical (LGT) discord community hackathon in December 2024.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prompt&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Greetings Elves! Christmas is around the corner and Santa needs your help. Billions of people participate in Secret Santa and many of them have no idea what they want for Christmas. Vise versa, many gifsters have no clue what kind of gift to give and want to ensure they're buying something valuable for their secret Santa. He needs a web application built that allows users to login and provide what they enjoy in life. This will help the secret Santa individuals narrow down a gift using Elecretanta…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/LetsGetTechnical/elecretanta" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;I don't usually monitor follower counts, but they've all been on the rise including hitting 125K followers on dev.to in early December which I think is pretty cool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing and Building
&lt;/h2&gt;

&lt;p&gt;I published 42 articles in 2025. December alone was 19 articles as I did the Advent of AI 2025 challenge, documenting my daily experiences with Goose.&lt;/p&gt;

&lt;p&gt;Here's some of the stuff I built:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/nickytonline/dev-to-mcp" rel="noopener noreferrer"&gt;dev.to MCP server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nickytonline/pimp-my-ride-mcp" rel="noopener noreferrer"&gt;Pimp My Ride MCP server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pomerium's &lt;a href="https://github.com/pomerium/mcp-app-demo" rel="noopener noreferrer"&gt;MCP app demo&lt;/a&gt; which is essentially an MCP client and it's not really a demo anymore since we use it internally. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nickytonline/mcp-typescript-template" rel="noopener noreferrer"&gt;TypeScript MCP template&lt;/a&gt; - A TypeScript template for building remote Model Context Protocol (MCP) servers with modern tooling and best practices while leveraging the MCP TypeScript SDK.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pomerium/chatgpt-app-typescript-template" rel="noopener noreferrer"&gt;ChatGPT apps template&lt;/a&gt; - A well-architected starter template demonstrating best practices for building ChatGPT apps using the Model Context Protocol (MCP) with React widgets. It leverages TypeScript, Tailwind CSS v4, Pino logging, Storybook, and Vitest for a robust development experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Guest Spots and Streaming
&lt;/h2&gt;

&lt;p&gt;I was a guest on some podcasts this year, including the freeCodeCamp podcast where I talked about turning open source into a job.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/m7nkioXNiik"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;I also was on &lt;a href="https://codetv.dev/" rel="noopener noreferrer"&gt;CodeTV's&lt;/a&gt; Web Dev Challenge with my partner in crime Shashi Lo!&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/ftYmXoH0V5I"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;It was another great year of live streaming at &lt;a href="https://nickyt.live" rel="noopener noreferrer"&gt;nickyt.live&lt;/a&gt;. I'm honestly always surprised how I land quality guests.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/evOvNWTxDPE"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;I also started the &lt;a href="https://www.youtube.com/@pomerium_io" rel="noopener noreferrer"&gt;Pomerium live stream&lt;/a&gt;! This interview with Den (an old co-worker) was one of my favourites.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/U9rSRnjis7c"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;I also continued doing the 2 Full 2 Stack live stream on the Certified Fresh Events YouTube channel. Brian Rinaldi (&lt;a class="mentioned-user" href="https://dev.to/remotesynth"&gt;@remotesynth&lt;/a&gt;) had invited me to do the live stream there a couple years ago, and it was always a blast. Sponsors dried up though, and I think it became less and less fun for Brian as the sponsorships disappeared. It was the right decision, but still sad to see it end.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/YOjFpR3vrEQ"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;h2&gt;
  
  
  My Newsletter
&lt;/h2&gt;

&lt;p&gt;My newsletter &lt;a href="https://onetipaweek.com" rel="noopener noreferrer"&gt;One Tip a Week&lt;/a&gt; had a great year. I started with 122 subscribers at the beginning of the year and ended with 343. Pretty solid steady growth. June was huge, adding 100 new subscribers (shoutout to Cassidy Williams for the boost). Engagement stayed solid: 53.3% open rate, 9.2% click-through rate.&lt;/p&gt;

&lt;p&gt;The key was keeping it simple. One tip per week, building up a backlog so there's no weekly pressure. The backlog of issues was key as it doesn't feel like a chore (like my old newsletter did).&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nickytonline/my-newsletter-growth-fun-slow-steady-4fpd" class="crayons-story__hidden-navigation-link"&gt;My Newsletter: Growth, Fun, Slow &amp;amp; Steady&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nickytonline" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F9597%2F8f1ed696-3e87-4bd4-809a-0ee9bb905c3c.jpg" alt="nickytonline profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nickytonline" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Nick Taylor
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Nick Taylor
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3105510" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nickytonline" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F9597%2F8f1ed696-3e87-4bd4-809a-0ee9bb905c3c.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Nick Taylor&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nickytonline/my-newsletter-growth-fun-slow-steady-4fpd" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Dec 15 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nickytonline/my-newsletter-growth-fun-slow-steady-4fpd" id="article-link-3105510"&gt;
          My Newsletter: Growth, Fun, Slow &amp;amp; Steady
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/contentcreation"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;contentcreation&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devtips"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devtips&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nickytonline/my-newsletter-growth-fun-slow-steady-4fpd" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;25&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nickytonline/my-newsletter-growth-fun-slow-steady-4fpd#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              11&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Looking Back
&lt;/h2&gt;

&lt;p&gt;2025 was about finding my footing in a completely new domain which I think I did really well. The learning curve was steep and I'm still learning, but it's been a lot of fun. And I gotta say it felt really good being recognized by the community.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking Ahead to 2026
&lt;/h2&gt;

&lt;p&gt;Heading into 2026, it's definitely more AI, security, conference talks and good times.&lt;/p&gt;

&lt;p&gt;If you want to stay in touch, all my socials are on &lt;a href="https://nickyt.online" rel="noopener noreferrer"&gt;nickyt.online&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@reskp?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Jametlene Reskp&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-penguin-standing-in-front-of-a-sign-that-says-205-0fRuFBueR40?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>yearinreview</category>
      <category>mcp</category>
      <category>career</category>
      <category>devwrapped</category>
    </item>
    <item>
      <title>What Makes Goose Different From Other AI Coding Agents</title>
      <dc:creator>Nick Taylor</dc:creator>
      <pubDate>Mon, 29 Dec 2025 05:43:03 +0000</pubDate>
      <link>https://dev.to/nickytonline/what-makes-goose-different-from-other-ai-coding-agents-2edc</link>
      <guid>https://dev.to/nickytonline/what-makes-goose-different-from-other-ai-coding-agents-2edc</guid>
      <description>&lt;p&gt;I just finished Goose’s &lt;a href="https://adventofai.dev/" rel="noopener noreferrer"&gt;Advent of AI&lt;/a&gt;. Everything from CI automation to hand-gesture controlled apps to creating model context protocol (MCP) servers with UI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://flightboard.nickyt.co/" rel="noopener noreferrer"&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%2Fmpc0zvsraivv3qvkf4xu.png" alt="Me testing out my hand gesture project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built almost all of these challenges in Goose. Mostly in the GUI at first, shifting to the CLI as I learned more. Occasionally by I’d jump to Claude to finish something, but Goose was my primary development environment. My workflow: convert the challenge to a &lt;a href="https://en.wikipedia.org/wiki/Product_requirements_document" rel="noopener noreferrer"&gt;product requirements document&lt;/a&gt; (PRD) and sometimes I’d compliment it with an evaluation rubric if the project was more complex, then implement it.&lt;/p&gt;

&lt;p&gt;After building with it daily for over two weeks, I can tell you what makes Goose different. But first, the baseline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Table Stakes Stuff
&lt;/h2&gt;

&lt;p&gt;Goose does all the things you expect an AI agent to do. A GUI with a chat UI, a CLI, and like some other agents, it’s model-agnostic (works with any LLM provider including local models), and in the case of some CLIs useful in CI/CD too.&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%2F2j67b1zxr39c1f4gdcci.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%2F2j67b1zxr39c1f4gdcci.png" alt="Goose chat UI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8jqapgv6kvetjuoxix4s.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%2F8jqapgv6kvetjuoxix4s.png" alt="Model modal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It also has first class support for MCPs, chat history, named sessions, &lt;a href="https://block.github.io/goose/docs/guides/subagents/" rel="noopener noreferrer"&gt;subagents for parallel task execution&lt;/a&gt;, &lt;a href="https://block.github.io/goose/docs/guides/context-engineering/using-skills/" rel="noopener noreferrer"&gt;skills for custom context&lt;/a&gt;, and like a lot of great software, it’s open source.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/block" rel="noopener noreferrer"&gt;
        block
      &lt;/a&gt; / &lt;a href="https://github.com/block/goose" rel="noopener noreferrer"&gt;
        goose
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      an open source, extensible AI agent that goes beyond code suggestions - install, execute, edit, and test with any LLM
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;goose&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;a local, extensible, open source AI agent that automates engineering tasks&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
  &lt;a href="https://opensource.org/licenses/Apache-2.0" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5b60841bea9e11d9d0b0950d690c9bc554e06385634056a7d5d62a15d1a4eabe/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4170616368655f322e302d626c75652e737667"&gt;&lt;/a&gt;
  &lt;a href="https://discord.gg/goose-oss" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2519ab6b1b4d6d4b7c86e1a0a3dfb43ccc449f504f9193bef87357f5f261552a/68747470733a2f2f696d672e736869656c64732e696f2f646973636f72642f313238373732393931383130303234363635343f6c6f676f3d646973636f7264266c6f676f436f6c6f723d7768697465266c6162656c3d4a6f696e2b557326636f6c6f723d626c756576696f6c6574" alt="Discord"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/block/goose/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/54c1aa543ed5e2f73ee65c268b0f32bc59c9ca507e5501d941d18641da65eff3/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f626c6f636b2f676f6f73652f63692e796d6c3f6272616e63683d6d61696e" alt="CI"&gt;&lt;/a&gt;
&lt;/p&gt;


&lt;/div&gt;

&lt;p&gt;goose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - &lt;em&gt;autonomously&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Whether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.&lt;/p&gt;

&lt;p&gt;Designed for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/D-DpDunrbpo" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F113943282%2F494478532-ddc71240-3928-41b5-8210-626dfb28af7a.jpg%3Fjwt%3DeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQ2NDE5MjUsIm5iZiI6MTc3NDY0MTYyNSwicGF0aCI6Ii8xMTM5NDMyODIvNDk0NDc4NTMyLWRkYzcxMjQwLTM5MjgtNDFiNS04MjEwLTYyNmRmYjI4YWY3YS5qcGc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjYwMzI3JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI2MDMyN1QyMDAwMjVaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1kMGRjNmYyNTFhZTU4NzJkMmQyZDE0MzkyY2VkYzM3MzM3Y2U0ZGM4ZGNiYzBkZWYzNGRkZjJiZjQ1YjIzMWQ2JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.kxZAuzDsF8nlxiEIDmd0zIKAt0ip0S_VtRaMvb_S8jY" alt="Watch the video"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Quick Links&lt;/h1&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/quickstart" rel="nofollow noopener noreferrer"&gt;Quickstart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/getting-started/installation" rel="nofollow noopener noreferrer"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/category/tutorials" rel="nofollow noopener noreferrer"&gt;Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/category/getting-started" rel="nofollow noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/block/goose/blob/main/GOVERNANCE.md" rel="noopener noreferrer"&gt;Governance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/block/goose/blob/main/CUSTOM_DISTROS.md" rel="noopener noreferrer"&gt;Custom Distributions&lt;/a&gt; - build your own goose distro with preconfigured providers…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/block/goose" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;Most of these features aren’t unique. OpenCode (also open source), Cursor, Copilot, Claude Code, and Windsurf all have most of these capabilities too.&lt;/p&gt;

&lt;p&gt;If that’s all Goose offered, this wouldn’t be worth writing about.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Actually Makes Goose Different
&lt;/h2&gt;

&lt;p&gt;Goose isn’t competing with Cursor’s autocomplete or Copilot’s inline suggestions. It’s a different category. Think build system for agent behavior, not better IDE integration.&lt;/p&gt;

&lt;p&gt;After all that daily use, three things stand out. (And apparently I’m not alone: according to Block, 60% of their workforce uses Goose weekly with reported 50-75% development time savings.)&lt;/p&gt;
&lt;h3&gt;
  
  
  Recipes: Reusable AI Workflows
&lt;/h3&gt;

&lt;p&gt;Most AI tools give you saved prompts or templates. Goose recipes are structured workflow definitions with actual capabilities.&lt;/p&gt;

&lt;p&gt;For &lt;a href="https://dev.to/nickytonline/advent-of-ai-2025-day-9-building-a-gift-tag-generator-with-goose-recipes-3i73"&gt;day 9&lt;/a&gt; and &lt;a href="https://dev.to/nickytonline/advent-of-ai-2025-day-15-goose-sub-recipes-3mnd"&gt;day 15&lt;/a&gt; of Advent of AI, I built projects that used &lt;a href="https://block.github.io/goose/docs/guides/recipes/" rel="noopener noreferrer"&gt;recipes&lt;/a&gt; and &lt;a href="https://block.github.io/goose/docs/guides/recipes/subrecipes/" rel="noopener noreferrer"&gt;sub-recipes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here’s what recipes actually give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parameter passing between workflow stages - Define &lt;code&gt;{{event_name}}&lt;/code&gt; once, use it everywhere&lt;/li&gt;
&lt;li&gt;Sub-recipe composition - Call sub-recipes from one parent recipe&lt;/li&gt;
&lt;li&gt;Environment extensions - Recipes automatically get access to globally configured MCP servers and built-in extensions&lt;/li&gt;
&lt;li&gt;Explicit extension specification - Pin specific MCP servers/tools for a recipe (supported in only YAML at the moment)&lt;/li&gt;
&lt;li&gt;Structured inputs - Define parameters with types, requirements, descriptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s what this looks like using the social media campaign recipe (&lt;a href="https://www.nickyt.co/blog/advent-of-ai-2025-day-15-goose-sub-recipes-3mnd/" rel="noopener noreferrer"&gt;Advent of AI day 15&lt;/a&gt;) that orchestrates three platform-specific sub-recipes:&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%2Fgxi94yjbasoply9i5i00.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%2Fgxi94yjbasoply9i5i00.png" alt="Recipe UI showing parameters"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full YAML structure:&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;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Social Media Campaign Generator&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;Generate complete cross-platform social media campaign using sub-recipes&lt;/span&gt;
&lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;You are a social media campaign coordinator creating a comprehensive multi-platform campaign.&lt;/span&gt;

  &lt;span class="s"&gt;Generate a complete social media campaign for the following event:&lt;/span&gt;
  &lt;span class="s"&gt;- event_name: {{event_name}}&lt;/span&gt;
  &lt;span class="s"&gt;- event_date: {{event_date}}&lt;/span&gt;
  &lt;span class="s"&gt;- event_description: {{event_description}}&lt;/span&gt;
  &lt;span class="s"&gt;- target_audience: {{target_audience}}&lt;/span&gt;
  &lt;span class="s"&gt;- call_to_action: {{call_to_action}}&lt;/span&gt;

  &lt;span class="s"&gt;Campaign Strategy:&lt;/span&gt;
  &lt;span class="s"&gt;Execute the following sub-recipes to create platform-specific content:&lt;/span&gt;

  &lt;span class="s"&gt;1. Instagram Content: Run the instagram-post.yaml recipe&lt;/span&gt;
  &lt;span class="s"&gt;2. Twitter/X Thread: Run the twitter-thread.yaml recipe&lt;/span&gt;
  &lt;span class="s"&gt;3. Facebook Event: Run the facebook-event.yaml recipe&lt;/span&gt;

  &lt;span class="s"&gt;[Format and present complete campaign organized by platform]&lt;/span&gt;

&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate complete social media campaign for {{event_name}}&lt;/span&gt;

&lt;span class="na"&gt;sub_recipes&lt;/span&gt;&lt;span class="pi"&gt;:&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instagram_content"&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./instagram-post.yaml"&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;event_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{event_name}}"&lt;/span&gt;
      &lt;span class="na"&gt;event_date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{event_date}}"&lt;/span&gt;
      &lt;span class="na"&gt;event_description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{event_description}}"&lt;/span&gt;
      &lt;span class="na"&gt;target_audience&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{target_audience}}"&lt;/span&gt;
      &lt;span class="na"&gt;call_to_action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{call_to_action}}"&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;twitter_content"&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./twitter-thread.yaml"&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;event_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{event_name}}"&lt;/span&gt;
      &lt;span class="na"&gt;event_date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{event_date}}"&lt;/span&gt;
      &lt;span class="na"&gt;event_description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{event_description}}"&lt;/span&gt;
      &lt;span class="na"&gt;target_audience&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{target_audience}}"&lt;/span&gt;
      &lt;span class="na"&gt;call_to_action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{call_to_action}}"&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;facebook_content"&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./facebook-event.yaml"&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;event_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{event_name}}"&lt;/span&gt;
      &lt;span class="na"&gt;event_date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{event_date}}"&lt;/span&gt;
      &lt;span class="na"&gt;event_description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{event_description}}"&lt;/span&gt;
      &lt;span class="na"&gt;target_audience&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{target_audience}}"&lt;/span&gt;
      &lt;span class="na"&gt;call_to_action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{call_to_action}}"&lt;/span&gt;

&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;event_name&lt;/span&gt;
    &lt;span class="na"&gt;input_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;requirement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;required&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;Name of the festival event&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;event_date&lt;/span&gt;
    &lt;span class="na"&gt;input_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;requirement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;required&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;When the event is happening&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;event_description&lt;/span&gt;
    &lt;span class="na"&gt;input_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;requirement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;required&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;What the event is about&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;target_audience&lt;/span&gt;
    &lt;span class="na"&gt;input_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;requirement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;required&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;Who should attend&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;call_to_action&lt;/span&gt;
    &lt;span class="na"&gt;input_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;requirement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;required&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;What you want people to do&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run this recipe, Goose executes each sub-recipe with the same parameters. Each sub-recipe handles its platform’s specifics. The parent recipe just coordinates.&lt;/p&gt;

&lt;p&gt;You can also pin which MCP extensions a recipe should use. This is currently only available in YAML (not the UI), but it’s powerful for reproducible workflows. This guarantees the recipe runs with specific tools, regardless of what’s in your global config.&lt;/p&gt;

&lt;p&gt;A more typical engineering workflow would be generating weekly status updates by querying Linear, GitHub, and Notion. Check out this &lt;a href="https://gist.github.com/nickytonline/9e6702893ed2ca6ad9a62e2337583ba9" rel="noopener noreferrer"&gt;gist for the recipe&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also make a recipe a slash command.&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%2Fjb8c6k5dc8f8czc5z640.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%2Fjb8c6k5dc8f8czc5z640.png" alt="Configuring a recipe as a slash command"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that set up, if I want to get my weekly status update, all I need to do is run &lt;code&gt;/weekly-status&lt;/code&gt; in the GUI or REPL and get a formatted report with links to the issues, pull requests etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters architecturally:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without recipes, you’d copy-paste prompts and manually pass values between steps. With recipes you get version control (YAML in git means you can diff changes, revert mistakes), team sharing (your team uses the exact same workflow, not “here’s a prompt I used once”), composition (build complex workflows from simple, tested building blocks), and debugging (each sub-recipe runs independently, so you can test in isolation).&lt;/p&gt;

&lt;p&gt;This is different from “I saved a good prompt.” This is workflow infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How this differs from .cursor/rules or custom instructions:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most AI coding tools support rules in some form. Cursor has &lt;code&gt;.cursor/rules&lt;/code&gt;, Cline has &lt;code&gt;.clinerules&lt;/code&gt;, and there’s the &lt;code&gt;AGENTS.md&lt;/code&gt; standard that works across tools. These are project-specific prompting guidelines. They improve how the agent understands your codebase and preferences, but they’re not reusable workflows.&lt;/p&gt;

&lt;p&gt;The difference: Rules files tell the agent “when you write code in this project, follow these patterns.” Recipes say “execute these specific steps in this order with these parameters.” One is better prompting. The other is workflow orchestration.&lt;/p&gt;

&lt;p&gt;Put simply: Rules change how the agent behaves. Recipes change what the agent does.&lt;/p&gt;

&lt;p&gt;You can’t compose rules files together, pass parameters between them, or run them as isolated workflows. They’re context for better prompting, not executable infrastructure.&lt;/p&gt;

&lt;p&gt;Compare this to slash commands in other tools. Those are saved prompts with placeholders. They can’t orchestrate multi-stage workflows, adjust parameters between stages, or compose with other workflows.&lt;/p&gt;

&lt;h4&gt;
  
  
  Recipes + Subagents: Parallel Workflows
&lt;/h4&gt;

&lt;p&gt;With &lt;a href="https://block.github.io/goose/docs/guides/subagents/" rel="noopener noreferrer"&gt;Subagents&lt;/a&gt;, recipes give you infrastructure to orchestrate them effectively. Here’s a video processing recipe that spawns 4 parallel subagents:&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;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Video Tools&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;A set of tools for processing videos&lt;/span&gt;
&lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;You are a video processing assistant&lt;/span&gt;

  &lt;span class="s"&gt;Process {{video_file}} with real-time progress updates.&lt;/span&gt;

  &lt;span class="s"&gt;STEP 1 - Immediate acknowledgment:&lt;/span&gt;
  &lt;span class="s"&gt;Run: echo "🎬 Processing video: {{video_file}}" | tee /dev/tty&lt;/span&gt;
  &lt;span class="s"&gt;STEP 2 - Check dependencies (ffmpeg, ffprobe)&lt;/span&gt;
  &lt;span class="s"&gt;STEP 3 - Analyze video (duration, resolution, codec, audio presence)&lt;/span&gt;
  &lt;span class="s"&gt;STEP 4 - Launch parallel subagents:&lt;/span&gt;

  &lt;span class="s"&gt;=== SUBAGENT 1: Smart Compression ===&lt;/span&gt;
  &lt;span class="s"&gt;- Analyze video type (screencast vs camera footage)&lt;/span&gt;
  &lt;span class="s"&gt;- Choose optimal CRF value (18-28 range) based on content&lt;/span&gt;
  &lt;span class="s"&gt;- Adjust resolution if beneficial (4K→1080p for screencasts)&lt;/span&gt;
  &lt;span class="s"&gt;- Compress with ffmpeg: -c:v libx264 -crf [chosen] -preset medium&lt;/span&gt;
  &lt;span class="s"&gt;- Output: {{video_file}}_compressed.mp4&lt;/span&gt;
  &lt;span class="s"&gt;- Report before/after sizes and compression ratio&lt;/span&gt;

  &lt;span class="s"&gt;=== SUBAGENT 2: Intelligent Thumbnails ===&lt;/span&gt;
  &lt;span class="s"&gt;- Detect video content type&lt;/span&gt;
  &lt;span class="s"&gt;- For screencasts: extract at 20%, 40%, 60%, 80%, 100% intervals&lt;/span&gt;
  &lt;span class="s"&gt;- For camera footage: use ffmpeg scene detection for key frames&lt;/span&gt;
  &lt;span class="s"&gt;- Generate 5 thumbnails at 320px width&lt;/span&gt;
  &lt;span class="s"&gt;- Output: {{video_file}}_thumb_1.jpg through _thumb_5.jpg&lt;/span&gt;

  &lt;span class="s"&gt;=== SUBAGENT 3: Audio Extraction (ONLY IF AUDIO DETECTED) ===&lt;/span&gt;
  &lt;span class="s"&gt;- Extract audio using ffmpeg: -c:a libmp3lame -q:a 2&lt;/span&gt;
  &lt;span class="s"&gt;- Output: {{video_file}}_audio.mp3&lt;/span&gt;
  &lt;span class="s"&gt;- Report audio file size and bitrate&lt;/span&gt;

  &lt;span class="s"&gt;=== SUBAGENT 4: Transcription &amp;amp; Analysis (ONLY IF AUDIO DETECTED) ===&lt;/span&gt;
  &lt;span class="s"&gt;- Run: uv run --with openai-whisper whisper {{video_file}} --model base&lt;/span&gt;
  &lt;span class="s"&gt;- Output: .txt, .srt, .vtt, .tsv, .json caption files&lt;/span&gt;
  &lt;span class="s"&gt;- Analyze transcript: word count, speaking pace, content type&lt;/span&gt;
  &lt;span class="s"&gt;- Brief content summary&lt;/span&gt;

  &lt;span class="s"&gt;STEP 5 - Monitor subagents and report completions immediately&lt;/span&gt;
  &lt;span class="s"&gt;STEP 6 - Final summary with all generated files&lt;/span&gt;

&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;process {{video_file}}&lt;/span&gt;

&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;video_file&lt;/span&gt;
    &lt;span class="na"&gt;input_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;requirement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;required&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;The video file to optimize&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want to try this directly in Goose?&lt;/p&gt;




&lt;p&gt;Click on the &lt;a&gt;recipe share link&lt;/a&gt; to import this recipe directly into Goose, no copy-paste needed.&lt;/p&gt;




&lt;p&gt;Each subagent runs independently with progress updates via &lt;code&gt;echo | tee /dev/tty&lt;/code&gt;. The recipe coordinates them, handles failures gracefully (if compression fails, you still get thumbnails), and provides a unified summary.&lt;/p&gt;

&lt;p&gt;Without recipes, you’d have a saved prompt where you manually edit the filename each time: “Process my-video.mp4 by spawning 4 subagents…” Want to process a different video? Find and replace the filename, hope you didn’t miss one. Want to share this workflow with a teammate? Send them your entire conversation.&lt;/p&gt;

&lt;p&gt;With recipes, I can process a video in one line &lt;code&gt;/video-tools any-file.mp4&lt;/code&gt;. I can also &lt;a&gt;share it as a deeplink&lt;/a&gt; that imports the recipe with one click.&lt;/p&gt;

&lt;p&gt;Recipes are parameterized, versionable (if you want), and shareable as standalone workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why infrastructure matters for teams:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Recipes aren’t just about personal productivity. They’re definitely great for that, but they’re also about making agent workflows institutional knowledge instead of undocumented knowledge.&lt;/p&gt;

&lt;p&gt;Auditability: Goose sessions can be exported as JSON with full metadata: token usage, model config, working directory, timestamps. You know exactly what a workflow cost and how it executed as well as the conversation history.&lt;/p&gt;

&lt;p&gt;Here’s what a session export looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20251228_72"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"working_dir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/Users/nicktaylor/dev/oss/wishlist-mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Nike Sales Bar Chart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-12-28T19:53:24Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10297&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10034&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;263&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provider_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"anthropic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model_config"&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;"model_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"claude-opus-4-5-20251101"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"context_limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200000&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;"conversation"&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="err"&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;Compare this to “export all your data” bulk downloads that most cloud models offer. With per-session exports, you can track workflow costs, analyze token usage patterns, and reproduce exact execution environments.&lt;/p&gt;

&lt;p&gt;Onboarding: New team members can run &lt;code&gt;/onboarding&lt;/code&gt; instead of hunting down the 17-step checklist your senior engineer keeps in a Google Doc.&lt;/p&gt;

&lt;p&gt;Consistency: When everyone runs the same recipe with the same pinned extensions, you get fewer “works on my machine” agent outcomes.&lt;/p&gt;

&lt;p&gt;Separation of concerns: Recipe authors encode expertise once. Anyone on the team can execute workflows without needing to understand every implementation detail.&lt;/p&gt;

&lt;p&gt;Standards-based: Goose is an early contributor to the Linux Foundation’s &lt;a href="https://block.xyz/inside/block-anthropic-and-openai-launch-the-agentic-ai-foundation" rel="noopener noreferrer"&gt;AI Agent Interoperability Foundation&lt;/a&gt; (AAIF), alongside Anthropic and OpenAI. Recipes and MCP integration position you for the emerging standard, not a proprietary lock-in.&lt;/p&gt;

&lt;p&gt;Prompts are individual knowledge. Recipes can be institutional knowledge.&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP-UI Rendering Support
&lt;/h3&gt;

&lt;p&gt;Most MCP client implementations render tool responses as text. Goose’s GUI can also render &lt;a href="https://mcpui.dev/" rel="noopener noreferrer"&gt;MCP-UI&lt;/a&gt; components as actual interactive widgets.&lt;/p&gt;

&lt;p&gt;On Day 17 of Advent of AI, I &lt;a href="https://www.nickyt.co/blog/advent-of-ai-2025-day-17-building-a-wishlist-app-with-goose-and-mcp-ui-330l/" rel="noopener noreferrer"&gt;built a wishlist MCP server&lt;/a&gt; that returns UI components.&lt;/p&gt;

&lt;p&gt;In Goose, it showed up as an interactive widget. In other MCP clients without UI support, it would just be text.&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%2Fn8x9515ndgfeqb85jbos.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%2Fn8x9515ndgfeqb85jbos.png" alt="Wishing to hang with Snoop and Martha"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://block.github.io/goose/docs/mcp/autovisualiser-mcp/" rel="noopener noreferrer"&gt;auto-visualizer extension&lt;/a&gt; leverages this too. Instead of text dumps of data, you get rendered visualizations you can interact with.&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%2Fdxlv6tt4cy6o7th4q8g5.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%2Fdxlv6tt4cy6o7th4q8g5.png" alt="auto-visualizer extension in action"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Right now, only three clients support MCP-UI properly: Goose, ChatGPT (via their Apps SDK), and LibreChat. Everyone else does text-based responses. Having a client that can also render UI components instead of just text is a better experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Terminal Integration Architecture
&lt;/h3&gt;

&lt;p&gt;Goose has two modes: the full REPL (chat back and forth) like other CLIs, and terminal integration (&lt;code&gt;@goose "do this"&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://block.github.io/goose/docs/guides/terminal-integration/" rel="noopener noreferrer"&gt;Terminal integration&lt;/a&gt; is ambient assistance. You’re working in your normal terminal. You invoke Goose for a specific task. It executes. You’re back to your normal workflow.&lt;/p&gt;

&lt;p&gt;Example from Day 13:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ @goose &lt;span class="s2"&gt;"continue with the PRD, we were at data organization"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Goose reads the PRD, checks what’s already done in the project, tells you the next step. All from one command. No session management.&lt;/p&gt;

&lt;p&gt;This architecture lets you hand off tasks the agent can’t do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ @goose &lt;span class="s2"&gt;"install this package globally"&lt;/span&gt;
&lt;span class="c"&gt;# Goose: "You need to run: sudo npm install -g whatever"&lt;/span&gt;
❯ &lt;span class="nb"&gt;sudo &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; whatever  &lt;span class="c"&gt;# You run it with your privileges&lt;/span&gt;
❯ @goose &lt;span class="s2"&gt;"okay continue"&lt;/span&gt;  &lt;span class="c"&gt;# Goose sees you ran it, continues&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No need to open a separate terminal to do the thing the agent can’t do and then come back to where you left off in your REPL session. Stay in your flow in your current terminal session.&lt;/p&gt;

&lt;p&gt;Terminal integration confused me initially, but once but once I had the aha moment thanks to a similar issue I run into with &lt;code&gt;sudo&lt;/code&gt; all the time with AI, I realized this is a great flow.&lt;/p&gt;

&lt;p&gt;There’s also named sessions which persist across terminal closes:&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="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;goose term init zsh &lt;span class="nt"&gt;--session&lt;/span&gt; my-project&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Close your terminal, come back tomorrow, run the same command, you’re back in the conversation.&lt;/p&gt;

&lt;p&gt;This is infrastructure for integrating AI assistance into your existing workflow, not replacing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When It Works (And When It Doesn’t)
&lt;/h2&gt;

&lt;p&gt;The GUI is better for longer back-and-forth or even the CLI in REPL mode. Terminal integration is better for quick tasks or continuing work.&lt;/p&gt;

&lt;p&gt;As mentioned, Goose supports subagents for parallel task execution. Each subagent runs in its own isolated session. If one fails, you only get results from successful ones.&lt;/p&gt;

&lt;p&gt;The subagent infrastructure is solid. The only thing I ran into was with GPT-4.1 (switched to it when my tokens were low). Goose wasn't able to spawn subagents with GPT-4.1. Not sure why. Anthropic's Sonnet 4.x and Opus models worked perfectly with subagents.&lt;/p&gt;

&lt;p&gt;Recipe YAML validation could be better. When I asked the model to generate recipes during Day 9, it got the format wrong multiple times. I’d included &lt;a href="https://block.github.io/goose/llms.txt" rel="noopener noreferrer"&gt;https://block.github.io/goose/llms.txt&lt;/a&gt; as reference, so I’m not sure why the model struggled with it. Not necessarily a direct Goose issue, but I'm surprised with access to Goose's llms.txt, the model couldn't generate a valid Goose recipe YAML file.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Still Use Claude.ai For
&lt;/h2&gt;

&lt;p&gt;If I’m being honest, I still write my blog posts on Claude.ai, not Goose. Not because Goose is bad at it, but because I already have Claude Projects set up with my DevRel system prompts, evaluation rubrics, and content templates. I’m just used to that workflow.&lt;/p&gt;

&lt;p&gt;Maybe that’ll change as I use Goose more. Right now, Goose is where I automate things like weekly status updates. Claude.ai is where I write about them. Different tools for different jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick the Right Tool for the Job
&lt;/h2&gt;

&lt;p&gt;Different tools solve different problems.&lt;/p&gt;

&lt;p&gt;If you want the best inline autocomplete, go with Cursor or GitHub Copilot in VS Code (their multi-line suggestions and context awareness are excellent). That said I think this kind of workflow is going to go the way of the dinosaur. As someone whose done a lot of frontend work, I'm noticing myself less and less editing in the IDE and more just code reviewing what got generated.&lt;/p&gt;

&lt;p&gt;If you need tight VS Code integration, go with GitHub Copilot (native extension with deep editor hooks).&lt;/p&gt;

&lt;p&gt;If you want a polished terminal interface, Claude Code or OpenCode are excellent choices.&lt;/p&gt;

&lt;p&gt;If you want infrastructure for repeatable workflows, go with Goose.&lt;/p&gt;

&lt;p&gt;Goose makes sense if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build the same type of project repeatedly (recipes save you from re-prompting)&lt;/li&gt;
&lt;li&gt;Want AI assistance without leaving your terminal (ambient mode)&lt;/li&gt;
&lt;li&gt;Work with teams that need reproducible workflows (YAML in git)&lt;/li&gt;
&lt;li&gt;Build tools that need interactive UIs (MCP-UI rendering)&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%2F7h5envbtewzeublll2v9.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%2F7h5envbtewzeublll2v9.png" alt="Geese infra"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And of course it has all the table stakes features you expect in an AI agent which also makes Goose a solid choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;You can bring your existing AI subscriptions to Goose: GitHub Copilot, Cursor, OpenAI, Anthropic, or any OpenAI-compatible provider. Goose is model-agnostic.&lt;/p&gt;

&lt;p&gt;If you don’t have any subscriptions, start with &lt;a href="https://block.github.io/goose/blog/2025/03/14/goose-ollama/" rel="noopener noreferrer"&gt;Ollama and local models&lt;/a&gt;. Free, private, and runs entirely on your machine.&lt;/p&gt;

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

&lt;p&gt;The fastest way to understand Goose is the &lt;a href="https://adventofai.dev" rel="noopener noreferrer"&gt;Advent of AI challenges&lt;/a&gt;. Even though it’s over for this year, it’s a great way to wrap your head around what Goose has to offer.&lt;/p&gt;

&lt;p&gt;Don’t start by migrating your current workflow. Start by automating one repetitive task as a recipe. Later on, consider migrating your current workflow.&lt;/p&gt;

&lt;p&gt;Check out my &lt;a href="https://github.com/nickytonline/advent-of-ai-2025" rel="noopener noreferrer"&gt;Advent of AI 2025 repo&lt;/a&gt; to see what I built.&lt;/p&gt;

&lt;p&gt;Further reading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://block.github.io/goose/docs/tutorials/rpi/" rel="noopener noreferrer"&gt;RPI pattern&lt;/a&gt; (Research → Plan → Implement)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://block.github.io/goose/blog/2025/07/28/streamlining-detection-development-with-goose-recipes/" rel="noopener noreferrer"&gt;Security detection workflow&lt;/a&gt; (real team usage)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://block.github.io/goose/blog/2025/12/15/code-mode-mcp/" rel="noopener noreferrer"&gt;Code Execution mode&lt;/a&gt; (experimental, more efficient MCP)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://open.spotify.com/episode/20iTChEyuXaXryZOVAJoSi" rel="noopener noreferrer"&gt;Steve Yegge on the Latent Space podcast&lt;/a&gt; discussing vibe coding and agent orchestration (where this is all heading)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to stay in touch, all my socials are on &lt;a href="https://nickyt.online" rel="noopener noreferrer"&gt;nickyt.online&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Until the next one!&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@paolo_gregotti?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Paolo Gregotti&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-white-goose-standing-in-front-of-a-blue-wall-JR83i95gTqg?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>goose</category>
      <category>ai</category>
      <category>agents</category>
      <category>cli</category>
    </item>
    <item>
      <title>Advent of AI 2025 - Day 17: Building a Wishlist App with Goose and MCP-UI</title>
      <dc:creator>Nick Taylor</dc:creator>
      <pubDate>Sat, 27 Dec 2025 15:33:49 +0000</pubDate>
      <link>https://dev.to/nickytonline/advent-of-ai-2025-day-17-building-a-wishlist-app-with-goose-and-mcp-ui-330l</link>
      <guid>https://dev.to/nickytonline/advent-of-ai-2025-day-17-building-a-wishlist-app-with-goose-and-mcp-ui-330l</guid>
      <description>&lt;p&gt;I've edited this post, but AI helped. These are meant to be quick posts related to the Advent of AI. I don't have time if I'm doing one of these each day to spend a couple hours on a post. 😅&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://adventofai.dev" rel="noopener noreferrer"&gt;advent of AI&lt;/a&gt; series leverages Goose, an open source AI agent. If you've never heard of it, check it out!&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/block" rel="noopener noreferrer"&gt;
        block
      &lt;/a&gt; / &lt;a href="https://github.com/block/goose" rel="noopener noreferrer"&gt;
        goose
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      an open source, extensible AI agent that goes beyond code suggestions - install, execute, edit, and test with any LLM
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;goose&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;a local, extensible, open source AI agent that automates engineering tasks&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
  &lt;a href="https://opensource.org/licenses/Apache-2.0" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5b60841bea9e11d9d0b0950d690c9bc554e06385634056a7d5d62a15d1a4eabe/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4170616368655f322e302d626c75652e737667"&gt;&lt;/a&gt;
  &lt;a href="https://discord.gg/goose-oss" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2519ab6b1b4d6d4b7c86e1a0a3dfb43ccc449f504f9193bef87357f5f261552a/68747470733a2f2f696d672e736869656c64732e696f2f646973636f72642f313238373732393931383130303234363635343f6c6f676f3d646973636f7264266c6f676f436f6c6f723d7768697465266c6162656c3d4a6f696e2b557326636f6c6f723d626c756576696f6c6574" alt="Discord"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/block/goose/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/54c1aa543ed5e2f73ee65c268b0f32bc59c9ca507e5501d941d18641da65eff3/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f626c6f636b2f676f6f73652f63692e796d6c3f6272616e63683d6d61696e" alt="CI"&gt;&lt;/a&gt;
&lt;/p&gt;


&lt;/div&gt;

&lt;p&gt;goose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - &lt;em&gt;autonomously&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Whether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.&lt;/p&gt;

&lt;p&gt;Designed for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/D-DpDunrbpo" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F113943282%2F494478532-ddc71240-3928-41b5-8210-626dfb28af7a.jpg%3Fjwt%3DeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQ2NDE5MTIsIm5iZiI6MTc3NDY0MTYxMiwicGF0aCI6Ii8xMTM5NDMyODIvNDk0NDc4NTMyLWRkYzcxMjQwLTM5MjgtNDFiNS04MjEwLTYyNmRmYjI4YWY3YS5qcGc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjYwMzI3JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI2MDMyN1QyMDAwMTJaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1jOTJiY2RmNWQ2MmYwZTQ0ZGE1OWM1OWM3ZTY5ZDZmNDM1OTczNzhmNTcwYzc4MTU3OTM3ZDBhN2I1M2FkNjNkJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.vHNmtMHKOvtZvuUcySO81VAChRlF46a7CTVHdroT4W8" alt="Watch the video"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Quick Links&lt;/h1&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/quickstart" rel="nofollow noopener noreferrer"&gt;Quickstart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/getting-started/installation" rel="nofollow noopener noreferrer"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/category/tutorials" rel="nofollow noopener noreferrer"&gt;Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/category/getting-started" rel="nofollow noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/block/goose/blob/main/GOVERNANCE.md" rel="noopener noreferrer"&gt;Governance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/block/goose/blob/main/CUSTOM_DISTROS.md" rel="noopener noreferrer"&gt;Custom Distributions&lt;/a&gt; - build your own goose distro with preconfigured providers…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/block/goose" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;For &lt;a href="https://adventofai.dev/challenges/17" rel="noopener noreferrer"&gt;Day 17 of the Advent of AI challenge&lt;/a&gt;, I built a Winter Wishlist app using &lt;a href="https://mcpui.dev/" rel="noopener noreferrer"&gt;MCP-UI&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;A wishlist MCP server that renders a visual UI directly in Goose. Users can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add wishes with a quick message to Goose&lt;/li&gt;
&lt;li&gt;View all their wishes in a nicely formatted UI&lt;/li&gt;
&lt;li&gt;Grant wishes when they come true&lt;/li&gt;
&lt;li&gt;Delete wishes they no longer want&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%2Fs7ebr2hjj63cykxhh3so.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%2Fs7ebr2hjj63cykxhh3so.png" alt="Asking the wishlist MCP server for a wish"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check out the repo:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nickytonline" rel="noopener noreferrer"&gt;
        nickytonline
      &lt;/a&gt; / &lt;a href="https://github.com/nickytonline/wishlist-mcp" rel="noopener noreferrer"&gt;
        wishlist-mcp
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Advent of AI Day 17: Wishlist MCP App
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🎄 Winter Fairy's Wishbox&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A magical &lt;a href="https://mcpui.dev/" rel="nofollow noopener noreferrer"&gt;MCP-UI&lt;/a&gt; application that brings the Winter Festival's enchanted wishbox to life! Built for &lt;a href="https://adventofai.com/" rel="nofollow noopener noreferrer"&gt;Advent of AI 2025 - Day 17&lt;/a&gt;, this app lets you make wishes, view them in a beautiful UI, grant them when they come true, and release them when needed - all within your MCP client like goose or ChatGPT.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📖 The Story&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;In the center of the Winter Festival stands an ancient, frost-covered mailbox known as the Winter Fairy's Wishbox. For generations, children (and adults too!) have written their wishes on paper and dropped them in, hoping the Winter Fairy would see them. This year, the magic has gone digital! Make wishes, watch them appear in a beautiful enchanted interface, and track them as the Winter Fairy grants them!&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;✨ Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;🌟 Make Wishes&lt;/strong&gt; - Tell goose your wishes with categories (toy/experience/kindness/magic) and priorities (dream wish/hopeful wish/small wish)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;📋&lt;/strong&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nickytonline/wishlist-mcp" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;The implementation stores wishes in memory based on MCP session ID. Obviously something more robust would make sense for production, but for this challenge it works great.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ChatGPT App Template Foundation
&lt;/h2&gt;

&lt;p&gt;I based this on a &lt;a href="https://github.com/pomerium/chatgpt-app-typescript-template" rel="noopener noreferrer"&gt;ChatGPT app TypeScript template&lt;/a&gt; I had created for work just before the holiday. Having that foundation made spinning up the wishlist server much faster.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/pomerium" rel="noopener noreferrer"&gt;
        pomerium
      &lt;/a&gt; / &lt;a href="https://github.com/pomerium/chatgpt-app-typescript-template" rel="noopener noreferrer"&gt;
        chatgpt-app-typescript-template
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      ChatGPT app template using Pomerium, OpenAI Apps SDK and Model Context Protocol (MCP), with a Node.js server and React widgets.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;MCP Apps Template&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A well-architected starter template demonstrating best practices for building MCP Apps using the &lt;a href="https://modelcontextprotocol.io/" rel="nofollow noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; (MCP) with &lt;a href="https://react.dev/" rel="nofollow noopener noreferrer"&gt;React&lt;/a&gt; widgets. It leverages TypeScript, Tailwind CSS v4, Pino logging, Storybook, and Vitest for a robust development experience.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP Server&lt;/strong&gt; - Node.js server with &lt;code&gt;McpServer&lt;/code&gt; and MCP Apps helpers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Echo Tool&lt;/strong&gt; - Example tool with &lt;a href="https://zod.dev/" rel="nofollow noopener noreferrer"&gt;Zod&lt;/a&gt; validation and UI binding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React Widgets&lt;/strong&gt; - Interactive Echo component with MCP Apps &lt;code&gt;App&lt;/code&gt; API demo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Display Modes&lt;/strong&gt; - Inline, picture-in-picture, and fullscreen with runtime toggling via &lt;code&gt;requestDisplayMode()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App API Demo&lt;/strong&gt; - &lt;code&gt;callServerTool&lt;/code&gt;, &lt;code&gt;openLink&lt;/code&gt;, &lt;code&gt;sendMessage&lt;/code&gt;, &lt;code&gt;updateModelContext&lt;/code&gt; showcased in the Echo widget&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI Capability Negotiation&lt;/strong&gt; - Server detects host capabilities and falls back to text-only for non-UI clients&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inline Widget Assets&lt;/strong&gt; - Self-contained HTML mode for hosts that sandbox iframes (e.g. Claude.ai)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container Dimensions&lt;/strong&gt; - Responsive widget sizing using host-provided &lt;code&gt;containerDimensions&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mock App&lt;/strong&gt; - Drop-in &lt;code&gt;createMockApp()&lt;/code&gt; helper for testing and…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/pomerium/chatgpt-app-typescript-template" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;One of the best parts of this setup is the development workflow. You can build out your components in Storybook, make live edits to widgets, and see them update in Goose in real-time. This makes iterating on the UI super fast.&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%2F4ncywtm1geo840fsd41s.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ncywtm1geo840fsd41s.gif" alt="components in Storybook"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The iframe Sizing Battle
&lt;/h2&gt;

&lt;p&gt;The main issue I ran into was iframe sizing. In ChatGPT apps, the iframe sizing seems to handle itself based on content. But in Goose? Not so much. I had to add some JavaScript to handle the sizing properly.&lt;/p&gt;

&lt;p&gt;Initially I thought I was doing something silly because all my iframes weren't sizing to my content. There's additional metadata you can add to the UI resource, but that didn't change anything. I even noticed while debugging that my iframe was inside another iframe, which seemed odd.&lt;/p&gt;

&lt;p&gt;Then &lt;a href="https://github.com/block/goose/discussions/6249#discussioncomment-15338156" rel="noopener noreferrer"&gt;Rizel in a GitHub discussion&lt;/a&gt; (&lt;a class="mentioned-user" href="https://dev.to/blackgirlbytes"&gt;@blackgirlbytes&lt;/a&gt;) came through with the solution. You need to use a ResizeObserver on your content container and post messages to the parent frame for Goose:&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;// Auto-resize the iframe to fit content - observe only the container&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.container&lt;/span&gt;&lt;span class="dl"&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;container&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Send initial size&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&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="s2"&gt;ui-size-change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetHeight&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;100&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="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Observe container only (not document which snowflakes affect)&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ResizeObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&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="s2"&gt;ui-size-change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentRect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;100&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="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&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 fixed it completely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Learnings
&lt;/h2&gt;

&lt;p&gt;MCP-UI is powerful for creating visual interfaces right in your AI chat. Having a solid template to start from made a huge difference. If you're building MCP servers, especially UI-based ones, starting with a good foundation saves a ton of time.&lt;/p&gt;

&lt;p&gt;Even if you missed the Advent of AI this year I encourage you to head over to &lt;a href="https://AdventOfAI.dev" rel="noopener noreferrer"&gt;AdventOfAI.dev&lt;/a&gt; and jump in.&lt;/p&gt;

&lt;p&gt;If you want to stay in touch, all my socials are on &lt;a href="https://nickyt.online" rel="noopener noreferrer"&gt;nickyt.online&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Until the next one!&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@philstanier?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Phil Stanier&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-dandelion-is-blowing-in-the-wind-jRVweo-NE1g?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>goose</category>
    </item>
    <item>
      <title>Advent of AI 2025 - Day 16: Planning With .goosehints</title>
      <dc:creator>Nick Taylor</dc:creator>
      <pubDate>Tue, 23 Dec 2025 15:52:23 +0000</pubDate>
      <link>https://dev.to/nickytonline/advent-of-ai-2025-day-16-planning-with-goosehints-875</link>
      <guid>https://dev.to/nickytonline/advent-of-ai-2025-day-16-planning-with-goosehints-875</guid>
      <description>&lt;p&gt;I've edited this post, but AI helped. These are meant to be quick posts related to the Advent of AI. If I'm doing them correctly, they should take me between 30 minutes to 1 hour max to write.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://adventofai.dev" rel="noopener noreferrer"&gt;advent of AI&lt;/a&gt; series leverages Goose, an open source AI agent. If you've never heard of it, check it out!&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/block" rel="noopener noreferrer"&gt;
        block
      &lt;/a&gt; / &lt;a href="https://github.com/block/goose" rel="noopener noreferrer"&gt;
        goose
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      an open source, extensible AI agent that goes beyond code suggestions - install, execute, edit, and test with any LLM
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;goose&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;a local, extensible, open source AI agent that automates engineering tasks&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
  &lt;a href="https://opensource.org/licenses/Apache-2.0" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5b60841bea9e11d9d0b0950d690c9bc554e06385634056a7d5d62a15d1a4eabe/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4170616368655f322e302d626c75652e737667"&gt;&lt;/a&gt;
  &lt;a href="https://discord.gg/goose-oss" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2519ab6b1b4d6d4b7c86e1a0a3dfb43ccc449f504f9193bef87357f5f261552a/68747470733a2f2f696d672e736869656c64732e696f2f646973636f72642f313238373732393931383130303234363635343f6c6f676f3d646973636f7264266c6f676f436f6c6f723d7768697465266c6162656c3d4a6f696e2b557326636f6c6f723d626c756576696f6c6574" alt="Discord"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/block/goose/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/54c1aa543ed5e2f73ee65c268b0f32bc59c9ca507e5501d941d18641da65eff3/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f626c6f636b2f676f6f73652f63692e796d6c3f6272616e63683d6d61696e" alt="CI"&gt;&lt;/a&gt;
&lt;/p&gt;


&lt;/div&gt;

&lt;p&gt;goose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - &lt;em&gt;autonomously&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Whether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.&lt;/p&gt;

&lt;p&gt;Designed for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/D-DpDunrbpo" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F113943282%2F494478532-ddc71240-3928-41b5-8210-626dfb28af7a.jpg%3Fjwt%3DeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQ2NDE4NjcsIm5iZiI6MTc3NDY0MTU2NywicGF0aCI6Ii8xMTM5NDMyODIvNDk0NDc4NTMyLWRkYzcxMjQwLTM5MjgtNDFiNS04MjEwLTYyNmRmYjI4YWY3YS5qcGc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjYwMzI3JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI2MDMyN1QxOTU5MjdaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1iZDE3NWFmNWEwNDg5NDYzYjI0OWY0ZGQ4ZDNlNjAyYjVjNGM1MzUxY2Y5NzQ2NTAxZWFjNzM0YzkxYzE2MTdkJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.pbrRGOY-7dN3QK-CX2HjLLjj_EXgzftlC0LHnN9mFDA" alt="Watch the video"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Quick Links&lt;/h1&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/quickstart" rel="nofollow noopener noreferrer"&gt;Quickstart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/getting-started/installation" rel="nofollow noopener noreferrer"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/category/tutorials" rel="nofollow noopener noreferrer"&gt;Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/category/getting-started" rel="nofollow noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/block/goose/blob/main/GOVERNANCE.md" rel="noopener noreferrer"&gt;Governance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/block/goose/blob/main/CUSTOM_DISTROS.md" rel="noopener noreferrer"&gt;Custom Distributions&lt;/a&gt; - build your own goose distro with preconfigured providers…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/block/goose" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;&lt;a href="https://adventofai.dev/challenges/16" rel="noopener noreferrer"&gt;Advent of AI day 16&lt;/a&gt; is about structuring projects before building them using the &lt;a href="https://block.github.io/goose/docs/guides/context-engineering/using-goosehints/" rel="noopener noreferrer"&gt;.goosehints file&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's &lt;a href="https://github.com/nickytonline/advent-of-ai-2025/tree/main/day-16" rel="noopener noreferrer"&gt;my day 16 submission&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;Up until now, the Advent of AI challenges were pretty open-ended. You'd chat with Goose and build things organically. Day 16 changes that. You're supposed to structure the project first using two components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;.goosehints&lt;/code&gt; - tells Goose HOW to work (tech stack, conventions, file structure)
&lt;/li&gt;
&lt;li&gt;A planning approach - tells Goose WHAT to build&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The actual task is building a festival countdown app that shows days/hours/minutes/seconds until next year's festival, has rotating fun facts, an email signup form, and a winter theme. Mobile responsive.&lt;/p&gt;
&lt;h2&gt;
  
  
  What's .goosehints?
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;.goosehints&lt;/code&gt; file is project context for Goose. Tech stack choices, coding conventions, file organization, style preferences. It lives in the project root (or subdirectories) and Goose automatically reads it when starting a session.&lt;/p&gt;

&lt;p&gt;Quick note on &lt;code&gt;.goosehints&lt;/code&gt; vs &lt;code&gt;AGENTS.md&lt;/code&gt;: Goose actually supports both, but with different scopes. &lt;code&gt;AGENTS.md&lt;/code&gt; is repository-wide context that works across multiple AI tools (Claude, Cursor, Windsurf). You can learn more about the &lt;code&gt;AGENTS.md&lt;/code&gt; format at &lt;a href="https://agents.md/" rel="noopener noreferrer"&gt;agents.md&lt;/a&gt;. &lt;code&gt;.goosehints&lt;/code&gt; is directory-scoped and Goose-specific. For most projects, &lt;code&gt;AGENTS.md&lt;/code&gt; is sufficient since the entire project context fits in one file and you get cross-tool compatibility. But for Day 16, I used &lt;code&gt;.goosehints&lt;/code&gt; since that's what the challenge specifically called for.&lt;/p&gt;

&lt;p&gt;Looking at the &lt;a href="https://github.com/block/goose/blob/main/.goosehints" rel="noopener noreferrer"&gt;Goose repo's own .goosehints file&lt;/a&gt;, you see patterns like:&lt;/p&gt;

&lt;p&gt;Tech Stack:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Language: Rust
&lt;/li&gt;
&lt;li&gt;Framework: tokio for async
&lt;/li&gt;
&lt;li&gt;Testing: Standard Rust test framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For my countdown app, I kept mine simple. HTML/CSS/JavaScript, mobile-first responsive design, vanilla JavaScript (no frameworks), Playfair Display and Poppins fonts to match the festival website from Day 4.&lt;/p&gt;

&lt;p&gt;The key is being specific. Not just "use good practices" but actual constraints like "use CSS custom properties for theming" or "create separate files for countdown logic, facts rotation, and form handling."&lt;/p&gt;

&lt;h2&gt;
  
  
  Planning With TODO Extension
&lt;/h2&gt;

&lt;p&gt;Goose has three planning approaches you can use. I went with the TODO extension because I wanted to see the full breakdown upfront and track progress as Goose worked.&lt;/p&gt;

&lt;p&gt;The TODO extension is an MCP server that maintains a task list for your project. You can read and write to it. Goose updates it as it completes tasks.&lt;/p&gt;

&lt;p&gt;I started by asking Goose to create a PRD (Product Requirements Document) and break it into stages. Goose generated eight stages with specific tasks for each:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Planning &amp;amp; Setup (create .goosehints, directory structure, initialize TODO)
&lt;/li&gt;
&lt;li&gt;Core Countdown Functionality (HTML structure, JavaScript logic, handle edge cases)
&lt;/li&gt;
&lt;li&gt;Fun Facts Rotation (data structure, rotation logic, CSS transitions)
&lt;/li&gt;
&lt;li&gt;Email Signup Form (validation, localStorage, success/error feedback)
&lt;/li&gt;
&lt;li&gt;Winter Theme &amp;amp; Styling (color palette, typography, visual elements)
&lt;/li&gt;
&lt;li&gt;Responsive Design (mobile-first breakpoints, touch targets)
&lt;/li&gt;
&lt;li&gt;Testing &amp;amp; Polish (accessibility, performance, SEO)
&lt;/li&gt;
&lt;li&gt;Documentation &amp;amp; Deployment (deployment guide, README)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The TODO list had checkboxes for every task. As Goose completed each one, it marked it done. You could see exactly what was finished and what was left.&lt;/p&gt;

&lt;h2&gt;
  
  
  Actually Building It
&lt;/h2&gt;

&lt;p&gt;Once the plan was set, I told Goose to start executing. It went through each stage systematically.&lt;/p&gt;

&lt;p&gt;Stage 2, it built the countdown. Created &lt;code&gt;js/countdown.js&lt;/code&gt; with logic to calculate time difference to December 1, 2026 at 10:00 AM. Updates every second. Handles timezone properly.&lt;/p&gt;

&lt;p&gt;Stage 3, it added facts rotation. Created &lt;code&gt;js/facts.js&lt;/code&gt; with an array of facts and rotation logic. Fades out the current fact, waits, fades in the next one. Six second intervals.&lt;/p&gt;

&lt;p&gt;Stage 4, it built the email form. Created &lt;code&gt;js/form.js&lt;/code&gt; with validation and localStorage. Doesn't actually send emails (that would be a bonus feature), but validates and stores them locally.&lt;/p&gt;

&lt;p&gt;The styling was interesting. Goose pulled the color palette and fonts from my Day 4 festival website. Blues, whites, purples. Gradient backgrounds. It created separate CSS files for base styles, animations, and responsive breakpoints.&lt;/p&gt;

&lt;p&gt;For responsive design, it implemented mobile-first with breakpoints at 768px (tablet) and 1024px (desktop). Adjusted font sizes, spacing, and layout for each screen size.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bonus Features
&lt;/h2&gt;

&lt;p&gt;After finishing the core requirements, I tackled four bonus features. Each one got its own conventional commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Snowfall animation&lt;/strong&gt; - Added 20 snowflakes falling at different speeds with left/right drift. Different sizes and delays for a natural effect. Respects &lt;code&gt;prefers-reduced-motion&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dark mode toggle&lt;/strong&gt; - Moon/sun icon in the top-right corner. Persists preference in localStorage. Respects system preference on first visit. Smooth CSS variable transitions between themes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extended fun facts&lt;/strong&gt; - Expanded from 5 facts to 16. Added stats about vendors, donations, activities, toy drive, parade, music, skating, lights, maze, Santa photos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sound effects toggle&lt;/strong&gt; - Speaker/mute icon below dark mode. Festive beep sounds on button clicks using Web Audio API. Starts muted by default for accessibility.&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%2Fnwxhg2wt0qksb66z9x07.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnwxhg2wt0qksb66z9x07.gif" alt="website i built"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  .goosehints vs AGENTS.md
&lt;/h2&gt;

&lt;p&gt;I use &lt;a href="https://agents.md/" rel="noopener noreferrer"&gt;AGENTS.md&lt;/a&gt; (a new standard) in projects now and this is actually the better choice for most projects. It works across multiple AI tools (Claude, Cursor, Windsurf, Goose etc.) and provides repository-wide context. Check out &lt;a href="https://agents.md/" rel="noopener noreferrer"&gt;agents.md&lt;/a&gt; for the format details.&lt;/p&gt;

&lt;p&gt;From what I can tell, &lt;code&gt;.goosehints&lt;/code&gt; is more useful when you need directory-scoped context that differs from your repo-wide guidelines. For something like my MCP servers where the entire project context fits in one file, &lt;code&gt;AGENTS.md&lt;/code&gt; would be the way to go.&lt;/p&gt;

&lt;p&gt;But for this challenge, &lt;code&gt;.goosehints&lt;/code&gt; was specifically required, so that's what I used.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Final Result
&lt;/h2&gt;

&lt;p&gt;The app has all the core requirements working. Real-time countdown to December 1, 2026 at 10:00 AM. Fun facts rotating every 6 seconds with smooth fade transitions. Email signup form with validation and localStorage. Winter-themed design with blues, whites, and purples. Fully responsive on mobile and desktop.&lt;/p&gt;

&lt;p&gt;The four bonus features add nice polish. Snowfall animation in the background. Dark mode toggle that persists. 16 fun facts instead of 5. Sound effects you can toggle on and off.&lt;/p&gt;

&lt;p&gt;Total files: 14 (6 JavaScript, 3 CSS, HTML, README, deployment guide, etc.) Lines of code: 1,200+ Core features: 6 completed Bonus features: 4 completed&lt;/p&gt;

&lt;p&gt;You can check out the complete project in my repo: &lt;a href="https://github.com/nickytonline/advent-of-ai-2025/tree/main/day-16" rel="noopener noreferrer"&gt;https://github.com/nickytonline/advent-of-ai-2025/tree/main/day-16&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More about &lt;code&gt;.goosehints&lt;/code&gt;: &lt;a href="https://block.github.io/goose/docs/guides/context-engineering/using-goosehints/" rel="noopener noreferrer"&gt;https://block.github.io/goose/docs/guides/context-engineering/using-goosehints/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to stay in touch, all my socials are on nickyt.online. Until the next one!&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@mediamodifier?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Mediamodifier&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/black-magnifying-glass-on-white-paper-yx17UuZw1Ck?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>adventofai</category>
      <category>goose</category>
      <category>ai</category>
    </item>
    <item>
      <title>Advent of AI 2025 - Day 15: Goose Sub-Recipes</title>
      <dc:creator>Nick Taylor</dc:creator>
      <pubDate>Sun, 21 Dec 2025 17:44:42 +0000</pubDate>
      <link>https://dev.to/nickytonline/advent-of-ai-2025-day-15-goose-sub-recipes-3mnd</link>
      <guid>https://dev.to/nickytonline/advent-of-ai-2025-day-15-goose-sub-recipes-3mnd</guid>
      <description>&lt;p&gt;I've edited this post, but AI helped. These are meant to be quick posts related to the Advent of AI. If I'm doing them correctly, they should take me between 30 minutes to 1 hour max to write.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://adventofai.dev" rel="noopener noreferrer"&gt;advent of AI&lt;/a&gt; series leverages Goose, an open source AI agent. If you've never heard of it, check it out!&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/block" rel="noopener noreferrer"&gt;
        block
      &lt;/a&gt; / &lt;a href="https://github.com/block/goose" rel="noopener noreferrer"&gt;
        goose
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      an open source, extensible AI agent that goes beyond code suggestions - install, execute, edit, and test with any LLM
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;goose&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;a local, extensible, open source AI agent that automates engineering tasks&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
  &lt;a href="https://opensource.org/licenses/Apache-2.0" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5b60841bea9e11d9d0b0950d690c9bc554e06385634056a7d5d62a15d1a4eabe/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4170616368655f322e302d626c75652e737667"&gt;&lt;/a&gt;
  &lt;a href="https://discord.gg/goose-oss" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2519ab6b1b4d6d4b7c86e1a0a3dfb43ccc449f504f9193bef87357f5f261552a/68747470733a2f2f696d672e736869656c64732e696f2f646973636f72642f313238373732393931383130303234363635343f6c6f676f3d646973636f7264266c6f676f436f6c6f723d7768697465266c6162656c3d4a6f696e2b557326636f6c6f723d626c756576696f6c6574" alt="Discord"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/block/goose/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/54c1aa543ed5e2f73ee65c268b0f32bc59c9ca507e5501d941d18641da65eff3/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f626c6f636b2f676f6f73652f63692e796d6c3f6272616e63683d6d61696e" alt="CI"&gt;&lt;/a&gt;
&lt;/p&gt;


&lt;/div&gt;

&lt;p&gt;goose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - &lt;em&gt;autonomously&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Whether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.&lt;/p&gt;

&lt;p&gt;Designed for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/D-DpDunrbpo" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F113943282%2F494478532-ddc71240-3928-41b5-8210-626dfb28af7a.jpg%3Fjwt%3DeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQ2NDE4NDgsIm5iZiI6MTc3NDY0MTU0OCwicGF0aCI6Ii8xMTM5NDMyODIvNDk0NDc4NTMyLWRkYzcxMjQwLTM5MjgtNDFiNS04MjEwLTYyNmRmYjI4YWY3YS5qcGc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjYwMzI3JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI2MDMyN1QxOTU5MDhaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT02ZTJkNjIxYTA4ZmI3ODM5YWU2ODg4ODI0NjI1Y2U3OGZhODhkMGRmMmZkMGYyYTJiZGEwYjNlZmNjNDE3Y2JhJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.ctNmJ5_VfUq05_U_xVvAGVwzi3tt7tFU1ol6G8Qbghc" alt="Watch the video"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Quick Links&lt;/h1&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/quickstart" rel="nofollow noopener noreferrer"&gt;Quickstart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/getting-started/installation" rel="nofollow noopener noreferrer"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/category/tutorials" rel="nofollow noopener noreferrer"&gt;Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/category/getting-started" rel="nofollow noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/block/goose/blob/main/GOVERNANCE.md" rel="noopener noreferrer"&gt;Governance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/block/goose/blob/main/CUSTOM_DISTROS.md" rel="noopener noreferrer"&gt;Custom Distributions&lt;/a&gt; - build your own goose distro with preconfigured providers…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/block/goose" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;&lt;a href="https://adventofai.dev/challenges/15" rel="noopener noreferrer"&gt;Day 15's challenge&lt;/a&gt; was all about &lt;a href="https://block.github.io/goose/docs/guides/recipes/subrecipes" rel="noopener noreferrer"&gt;sub-recipes&lt;/a&gt;. If you've been following along, you know recipes are Goose's way of automating workflows.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nickytonline/advent-of-ai-2025-day-7-goose-recipes-5d1c" class="crayons-story__hidden-navigation-link"&gt;Advent of AI 2025 - Day 7: Goose Recipes&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nickytonline" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F9597%2F8f1ed696-3e87-4bd4-809a-0ee9bb905c3c.jpg" alt="nickytonline profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nickytonline" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Nick Taylor
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Nick Taylor
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3096284" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nickytonline" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F9597%2F8f1ed696-3e87-4bd4-809a-0ee9bb905c3c.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Nick Taylor&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nickytonline/advent-of-ai-2025-day-7-goose-recipes-5d1c" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Dec 10 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nickytonline/advent-of-ai-2025-day-7-goose-recipes-5d1c" id="article-link-3096284"&gt;
          Advent of AI 2025 - Day 7: Goose Recipes
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/adventofai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;adventofai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/goose"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;goose&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/automation"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;automation&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nickytonline/advent-of-ai-2025-day-7-goose-recipes-5d1c" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;13&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nickytonline/advent-of-ai-2025-day-7-goose-recipes-5d1c#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              2&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            18 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;p&gt;Sub-recipes take this further by letting you compose smaller recipes into larger ones.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;The scenario: Zara, a social media coordinator, needs to create content for three different platforms for the Grand Ice Sculpture Unveiling event. Each platform needs completely different content styles.&lt;/p&gt;

&lt;p&gt;Instagram wants visual captions with hashtags. Twitter/X needs a concise thread. Facebook needs detailed event descriptions. Manually customizing content for each platform is tedious and time-consuming.&lt;/p&gt;

&lt;p&gt;The goal: create one main recipe that orchestrates three sub-recipes, one for each platform.&lt;/p&gt;

&lt;p&gt;I created four recipe files:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://github.com/nickytonline/advent-of-ai-2025/blob/main/day-15/instagram-post.yaml" rel="noopener noreferrer"&gt;instagram-post.yaml&lt;/a&gt; for Instagram content
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nickytonline/advent-of-ai-2025/blob/main/day-15/twitter-thread.yaml" rel="noopener noreferrer"&gt;twitter-thread.yaml&lt;/a&gt; for Twitter/X threads
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nickytonline/advent-of-ai-2025/blob/main/day-15/facebook-event.yaml" rel="noopener noreferrer"&gt;facebook-event.yaml&lt;/a&gt; for Facebook event posts
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nickytonline/advent-of-ai-2025/blob/main/day-15/social-campaign.yaml" rel="noopener noreferrer"&gt;social-campaign.yaml&lt;/a&gt; as the main orchestrator&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All recipes accept the same core parameters: event name, date, description, target audience, and call to action. The main recipe passes these values to each sub-recipe.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Sub-recipes are powerful for composability. Instead of one massive recipe trying to do everything, you break it into focused pieces. Each sub-recipe has one job and does it well.&lt;/p&gt;

&lt;p&gt;The main recipe's job is coordination. It defines the workflow, passes the right data to each sub-recipe, and presents the results in a useful format.&lt;/p&gt;

&lt;p&gt;The main recipe's &lt;code&gt;sub_recipes&lt;/code&gt; section maps values from the main parameters to each sub-recipe's expected parameters.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
yaml
version: 1.0.0
title: Social Media Campaign Generator
description: Generate complete cross-platform social media campaign using sub-recipes
instructions: |

  You are a social media campaign coordinator creating a comprehensive multi-platform campaign.

  Generate a complete social media campaign for the following event:
  - event_name: {{event_name}}
  - event_date: {{event_date}}
  - event_description: {{event_description}}
  - target_audience: {{target_audience}}
  - call_to_action: {{call_to_action}}

  **Campaign Strategy:**
  Execute the following sub-recipes to create platform-specific content:

  1. **Instagram Content**: Run the instagram-post.yaml recipe
  2. **Twitter/X Thread**: Run the twitter-thread.yaml recipe
  3. **Facebook Event**: Run the facebook-event.yaml recipe

  **Output Format:**
  Present the complete campaign organized by platform:



  ```
  # 📱 SOCIAL MEDIA CAMPAIGN: {{event_name}}

  ---

  ## 📷 INSTAGRAM POST

  [Output from instagram-post.yaml recipe]

  ---

  ## 🐦 TWITTER/X THREAD

  [Output from twitter-thread.yaml recipe]

  ---

  ## 👥 FACEBOOK EVENT

  [Output from facebook-event.yaml recipe]

  ---

  ## 📊 CAMPAIGN SUMMARY
  ✅ Instagram: Ready to post
  ✅ Twitter/X: Thread ready (3-5 tweets)
  ✅ Facebook: Event description ready

  **Posting Strategy:**
  - Post to Facebook first (most details, drives event RSVPs)
  - Post to Instagram 2-4 hours later (visual engagement)
  - Post Twitter thread 1-2 hours after Instagram (conversation starter)

  **Engagement Tips:**
  - Monitor comments in first 2 hours for maximum reach
  - Respond to questions quickly
  - Share user-generated content
  - Cross-promote between platforms
  ```



  **Execution Instructions:**
  1. Call each sub-recipe with the provided parameters
  2. Collect all outputs
  3. Format as shown above
  4. Provide strategic posting guidance

  **Rules:**
  - Execute all three sub-recipes
  - Present output in a clear, organized format
  - Include campaign summary and strategy
  - Make it ready for immediate deployment
prompt: Generate complete social media campaign for {{event_name}}
extensions: []
sub_recipes:
  - name: "instagram_content"
    path: "./instagram-post.yaml"
    values:
      event_name: "{{event_name}}"
      event_date: "{{event_date}}"
      event_description: "{{event_description}}"
      target_audience: "{{target_audience}}"
      call_to_action: "{{call_to_action}}"
  - name: "twitter_content"
    path: "./twitter-thread.yaml"
    values:
      event_name: "{{event_name}}"
      event_date: "{{event_date}}"
      event_description: "{{event_description}}"
      target_audience: "{{target_audience}}"
      call_to_action: "{{call_to_action}}"
  - name: "facebook_content"
    path: "./facebook-event.yaml"
    values:
      event_name: "{{event_name}}"
      event_date: "{{event_date}}"
      event_description: "{{event_description}}"
      target_audience: "{{target_audience}}"
      call_to_action: "{{call_to_action}}"
activities: []
parameters:
  - key: event_name
    input_type: string
    requirement: required
    description: Name of the festival event
  - key: event_date
    input_type: string
    requirement: required
    description: When the event is happening
  - key: event_description
    input_type: string
    requirement: required
    description: What the event is about
  - key: target_audience
    input_type: string
    requirement: required
    description: Who should attend
  - key: call_to_action
    input_type: string
    requirement: required
    description: What you want people to do


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

&lt;/div&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;Running the campaign generator with event details produces ready-to-post content for all three platforms. Instagram gets its caption with strategic hashtags. Twitter/X gets a 4-tweet thread. Facebook gets a complete event description.&lt;/p&gt;

&lt;p&gt;The output even includes posting strategy and engagement tips, which I didn't explicitly ask for but the recipe generated based on the instructions.&lt;/p&gt;
&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;If you want to dive deeper into sub-recipes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://block.github.io/goose/docs/guides/recipes/recipe-reference" rel="noopener noreferrer"&gt;Recipe Reference - Sub-recipes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/docs/guides/recipes/" rel="noopener noreferrer"&gt;Recipes Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My full solution is on GitHub: &lt;a href="https://github.com/block/goose/discussions/6195#discussioncomment-15311588" rel="noopener noreferrer"&gt;Advent of AI 2025 - Day 15&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The original challenge is here: &lt;a href="https://adventofai.dev/challenges/15" rel="noopener noreferrer"&gt;Day 15: The Social Media Blitz&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it for Day 15. Quick post, but sub-recipes are a solid pattern for building reusable workflows.&lt;/p&gt;

&lt;p&gt;If you want to stay in touch, all my socials are on &lt;a href="https://nickyt.online" rel="noopener noreferrer"&gt;nickyt.online&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Until the next one!&lt;/p&gt;

&lt;p&gt;Find me on the web at &lt;a href="https://nickyt.online/" rel="noopener noreferrer"&gt;nickyt.online&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@hybridnighthawk?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;hybridnighthawk&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-couple-of-chefs-working-in-a-kitchen-u0NElYxby4c?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>adventofai</category>
      <category>goose</category>
      <category>ai</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
