agterm docs
← home GitHub ↗
Documentation

agterm

A native macOS terminal for working with AI coding agents across many sessions at once.

Overview

agterm is intentionally opinionated: rather than scattering shells across tabs, it organizes them into named workspaces, each holding the sessions for one project or context. Several agent-driven sessions run side by side and you move between them without losing track of which is which.

The design is deliberately minimal: a sensible minimum out of the box, plus a complete control API and CLI on top. Almost everything is scriptable, so anything past the defaults you build yourself. None of it is limited to agents — it works as a capable general-purpose terminal too.

For the real terminal work — rendering, VT parsing, and shell I/O — agterm embeds Ghostty's engine (libghostty); everything above is agterm's own.

Workspace organization

A vertical, two-level sidebar groups sessions under named workspaces.

Programmatic control

agtermctl drives almost everything over a local socket.

Splits, scratch & overlays

Split a session into two shells, drop a scratch over it, or run a program in an overlay.

Agent skill

An installable skill teaches Claude Code or Codex to drive agterm through agtermctl.

Status at a glance

Tinted glyphs show which of many agents needs you.

Install

Available now — Signed, notarized builds are published on the releases page. Install via Homebrew or the direct download below; building from source is optional.

Releases are signed and notarized for Apple Silicon (arm64) Macs running macOS 14 or later, so they open without any Gatekeeper workaround.

Homebrew

$ brew install --cask umputun/apps/agterm

The cask also installs the agtermctl command-line tool, so cask users shouldn't run the in-app installer as well.

Direct download

Download the latest .dmg from the releases page, open it, and drag agterm.app into /Applications.

Optional Help-menu installers

None are needed to use agterm as a terminal; each connects it to a wider workflow, and you can run any of them later.

  • Install Command Line Tool… puts the bundled agtermctl on your PATH (a symlink in /usr/local/bin) so you can script the app. The Homebrew cask already installs it.
  • Install Agent Status Hooks… lets a coding agent (Claude Code, Codex, or others) report its state onto its session's row, so you can tell which of several running agents is active, blocked, or finished.
  • Install Agent Skill… teaches Claude Code or Codex how to drive agterm through agtermctl, so an agent inside a session can build its own layout, run overlays, and manage windows without you explaining the API. It drives the app through the CLI, so install that one too.

Build from source

Requires macOS 14+, Xcode 26 with xcodegen on PATH (plus its Metal Toolchain), and Homebrew.

# build libghostty + stage resources (idempotent)
$ scripts/setup.sh
# generate the Xcode project, build Debug, launch
$ scripts/run.sh
# host-free unit tests, no Xcode needed
$ cd agtermCore && swift test

Workspaces & sessions

A session is one running shell with a name, a working directory, and its own scrollback — the unit you work in and the row you see in the sidebar. A workspace is a named group of sessions for one project or context. Sessions can move between workspaces while still running, keeping their shell and scrollback.

  • Two-level sidebar tree: workspaces, each containing sessions. Each row carries a leading kind icon — a filled folder for a workspace, an outlined terminal for a session.
  • Default session name is the basename of its working directory. Renaming pins a custom name; clearing it reverts.
  • Add workspaces and sessions from a two-icon bar at the bottom of the sidebar. Rename inline (double-click a row). Move a session between workspaces by dragging — it keeps running, shell and scrollback intact.
  • Narrow a crowded sidebar two ways: flag sessions across workspaces into a flat working-set view (a flag is durable and survives a move), or focus a single workspace to collapse the tree to just its sessions. The two are independent.
Session context menu
A session's right-click context menu.

Terminals: split, scratch, quick, search, overlay

Split panes ⌘D

A session can split into two shells side by side. Both panes share one sidebar row — a split is one session with two terminals, not two sessions. One pane is focused at a time and the divider position is remembered.

Quick terminal ⌃`

A single throwaway shell per window, not tied to any session, overlaid at 90% of the window and opening in the active session's directory. Click again or the surrounding margin to dismiss; hiding keeps its shell alive.

Per-session scratch ⌘J

An extra shell belonging to one session, for a quick aside next to your main work. It covers the session full-screen and hides again without killing it. Opens in the session's directory; not restored across launches.

In-terminal search ⌘F

A search bar at the top of the focused terminal highlights matches in live scrollback with an "N of M" counter. Enter steps forward, Shift-Enter back, Esc closes.

Overlay control API

Runs one program in a temporary terminal over a session and disappears when the program exits, leaving the session unchanged. Mostly driven from the control API to launch an interactive program (a diff viewer, a process monitor) — full-size or as a floating panel. See the agtermctl reference.

Windows

A window is a top-level bundle of workspaces and sessions in its own on-screen macOS window, with its own sidebar and its own sessions — so "work" and "personal" can run as two separate windows at once, each with its own tree. Keep a library of windows, open one per screen, and create, rename, or delete them from the File menu (New Window ⌥⌘N) or the action palette. The set of windows open at quit reopens on the next launch, frames restored.

Notifications

A program in any session can raise a desktop notification (via OSC 9 / 777, or agtermctl notify). It surfaces as a macOS banner and an unseen-count badge on the sidebar row (rolled up onto a collapsed workspace); clicking the banner brings agterm forward and focuses the exact pane that raised it, and focusing a session clears its badge. Banners and count badges toggle independently in General settings. For a coding agent that just needs to say it is waiting, agent status is usually the better fit.

Customization & settings

A live-preview theme picker (View ▸ Select Theme…, or the action palette) applies each of the 512 bundled themes to the open terminals as you move through the list — Enter commits and syncs it to Settings, Esc reverts to the one you started on. Settings (⌘,) has five tabs, and changes apply live:

General — mouse scroll speed and right-click-to-paste, where a new session opens, an opt-in re-run of each pane's foreground command on restart, an opt-in confirm before closing a session, and whether to load your global Ghostty config.
Appearance — terminal font and theme, window background opacity and blur, the sidebar tint, and how much the inactive split pane dims.
Notifications — the banner, the unseen-count badge, and the title-bar attention indicator.
Agent Status — the status-glyph colors and the blocked-session sound.
Key Mapping — the directory holding keymap.conf, a list of any parse errors, and a reload button.
Keymap editor
The keymap editor.

agtermctl reference

agterm can be driven from a script over a local unix-domain socket through the companion CLI, agtermctl. This is for fire-and-forget scripting that manages workspaces and sessions, injects text, and invokes control actions — there is no output streaming and no event subscription.

Each command targets a session or workspace by its UUID, a unique prefix of that UUID (git-style), or the keyword active (the selected session / current workspace). --target defaults to active, so the current one rarely needs naming. --target/--workspace take an id, a prefix, or active — never a name. Mutating commands print the affected id; add --json for the raw response, or --socket PATH to override the socket. The exit code is zero on success, non-zero on error.

Sessions & workspaces

# print the workspace/session tree with ids
agtermctl tree

# create a workspace, capture its id, open a session in it
ws=$(agtermctl workspace new work)
agtermctl session new --workspace "$ws" --cwd ~/src/agterm

# run a command as the session's process (argv-style; wrap in sh -c for shell syntax)
agtermctl session new --command "ssh user@host"
agtermctl session new --name myhost --workspace-name servers --create-workspace

# step / reorder / relocate
agtermctl session go --to next    # next|prev|first|last
agtermctl session move --to up    # reorder: up|down|top|bottom
agtermctl session move "$ws"     # relocate to a workspace
agtermctl workspace move --to top    # reorder a workspace
agtermctl workspace focus on    # collapse tree to active ws (on|off|toggle)

Typing, selection & text

# inject text (every newline is a real Return; use $'…\n' or --stdin)
agtermctl session type --target 9f3c $'make test\n'
echo 'make test' | agtermctl session type --stdin

# route to a pane: left (default) | right (split) | scratch (even when hidden)
agtermctl session type --pane right $'ls\n'

# a never-shown session needs --select to realize its surface first
agtermctl session type --target "$id" --select $'echo hi\n'

# read a pane's selection (does not touch the clipboard) or its output
sel=$(agtermctl session copy --target 9f3c)
agtermctl session text --pane scratch --target 9f3c

Splits, scratch, quick & search

agtermctl session split toggle
agtermctl session resize --split-ratio 0.7  # or --grow-left/--grow-right D
agtermctl session scratch toggle     # on|off|toggle
agtermctl session flag on        # on|off|toggle|clear
agtermctl sidebar mode flagged    # tree|flagged|toggle
agtermctl quick toggle
agtermctl font inc           # active surface font size

# open the search bar, print the "N of M" counter
agtermctl session search "error"
agtermctl session search --next     # --prev | --close

Overlays

# full overlay on a session (hides the shell beneath)
agtermctl session overlay open "revdiff HEAD~3" --target 9f3c

# floating framed panel at 70% of the pane, optionally tinted
agtermctl session overlay open "htop" --size-percent 70
agtermctl session overlay open "revdiff HEAD~3" --size-percent 80 --background-color "#2a1a3a"

# keep it open after exit, or block until it exits and inherit its status
agtermctl session overlay open "make test" --wait
agtermctl session overlay open "make test" --block
agtermctl session overlay close --target 9f3c
agtermctl session overlay result   # last overlay's exit status

--wait keeps a "press any key to close" prompt so you can read the final output; --block reports only the exit status (the overlay never captures stdout — a TUI writes its own result file). A full overlay renders only for the active session; a floating --size-percent overlay auto-selects its target. A (overlay) tag in agtermctl tree marks a session whose overlay is open.

Windows

agtermctl window list     # id name [open] [active]
w=$(agtermctl window new work)
agtermctl window select "$w"  # raise it (opening if closed)
agtermctl window rename "$w" personal
agtermctl window close "$w"   # close its window (bundle kept)
agtermctl window delete "$w"  # delete (last window can't be deleted)

# --window targets a specific window's tree on session/workspace/tree/font
agtermctl tree --window "$w"
agtermctl session new --window "$w" --cwd ~/src/agterm

Keymap, config & notify

agtermctl keymap reload  # re-read keymap.conf, returns the diagnostic count
agtermctl config reload  # re-read the ghostty config, returns the diagnostic count
agtermctl notify --title Build --body "tests passed"
In-session env — a shell inside agterm gets AGTERM_ENABLED, AGTERM_WINDOW_ID, AGTERM_WORKSPACE_ID, AGTERM_SESSION_ID, and AGTERM_SOCKET (the live socket path), so a script can drive its own window without hard-coding ids.

Customizing keys

agterm reads a user-editable, kitty-flavored keymap file at ~/.config/agterm/keymap.conf. It rebinds built-in menu shortcuts and defines custom shell commands bound to keys (and listed in the action palette). The file is optional — a commented starter is written on first launch, and the directory can be changed in Settings ▸ Key Mapping. Two verbs; blank lines and # comments are ignored.

# rebind a built-in to a single chord (no leader sequences for built-ins)
map cmd+shift+d   toggle_split
map ctrl+shift+k  command_palette

# define custom commands ("name" shows in the palette; chord optional)
command "Open in Zed"  cmd+shift+e  open -a Zed {AGT_SESSION_PWD}
command "Lazygit"      ctrl+a>g     agtermctl session overlay open lazygit --socket {AGT_SOCKET}
command "Deploy"               ./deploy.sh

A chord is modifier words (ctrl, cmd, opt, shift) joined by + and a base key (a single character or tab/space/return/delete). Custom commands may also use a leader sequence (ctrl+a>g), and their chord must include a modifier — a bare key can't shadow a plain terminal key.

Bindable built-in actions

new_window   rename_window   delete_window
new_workspace   rename_workspace   delete_workspace
new_session   open_directory   rename_session
close_session   clear_status
increase_font_size   decrease_font_size   reset_font_size
toggle_split   toggle_scratch   toggle_search
toggle_sidebar   toggle_flag   toggle_flagged_view
focus_left_pane   focus_right_pane   focus_workspace
previous_session   next_session   first_session   last_session
previous_attention_session   next_attention_session
quick_terminal   session_palette   command_palette
custom_command_palette   show_attention   select_theme

Command tokens

{AGT_SESSION_ID}   {AGT_SESSION_NAME}   {AGT_SESSION_PWD}
{AGT_WORKSPACE_ID}   {AGT_WORKSPACE_NAME}
{AGT_WINDOW_ID}   {AGT_WINDOW_NAME}
{AGT_PANE}   {AGT_SELECTION}   {AGT_SOCKET}

Tokens expand at fire time (also exported as $AGT_* env vars on the spawned process). A token is substituted raw into the shell line, so for content you don't control — {AGT_SELECTION}, and also {AGT_SESSION_NAME}/{AGT_SESSION_PWD} (a remote host can set these via OSC) — prefer the matching quoted env var, e.g. "$AGT_SELECTION".

Detached, no TTY — a custom command runs as a detached /bin/sh -c with no controlling terminal, so it suits fire-and-forget launches (GUI apps, scripts) — not interactive TUIs. Run a TUI like lazygit in an overlay (agtermctl session overlay open) or a scratch terminal, which have a real TTY. A non-zero exit posts a notification banner.

Open the file with File ▸ Edit Keymap… (or the ⌃⇧P palette): it opens in a 95% overlay running $VISUAL/$EDITOR (falling back to vi) and reloads on quit. Apply edits made elsewhere with File ▸ Reload Keymap or agtermctl keymap reload. A malformed line never discards the rest — it surfaces in the diagnostics list in Settings ▸ Key Mapping while the good lines still apply.

v1 limitations

  • Built-in rebinds are single-chord only; leader sequences work only for custom commands.
  • A few keys can't be expressed because they clash with the grammar's separators: the arrow keys, + (the chord-joiner), and > (the leader separator). The arrow-bound actions and increase_font_size keep their defaults unless mapped to a parseable chord.
  • The Ctrl-Tab MRU switcher and Ctrl-1/Ctrl-2 pane focus are not rebindable yet.

Ghostty config

agterm builds its terminal config from four sources, each overriding the one before it:

ghostty defaults → ~/.config/ghostty/config → <config dir>/ghostty.conf → agterm Settings
  (lowest)    (global, OFF by default)    (agterm-scoped)    (UI wins)

agterm is self-contained: by default it does not read your global ~/.config/ghostty/config, so a config written for the standalone Ghostty.app never silently changes agterm. Turn on Settings ▸ General ▸ Use my global Ghostty config to fold it in.

<config dir>/ghostty.conf (default ~/.config/agterm/ghostty.conf) is the place to customize agterm. It sits next to keymap.conf, is always loaded, and is scoped to agterm so the standalone Ghostty.app never reads it. Put any ghostty config key there — the keys agterm manages from Settings (font, theme, opacity, blur, scroll speed) still win. A common use: macos-option-as-alt = true. The full key reference is at ghostty.org/docs/config.

Programs can read and write the macOS clipboard over OSC 52. agterm prompts before a program reads your clipboard (a read hands its contents back to the program); a normal ⌘V paste is never prompted. Clipboard writes go through by default, matching other terminals so a remote tmux/vim yank still reaches your clipboard. To gate writes, set clipboard-write = ask or deny. Each prompt offers Don't ask again this session.

Open the file with File ▸ Edit ghostty.conf… (a 95% editor overlay, same as Edit Keymap). Apply edits made elsewhere with File ▸ Reload Config or agtermctl config reload. A malformed line is skipped and the good ones still apply; the returned diagnostic count covers every config source.

Agent status

A coding agent running in a session can flag its status on that session's sidebar row, so you can tell at a glance which of many concurrent agents needs you. The glyph shows just left of the notification badge on every non-idle session; a one-time completed flash auto-clears once you visit the session.

active
blue ellipsis
blocked
amber exclamation
completed
green check
idle
nothing

An agent sets it over the control channel:

# state is idle | active | completed | blocked
agtermctl session status active --target "$AGTERM_SESSION_ID"
agtermctl session status blocked --sound default --target "$AGTERM_SESSION_ID"
agtermctl session status completed --auto-reset --target "$AGTERM_SESSION_ID"
agtermctl session status idle --target "$AGTERM_SESSION_ID"  # clear it

--auto-reset clears the indicator the moment you visit the session; --blink pulses the glyph; and --sound plays a one-shot sound (default, or a system sound name like Basso/Ping/Tink). To make every blocked prompt sound without touching the hooks, set Settings ▸ Appearance ▸ Agent Status ▸ Blocked sound. Typing into a blocked/completed session clears its status; Esc interrupts an active one and clears it too.

When the sidebar is hidden the glyphs go with it, so an optional title-bar bell (Settings ▸ Notifications ▸ Show attention indicator, off by default) reflects the window: dimmed when nothing needs you, plain when a session is active or completed, filled amber when any is blocked. Clicking it — or ⌃⇧I, Navigate ▸ Go to Attention…, or the action palette's "Show Attention" — opens the attention list: this window's non-idle sessions, sorted blocked → active → completed. Over the control channel, agtermctl tree --json reports each session's status.

To wire it up automatically, Help ▸ Install Agent Status Hooks… installs a hooks package: a generic bash/zsh/fish shell integration (flags active while a command matching AGTERM_AGENT_RE runs; the default set is codex, gemini, cursor-agent, aider, opencode, crush, goose), and four Claude Code hooks (prompt → active, tool run → active, Stop → completed, permission prompt → blocked). For Codex it prints a notify line to add to ~/.codex/config.toml. Re-running is idempotent.

Restore & persistence

Sessions come back on the next launch with their directory, font size, and split state restored. Restore reconstructs the structure, not the running processes — three limitations follow from that design:

  • Live processes are not reattached. By default a restored session re-spawns a fresh login shell in its saved directory. The optional Restore running commands on restart toggle (General settings, off by default) re-runs the command each pane had in the foreground at the last clean quit — a re-run, not a reattach. Only a single-process command restores faithfully; a force-quit or crash captures nothing; and the multiplexers in restore-denylist.conf (seeded with tmux/screen/zellij) start fresh.
  • The saved directory depends on OSC 7. It relies on Ghostty shell-integration (auto-injected for zsh, bash, fish, and nu). If the working directory is never reported, a session restores to the directory it was created in.
  • Directory is saved on structural changes, not every cd. It persists on quit and on each add/close/move/rename/select — not on every prompt redraw, which would thrash the disk. A crash loses only the directory changes made since the last structural change or quit.

Troubleshooting

Where the logs and config live, how to read them, and the common problems (a keymap editor that won't open, a custom action that does nothing, missing notifications) are covered in the repo's troubleshooting guide.

troubleshooting.md ↗ Open an issue ↗ Start a discussion ↗
agterm embeds libghostty, the terminal engine from Ghostty (MIT) — rendering, VT parsing, and shell I/O, built from upstream source at a pinned commit. The way agterm drives libghostty's C API from a SwiftUI/AppKit app was learned from macterm (MIT), and SwiftUI guidance came from the SwiftUI Agent Skill (MIT). The model, sidebar, persistence, control channel, and multi-window code are original to agterm.
← agterm.com
GitHub Issues Discussions