<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Amith GC]]></title><description><![CDATA[Amith GC]]></description><link>https://amithgc.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 05 Nov 2026 19:22:29 GMT</lastBuildDate><atom:link href="https://amithgc.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How I built Quarters: a Go workspace for parallel CLI coding agents with passkey phone access]]></title><description><![CDATA[When you run one coding agent in a terminal tab, any terminal emulator works. When you run Claude Code, Codex, Gemini CLI, or Aider across ten repositories…]]></description><link>https://amithgc.hashnode.dev/how-i-built-quarters-a-go-workspace-for-parallel-cli-coding-agents-with-passkey-phone-access</link><guid isPermaLink="true">https://amithgc.hashnode.dev/how-i-built-quarters-a-go-workspace-for-parallel-cli-coding-agents-with-passkey-phone-access</guid><dc:creator><![CDATA[Amith GC]]></dc:creator><pubDate>Tue, 03 Nov 2026 18:46:59 GMT</pubDate><content:encoded><![CDATA[<p>When you run one coding agent in a terminal tab, any terminal emulator works. When you run Claude Code, Codex, Gemini CLI, or Aider across ten repositories at once—each with its own dev server, database port, and permission prompts—standard terminal tabs start to break down.</p>
<p>You end up cycling through tabs to find out which agent stopped five minutes ago to ask a question, or wondering how to undo a prompt that touched twelve files without wrecking your git index, or wishing you could approve a migration from the sofa on your phone.</p>
<p>I built <strong><a href="https://github.com/amithgc/quarters?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=e54_quarters_arch">Quarters</a></strong> (<a href="https://quarters.amithgc.com?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=e54_quarters_arch">quarters.amithgc.com</a>) to solve that workflow for myself. It is a native macOS app (plus a headless Linux server; Windows is supported as a browser client only) and is <strong>free and open source (MIT)</strong>.</p>
<blockquote>
<p><em>Independence note: Quarters is an independent open-source project. It is not made by, affiliated with, or endorsed by Anthropic, OpenAI, Google, GitHub, or the makers of the other agents it runs.</em></p>
</blockquote>
<p>Here is how the three core pieces work under the hood: PTY session multiplexing across desktop and mobile, zero-index-touch git checkpoints before every prompt, and passkey-paired remote access.</p>
<hr />
<h2>1. One PTY per session, shared across the Mac window and phone browsers</h2>
<p>Quarters is built with <a href="https://wails.io">Wails</a>: a Go backend driving a plain ES-module JavaScript frontend (no bundler) inside the system WebView, with <a href="https://xtermjs.org">xterm.js</a> rendering each terminal.</p>
<p>Each project gets its own "room" (<code>internal/projects</code>), and each tab inside a room—whether it is an agent (<code>claude</code>, <code>codex</code>, <code>gemini</code>, <code>aider</code>, <code>opencode</code>, <code>copilot</code>, <code>devin</code>, <code>amp</code>, <code>goose</code>, <code>qwen</code>, <code>agy</code>, or <code>agent</code>) or a shell—runs in a pseudo-terminal managed by Go (<code>internal/session</code>).</p>
<p>Because the Go backend owns the PTY stream:</p>
<ul>
<li><strong>Multi-window &amp; phone sync:</strong> The Mac window and any connected phone or laptop browser subscribe to the same live PTY session over a WebSocket. Whichever window you are actively typing in sets the terminal's dimensions.</li>
<li><strong>"Your turn" detection (<code>◐ waiting</code>):</strong> Quarters watches terminal activity and session logs so when an agent finishes a turn or pauses for input in a background project, the room gets marked and moves to the top of the <code>ACTIVE</code> list (sorted longest-waiting first, reachable from anywhere with <code>⌘J</code>).</li>
<li><strong>In-process subagents:</strong> When launching Claude Code, Quarters passes <code>--settings '{"teammateMode":"in-process"}'</code> (unless you set your own <code>--settings</code>) and strips inherited <code>TMUX</code> or <code>STY</code> environment variables. That keeps subagents inside the tab you can see on both your Mac and your phone instead of spawning invisible tmux panes.</li>
<li><strong>Low idle footprint:</strong> Git status checks and file watchers only run when filesystem events fire, and timers pause while the window is hidden. Measured on a Mac with 14 projects open, Quarters sits at roughly <strong>2% of one CPU core</strong> when idle in the background.</li>
</ul>
<hr />
<h2>2. Snapshotting every prompt without touching your git index (<code>refs/quarters/checkpoints/</code>)</h2>
<p>Every time you send a prompt to an agent, you want a clean "undo" button in case the agent goes down the wrong path—even if you had uncommitted work in your working tree before the prompt started.</p>
<p>Using <code>git stash</code> or commits on your current branch causes constant friction with your real staging area. Instead, Quarters snapshots the project before every prompt using a temporary git index:</p>
<ol>
<li>Quarters copies the current index state into a temporary <code>GIT_INDEX_FILE</code>.</li>
<li>It stages all non-ignored working-tree files into that temporary index and writes a tree object (<code>git write-tree</code>).</li>
<li>It creates a commit object stored under <code>refs/quarters/checkpoints/</code> (keeping the last 50 per project).</li>
</ol>
<p>Because these commits live under <code>refs/quarters/checkpoints/</code>:</p>
<ul>
<li>Your actual branch, <code>HEAD</code>, staging index, and stash are <strong>never touched</strong>.</li>
<li>A plain <code>git push</code> never uploads checkpoint commits to your remote.</li>
<li>Opening the <strong>Checkpoints</strong> panel shows the exact file-by-file diff since any prompt. Clicking <strong>Restore</strong> takes a fresh checkpoint first (so even a restore is reversible) and puts your files back where they were.</li>
</ul>
<p>Similarly, the built-in Git commit dialog refuses to stage or commit files that look like secrets (<code>.env</code>, <code>*.pem</code>, private keys)—stopping the commit and naming the blocked files before they ever reach history.</p>
<hr />
<h2>3. Remote access from a phone: passkeys, loopback listener, and origin checks</h2>
<p>Because a terminal tab runs with your user's privileges, anyone who can type into it can run commands as you. Remote access is <strong>designed to be safe to expose; read <a href="https://github.com/amithgc/quarters/blob/main/SECURITY.md">SECURITY.md</a></strong> for the full threat model and implementation details.</p>
<p>Key design choices in <code>internal/remote</code>:</p>
<ul>
<li><strong>Off until you start it:</strong> The HTTP/WebSocket server does not run until you explicitly start it (<code>⌥⌘R</code> or <em>Remote Access → Start server</em>). By default it binds only to <code>127.0.0.1</code> and expects to be reached through your own tunnel (such as Cloudflare Tunnel or Tailscale Funnel), so no router ports are opened.</li>
<li><strong>Paired from the Mac with WebAuthn passkeys:</strong> Clicking <em>Pair a device</em> on the Mac generates a QR code carrying a single-use token in the URL fragment (never logged, valid for 10 minutes, burned after 5 failed attempts). Signing in uses WebAuthn passkeys (<code>go-webauthn</code>) requiring device user verification (Face ID, Touch ID, or PIN) bound to your domain. If a passkey's signature counter ever moves backward—a sign of credential cloning—Quarters refuses it and alerts you on the Mac.</li>
<li><strong>In-memory SHA-256 session hashes &amp; strict allowlists:</strong> Sessions are random 256-bit tokens kept only in memory as SHA-256 hashes (restarting Quarters signs everyone out) with <code>__Host-</code> prefixed, <code>HttpOnly</code>, <code>Secure</code>, <code>SameSite=Strict</code> cookies. Every WebSocket message re-verifies the session, checks <code>Host</code> and <code>Origin</code> headers to block DNS rebinding and cross-site WebSocket hijacking, and passes through an explicit RPC allowlist (<code>remote_app.go</code>) that keeps device pairing and security settings Mac-only.</li>
<li><strong>Phone-friendly cards + full terminal:</strong> On a phone browser, you can read the conversation as clean message cards, approve or reject tool prompts with a tap, queue your next prompt while the agent works, or flip to the live xterm.js view.</li>
</ul>
<hr />
<h2>Try it or read the code</h2>
<p>Quarters is <strong>free and open source (MIT)</strong>:</p>
<ul>
<li><strong>GitHub repository:</strong> <a href="https://github.com/amithgc/quarters?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=e54_quarters_arch">https://github.com/amithgc/quarters</a></li>
<li><strong>Website &amp; tour:</strong> <a href="https://quarters.amithgc.com?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=e54_quarters_arch">https://quarters.amithgc.com</a></li>
<li><strong>Security architecture:</strong> <a href="https://github.com/amithgc/quarters/blob/main/SECURITY.md">SECURITY.md</a></li>
<li><strong>Homebrew (macOS):</strong><pre><code class="language-sh">brew install --cask amithgc/tap/quarters
</code></pre>
</li>
</ul>
<p>If you run multiple CLI agents across projects—or have solved terminal multiplexing and mobile approvals differently—I would love to hear what your setup looks like.</p>
]]></content:encoded></item><item><title><![CDATA[How I make sure none of my puzzles needs a guess]]></title><description><![CDATA[The small solver scripts behind Little Grids, my grid-puzzle app for iPhone, iPad and Mac]]></description><link>https://amithgc.hashnode.dev/how-i-make-sure-none-of-my-puzzles-needs-a-guess</link><guid isPermaLink="true">https://amithgc.hashnode.dev/how-i-make-sure-none-of-my-puzzles-needs-a-guess</guid><category><![CDATA[Game Development]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[iOS]]></category><category><![CDATA[puzzle]]></category><category><![CDATA[Indie Games]]></category><dc:creator><![CDATA[Amith GC]]></dc:creator><pubDate>Mon, 02 Nov 2026 12:50:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ae886248831ba3a85b16257/70812484-5980-4933-b367-64a8b51b678d.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've always hated the moment in a Minesweeper game where you've done everything right and you still have to flip a coin. So when I started building Little Grids, a collection of 16 small grid puzzles, I set myself one rule: a logic board only ships if it can be finished by reasoning alone.</p>
<p>That rule turned out to be most of the work. Every game started life as a browser game, and each one has its own little generator and solver written as Node scripts. The boards in the app are exported straight from those scripts and never edited by hand. Here's how a few of the games are checked.</p>
<p><strong>Mines.</strong> Each board has a fixed opening. A solver starts from that opening and only makes moves it can prove are safe. If it ever gets stuck, the board is thrown away. So you never face a 50/50, and the board doesn't move mines around after your first tap to hide that.</p>
<p><strong>Nonogram.</strong> I drew 42 pictures by hand, in 5x5, 10x10 and 15x15. A picture is kept only if solving it one line at a time, with no guessing, finishes the whole thing. Some of my favourite drawings didn't survive that test, and I had to redraw them.</p>
<p><strong>Shikaku.</strong> The generator cuts the grid into rectangles first, then an exhaustive solver checks the clues. A board is kept only if there is exactly one answer.</p>
<p>Loop and Switch Off are solvable by construction. Loop scrambles a random spanning tree, and Switch Off starts from a board with every light off and applies random taps, so there is always a way back.</p>
<p>Word Snake lays each word as a path that turns, adds decoy starting letters, and then searches the finished grid to make sure every word is really there.</p>
<p>The app itself is written in Flutter, and the Dart game engines are tested against the web code: for each game, a script runs the web engine and solver and saves real solutions, and the Dart tests replay them move by move. If the two ever disagree, the test fails.</p>
<p>Why bother? Because a puzzle that needs a guess isn't really a puzzle. When you're stuck in Little Grids, there's always a reason you can find, and the hint (a ghost finger that shows the next move) is computed by the same solvers that checked the board.</p>
<p>Little Grids is free to download on iPhone, iPad and Mac, and every game has a free part with no time limit: <a href="https://apps.apple.com/app/id6816600512">https://apps.apple.com/app/id6816600512</a></p>
<p>This Sunday, 8 November, there's a Big Sunday board event in the app if you want to try it on something bigger.</p>
<p>I'd love to hear from other people who build puzzles: how do you check yours?</p>
<p>- Amith</p>
]]></content:encoded></item></channel></rss>