<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>2026-09 on Funky Si's Blog (Dev)</title><link>https://blog-dev.funkysi1701.com/2026/09/</link><description>Recent content in 2026-09 on Funky Si's Blog (Dev)</description><generator>Hugo -- gohugo.io</generator><language>en-gb</language><managingEditor>funkysi1701@gmail.com (Simon Foster)</managingEditor><webMaster>funkysi1701@gmail.com (Simon Foster)</webMaster><lastBuildDate>Mon, 21 Sep 2026 12:00:00 +0000</lastBuildDate><atom:link href="https://blog-dev.funkysi1701.com/2026/09/index.xml" rel="self" type="application/rss+xml"/><item><title>Signal Diff CLI for coding agents</title><link>https://blog-dev.funkysi1701.com/posts/2026/signal-diff-cli/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 21 Sep 2026 12:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2026/signal-diff-cli/</guid><category term="AI">AI</category><category term="DevOps">DevOps</category><category term="SignalDiff">SignalDiff</category><category term="OpenSource">OpenSource</category><category term="SideProjects">SideProjects</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/2026/signal-diff-cli.png"/><description>&lt;p&gt;I already know whether a deploy of this blog broke titles, descriptions, or routes. After the Static Web Apps job finishes, &lt;a href="https://signaldiff.dev/" target="_blank" rel="noopener noreferrer"&gt;Signal Diff&lt;/a&gt;
crawls the sitemap and compares the live site with a baseline. I wrote that loop up in &lt;a href="https://blog-dev.funkysi1701.com/posts/2026/implementing-gitops-with-azure-devops-for-net-apps/"&gt;GitOps with Azure DevOps for .NET apps&lt;/a&gt;
. The Action is the unattended gate. It sits quiet during the hour I am in Cursor, halfway through a layout change, wanting the same answer before I push.&lt;/p&gt;
&lt;p&gt;That is the job of the &lt;code&gt;signaldiff&lt;/code&gt; CLI. Coding agents already have a terminal. A terminal is enough to crawl a sitemap and read the report. There is no browser session to open, and no MCP server to configure.&lt;/p&gt;
&lt;h2 id="what-signal-diff-is-doing"&gt;What Signal Diff is doing&lt;a class="anchor ms-1" href="#what-signal-diff-is-doing" aria-label="Permalink: What Signal Diff is doing"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://signaldiff.dev/" target="_blank" rel="noopener noreferrer"&gt;Signal Diff&lt;/a&gt;
fetches the URLs in a sitemap and looks for on-page SEO and crawl problems: titles, meta descriptions, redirects, slow responses, and the rest of the check list. A later run can diff against a baseline, often the previous deploy, so you see what changed.&lt;/p&gt;
&lt;p&gt;The site and the GitHub Action came first. I still use those. The CLI is the same product when the caller is a shell, which is where Cursor, Copilot, and Claude Code already are. Signal Diff also has a customer-hosted crawler for teams who want the crawl to stay on their network. This post stays on the coding-agent path.&lt;/p&gt;
&lt;h2 id="why-i-added-a-cli"&gt;Why I added a CLI&lt;a class="anchor ms-1" href="#why-i-added-a-cli" aria-label="Permalink: Why I added a CLI"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I wanted a crawl with no account. If I am showing an agent a site, or trying the tool on a sitemap I do not monitor yet, sign-in can wait. &lt;code&gt;signaldiff --sitemap&lt;/code&gt; writes an HTML report on this machine. No API key.&lt;/p&gt;
&lt;p&gt;I wanted the dashboard questions in the terminal once a key does exist: which sites, what the last run found, what changed since the baseline. The subcommands &lt;code&gt;sites&lt;/code&gt;, &lt;code&gt;runs&lt;/code&gt;, &lt;code&gt;diff&lt;/code&gt;, &lt;code&gt;findings&lt;/code&gt;, and &lt;code&gt;scan&lt;/code&gt; call the &lt;a href="https://signaldiff.dev/docs/agent-api" target="_blank" rel="noopener noreferrer"&gt;Agent API&lt;/a&gt;
. The agent stays in the repo. I stay out of a second tab.&lt;/p&gt;
&lt;p&gt;An MCP host would have been another install, then another client to teach. The tools I already use can run a command and read a file. One executable on &lt;code&gt;PATH&lt;/code&gt; is the setup I wanted. The command reference is at &lt;a href="https://signaldiff.dev/docs/cli" target="_blank" rel="noopener noreferrer"&gt;signaldiff.dev/docs/cli&lt;/a&gt;
.&lt;/p&gt;
&lt;p&gt;A few behaviours matter once an agent is the one typing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;signaldiff&lt;/code&gt; with no arguments prints help and exits. It does not start a crawl.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--json&lt;/code&gt; belongs on cloud subcommands. Success JSON goes to stdout. A failure is one JSON object on stderr, with a status code and, on a rate limit, how many seconds to wait. Progress logs stay on stderr, so a pipe keeps the result.&lt;/li&gt;
&lt;li&gt;A local &lt;code&gt;--sitemap&lt;/code&gt; crawl refuses &lt;code&gt;--json&lt;/code&gt; and still writes HTML.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="install-then-crawl-this-blog"&gt;Install, then crawl this blog&lt;a class="anchor ms-1" href="#install-then-crawl-this-blog" aria-label="Permalink: Install, then crawl this blog"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The installers are self-contained. You do not need the .NET SDK, and you do not need a clone of the repo. On Windows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;irm &lt;span style="color:#e6db74"&gt;&amp;#39;https://signaldiff.dev/install/cli.ps1&amp;#39;&lt;/span&gt; -OutFile &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$env:TEMP&lt;span style="color:#e6db74"&gt;\signaldiff-install-cli.ps1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;amp; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$env:TEMP&lt;span style="color:#e6db74"&gt;\signaldiff-install-cli.ps1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On Linux or macOS:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -fsSL &lt;span style="color:#e6db74"&gt;&amp;#39;https://signaldiff.dev/install/cli.sh&amp;#39;&lt;/span&gt; | bash
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Open a new terminal so &lt;code&gt;PATH&lt;/code&gt; picks up the binary (&lt;code&gt;~/.local/bin&lt;/code&gt; on Linux and macOS). Then crawl a real sitemap, with a page cap so the first run stays small:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;signaldiff --sitemap https&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;//www.funkysi1701.com/sitemap.xml --max-pages &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt; --output seo-report.html --quiet
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;--output&lt;/code&gt; creates missing folders before the crawl starts. &lt;code&gt;--quiet&lt;/code&gt; keeps the summary short. The report path is on stdout. I point this at my own sitemap because that is the site I already monitor. Swap in yours.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;signaldiff update&lt;/code&gt; replaces the install with the latest published build and checks the SHA256. &lt;code&gt;signaldiff update --check&lt;/code&gt; tells you whether one is waiting, and leaves the current binary alone.&lt;/p&gt;
&lt;h2 id="what-i-tell-the-agent"&gt;What I tell the agent&lt;a class="anchor ms-1" href="#what-i-tell-the-agent" aria-label="Permalink: What I tell the agent"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The API key stays in the environment. It stays out of the chat. Here is the prompt I give Cursor, Copilot, or Claude Code:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Install the Signal Diff CLI from &lt;a href="https://signaldiff.dev/docs/cli" target="_blank" rel="noopener noreferrer"&gt;https://signaldiff.dev/docs/cli&lt;/a&gt;
. Then, in a new terminal, run &lt;code&gt;signaldiff --sitemap https://www.funkysi1701.com/sitemap.xml --max-pages 20 --output seo-report.html --quiet&lt;/code&gt;. Read &lt;code&gt;seo-report.html&lt;/code&gt; and list the SEO and crawl issues worth fixing, with the URL for each and a concrete change. Use cloud commands only if &lt;code&gt;SIGNALDIFF_API_KEY&lt;/code&gt; is already set. Leave the key out of the chat.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is the loop. The agent installs if it must, runs a capped crawl, and works from the HTML. I still read the suggestions. A title that is two characters over the limit is a real fix. A rewrite of a post I care about is a suggestion until I agree with it.&lt;/p&gt;
&lt;p&gt;This sits next to the split I described in &lt;a href="https://blog-dev.funkysi1701.com/posts/2026/how-i-use-ai-on-side-projects/"&gt;how I use AI on side projects&lt;/a&gt;
. ChatGPT when the question needs no repo. Cursor when the answer is in the files. The CLI when the question is about the live sitemap.&lt;/p&gt;
&lt;h2 id="when-a-key-is-already-set"&gt;When a key is already set&lt;a class="anchor ms-1" href="#when-a-key-is-already-set" aria-label="Permalink: When a key is already set"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Cloud commands need a paid key from the Signal Diff dashboard (Developers → API keys). Set it in the environment. Prefer that to &lt;code&gt;--api-key&lt;/code&gt;, which shows up in the process list.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$env:SIGNALDIFF_API_KEY = &lt;span style="color:#e6db74"&gt;&amp;#34;sck_…&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$env:SIGNALDIFF_API_BASE_URL = &lt;span style="color:#e6db74"&gt;&amp;#34;https://signaldiff.dev/api&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The base URL includes &lt;code&gt;/api&lt;/code&gt;. That is the easy one to get wrong. The GitHub Action uses a different variable, and the site origin with no &lt;code&gt;/api&lt;/code&gt; suffix. Same secret value. Different names, different URL shape.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Caller&lt;/th&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Base URL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CLI and the Agent API&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SIGNALDIFF_API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://signaldiff.dev/api&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Action&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SIGNALDIFF_CI_API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://signaldiff.dev&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;These are the commands I want an agent to run once that is set:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You want&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sites you already monitor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;signaldiff sites list&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A short brief for one run&lt;/td&gt;
&lt;td&gt;&lt;code&gt;signaldiff runs summary &amp;lt;runId&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Only the deploy diff&lt;/td&gt;
&lt;td&gt;&lt;code&gt;signaldiff diff get &amp;lt;runId&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Errors from that run&lt;/td&gt;
&lt;td&gt;&lt;code&gt;signaldiff findings list &amp;lt;runId&amp;gt; --severity Error --limit 50&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Machine-readable output&lt;/td&gt;
&lt;td&gt;add &lt;code&gt;--json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;runs summary&lt;/code&gt; is the one I point agents at. It is a capped brief: headline counts, top regressions, top improvements, and fix hints. It leaves out the full page payload. &lt;code&gt;diff get&lt;/code&gt; is there when the only question is what changed against the baseline.&lt;/p&gt;
&lt;p&gt;Starting a cloud scan is a separate decision. &lt;code&gt;signaldiff scan start&lt;/code&gt; queues work on the service, and &lt;code&gt;signaldiff scan wait &amp;lt;scanId&amp;gt; --json&lt;/code&gt; polls until it finishes. I leave that out of the default prompt. A local HTML report answers &amp;ldquo;what is wrong on this sitemap right now&amp;rdquo;. A cloud scan spends quota and shows up in the dashboard, so I ask for it on purpose.&lt;/p&gt;
&lt;h2 id="the-action-still-runs-after-deploy"&gt;The Action still runs after deploy&lt;a class="anchor ms-1" href="#the-action-still-runs-after-deploy" aria-label="Permalink: The Action still runs after deploy"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The pipeline check stays. On this blog the Action still crawls after a Static Web Apps deploy, with the API key in GitHub secrets and a pinned action version. The wiring is in the &lt;a href="https://blog-dev.funkysi1701.com/posts/2026/implementing-gitops-with-azure-devops-for-net-apps/"&gt;GitOps post&lt;/a&gt;
. The Action answers &amp;ldquo;did this deploy stay healthy?&amp;rdquo; when I am not looking. The CLI answers the same question while an agent and I are still in the change.&lt;/p&gt;
&lt;h2 id="try-it-on-a-sitemap-you-care-about"&gt;Try it on a sitemap you care about&lt;a class="anchor ms-1" href="#try-it-on-a-sitemap-you-care-about" aria-label="Permalink: Try it on a sitemap you care about"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Install the CLI, cap a crawl with &lt;code&gt;--max-pages&lt;/code&gt;, and have the agent read the HTML before it edits templates. If you already have a key, ask for &lt;code&gt;runs summary&lt;/code&gt; on the latest run and see whether the brief is enough to act on.&lt;/p&gt;
&lt;p&gt;I am curious how you give an agent this kind of check. A CLI it can run, an MCP server, or a dashboard you still open by hand? Tell me in the comments. Command reference, updates, and checksums are on the &lt;a href="https://signaldiff.dev/docs/cli" target="_blank" rel="noopener noreferrer"&gt;CLI docs&lt;/a&gt;
.&lt;/p&gt;</description></item></channel></rss>