The desktop app
Install
brew install --cask shubham-1994/tern/tern-desktop
Or download Tern.dmg (universal: Apple silicon and Intel) from https://github.com/Shubham-1994/homebrew-tern/releases.
Footprint
Measured on an Apple Silicon Mac with v0.7.1 and one session, on 2026-10-04.
| Process | RSS |
|---|---|
tern-desktop (Go + AppKit) |
87 MB |
| WebKit WebContent | 21 MB |
| WebKit GPU | 18 MB |
| WebKit Networking | 7 MB |
| total UI | ≈133 MB |
| daemon (shared with the TUI) | 29–33 MB (1–4 sessions) |
- Downloads:
Tern.dmgis 20 MB. The app is a universal binary of 47 MB, 22 MB per architecture, including the front end: the editor with its languages, the terminal renderer and three font families. TheternCLI, terminal UI included, is 12 MB. - How it’s measured:
ps -o rssfor the app, its WebKit processes andtern-desktop daemon, with the app open on one project. - Agents’ own memory isn’t counted here; each agent CLI uses what it uses in any terminal.
Workspaces (left sidebar)
A workspace is a git worktree. The left sidebar lists each open project (a repository) with its workspaces under it: the main checkout first, then its worktrees, each with its agents and terminals underneath. A project that isn’t a repository, or has no worktrees, lists its sessions straight under it.
- A project row shows the name and how many sessions are running or waiting for you. Click it to go to the workspace you were last on there, and click its arrow to fold it. Right-click it to:
- start a new agent, a terminal, or an agent in a new worktree
- reveal it in Finder, copy its path, or open it in another app
- close it (its sessions keep running)
- A worktree row shows its name (yours, else its task’s or folder’s), its branch when that isn’t just
prefix/name, its status, a pin, and how many sessions run or wait there. Pinned worktrees come first, then the newest; a worktree with a parent sits under it.- Click it to switch to it. Each worktree has its own panes and tabs, file tree and Git view; they come back when you return, and its sessions keep running while you are elsewhere.
- Right-click it for: Open, New Agent / Terminal Here, Rename…, Move to Status ▸ (To Do, In Progress, In Review, Done), Pin to Top, Set Parent ▸ / Remove from Parent, Add Note…, Review Changes, Sleep Agents, Mark All Read, Copy Path / Branch, Reveal in Finder, Open in ▸, and Delete Worktree….
- Delete stops its sessions and deletes the folder. For a worktree Tern made, its branch goes too when it is merged; a branch with unmerged commits is kept, and Tern says so (or choose Keep the Branch). For one made elsewhere, only the folder goes. Uncommitted changes are only discarded after a second confirm.
- Worktrees made outside Tern (by hand, or by other tools) are found but not listed until you ask: click N worktrees made outside Tern, or turn on Settings → Worktrees → List worktrees made outside Tern. One with a session running in it, or in front, is always listed.
- An agent row shows a status mark (running ■, waiting ●, done ✓), the name, the agent and what it is doing, its CPU and memory, and how long it has run.
- Click it to show it, even in another workspace (that workspace comes to the front). Double-click it to rename it inline.
- Right-click it to open it in a split, rename it, pin its tab, reveal its folder in Finder, show a task’s changes, review a task, or stop or close it.
- A session belongs to the worktree its folder is in, whoever started it: a terminal you open in a worktree, an agent from the CLI, a run’s step.
- Sub-agents an agent starts (Claude Code’s Task / Agent tool) appear indented under it with their type and description. Running ones have a pulsing mark, and finished ones fade out after a minute and a half. They come from Claude’s
SubagentStart/SubagentStopand tool hooks. - ⇧⌘O, or the project name in the top bar, opens a menu of open and recent projects. ⌘O opens a folder (a worktree’s folder opens its repository, on that worktree). ⌘J finds worktrees too.
- Open projects, and the workspace each was on, are restored on the next launch.
New worktrees (⌘N)
⌘N opens the composer with New worktree chosen: the prompt, the agent, a name, the branch to start from, a PR or issue, and (under More) a sparse checkout. The name is optional: without one Tern makes one up and renames the branch from your first prompt. Start closes the composer at once; a Creating… row shows in the sidebar until the worktree is there, then it comes to the front with its agent. If making it fails, the row says why, with Retry. ⌘T still starts an agent in the workspace in front.
Settings → Worktrees:
- Branch prefix: new branches are
prefix/name. Empty means your git user name (git config github.user, elseuser.name);nonemeans no prefix; anything else is used as given. - Folder: where new worktrees go (default
~/.tern/worktrees). Worktrees made before stay where they are and are still found. - tern.yaml setup: ask when the project’s setup changes (the default), always run it, or never run it.
- List worktrees made outside Tern.
The board (View ▸ Board) shows the project’s worktrees by status; drag a card to another column to change it. A worktree without a status goes where its sessions suggest.
Right sidebar
The right sidebar has four tabs: Files (⌘2), Git (⌘3), Debug (⌘4) and Info (⌘5). Info shows what the session in front changed, the task’s branch, and the conflict radar.
- ⌘B hides or shows the left sidebar, and ⌥⌘B the right one. Drag a sidebar’s inner edge to resize it.
- View ▸ Swap Sidebars, or right-clicking a sidebar’s header, puts the workspaces on the right and the files on the left.
CPU and memory
- Top bar: total CPU, plus memory split in two: TERN (the daemon and the app window) and AGENTS (everything running in sessions). An agent like Claude Code usually uses far more memory than Tern itself. Hover over either figure for the per-part breakdown.
- Each session: its sidebar card and header show its own figures, covering the agent and everything it started (servers, test runners and so on).
- TUI: shows the total in its top bar and each session’s figures in the session header.
- How it’s measured:
- The daemon measures everything with one
pscall every 2 seconds. - CPU is the recent average, and 100% means one core.
- Memory is resident memory.
- The app window’s WebKit helper processes aren’t counted, because macOS doesn’t say which app they serve.
- The daemon measures everything with one
Panes and tabs
The centre is a set of panes, each with its own tab bar. Split it as far as you like.
-
⌘D splits the focused pane to the right and ⇧⌘D splits it down. The new pane opens a terminal. ⌥⌘ and an arrow key moves the focus to the pane in that direction.
-
Move a tab by dragging it:
- onto another tab bar, to place it there
- onto a pane’s middle, to move it into that pane
- onto a pane’s edge, to split that pane and put the tab beside it
The tab’s right-click menu also has Move to Split Right / Down. Drag the line between panes to resize them, and double-click it to even them out.
-
The + in a tab bar offers:
- a new terminal, and each installed agent (Claude Code, Codex, Gemini, …)
- the default agent in a new git worktree
- a new markdown file, and a new file
⌘T starts the default agent at once: the one you last picked here, otherwise Claude Code. Nothing asks a question first.
-
Agents run in your shell. The agent’s command is typed into a login shell, so when you quit the agent the shell is still there, in the same tab.
-
Pinned tabs (right-click ▸ Pin Tab) stay at the front of their pane with a pin icon and can’t be closed by accident. Unpin a tab to close it.
-
Tab right-click menu: pin, move to a split, split the terminal right / down, rename, close, close others, close to the right, copy path, reveal in Finder, and the markdown preview.
-
Layouts are remembered per workspace. Terminal tabs come back after a restart; documents don’t.
Kinds of tab:
- Sessions are agents and terminals.
- Documents are files in the editor (see The editor), working-tree and task diffs (Git view, F4, Info), commits, and markdown previews.
- A file you haven’t edited follows the disk, so it updates when an agent rewrites it. If you save over a change made on disk since you opened the file, a toast offers Overwrite with mine or Reload from disk.
Closing (⌘W or the tab’s ×) with no dialogs:
- A running agent closes on a second ⌘W within four seconds; the tab turns red and a toast says so.
- A file with unsaved edits works the same way, and ⌘S saves it instead.
Terminal scrolling:
- The wheel scrolls a terminal’s history, and a chip shows how far up you are. Click the chip or type to return to the end.
- Full-screen programs (vim, less, Claude Code’s own views) get the wheel themselves.
No dialog boxes:
- Renaming, new files and folders, watch expressions and breakpoint conditions open a small inline field where you are: Enter accepts and Esc cancels.
- Questions such as “merge?” or “install the debugger?” are toasts with buttons.
- Moving a file to the Trash happens at once, with Undo.
Terminal
- Find (⌘F): searches the history as well as the screen, with match case and regular expressions. ↵ / ⌘G goes to older matches and ⇧↵ / ⇧⌘G to newer ones; the terminal scrolls to each.
- Prompts: shells Tern starts (zsh and bash) mark each prompt, through a small startup file that runs your own first. ⌘↑ / ⌘↓ jump between prompts, and a command that failed gets a red tick beside its prompt.
- Links: URLs (even ones that wrap at the edge), paths with
:line:coland OSC 8 links are underlined; ⌘-click opens them, files in the editor. - Images: programs that show images the iTerm2 or kitty way (
imgcat,kitty +kitten icat, …) show them inline, in the history too. - Clipboard: a program can copy to your clipboard (OSC 52); it can’t read it.
- Keys: programs that ask for the kitty keyboard protocol get Shift+Enter, Ctrl+Enter and friends as themselves.
- Quick commands (⌃⌘K, or right-click a terminal): saved commands typed into the terminal, or prompts that start an agent; add them in Settings.
- Floating terminal (⌥⌘A): a shell in your home folder over everything; it keeps running while hidden.
Tabs
- Preview tabs: a click in the file tree opens a file in a preview tab (its title in italics), which the next click replaces. Edit it, pin it, drag it, open it again or double-click it to keep it. Settings → Editor turns them off.
- Colours: right-click a tab ▸ Colour.
- Autosave: Settings → Editor: after a pause in typing, or when the file loses focus. A file an agent changed on disk is never saved over; autosave waits for you.
Viewers
- Images and PDFs open in viewers instead of as text.
- CSV and TSV files have a table preview (⇧⌘V); click a column header to sort.
- Jupyter notebooks open rendered: markdown, code and outputs (HTML sandboxed, images, tracebacks). Use Edit source for the JSON.
- A changed image in a diff shows before and after: side by side, swipe or onion skin.
Browser tabs
File ▸ New Browser Tab (or + ▸ Browser) is a real WebKit browser in a tab. Type a URL or a search; localhost:3000 works. Right-click ▸ Inspect Element opens Safari’s Web Inspector. Grab element lets you click something on the page and send what it is (selector, text, attributes, box, styles) to an agent; values typed into fields are left out, and the page itself can’t fake a grab. + ▸ Browser in a Profile keeps a profile’s cookies and logins apart (macOS 14+), or keeps nothing (Private).
Themes
View ▸ Theme offers Tern Dark, Tern Light, Solarized Dark, Nord, or System, which follows macOS light and dark mode. The theme covers the whole window: the UI, the editor’s syntax colours, the terminal’s 16-colour palette, diffs and the markdown preview. More Themes and Import… imports the theme your Ghostty config uses or a Warp theme (YAML); Tern builds its UI colours from the terminal palette.
Markdown
- ⇧⌘V, the Preview button in a markdown file’s header, or right-click ▸ Open Markdown Preview shows the rendered file beside it, in the other pane or a new split.
- The preview updates as you type. It supports GitHub-flavoured markdown: tables, task lists, and code blocks coloured like the editor.
- HTML in the file is sanitised.
- + ▸ New Markdown File creates
untitled.md(oruntitled-2.md, …) in the project and opens it. - Mermaid diagrams (
```mermaidblocks) are drawn in the preview. - Contents in the preview’s header lists the headings and marks the one you’re reading.
- A preview beside its file follows the editor as you scroll.
- Export PDF… prints the preview (diagrams included) to an A4 PDF.
Pages
- View ▸ Automations: prompts an agent runs on a schedule, in a fresh worktree or the project folder. The daemon runs them with or without the window;
tern autofrom the terminal. - View ▸ Usage: tokens and estimated cost per day, model, session and project, read from the agents’ own transcripts.
- View ▸ Issues: GitHub and GitLab issues (with
gh/glabsigned in), Linear and Jira (Settings → Integrations); Start a task from it. - View ▸ Skills: the skills each agent can use. Install one from a git URL or a folder (into
~/.agents/skills, linked for the agents you pick), share it with more agents, update or remove it. - Settings → Accounts: more than one Claude Code or Codex login; new sessions use the one you pick.
- Settings → Network: a proxy for agents and for Tern’s own requests.
- Updates: Tern looks for a newer release soon after it starts and every 4 hours (Help ▸ Check for Updates… asks now). When there is one, a card at the bottom right offers Download:
- it downloads
Tern.dmgfrom the release, checks it against the release’schecksums.txt, and checks the new app’s signature, bundle id and version; - then Restart to update swaps in the new Tern.app and reopens it. Later leaves it for when you quit Tern;
- the daemon keeps running through an update, so terminals and agents carry on. The app says when the daemon is older than it;
- a copy that can’t replace itself (its folder isn’t writable) offers Homebrew’s upgrade, or the download page.
- Settings → Updates has the same, and the channel: stable, or beta for pre-releases.
- it downloads
- Help ▸ Report an Issue: a zip of logs, versions and settings (home folders, tokens and email addresses masked) to look through before you share it. Nothing is uploaded.
Computer use
Tern.app/Contents/MacOS/tern-desktop computer lets an agent see and use other apps, with your go-ahead: apps, windows, state --app A (the window as numbered nodes), click, type, key, scroll, screenshot. macOS asks once for Accessibility and Screen Recording for Tern. Password managers are off limits, secure fields are never read, every action is logged in ~/.tern/run/computer.log, and the window shows which app an agent is controlling.
The editor’s right-click menu
The menu has:
- Go to Definition, Find References
- Cut, Copy, Paste
- Toggle Comment, Format Document, Toggle Breakpoint
- Copy Path, Copy Relative Path, Copy Path to Line
- Reveal in Finder, Open Markdown Preview
- Move to Split Right
Git view:
- + stages a file and − unstages it. The same buttons in the section headers stage or unstage everything.
- Push and Pull run in the background and report back in a toast.
When the daemon is older than the app: a daemon left running by an older tern build lacks newer features, so the app says so and offers Restart daemon. Restarting stops the running sessions and keeps worktrees and files.
The editor
- Highlighting for about 70 languages and file types: TypeScript/JavaScript, Python, Go, Rust, C/C++, Java, Kotlin, Swift, C#, PHP, Ruby, SQL, HTML/CSS, JSON, YAML, TOML, XML, Markdown, shell, Dockerfile, Makefile, Protocol Buffers, Lua, and more.
- Completion as you type. It offers the language’s own suggestions first, then project symbols from Tern’s index, then words in the file. Press ↵ to accept, or ⌃Space to open it.
- Go to definition with ⌘-click or F12, references with ⇧F12, and ⌃- to go back. Both work on unsaved edits.
- Multiple cursors:
- ⌃⌘D adds the next occurrence (⌘D splits the pane), and ⇧⌘L selects all occurrences.
- ⌥-click adds a cursor.
- ⌥-drag makes a column selection.
- Line editing:
- ⌘/ toggles a comment.
- ⇧⌘K deletes the line, and ⌘L selects it.
- ⌥↑ / ⌥↓ move the line, and ⇧⌥↓ copies it.
- Tab / ⇧Tab indent and outdent.
- Find and replace:
- ⌘F opens find, and ⌥⌘F opens replace (regex, case and whole-word options are in the panel).
- ⌘G / ⇧⌘G go to the next or previous match.
- ⌃G goes to a line.
- Folding: use the gutter arrows, or ⌥⌘[ and ⌥⌘]. The Code menu also has Fold All and Unfold All.
- Git gutter: a mark beside every line that differs from HEAD, updated as you type. Green is added, blue is changed, red is deleted.
- Status line: shows the cursor position, the selection size, the number of cursors, the indentation, the line endings and the language. Click the position to go to a line.
- Indentation and line endings: detected per file (tabs or spaces, and the indent width), and CRLF files are saved as CRLF.
- Format Document: press ⇧⌥F. It uses the project’s formatter: prettier from
node_modules(or on PATH), then ruff or black, gofmt, rustfmt, shfmt or clang-format. It applies as one undoable edit. - Word wrap is ⌥Z, and font size is ⌘= / ⌘- / ⌘0. Both are remembered.
- Saving: ⌘S saves the file and ⌥⌘S saves all files.
- Tabs: ⌃Tab / ⌃⇧Tab cycle through them.
- Files view:
- The header buttons make a new file or folder, and ⌥⌘N makes a new file.
- Right-click a row to open it, create files or folders next to it, copy its path, rename it, or move it to the Trash. Renaming keeps open tabs pointed at the file, and nothing is deleted outright.
Remote hosts (SSH)
File ▸ Open Remote… opens a window whose terminals, agents, files and git are on another machine.
- Connecting: pick a host from your
~/.ssh/config(or typeuser@host) and, optionally, a folder (default: the last one you used there, else the home folder).- Tern uses your own
ssh, so everything in~/.ssh/configapplies as it does in a terminal: aliases, ProxyJump, keys, the agent,known_hosts. - When ssh asks something (a password, a key’s passphrase, a 2FA code, whether to trust a new host key), the window shows its question; Tern sends the answer to ssh and keeps nothing.
- Tern uses your own
- On the host: Tern copies its own binary to
~/.tern/bin/tern(once per version, checked by SHA-256) and runs its daemon there. Nothing listens on the network: the window talks to that daemon through the SSH connection. - What runs where: in a host’s window, terminals, agents and their hooks, tasks and worktrees, review and merge, runs, search, files, git and
ghare all on the host. Settings, keyboard shortcuts, themes and browser tabs stay on this Mac. The debugger, Reveal in Finder and opening files in other Mac apps aren’t available in a host’s window yet. - Disconnects: the host’s daemon keeps terminals and agents running. The window reconnects by itself (backing off up to 30 s), and comes back with each terminal’s full screen and scrollback. Restarting the window does the same.
- Ports: in the Ports list, Open forwards the port from the host first (
ssh -L; a port below 1024 maps to 10000 + port, or any free port if that’s taken), then opens it in your browser. Forwards end with the window. - Each host has its own window and its own list of projects. A host’s first window takes this Mac’s settings, without its projects.
- From the command line:
tern --host devbox <command>(orTERN_HOST=devbox) runs any command against the host’s daemon, for exampletern --host devbox lsortern --host devbox new --agent claude -C ~/proj.tern ssh devboxopens the terminal console there.
Runs (orchestration)
A run is a plan of agent steps that Tern carries out: each step is an agent in its own worktree, started as soon as the steps it needs are done. View ▸ Runs lists the project’s runs and its plans (.tern/runs/*.yaml).
name: Dark mode
base: main # the branch to start from (default: the current one)
parallel: 3 # steps running at once
agent: claude # the default agent
finish: merge # merge (into base), pr, or none
steps:
- id: tokens
prompt: Add colour tokens for a dark theme to src/theme.ts.
check: npm test # a gate: must pass
- id: toggle
needs: [tokens]
prompt: Add a dark-mode toggle to Settings that uses the new tokens.
approve: true # a gate: you approve the result
- id: docs
needs: [tokens]
agent: codex
prompt: Document the theme tokens in docs/theming.md.
- Writing a plan: New run opens an editor that checks the plan as you type (unknown fields, missing steps, loops in
needs, unknown agents) and draws its graph. Plan with an agent asks an agent to write one into.tern/runs/for you to check and start. - Branches: a step with no needs starts from
base; with one, on top of that step’s branch; with several, from a merge of their branches. A conflict stops the step as blocked; resolve it in its worktree, then Retry. - When a step is done: its agent finishes (its Stop hook) or runs
tern step done "summary". Then its gates run:check: the command runs in the step’s worktree. If it fails, its output goes back to the same agent, up toattemptstries (default 3).approve: the step waits for you. Reject… sends your note to the agent and it tries again.
- The run page shows the steps as a graph with live status, plus the selected step’s tries, branch, what the agent said, why it failed, its prompt and check output. Open terminal, Review changes, Approve, Reject…, Retry, Skip, and for the run Pause, Resume, Cancel (worktrees are kept) and Delete.
- A failed step blocks the steps after it; the rest of the run carries on unless the plan says
stop_on_failure: true. - Finishing: the last steps’ branches are merged into an integration worktree (a task, so it shows in Review).
finish: mergethen merges it intobasewhen the main checkout is onbaseand clean; otherwise it waits in Review for you.finish: prpushes it and opens a pull request. - The daemon runs the plan, so it carries on with the window closed, and picks up again after a restart. Each step’s terminal is in the sidebar, without a tab of its own.
- From the command line:
tern orchestrate start|check|ls|status|pause|resume|cancel|rm, andtern step done|fail|approve|reject|retry|skip.
Your phone (remote access)
Settings → Remote access serves Tern’s web app from the daemon, so a phone can follow your agents: see sessions and their live screens, get a notification when one finishes or needs you, and (if you allow it) approve, answer, type, start and stop agents. Nothing goes through Tern’s servers; there aren’t any.
- Serve over:
- Tailscale: my devices: the daemon listens on 127.0.0.1 and
tailscale servepublishes it on your tailnet athttps://<this Mac>.<tailnet>.ts.net:7443, with a real certificate. Only your tailnet’s devices can reach it. Install Tailscale and log in on the Mac and the phone, and turn on HTTPS certificates for the tailnet (once, login.tailscale.com/admin/dns); Settings shows what is left to do. - Anywhere: a link (Tailscale Funnel): nothing to install on the phone.
tailscale funnelpublishes Tern on the internet athttps://<this Mac>.<tailnet>.ts.net:8443with a real certificate; the phone just opens the pairing link. Anyone can reach the page, but only paired devices get in. Needs Tailscale on the Mac only, HTTPS certificates on for the tailnet, and Funnel allowed for this Mac: when it isn’t, Tailscale offers a one-click link, which Settings shows as a button. - Through a relay (Tern Relay): for any network, without Tailscale. Run
tern relayon a server you control (tern relay --domain relay.example.comgets its own certificate;--hosts-key Kkeeps it to your daemons), then give its address in Settings. The daemon keeps an outbound connection to it, so nothing listens on the Mac. The relay forwards traffic it can’t read: the phone and the daemon talk inside an end-to-end encrypted channel (ECDH P-256, HKDF-SHA256, AES-256-GCM) pinned to the daemon’s key, which the pairing link carries in the part of the URL a browser never sends. The device token stays on the phone, inside that channel. One limit: the relay serves the page’s code, so use a relay you run or trust. - This network: HTTPS on port 7310 for devices on the same Wi-Fi, with a self-signed certificate. The phone warns once; check that the fingerprint it shows matches the one in Settings (a page that skips that check could be someone else’s). It listens on every network interface, so use it on networks you trust; Tailscale is safer.
- My own HTTPS proxy: the daemon listens on 127.0.0.1:7310 over plain HTTP; point Caddy, nginx or cloudflared at it and give its public
https://address.
- Tailscale: my devices: the daemon listens on 127.0.0.1 and
- Pairing: Pair to control… or Pair to watch… shows a QR code. Scan it with the phone’s camera (or open the link). A code works once, for 10 minutes. The phone keeps a 256-bit token in a cookie (the Mac keeps only its SHA-256); a device that isn’t used for 30 days is forgotten. Revoke signs a device out at once, live screens included. Too many wrong codes from one address lock it out for 5 minutes.
- Proxy mode: the proxy should send
X-Forwarded-For, so wrong pairing codes lock out only the address that sent them; paired devices always get in. - Watch or control: a watching device sees sessions, screens and notifications. A controlling device can also approve or deny requests, answer questions, send prompts, press keys (Enter, Esc, arrows, Tab, ^C), start agents (in projects open in Tern) and stop sessions. Watching a session from the phone doesn’t resize it or wake it, and doesn’t count as you having seen it.
- Notifications are Web Push, sent by the daemon straight to the phone’s push service (Apple’s, Google’s or Mozilla’s), encrypted for that phone (RFC 8291): the push service sees only ciphertext. On an iPhone, add the page to the Home Screen first (Share → Add to Home Screen; iOS 16.4 or later), then turn notifications on in it. By default nothing is pushed while a Tern window is in front, and Include what the agent said can be turned off to send only “Done” or “Needs you”.
- From the command line:
tern remote status,tern remote on [--mode tailscale|funnel|lan|proxy|relay] [--port N] [--url https://…],tern remote pair [--control](prints the QR code in the terminal),tern remote revoke <device>,tern remote off;tern relayruns a relay.
Dictation
⌥⌘V (or the composer’s microphone) dictates with macOS’s speech recognizer. Tap it to start and stop, or hold it and let go when you’re done. The text goes where you were typing: a prompt, an input, the editor, or the focused terminal.
- The bar at the bottom shows the words as they come and a level meter, so you can see the microphone hears you. Done (or ↵) finishes, Cancel (or Esc) drops it.
- In a terminal the text is pasted as one line, not run, unless you end with “… Send.” after a pause (or turn on Send when I stop). “New line” starts a new line in prompts and the editor.
- Text goes where the focus was when you started, even if you click elsewhere meanwhile; if that place is gone, it’s put on the clipboard.
- Settings → Voice: the language (any your Mac recognizes), and Keep the audio on this Mac (on-device recognition, when the language has it; otherwise Apple’s service hears it).
- macOS asks once for the microphone and speech recognition. If you said no, Tern offers to open the right pane of System Settings.
App icon
Settings → App icon picks Tern’s icon: Ink, Paper, Terminal, Blueprint or Sunset. It changes the Dock icon while Tern runs. Keep it in the Dock when Tern isn’t running also sets it on Tern.app (as Finder’s Get Info does); macOS’s strict signature check then reports the app as modified, though it runs as before.
Debugging
Tern debugs through the Debug Adapter Protocol, the protocol VS Code and Cursor use, so it works with the same debuggers:
| Language | Debugger | Install |
|---|---|---|
| Python | debugpy, in the project’s .venv / venv (or python3 on PATH) |
python -m pip install debugpy |
| Go | Delve (dlv dap) |
go install github.com/go-delve/delve/cmd/dlv@latest |
| C, C++, Objective-C, Swift, Rust, Zig | lldb-dap (ships with Xcode’s command line tools) | xcode-select --install |
| Dart | the Dart SDK (dart debug_adapter) |
brew tap dart-lang/dart && brew install dart |
| Flutter | the Flutter SDK (flutter debug_adapter) |
brew install --cask flutter |
| JavaScript, TypeScript (Node.js, Chrome) | js-debug, VS Code’s JavaScript debugger, in ~/.tern/debuggers |
the app downloads it |
| Ruby | rdbg (the debug gem) |
gem install debug |
| Anything else | a debug adapter you describe in ~/.tern/debuggers/*.json (see below) |
When a debugger is missing, the app shows the command that installs it and offers to run it in a terminal.
Starting a session:
- Press F5, or use the Debug view (⌘4) and Start. The view has a picker for what to debug:
- The file in front. This needs no setup:
- a Python script;
- a Go package (its tests, for a
_test.gofile); - a C, C++, Objective-C, Swift, Rust or Zig file, built with debug info first (
clang,swiftc,rustc;cargo buildin a Cargo project,swift buildin a Swift package). The build’s output is in the debug console. - a Dart program or test;
- a Flutter app (its
lib/main.dart, or the file in front when it has amain), or a Flutter test; - a JavaScript or TypeScript file, run by Node.js (TypeScript needs Node 22.6 or later);
- a Ruby script.
- The configurations in
.vscode/launch.json, as VS Code writes them, withlaunchorattach. Types:debugpy/python,go,lldb/lldb-dap/codelldb/cppdbg,dart(Flutter projects are detected, as in VS Code),node/pwa-node/chrome/pwa-chrome/msedge,rdbg, and the types of your own adapters.- Comments and trailing commas are fine.
- These variables are filled in:
${workspaceFolder},${file},${fileDirname},${fileBasenameNoExtension},${relativeFile}and${env:NAME}. ${input:…}and${command:…}are not supported, and neither arepreLaunchTaskandpostDebugTask. Tern’s own"ternBuild": [["cmd", "arg", …], …]runs build commands before the launch."console": "integratedTerminal"runs the program in a Tern terminal, so it can read input.
- The file in front. This needs no setup:
- Only one debug session runs at a time. It ends when the window closes.
Flutter:
- With no
deviceIdin the configuration, Tern picks one when the session starts: a phone or emulator that’s on, then this Mac (the app needs amacos/folder, and Xcode to build it), then Chrome (the app needs aweb/folder). The debug console says which. Set"deviceId"(and"flutterMode": "profile") in.vscode/launch.jsonto choose. - The first build of an app can take minutes; the build’s output is in the debug console.
- The toolbar has Hot Reload and Hot Restart. Saving a
.dartfile (⌘S; not autosave) hot reloads too.
Your own debuggers: any debugger that speaks the Debug Adapter Protocol can be added as a JSON file in ~/.tern/debuggers/ (one adapter, or a list, per file). For example, .NET:
{
"id": "dotnet", "name": "netcoredbg (the .NET debugger)",
"types": ["coreclr"],
"command": ["netcoredbg", "--interpreter=vscode"],
"install": "see https://github.com/Samsung/netcoredbg/releases",
"files": [{ "extensions": [".cs"], "config": { "program": "${workspaceFolder}/bin/Debug/net8.0/${workspaceFolderBasename}.dll" } }]
}
-
commandstarts the adapter.${port}is a free port Tern picks,${home}is~/.tern, and${config:NAME}is a field of the configuration. -
transportisstdio(the default) ortcp: DAP on${port}, or on the address the adapter prints (listening at 127.0.0.1:4711). -
filesmakes the file in front debuggable without a launch.json.${buildDir}is a scratch folder for aternBuildstep’s output. -
Also:
launch(default launch arguments),typeMap(renames the type for the adapter),requires(filesinstallputs there),startSeconds, andoutput(the adapter’s own output is the program’s). -
Your adapters win over the built-in ones for the same type.
-
Only one debug session runs at a time. It ends when the window closes.
Breakpoints:
- Click beside a line number, or press F9.
- Right-click that gutter to add a condition, a hit count or a logpoint (a message printed instead of stopping), or to disable or remove the breakpoint.
- Breakpoints move with the lines as you edit and are remembered per project. The adapter receives them again when you save.
- A hollow marker is a breakpoint the debugger reported it couldn’t bind. When the debugger moves a breakpoint to the nearest line it can stop on, the marker moves too.
- The Breakpoints section lists them and has the language’s exception filters (raised, uncaught).
Paused:
- The line is highlighted yellow. Picking a lower frame in the call stack highlights its line green.
- The toolbar over the editor has these controls:
- continue / pause (F5 / F6)
- step over (F10), step into (F11), step out (⇧F11)
- restart (⇧⌘F5), stop (⇧F5)
- The Debug view shows:
- variables as a tree (double-click a value to change it)
- watch expressions
- the call stack, per thread
- Hovering a name in the editor shows its value.
- The debug console (⇧⌘Y) shows the program’s output and evaluates expressions in the paused frame (↑ for history).
F-keys: F5 still answers an agent’s pending request first when no debug session is running. F6 pauses only while debugging, and otherwise moves to the next session. F9 toggles a breakpoint only while a file is in front.
macOS approval: with macOS Developer Mode off, Delve and lldb-dap (so C, C++, Swift, Rust) make macOS ask an administrator to allow each session. The debug console says so. Run sudo DevToolsSecurity -enable once to stop the prompts. The other debuggers don’t need this approval.
Context menus
Right-click almost anything (or press ⇧F10 or the Menu key on what has the focus) for what it can do. Menus have letters for each item, ↑↓ / ← → (submenus) / ↵ / Esc, and say why an item is greyed out. ⌘-click or ⇧-click selects several rows in the Files and Git views; the menu then acts on all of them.
- Text fields: spelling suggestions (macOS’s), Add to Dictionary, Cut, Copy, Paste, Select All.
- Terminal: Copy, Paste, Select All, Find, Quick Commands ▸, Split Right / Down, Equalize Panes, Maximize Pane / Restore, Rename, Continue in New Session, Fork into New Worktree, Copy Agent Session ID / Working Directory, Clear Screen, Reset Terminal View, Close.
- Tabs: Pin, Colour ▸, Keep Open, Move to Split ▸ (right, left, down, up), Move to the Other Pane, Rename, Split, Reveal, Copy Path / Relative Path / Remote URL, Open Preview, browser tabs’ Reload / Duplicate / Copy URL / Open in Your Browser, Close / Close Others / Left / Right / All Editor Tabs.
- Editor: Go to Definition, Find References, Cut / Copy / Paste, Toggle Comment, Format, Search in Files for the selection, Ask an Agent About This, Toggle Breakpoint, Copy Path / Relative Path / Path to Line / Remote URL (a permanent link to the line), Reveal, Markdown Preview, Move to Split. The gutter: breakpoints, Copy Path to Line, Copy Remote URL.
- Files: Open, Open to the Side, Preview, Open in Browser Tab (HTML), New File / Folder, Duplicate, Copy Path / Relative Path, Open in Terminal (a shell in that folder), Find in Folder (search scoped to it), Collapse, Reveal, Open in ▸ (the editors, terminals and Finder on this Mac), Rename, Move to Trash.
- Git: a changed file: Open Diff / File, Stage / Unstage, Discard Changes (untracked: Move to Trash), Copy Path, Reveal, Open in ▸, Show in Files; a commit: Show, Open on the Web, Copy Hash / Short Hash / Message, Explain Changes (an agent reads it), New Branch Here, Revert.
- Browser pages: Tern’s items as a native menu over the page: links (open in a tab or your browser, copy), images, the selection (copy, search the web), Back / Forward / Reload, Open in Your Browser, Copy Page URL, Send an Element to an Agent. ⌥-right-click gives the page’s own menu (Inspect Element).
- Sidebar: a session: Open, Split, Rename, Pin, Reveal, Copy Working Directory / Branch / Agent Session ID, Show Changes, Review, Continue, Fork, Mark Read / Unread, Sleep Now, Open in ▸, Stop / Close; a project: New Agent / Terminal / Agent in a Worktree, Reveal, Copy Path, Open in ▸, Mark All Read, Close; a worktree (and its board card): Open, New Agent / Terminal Here, Rename, Move to Status ▸, Pin, Set Parent ▸, Note, Review Changes, Sleep Agents, Mark All Read, Copy Path / Branch, Reveal, Open in ▸, Delete.
- Lists: ports, History, the review queue, runs and their steps, issues, PR checks and comments (Quote in Reply), automations, skills, dashboard cards, activity rows and search results each offer their own actions and copy items.
- The menu-bar item: what needs you, what finished, Keep the Mac awake, Settings, Check for Updates.
Keys
Every ⌘ shortcut is also an item in the menu bar (File, Code, Run, Go, Session, View, Help). The in-app list is F1. On macOS the menu bar receives ⌘-combinations before the web view does, and it passes each one on to the page.
Keyboard Shortcuts (Shortcuts in the bottom bar, Help ▸ Keyboard Shortcuts) changes any shortcut: search for it, press Change, then the new keys. Remove unbinds it and Reset brings back the default. Changes are kept in ~/.tern/keybindings.json ({"version": 1, "bindings": {"tab.recent": ["Ctrl+Tab"]}}; an empty list unbinds), which you can also edit by hand. A changed shortcut that collides with another one in the same place isn’t used: it shows struck out, with the reason.
The desktop app uses the TUI’s keys (F1–F9, j/k, ]/[, c, m, p, D in review) and adds these:
| Key | Action |
|---|---|
| ⌘T · F2 | new agent tab (the default agent), in the workspace in front |
| ⌘N | new worktree (the composer) |
| ⌃` | new terminal (login shell in the workspace in front) |
| ⌘D · ⇧⌘D | split the pane right · down |
| ⌥⌘ ← → ↑ ↓ | focus the pane in that direction |
| ⌃Tab | the tab you were on before (hold ⌃ for the pane’s recent tabs) |
| ⌃PageDown · ⌃PageUp | next / previous tab in the pane |
| ⌘F · ⌘G · ⇧⌘G | in a terminal: find in its history, older / newer match |
| ⌘↑ · ⌘↓ | in a terminal: previous / next shell prompt |
| ⌃⌘K | quick commands |
| ⌥⌘A | the floating terminal |
| ⌘W | close the tab (press again for a running agent or unsaved file) |
| ⌘-click · F12 | go to definition (⌃- goes back); hold ⌘ to see links |
| ⌘P / ⌘K | file palette · ⌘⇧F text search · ⌥⌘O symbols |
| ⇧⌘V | markdown preview beside the file |
| ⌘R | review queue |
| ⌘J | jump to any session, worktree, task or project |
| ⌘1 | workspaces · ⌘2 ⌘3 ⌘4 ⌘5: files · git · debug · info |
| ⌘B · ⌥⌘B | hide / show the left · right sidebar (drag their inner edge to resize, double-click the edge to reset; saved in ~/.tern/desktop-prefs.json) |
| ⌘] ⌘[ | next / previous session |
| ⇧⌘R, or double-click a tab or row | rename the session inline (a task keeps its branch and worktree name) |
| ⌘O · ⇧⌘O | open a folder · switch workspace |
Limitations
- The desktop app is macOS only, for now; the CLI and terminal UI also run on Linux and FreeBSD.
- Releases aren’t notarized by Apple yet. The first time you open the app: on macOS 15 and later, System Settings ▸ Privacy & Security ▸ Open Anyway; on macOS 12–14, right-click it ▸ Open. Or run
xattr -dr com.apple.quarantine /Applications/Tern.app. - Open documents (files, diffs, commits) are not restored after the window reloads. Sessions are, because they live in the daemon.
- Go to definition and references use the file’s language server when one is installed (gopls, pyright or pylsp, typescript-language-server, rust-analyzer, and so on). Without one:
- variables, parameters, loop and
with/exceptnames and imports resolve in the file, scope-aware for Python (the enclosing function, then the module; a method doesn’t see its class’s names) from x import yfollows intox’s file, including parenthesized multi-line imports and re-exports through__init__.pyself.attr/this.attrresolves in the enclosing class, preferring the assignment in__init__obj.attruses the object’s type: a parameter or variable annotated with a project class (config: AgentConfig | None,Optional[…]), a constructor call (x = AgentConfig()), orself.xset from one; the attribute is then looked up in that class (dataclass fields, class attributes, properties, methods), even in another file- functions, classes, module-level variables and
self.attributes come from Tern’s symbol index; when several files define the name, it prefers the current file, then a module the file imports, and otherwise lists them to pick from - attributes of library objects (for example
response.content) need a language server, because they depend on types - references fall back to whole-word text matches
- variables, parameters, loop and