Skip to content

Latest commit

 

History

1,274 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TerMinal

TerMinal

The terminal, rebuilt for agents that do the work.

TerMinal terminal tab with the live cockpit sidebar

Your terminal was designed for a human typing one command at a time. Your actual workload is now five Claude Code sessions, a Codex agent grinding a backlog on a schedule, and a review pass you'd like to trust — and the terminal shows you a blinking cursor.

TerMinal is a macOS app that hosts the real agent CLIsClaude Code, Codex, Cursor Agent, plus OpenRouter and Hermes harnesses for everything else — and wraps them in the management layer an agent workforce needs: a backlog agents own, schedules that fire with the app closed, a review surface for every PR, a cost ledger for every run, and one inbox for the moments that genuinely need a human.

One rule holds the whole thing together: agents never merge. They design, ticket, branch, implement, open the PR, and stop. The merge to main is yours, every time.

And it's local-first, all the way down. No server, no account, no telemetry. Sessions run on your claude/gh/glab auth. Every artifact — activity feed, run logs, schedules, review results, the Inbox — is a file on your machine that renders offline.

macOS-first (why). Open source, MIT.


Screenshots

Tickets tab Agents tab
Runs tab Schedules tab

What you get

A real terminal, multiplied

  • Real CLIs, real PTYs. Each session runs the actual engine binary in its own pseudo-terminal (the same xterm.js + node-pty pattern as VS Code's terminal). Nothing is reimplemented or proxied; resume uses each engine's native resume.
  • Workspaces as tabs. Repos across the top, sessions within each, split and 4-up grid layouts across repos, ⌘K palette to jump anywhere. Terminals stay mounted when you switch tabs — a session never drops.
  • A cockpit per session. A sidebar of live, per-session telemetry: context window %, token burn, your plan's 5-hour/weekly usage (a live /usage mirror), what the agent is doing right now, its todo list, git state, and the latest TDD/code-review verdict. Every number describes one session, never an aggregate.
  • Five engines, one launcher. Claude, Codex, and Cursor run interactive sessions and agent jobs; OpenRouter and Hermes run agent jobs on any model slug — with a curated menu, per-run cost capture, and your API key sealed in the OS keychain.

A software factory around it

  • Tickets agents can own. Markdown backlog by default; GitHub Issues or Linear as drop-in providers; team boards embed read-only. Every ticket has an owner agent, acceptance criteria, and links to the runs and PRs it produced.
  • Agents are the unit of work — a roster of classic (prompt/script) and persistent (memory-backed) agents with model policy, deterministic checks, optional LLM judges, and per-repo overrides in .agents/. Code-changing runs get an isolated git worktree; nothing touches your checkout.
  • One ledger for runs. In-process, scheduled, background, and terminal-launched runs normalize into a single view: live log, lineage back to the ticket and PR, evaluation results, dollar cost, rerun/cancel.
  • Schedules that outlive the app. Local schedules are real launchd jobs (a headless runner ships in the bundle); schedules can also target always-on Linux hosts over SSH — systemd timers or k8s CronJobs — so the factory keeps running when your laptop doesn't. A failed run files itself to the inbox.
  • PR/MR review built in. gh/glab auto-detected per repo, full diff viewer (unified/split, per-file viewed-state), review findings and suggestions, forge CI status, and a merge button that is deliberately the only one in the app.
  • Loops for goal convergence (experimental — enable in Settings → Experimental). Pair a driver session that writes a gradable contract and adversarially grades, with a worker session implementing in a worktree — or run single-session mode where fresh evaluators grade a long-lived generator. The generator never grades itself.
  • Observability you can act on. Cross-repo throughput, cycle time (ticket-filed → PR-merged with stage splits and a funnel), success rates, an AI-spend explorer down to per-request payloads, and a /factory orchestrator that works the backlog until it's dry.

A human gate that actually gates

  • One inbox for everything human. Approvals, credentials, decisions, hard blockers — agents file items to a global Inbox from any repo, each pings Telegram, and the app badges the count until you resolve it.
  • AFK control from your phone. The Telegram bridge is two-way: /feature <idea> drafts a ticket and offers a start-work button; /runs, /tail, /inbox, /cancel and friends steer the factory while you're away.
  • Merges stay yours. Agents stop at "PR open" by design, and the tooling itself enforces it — a prompt can't talk an agent past the gate.

Quick start

Requires bun and at least one engine CLI on your PATH (claude or codex).

git clone https://github.com/trevormil/TerMinal.git
cd TerMinal
git submodule update --init   # optional: vendored references (vendor/)
bun install                   # also rebuilds node-pty against Electron's ABI
bun run dev

First launch is a two-step setup: step one probes your machine (which of claude/codex/cursor/gh/glab are installed and authenticated), confirms your projects folder, and lets you pick a default agent engine; step two offers the optional connections — Telegram, the MCP server, the activity hook, an OpenRouter key. Everything has a working default and both steps are skippable. A one-time orientation screen then maps the tabs, and each repo gets its own orientation on first open.

From the session picker: resume an existing session, start a new one in any folder, scaffold a new project from the template, or launch a paired loop. gh and glab are optional — they light up the forge features that use them.

Platform: macOS (Apple Silicon). Packaging, launchd scheduling, and the editor/browser handoffs assume macOS. Dev works on Linux minus local schedules (use remote hosts). Rationale and a port checklist: ADR-0003.

Download the app

Download the latest .dmg (Apple Silicon). Every release ships the DMG plus a SHA256SUMS.txt.

Releases are signed with a Developer ID and notarized, so a normal double-click opens them — no Gatekeeper right-click dance. Verify the checksum anyway; it is cheap and it is the one check that does not depend on trusting the signature:

shasum -a 256 -c SHA256SUMS.txt   # expect: TerMinal-<ver>-arm64.dmg: OK
spctl --assess --type execute -vv /Applications/TerMinal.app   # → accepted

If the checksum does not match, do not open it. Signing is conditional on the release pipeline having the Apple credentials: a fork (or a tag built without them) falls back to an unsigned DMG, which still runs but needs right-click → Open the first time and cannot deliver macOS notifications. See docs/setup.md.

Or build and install it yourself

bun run release     # build → sign (if you have a Developer ID) → notarize → install → relaunch

bin/release picks its path from what your keychain actually holds: with a Developer ID it signs and notarizes (which needs an App Store Connect key — it aborts rather than shipping a signed-but-un-notarized build); with no identity, or with TERMINAL_UNSIGNED=1, it ad-hoc signs a local build instead. Ad-hoc is fine for development, but macOS will not deliver notifications from it (Electron 42+).

Details: docs/runbooks/build-and-release.md.

How a feature ships

The loop TerMinal is built to run, end to end:

  1. File it. A ticket lands in the repo's backlog — typed in the Tickets tab, filed by an agent, or texted in via Telegram /feature. It gets an owner agent and acceptance criteria.
  2. An agent picks it up. On demand, on a schedule, or via the /factory orchestrator. The run gets its own git worktree and branch; the run record starts streaming into Runs.
  3. TDD-first implementation (the project-template workflow): failing test → code → suite green → push → PR opened with the ticket linked.
  4. Review to a bar. A code-review agent scores the diff and writes findings into the repo's .reviews/; the MRs/PRs tab renders verdict, findings, and the diff side by side. Deterministic checks and optional judges run per the owning agent's contract.
  5. You merge. The one step no agent performs. Post-merge, ticket state reconciles automatically and cycle-time metrics update.

Anything that stalls — a credential, an approval, a failed scheduled run — files itself to the inbox and pings your phone instead of dying silently in a log.

The surfaces

Tabs are repo-aware and curated: the defaults stay focused, the rest enable in Settings → Tabs.

Surface What it's for
Terminal The engine CLI plus the per-session cockpit.
Tickets Browse/filter/create; inline status edits write back to markdown. Provider per repo: local, GitHub, Linear, webview.
MRs / PRs Live forge requests with diff, findings, CI state, and the merge button.
Agents The roster: definitions, model policy, contracts, run history, one-click ticket implementation.
Runs Every run with log, lineage, evaluation, and cost.
Schedules launchd/systemd/k8s-backed cadence with run history and a reconcile that surfaces dark jobs.
Monitoring Deterministic infra checks — HTTP, TLS-cert expiry, TCP, DNS, shell — on their own intervals, grouped by status. No inference, and it flags its own daemon going stale so a frozen green can't read as healthy.
Observability The AI-spend and trace explorer — every request, priced.
Reports Scheduled-agent output: reports/<kind>/<sha>.md grouped by agent, with each run's status, metrics, and PR/ticket links parsed out of its frontmatter.
Search One query across the workspace — files, tickets, MRs, docs, runs, activity, snippets, agent artifacts — with click-through to the owning tab.
Sessions The repo's session history: /session-start working notes plus a search over the raw engine transcripts.
Docs GitBook-style reader over docs/, grouped by category (Changelog, Maintainer, Developer, Personal, Other) with rendered markdown.
Activity The chronological event feed — runs, tickets, PR verdicts, deploys, errors — each row click-through to the surface it came from.
Inbox The global, cross-repo HITL queue: decisions, approvals, credentials, failed cron jobs. Badge counts unread and always visible.
Agent Config An editor over ~/.claude and ~/.codex — your global agent config, edited in place instead of in a side terminal.
Panels Pin arbitrary web dashboards (Grafana, a status page, anything) and view them embedded. Hidden until you configure one.
CI / Browser / Files / Notes Forge CI page, embedded webview, a CodeMirror editor with project search, autosaving markdown notes.
Inbox (top-right) The global Inbox — any category of item needing your eyes; badge always visible.

Not every surface is on by default. Activity, Docs, Notes, Reports, Sessions, Agent Config, and Help ship hidden; Panels appears only once you configure one. All of it is one toggle away in Settings → Tabs.

Extending it

Everything user-facing is a folder or a JSON file — no plugin API to learn beyond one object shape.

A widget is a folder under src/renderer/src/plugins/<id>/. It renders in the cockpit by default; the work column's accordion hosts the same spec for the few widgets listed in COLUMN_PLUGIN_IDS (Tickets, PRs/MRs), and each widget belongs to exactly one host:

import { Brain } from 'lucide-react'
import { Card, Big, Gauge } from '../../components/ui'
import type { Plugin, TranscriptStats } from '../../lib/types'

const plugin: Plugin<TranscriptStats> = {
  id: 'context',
  title: 'Context Window',
  icon: Brain,
  intervalMs: 2000,
  defaultEnabled: true,
  realtime: true, // also refresh the instant the transcript changes
  poll: (gt) => gt.transcript(),
  render: (d) =>
    d?.ok ? (
      <Card icon={Brain} title="Context Window">
        <Big value={`${d.contextPct.toFixed(1)}%`} />
        <Gauge pct={d.contextPct} />
      </Card>
    ) : null,
}
export default plugin

A tab is the same idea: src/renderer/src/tabs/<id>/index.tsx exporting { id, title, icon, order, appliesTo(ctx), Component }. appliesTo gates it per repo; an optional badge(gt) paints a live count.

A command widget needs no code at all — global (~/.config/TerMinal/widgets.json) or per-repo (<repo>/.TerMinal/widgets.json):

[
  {
    "id": "uncommitted",
    "title": "Uncommitted",
    "command": "git status --porcelain | wc -l | tr -d ' '",
    "intervalMs": 4000,
    "mode": "big"
  }
]

Trust: command widgets run shell commands. Global ones (~/.config/TerMinal/) are your own files and run freely. Per-repo ones (<repo>/.TerMinal/widgets.json, tabs.json) are inert until you approve them. The first time you open a session in a repo that defines any, the Plugins drawer shows the literal commands and asks you to approve that repo. The approval is keyed on the exact command set, so editing the file — or a git pull that changes it — asks again.

Agents integrate from the outside too: an MCP server (installable from Settings or onboarding) gives any Claude Code/Codex session cross-session views — tickets, runs, Inbox, activity — and the append-only stores under ~/.config/TerMinal/ mean a CI job or shell script can join the activity feed by appending a line.

New projects, ready-made workflow

TerMinal ships its project template embedded at templates/project-template — a scaffold carrying the whole workflow: sessions → tickets → branches → PRs → review → human merge, with the TDD gate, cadence checks, and the schemas these tabs read. It versions with the app itself (one repo, one history). What it puts in your repo is deliberately small — agent contracts, CI and docs. Skills come from the global tm plugin and workflow state lives in a per-project sidecar, so neither is copied per repo.

  • From the picker: "New project from template" — name it, pick a parent, Create. Fresh directory, git init, first commit, session opened, per-repo orientation shown. Details: docs/runbooks/new-project.md.
  • From the shell: bin/new-project my-app [parent-dir].

Existing repos adopt the same workflow via the in-app Bootstrap banner (or the per-repo orientation's Setup row) — your files are never clobbered; legacy per-repo skill copies (Claude and Codex) are moved to a backup, since the workflow now ships globally as the tm plugin. Tickets and reviews already committed in a repo keep working and stay visible; Settings → Updates offers the one-time move out of the checkout when you want it.

Setup & settings

The gear icon covers the rest, saved to ~/.config/TerMinal/settings.json: projects/worktrees paths, engine paths + default engine, forge preference, Telegram, ticket providers, remote SSH hosts, appearance, tab visibility. The full walkthrough — GitHub vs GitLab, global skills, Telegram, the activity-feed contract — is docs/setup.md.

env var default what it does
GT_CLAUDE_BIN claude Claude binary to launch (Settings → Engines also sets this)
GT_CONTEXT_LIMIT auto context-window cap; auto = 200k, bumps to 1M past 200k tokens

How it works

Electron in three layers: main owns PTYs, filesystem, and CLI calls; preload exposes one typed gt bridge; the renderer is React 19 + Tailwind v4. Cockpit data comes from the session's own transcript on disk; plan usage mirrors the /usage endpoint with the OAuth token Claude Code already stores in your keychain; review state reads the project sidecar's artifacts. Scheduling routes per schedule to launchd (local), systemd, or k8s (remote hosts). The full map, including the loop engine and the multi-session model: docs/architecture.md.

src/main/            Electron main: PTY spawn, IPC, all fs/CLI readers
src/preload/         the typed `gt` bridge (contextBridge)
src/renderer/src/
  App.tsx            multi-session shell (workspace tab bar)
  SessionView.tsx    one session: terminal + work column + cockpit + tabs
  plugins/<id>/      one folder = one widget (auto-discovered; hosted by the
                     cockpit, or by the work column's accordion)
  tabs/<id>/         one folder = one full-screen tab (auto-discovered)
bin/                 headless runners: terminal-cron, terminal-cli, MCP server, or-agent tier
templates/           project-template (embedded workflow scaffold)

Landing page

Static HTML in landing/, deployed to GitHub Pages from main (.github/workflows/pages.yml). Update landing/index.html, push, done.

Contributing

It's just code — fork it, drop a plugin or tab folder in, send a PR. bun run test runs the suite; bunx tsc --noEmit is the type gate; CI runs both. Merges to main are human-only here too.

License

MIT © Trevor Miller

About

A standalone, local-first software factory for coding agents — hosts the real Claude Code / Codex CLI with a live cockpit, autonomous build loop, scheduling, and cross-repo observability. No server, no account.

Resources

Code of conduct

Contributing

Security policy

Stars

9 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages