Anthropic killed Conductor.build's subscription pricing, so I built an open-source replacement in a weekend

Termic (termic.dev) is a native macOS app that spawns the real Claude Code, Codex, and Gemini CLIs in PTYs. Subscription stays valid, per-workspace sandbox, multi-repo workspaces. Rust, Tauri 2, React, no Electron, AGPL.

claude-codetaurirustdeveloper-tools
Termic main workspace showing the Claude Code CLI running inside a sandboxed worktree

Just got used to Conductor.build. Muscle memory finally there, parallel agents across worktrees, the whole flow clicked. Then Anthropic changed how the Claude Agent SDK is billed on a Claude plan: third-party harnesses (Conductor is exactly that) now draw from a $100/month API-credit pool instead of the subscription itself. That pool gets eaten in less than a day of real coding agent use. Effectively it stopped working on the plan I already pay for.

So I spent the weekend (and a long Monday night) building a replacement. It’s called Termic, it’s open source: termic.dev / github.com/simion/termic.

TL;DR. Termic spawns the real claude / codex / gemini CLIs in PTYs instead of wrapping the SDK. Your subscription stays valid. Per-workspace macOS sandbox (filesystem + network). Multi-repo workspaces with auto-allocated sibling ports. Bring your own agent in 30 seconds.

Why running the real CLIs

The SDK was the dependency that just broke for everyone. So I removed it.

Termic runs the official claude, codex, gemini binaries the same way iTerm runs them. PTY in, bytes out. No vendor-wrapped client, no version mismatch, no broken auth flow because some third party hasn’t updated their adapter yet.

Two things fall out of this:

  1. Subscription stays valid. The CLI is the official one, the auth is the official one, billing is whatever the vendor decided for first-party use.
  2. Whatever ships next in any of those CLIs (new flags, plan mode, model upgrades) lands in Termic the same day. There’s nothing to update on my side.

Pain points I addressed

Things that bit me daily in Conductor.

Worktree or repo-root, your choice

Conductor forces a git worktree every time. Most of the time that’s the right call: parallel agents on parallel branches is the killer feature. But sometimes I just want the agent to work on main directly without a worktree dance. Termic lets you pick per workspace.

Sandbox (filesystem + network)

This is the one I’m proudest of. The one I needed the most.

Every workspace can be sandboxed. macOS Seatbelt for the filesystem, an in-process CONNECT proxy with a per-CLI hostname allowlist for the network. With sandbox on, the agent gets read/write access only to the worktree directory and the agent’s own config dirs. Everything else — your home dir, credentials, browser data — is inaccessible by default unless you explicitly add it to the allowlist.

Network works the same way: the agent can only reach hosts on the allowlist. Defaults cover Anthropic / Google / OpenAI, GitHub, npm/pip/cargo. Add more per project.

The sandbox is pinned at workspace creation. There’s no “toggle off” mid-session. If you want to flip it, archive the workspace and create a new one. That’s the security boundary: the agent can’t talk its way out of the cage it was spawned in.

While testing this I noticed something. Claude Code attempts reads on ~/.config/gh/hosts.yml (GitHub auth tokens) and ~/Library/Application Support/{Chrome,Brave,Arc} (browser profile dirs) on idle. The sandbox blocked them. Without it those reads would have succeeded silently.

Termic sandbox whitelist blocking Claude Code from reading GitHub auth tokens and Chrome / Brave / Arc browser profile data

I’m reproducing the read-vs-stat distinction before writing that up properly. Either way, this is the reason the sandbox exists.

Multi-repo workspaces

A lot of features span multiple repos. Frontend in one, backend in another, sometimes infra in a third. Conductor makes you switch projects and lose context. Termic has a “multi-repo project” type: pick N member repos, get a wrapper directory with shared CLAUDE.md, and every workspace under it gets all members worktreed (or symlinked) into one dir.

Termic Add project dialog for a multi-repo project with name, optional host repository, and member list

Each member gets its own $TERMIC_PORT so parallel dev servers don’t collide. Sibling ports are exposed as $TERMIC_PORT_<MEMBER>. My frontend’s actual run script:

PORT=$TERMIC_PORT \
NUXT_API_READ_BASE_URL=http://localhost:$TERMIC_PORT_PYDPF \
NUXT_API_WRITE_BASE_URL=http://localhost:$TERMIC_PORT_PYDPF \
just run

One diff view aggregates changes across all members. One archive cleanly tears all worktrees down.

Bring your own agent

Settings → Agents. Add any PTY-based CLI in 30 seconds. OpenCode, aider, Ollama, custom shell scripts. The built-in three (claude / gemini / codex) you can also edit to point at a wrapper script, a different binary, whatever.

Termic Settings → Agents page showing claude/gemini/codex tabs with editable Command, Default args, YOLO args, Runtime YOLO command, and Resume args fields

Same future-proofing as running the real CLIs: whichever agent gets popular next is a one-minute add, not a feature request.

How I detect “agent is done”

This part took embarrassingly long to get right.

My first attempt: watch the PTY output cadence. If no bytes for N seconds, the agent must be done. Wrong about half the time. Long tool calls, slow API responses, all triggered false positives. Users would click into a workspace expecting “done” and find the agent mid-thought.

Then I realized the agents already tell the terminal exactly when they’re working. iTerm shows a spinner because the agent sends a sequence asking for one. Termic just needs to listen.

  • Claude Code emits OSC 9;4, the ConEmu/iTerm progress protocol. State 3 = busy, state 0 = idle.
  • Gemini sets the OSC 0 window title with explicit strings: ◇ Ready when idle, ✦ Working… while thinking, ✋ Action Required when waiting on you.
  • Codex does the same with literal words: Working / Thinking / Ready / Waiting / Action Required.

Termic classifies these and surfaces two tab-level icons: green check (turn complete), yellow bell (agent is blocked waiting on your input). Plus a desktop notification. Click the notification, you jump straight to that workspace and tab.

Switching to the sender-emitted signals dropped false positives effectively to zero. The lesson: when the protocol exists, use the protocol. Don’t pattern-match output like an animal.

Stack

Rust for the backend (PTY management, sandbox provisioning, git, IPC). Tauri 2 for the desktop shell, system WebView (WKWebView on macOS), not Electron, bundle around 15 MB. React 19, Vite, TypeScript on top. xterm.js + WebGL for terminals. CodeMirror 6 for the editor and the side-by-side diff. portable-pty (wezterm’s fork) for PTY handling.

Cold start under 400ms on M-series.

What’s next

Linux and Windows builds once I free up enough disk to spin up the VMs. Tauri makes this mostly a CI exercise but I keep procrastinating it.

Apple Developer signing. Right now you install via Homebrew because I haven’t expensed the $99/year yet:

brew install simion/termic/termic

Smarter sandbox auto-allow so first-launch with weird tooling (poetry, devbox, nix) is one-click.

Site: termic.dev. Code: github.com/simion/termic. AGPL-3.0. Issues and PRs welcome.