<?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: Andy Brummer</title>
    <description>The latest articles on DEV Community by Andy Brummer (@andylbrummer).</description>
    <link>https://dev.to/andylbrummer</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3662429%2Ff1894e9d-bd49-4d94-b160-0dd6d9aa0a34.png</url>
      <title>DEV Community: Andy Brummer</title>
      <link>https://dev.to/andylbrummer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andylbrummer"/>
    <language>en</language>
    <item>
      <title>Setting up tman with a Vite project</title>
      <dc:creator>Andy Brummer</dc:creator>
      <pubDate>Sat, 01 Aug 2026 03:12:17 +0000</pubDate>
      <link>https://dev.to/standardbeagle/setting-up-tman-with-a-vite-project-5gg1</link>
      <guid>https://dev.to/standardbeagle/setting-up-tman-with-a-vite-project-5gg1</guid>
      <description>&lt;p&gt;If you let AI coding agents loose on a frontend repo, they run &lt;code&gt;npm run build&lt;/code&gt; and &lt;code&gt;npm run dev&lt;/code&gt; &lt;em&gt;constantly&lt;/em&gt; — and often from several sessions at once. Nothing is broken, exactly. You just have four builds racing in one checkout, two dev servers fighting over a port, no way to tell them apart, and no record of what any of them did.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/standardbeagle/tman" rel="noopener noreferrer"&gt;tman&lt;/a&gt; fixes the everyday version of that problem: it gives every run a &lt;strong&gt;name&lt;/strong&gt;, a &lt;strong&gt;slot&lt;/strong&gt;, and a &lt;strong&gt;record&lt;/strong&gt;. The same suite never runs twice at once, excess runs queue instead of stampeding your cores, and every run leaves a JSON record you can query afterwards. Underneath sits a backstop that kills the rare run that genuinely hangs or leaks.&lt;/p&gt;

&lt;p&gt;It's a single ~3.8 MB NativeAOT binary with zero runtime dependencies (Linux, macOS, Windows). This post walks through adopting it in a stock Vite project.&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%2Fshare.sbdev.io%2Ftman-vite%2Fdemo.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%2Fshare.sbdev.io%2Ftman-vite%2Fdemo.gif" alt="Full walkthrough: init, supervised build, dev-server dedup, run records" width="800" height="473"&gt;&lt;/a&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;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @standardbeagle/tman
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or the shell one-liner if you don't want it in your global node modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/standardbeagle/tman/main/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A stock Vite project
&lt;/h2&gt;

&lt;p&gt;Nothing special about the target project — this is the plain scaffold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create vite@latest vite-demo &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--template&lt;/span&gt; react-ts
&lt;span class="nb"&gt;cd &lt;/span&gt;vite-demo
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which gives you the usual scripts in &lt;code&gt;package.json&lt;/code&gt;:&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="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsc -b &amp;amp;&amp;amp; vite build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"oxlint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"preview"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vite preview"&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;h2&gt;
  
  
  Step 1 — adopt tman
&lt;/h2&gt;

&lt;p&gt;One command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tman init &lt;span class="nt"&gt;--shims&lt;/span&gt; &lt;span class="nt"&gt;--gitignore&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F980kuw0407n34p3ybk1v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F980kuw0407n34p3ybk1v.png" alt="tman init detecting the Vite scripts and writing .tman.kdl plus build and lint shims" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tman init&lt;/code&gt; reads &lt;code&gt;package.json&lt;/code&gt;, detects the scripts worth supervising, and writes three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.tman.kdl&lt;/code&gt;&lt;/strong&gt; — the per-project config with an alias per detected script&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;shims&lt;/strong&gt; — tiny &lt;code&gt;./build&lt;/code&gt; and &lt;code&gt;./lint&lt;/code&gt; wrappers at the repo root, so existing muscle memory (and agents that just run &lt;code&gt;./build&lt;/code&gt;) go through tman transparently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.gitignore&lt;/code&gt; entries&lt;/strong&gt; for the shims&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note what it did &lt;em&gt;not&lt;/em&gt; scaffold: there's no &lt;code&gt;test&lt;/code&gt; script in a fresh Vite app, so there's no &lt;code&gt;./test&lt;/code&gt; shim pretending otherwise. Aliases tman can't detect are left commented out — a missing suite fails loudly instead of faking a pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — look at &lt;code&gt;.tman.kdl&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsxtnhx5s2b4amrw252u8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsxtnhx5s2b4amrw252u8.png" alt="The generated .tman.kdl: a defaults block and one alias per detected npm script" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;defaults&lt;/code&gt; block is deliberately conservative:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;stall "30m"&lt;/code&gt; — a &lt;em&gt;hang&lt;/em&gt; backstop, not a runtime budget. It only fires when a run is silent &lt;strong&gt;and&lt;/strong&gt; idle for 30 minutes. A cold &lt;code&gt;tsc -b&lt;/code&gt; can legitimately think for a long while without printing; a stall sized like an expected runtime would kill healthy work.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;max-parallel 2&lt;/code&gt; — at most two runs per bucket at once; the rest queue instead of racing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retain "24h"&lt;/code&gt; — how long finished run records stick around.&lt;/li&gt;
&lt;li&gt;The memory/CPU/wall-clock ceilings are commented out because builds legitimately saturate cores and eat RAM. You opt in when a suite has actually misbehaved.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each &lt;code&gt;alias&lt;/code&gt; block maps a name to a command, so &lt;code&gt;tman build&lt;/code&gt; (or the &lt;code&gt;./build&lt;/code&gt; shim) means &lt;code&gt;npm run build&lt;/code&gt; — supervised.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — run a supervised build
&lt;/h2&gt;



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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Firdopeorq7dn44ebsimi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Firdopeorq7dn44ebsimi.png" alt="Running ./build: identical vite build output, now with a name, a lock, and a record" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Identical output to &lt;code&gt;npm run build&lt;/code&gt;, because it &lt;em&gt;is&lt;/em&gt; &lt;code&gt;npm run build&lt;/code&gt; — plus a name, a dedup lock, a slot in the queue, and a run record. The supervision is invisible until the day you need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — the dev server problem
&lt;/h2&gt;

&lt;p&gt;Here's the case that sold me. Two agent sessions both decide the dev server should be running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tman run &lt;span class="nt"&gt;--name&lt;/span&gt; dev &lt;span class="nt"&gt;--&lt;/span&gt; npm run dev &amp;amp;
tman run &lt;span class="nt"&gt;--name&lt;/span&gt; dev &lt;span class="nt"&gt;--&lt;/span&gt; npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fumjcw4xrn8rw518c0xkw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fumjcw4xrn8rw518c0xkw.png" alt="The second dev server is refused: run 'dev' already active, use --replace to kill it" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second invocation is refused immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tman: run 'dev' already active (pid 2244513, id f281b5c2ad30); use --replace to kill it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No second Vite instance, no port fallback to 5174, no zombie server owning the port after the session that started it dies. If you &lt;em&gt;want&lt;/em&gt; takeover semantics — a fresh server after a config change — that's &lt;code&gt;--replace&lt;/code&gt;, which kills the old run and waits for it to hand the name back.&lt;/p&gt;

&lt;p&gt;Locks bucket by name &lt;strong&gt;and&lt;/strong&gt; directory, so a &lt;code&gt;dev&lt;/code&gt; run in this repo never blocks a &lt;code&gt;dev&lt;/code&gt; run in another checkout.&lt;/p&gt;

&lt;p&gt;One tuning note for Vite specifically: never give a dev server &lt;code&gt;--max-time&lt;/code&gt;. It's supposed to run forever. The wall-clock cap is for suites and builds, not servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — what ran, and how did it go?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tman list &lt;span class="nt"&gt;--all&lt;/span&gt;
tman status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0kefazabla9sc5j461nq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0kefazabla9sc5j461nq.png" alt="tman list --all showing the dev server still running and the build exited, with peak memory" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every run leaves one JSON record: command, cwd, exit code, peak memory, start and finish, and the caps it ran under. &lt;code&gt;tman status --json&lt;/code&gt; emits the same thing machine-readable, which is exactly what you want when an agent asks "did the build I started actually finish?" — or when &lt;em&gt;you&lt;/em&gt; ask what your agents ran overnight.&lt;/p&gt;

&lt;p&gt;Orphan reaping rides along for free: every tman command kills children whose runner died and prunes expired records. A crashed agent session doesn't leave a Vite server squatting on the port.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 — catch the runs that bypass the shims
&lt;/h2&gt;

&lt;p&gt;Shims only catch commands that go through a shell lookup. An agent calling its Bash tool with a bare &lt;code&gt;npm run build&lt;/code&gt; walks straight past them. For Claude Code, one hook applies the same policy a level up:&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;"hooks"&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;"PreToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tman hook pretooluse"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;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;It rewrites bare test/build commands through &lt;code&gt;tman run&lt;/code&gt;, leaves everything else untouched, and &lt;strong&gt;never blocks&lt;/strong&gt;: every failure path (tman missing, malformed request, unreadable project) leaves the command exactly as written. There are equivalent guides for &lt;a href="https://dev.standardbeagle.com/tman/" rel="noopener noreferrer"&gt;Codex CLI, Gemini CLI, Cursor, opencode, and others&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Total setup cost for a Vite project: one &lt;code&gt;npm install -g&lt;/code&gt;, one &lt;code&gt;tman init&lt;/code&gt;. From then on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicate builds and dev servers are &lt;strong&gt;refused&lt;/strong&gt;, not raced&lt;/li&gt;
&lt;li&gt;excess runs &lt;strong&gt;queue&lt;/strong&gt; for a slot instead of saturating cores&lt;/li&gt;
&lt;li&gt;every run leaves a &lt;strong&gt;record&lt;/strong&gt; you (or your agents) can query&lt;/li&gt;
&lt;li&gt;hangs and leaks get killed by a backstop that a healthy week never triggers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docs: &lt;a href="https://dev.standardbeagle.com/tman/" rel="noopener noreferrer"&gt;dev.standardbeagle.com/tman&lt;/a&gt; · Source: &lt;a href="https://github.com/standardbeagle/tman" rel="noopener noreferrer"&gt;github.com/standardbeagle/tman&lt;/a&gt; · npm: &lt;a href="https://www.npmjs.com/package/@standardbeagle/tman" rel="noopener noreferrer"&gt;&lt;code&gt;@standardbeagle/tman&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>vite</category>
      <category>node</category>
      <category>devtools</category>
      <category>ai</category>
    </item>
    <item>
      <title>I can finally use MCPs without fear</title>
      <dc:creator>Andy Brummer</dc:creator>
      <pubDate>Sun, 11 Jan 2026 06:03:02 +0000</pubDate>
      <link>https://dev.to/andylbrummer/i-can-finally-use-mcps-without-fear-3pfl</link>
      <guid>https://dev.to/andylbrummer/i-can-finally-use-mcps-without-fear-3pfl</guid>
      <description>&lt;p&gt;MCP promised to make agentic coding fast and powerful, instead it overwhelmed the senses of the LLMs that we trusted to write code for us. Even Anthropic took notice that context is the modern day coding juice that we are all dependent on. Their solution farm the whole thing out to typescript and write a bunch of integration code to build the whole infrastructure from scratch. Instead, I had Claude write a custom programming language, the Simple Language for Orchestrating Prompts or SLOP and shoved it into an MCP Turducken to call and automate other MCPs. &lt;/p&gt;

&lt;p&gt;Reading the Anthropic paper on the issue &lt;a href="https://www.anthropic.com/engineering/code-execution-with-mcp" rel="noopener noreferrer"&gt;https://www.anthropic.com/engineering/code-execution-with-mcp&lt;/a&gt; and having written my own chat apps with the same architecture, I realized that making Javascript or Python safe for deployment anywhere was going to be a huge task that everyone is eventually going to bear the burden of. Instead I figured how about creating a simple language that solves this one problem and SLOP was born.&lt;/p&gt;

&lt;p&gt;I quickly realized that the search and metadata services were the clear winners when Claude code just picked up searching for tools and calling them through the generic interface without a hitch. &lt;/p&gt;

&lt;p&gt;Please give it a try: &lt;a href="https://github.com/standardbeagle/slop-mcp" rel="noopener noreferrer"&gt;https://github.com/standardbeagle/slop-mcp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've got 318 tools installed &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%2Fhld7b8cmrtoxqc07wiyv.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%2Fhld7b8cmrtoxqc07wiyv.png" alt="11 MCP servers and 318 tools" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and Claude Code is cranking along fine without issue.&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%2Fcgwyux7lhnerkwadxvbd.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%2Fcgwyux7lhnerkwadxvbd.png" alt="Claude code with 128k free tokens context" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>agents</category>
    </item>
    <item>
      <title>Taming Windows Terminal's win32-input-mode in Go ConPTY Applications</title>
      <dc:creator>Andy Brummer</dc:creator>
      <pubDate>Mon, 15 Dec 2025 08:16:29 +0000</pubDate>
      <link>https://dev.to/andylbrummer/taming-windows-terminals-win32-input-mode-in-go-conpty-applications-7gg</link>
      <guid>https://dev.to/andylbrummer/taming-windows-terminals-win32-input-mode-in-go-conpty-applications-7gg</guid>
      <description>&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%2F5xhlbhn1w1sajdjv47x1.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%2F5xhlbhn1w1sajdjv47x1.jpeg" alt="gopher in hardhat looking at cryptic windows terminal codes" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When building &lt;a href="https://github.com/standardbeagle/agnt" rel="noopener noreferrer"&gt;agnt&lt;/a&gt;, a tool that gives AI coding agents browser superpowers, I hit a frustrating issue: my Ctrl+Y hotkey worked perfectly on macOS and Linux, but on Windows it produced garbage like &lt;code&gt;[89;21;25;1;8;1_&lt;/code&gt; instead of toggling my overlay menu.&lt;/p&gt;

&lt;p&gt;This article documents the debugging journey and the solution - a Go 1.23+ iterator-based parser that correctly handles Windows Terminal's win32-input-mode escape sequences.&lt;/p&gt;

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

&lt;p&gt;I was wrapping AI coding tools (Claude, Gemini, etc.) in a pseudo-terminal to inject an overlay UI. The overlay listens for Ctrl+Y (byte &lt;code&gt;0x19&lt;/code&gt;) to toggle a menu. Simple enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0x19&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// Ctrl+Y&lt;/span&gt;
    &lt;span class="n"&gt;overlay&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Toggle&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;On Unix, this worked immediately. On Windows with ConPTY, pressing Ctrl+Y produced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[89;21;25;1;8;1_
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What is this? It's a &lt;strong&gt;win32-input-mode&lt;/strong&gt; escape sequence.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is win32-input-mode?
&lt;/h2&gt;

&lt;p&gt;Windows Terminal introduced &lt;a href="https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md" rel="noopener noreferrer"&gt;win32-input-mode&lt;/a&gt; to provide richer keyboard input to ConPTY applications. Instead of sending raw bytes, it sends structured escape sequences:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ESC [ Vk ; Sc ; Uc ; Kd ; Cs ; Rc _
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vk&lt;/strong&gt; - Virtual key code (89 = 'Y')&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sc&lt;/strong&gt; - Scan code (21)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uc&lt;/strong&gt; - Unicode character (25 = Ctrl+Y)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kd&lt;/strong&gt; - Key down flag (1 = down, 0 = up)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cs&lt;/strong&gt; - Control key state (8 = Ctrl held)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rc&lt;/strong&gt; - Repeat count (1)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;code&gt;[89;21;25;1;8;1_&lt;/code&gt; decodes to: "Y key pressed with Ctrl held, unicode value 25".&lt;/p&gt;

&lt;p&gt;The unicode value &lt;code&gt;25&lt;/code&gt; (0x19) is exactly what I needed - but it's buried in an escape sequence!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Debugging Journey
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Isolate the Trigger
&lt;/h3&gt;

&lt;p&gt;First, I needed to understand &lt;em&gt;when&lt;/em&gt; Windows Terminal enables win32-input-mode. I created a diagnostic tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Direct stdin read - what bytes do we actually receive?&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;oldState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MakeRaw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fd&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Restore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fd&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="n"&gt;oldState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Read %d bytes: %q&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Finding 1:&lt;/strong&gt; Direct stdin reads showed raw bytes (0x19 for Ctrl+Y). Good.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finding 2:&lt;/strong&gt; When I created a ConPTY but didn't write its output anywhere, stdin still showed raw bytes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finding 3:&lt;/strong&gt; When I wrote ConPTY output to stdout, suddenly stdin switched to win32-input-mode sequences!&lt;/p&gt;

&lt;p&gt;The trigger: &lt;strong&gt;writing PTY output to stdout causes Windows Terminal to enable win32-input-mode for the session.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Attempt to Disable It
&lt;/h3&gt;

&lt;p&gt;ConPTY supports a sequence to disable win32-input-mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x1b&lt;/span&gt;&lt;span class="s"&gt;[?9001l"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Disable win32-input-mode&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This... didn't work reliably. Windows Terminal still sent the sequences in many scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Parse the Sequences
&lt;/h3&gt;

&lt;p&gt;If we can't disable it, we parse it. First attempt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;parseWin32InputMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Look for ESC [ ... _&lt;/span&gt;
    &lt;span class="c"&gt;// Extract the Uc (unicode char) field&lt;/span&gt;
    &lt;span class="c"&gt;// Return as raw bytes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked for single keypresses, but failed intermittently. The debug output revealed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[win32] seq=89;21;25;1;8;1 -&amp;gt; byte 25 (0x19)  // First sequence parsed!
[win32] passthrough byte 27 (0x1b)            // Wait, why?
[win32] passthrough byte 91 (0x5b) '['        // This is another ESC[!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After parsing the first sequence correctly, subsequent sequences in the same buffer were passed through as raw bytes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: The Buffer Boundary Bug
&lt;/h3&gt;

&lt;p&gt;The culprit: &lt;code&gt;os.Stdin.Read()&lt;/code&gt; returns arbitrary chunks. An escape sequence can be split across reads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read 1: ...25;1;8;1_\x1b     &amp;lt;- ends with ESC
Read 2: [17;29;0;0;0;1_...   &amp;lt;- starts with [
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Read 1 ends with &lt;code&gt;\x1b&lt;/code&gt;, my parser checked &lt;code&gt;data[i+1]&lt;/code&gt; for &lt;code&gt;[&lt;/code&gt;, but there was no next byte - it was in the next read! The ESC got passed through as a raw byte.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: An Iterator with Remainder Handling
&lt;/h2&gt;

&lt;p&gt;Go 1.23 introduced &lt;code&gt;iter.Seq&lt;/code&gt;, which is perfect for this. The iterator:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads from stdin&lt;/li&gt;
&lt;li&gt;Parses win32-input-mode sequences&lt;/li&gt;
&lt;li&gt;Yields extracted bytes&lt;/li&gt;
&lt;li&gt;Holds incomplete sequences for the next read
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ScanWin32Input returns an iterator that reads from r and yields parsed bytes.&lt;/span&gt;
&lt;span class="c"&gt;// Buffer boundaries are handled internally - incomplete sequences at the end&lt;/span&gt;
&lt;span class="c"&gt;// of a read are held and combined with the next read.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ScanWin32Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pending&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
        &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c"&gt;// Combine pending bytes with new data&lt;/span&gt;
                &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                    &lt;span class="n"&gt;pending&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="c"&gt;// Parse and yield bytes&lt;/span&gt;
                &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;remainder&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;parseWin32Sequences&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;pending&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;remainder&lt;/span&gt;

                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key is &lt;code&gt;parseWin32Sequences&lt;/code&gt; returning a &lt;strong&gt;remainder&lt;/strong&gt; - any incomplete sequence at the end of the buffer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;parseWin32Sequences&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;remainder&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
    &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0x1b&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;// ESC at end of buffer? Save as remainder&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'['&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c"&gt;// Look for sequence terminator '_'&lt;/span&gt;
                &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;findTerminator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c"&gt;// Complete sequence - parse it&lt;/span&gt;
                    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;extractUnicodeChar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="c"&gt;// No terminator found - might be incomplete&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;hitInvalidChar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c"&gt;// Save as remainder&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="c"&gt;// Regular byte - pass through&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;With the iterator, consuming parsed input is clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;ScanWin32Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;inputCh&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;

&lt;span class="c"&gt;// In main loop&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;inputCh&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0x19&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// Ctrl+Y now works!&lt;/span&gt;
        &lt;span class="n"&gt;overlay&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Windows Terminal enables win32-input-mode when you write PTY output to stdout.&lt;/strong&gt; There's no reliable way to disable it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Buffer boundaries will break naive parsers.&lt;/strong&gt; Always handle the case where a sequence is split across reads.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Go 1.23+ iterators are perfect for streaming parsers.&lt;/strong&gt; The &lt;code&gt;iter.Seq&lt;/code&gt; pattern encapsulates state cleanly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build diagnostic tools.&lt;/strong&gt; The keylog utility that tested different scenarios (direct read, PTY only, PTY + output) was essential for isolating the trigger.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Key-up events exist.&lt;/strong&gt; Win32-input-mode sends both key-down (Kd=1) and key-up (Kd=0) events. Only emit bytes for key-down.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Full Parser
&lt;/h2&gt;

&lt;p&gt;The complete implementation handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Buffer boundary splitting&lt;/li&gt;
&lt;li&gt;Key-down vs key-up filtering&lt;/li&gt;
&lt;li&gt;Focus in/out sequences (&lt;code&gt;ESC[I&lt;/code&gt; and &lt;code&gt;ESC[O&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Invalid sequence recovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find it in the &lt;a href="https://github.com/standardbeagle/agnt/blob/main/internal/overlay/input.go" rel="noopener noreferrer"&gt;agnt repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md" rel="noopener noreferrer"&gt;Win32 Input Mode Spec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session" rel="noopener noreferrer"&gt;ConPTY Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/standardbeagle/agnt" rel="noopener noreferrer"&gt;agnt - Browser superpowers for AI coding agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/aymanbagabas/go-pty" rel="noopener noreferrer"&gt;aymanbagabas/go-pty&lt;/a&gt; - Cross-platform PTY library for Go&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>win32</category>
      <category>terminal</category>
      <category>pty</category>
    </item>
  </channel>
</rss>
