Things I've built

Mostly small tools built to scratch a specific itch. A running theme: most of them run with no API key — they borrow my Claude Code subscription by shelling out to the CLI.

chrome-agent

2026working

A browser agent that drives my already-logged-in Chrome over CDP, reading the DOM instead of screenshots.

typescriptplaywrightcdpclaude-cli

A faster, self-hosted take on "AI in your browser". It attaches to the Chrome I'm already signed into via chromium.connectOverCDP, so every session, cookie and SSO token I already have just works — no separate login, no headless profile to keep warm.

Perception is DOM-first. It reads the accessibility tree and tags interactable elements as compact text, using screenshots only as a fallback. That's dramatically cheaper and faster than vision-on-every-step, and for most pages it's also more precise — a button's accessible name beats guessing at pixels.

The brain runs with no API key. It auto-detects a provider: if ANTHROPIC_API_KEY is set it uses the SDK, otherwise it shells out to the logged-in claude CLI. That means the agent costs nothing beyond the subscription I already pay for. Most of my tools work this way now.

Where it falls down: a DOM-only loop is imprecise on dense widget UIs — think a charting tool's drag-and-drop builder, where the meaningful state isn't in the accessibility tree at all. For those I hand-drive with CDP scripts and verify with screenshots. Vision fallback is the obvious fix and needs an API key, which is the tradeoff I've been avoiding.

It's also the source of mistake #002 — the DOM collector had to be injected as a raw JS string, because passing a TypeScript function let the compiler smuggle a helper into the page that didn't exist there.

gatekeeper

2026building

App-agnostic Android E2E testing — AI explores the app, deterministic tooling replays it.

typescriptmaestroadbanthropic-sdk

An end-to-end Android testing platform that doesn't need to be told what the app contains. It discovers flows itself, then pins them down as repeatable tests.

The split is the whole design:

  • Discovery is AI. Given an app, it walks the UI and works out what the screens and flows actually are. This is the part that can't be scripted, because scripting it is exactly the work you're trying to avoid.
  • Execution is deterministic. Once a flow is known it runs through Maestro and ADB — no model in the loop. A test that passes or fails based on a model's mood isn't a test.

Letting an LLM drive discovery but never the assertion is the pattern I keep coming back to. AI for the ambiguous part, plain code for the part that has to be reproducible.

lt

2026working

Solves the LeetCode daily problem at 5:40 AM, and writes down what it got wrong so tomorrow's run is smarter.

typescriptlaunchdclaude-cli

A scheduled agent that does the LeetCode problem of the day before I wake up.

The interesting part isn't the solving, it's the loop. On a wrong answer or a timeout it takes the failing testcase, feeds it back in, and retries. When it eventually passes it writes a lesson to disk — and the next morning's run reads every lesson it has written before it starts. So the prompt gets better on its own without me touching it.

Zero runtime dependencies, and the brain is the claude CLI, so it costs nothing per run.

Honest status: the agent works. The schedule didn't — the job has run essentially once. A launchd job that fails silently is indistinguishable from one that was never installed, which is its own lesson and probably its own post.

NoCheatCode

2026shipped

A LeetCode debugger that refuses to give you the answer — it only asks questions.

javascriptchrome-extensionsupabaserazorpay

A Chrome extension that sits next to a LeetCode problem and debugs it Socratically: it will ask you what your loop invariant is, what happens on an empty input, why you chose that data structure. It will not write the solution. That constraint is the entire product — every other AI coding helper is optimising for the opposite.

Built as a full micro-SaaS rather than a toy: auth, voice input, usage metering, Razorpay billing, and Supabase with row-level security so a user can only ever read their own rows.

Deliberately plain vanilla JS with no build step. For a Manifest V3 extension the bundler mostly buys you problems — content scripts, service workers and CSP all behave more predictably when the file you wrote is the file that runs.

x-studio

2026working

A local CMS that drafts posts from my own engagement data and posts them only after I approve each one.

typescriptplaywrightcdpclaude-cli

A semi-automated writing studio for X. It harvests my own timeline and past posts, scores them by engagement rate rather than raw counts, and feeds the winners back into the drafting prompt — so the model copies the pattern of what actually landed instead of what I once wrote in a plan document.

Three parts:

  • harvest — pulls my posts, their engagement, and the live feed over CDP into local JSONL.
  • draft — builds a prompt from a weekly cadence, a ground-truth context file about what I'm currently working on, and the top/flop posts, then generates candidates through the claude CLI. No API key.
  • cms — a local page where each draft is an editable card with a live character counter. Nothing posts automatically; every tweet needs a click.

What I got wrong. Two things, and they're the same thing twice. The drafting prompt told the model to leave a [bracket] wherever it lacked a real number rather than inventing one — correct engineering, and it meant every draft arrived as homework. And the harvest step, which supplies the personalisation, was never actually run, so drafts silently fell back to the plan document and read generic.

The tool works. It produced a queue of drafts and zero posts. Good systems that sit one manual step away from done are still zero.