v0.13.0 · stable
eTerm · A terminal for the Mac

A Mac terminal
that keeps
what the scrollback
throws away.

For a moment, a terminal knows which command is running, where its output begins and how long it takes — and a scrollback throws that away a line later. eTerm keeps it: fold a run to one line, diff it against the last one, and see whether a failure is new or the same as before. With a dev server in one tab and an agent in the next, it tells you which one is waiting for you. And it is a whole terminal besides — tabs and splits that come back, a quick terminal on a key, pictures, ligatures and keys you can change — written from scratch in Rust on gpui, the framework behind Zed.

Free to download and use. macOS on Apple Silicon, signed with a Developer ID and notarized by Apple, so it opens without warnings.

api · web ● tab wants you
▸ composer install folded · 2.1s · 0
▸ php artisan migrate folded · 0.8s · 0
▾ php artisan test --compact 14.2s · exit 1
Tests: 1 failed, 612 passed
FAILED Tests\Feature\OrderTest > it totals a refunded order
⇧⌘E · why did it fail?
The refund is subtracted before tax is applied; the assertion expects it after. Run the one test with the SQL echoed:
$ php artisan test --filter=refunded -vvv return runs it · esc leaves
$ git push origin fix/refund-tax

Blocks · failure strip · an approval waiting · a suggestion in grey

Download · v0.13.0

Download for macOS

One disk image. Open it, drag eTerm to Applications. The build is signed with a Developer ID and notarized by Apple, so Gatekeeper lets it through without a warning — and the checksum is beside it so you can check what you got.

Installation guide

macOS · Apple Silicon

DMG

eterm-0.13.0-arm64.dmg

7.4 MB

sha256 0e02c20e7a92a3d360a9c0781a400a5a17c4de3df43481f7fbeff642a0dac91d

Apple Silicon only. An Intel build would need a second toolchain and a machine to test it on, and shipping one nobody has run is worse than shipping none. Where something has not been measured — a CJK input method, an Intel build, anything on Linux — the docs say so instead of guessing.

T.01 — What it does

A shell that keeps
the receipts.

With shell integration on — and for zsh that means doing nothing — eTerm knows where every command's output begins and ends. Everything below follows from that one fact.

Command blocks

Every command and its output is a block. Fold one to a single line, jump between prompts, hand a block to the pane next door, diff two runs of the same command to see what changed, or copy the whole session as a runbook somebody else can follow. A strip down the edge shows where the failures are, so a red run three screens up is one glance away rather than a scroll. Since 0.9.0 a finished command is also set against its last run, and the verdict sits at the end of its last line: same failure as the last run, not the failure of the run 4 min ago · 2 new, 1 gone, the last run passed, or, in green, fixed. What is compared is the lines that talk about failing with their numbers taken out, so a new timing is still the same failure — which is the question you were about to answer by scrolling up. Since 0.12.0 a finished command can also tell something else: with on_command_finished under [hooks], eterm posts what ran, how it ended, how long it took, the directory, the tab and its last 40 lines, with tokens and keys replaced by [redacted] first, to an address on this Mac or over https, such as a Huskarl routine's webhook, so a bot can propose an issue for a failed deploy. Only failures, or only commands that ran a while, if you like; anything but this Mac or https is not sent.

AI mode that waits for you

Ask for a command over the session you are asking about. The model answers with one, you approve it, it runs, and what it printed becomes the next question. ⇧⌘E asks why the last command failed. Nothing runs that you did not approve, and approval is per command — not per session, not once at setup — with no mode that turns it off, because the value of an approval is exactly the attention it takes. A command that looks destructive needs a different chord, so return does nothing at all.

Review a pull request in the terminal

Reading a pull request means leaving the terminal for a browser, and gh pr diff gives you somewhere to read one but nowhere to say anything. ⌘R opens a window on the repository your session is standing in: read the diff with the code coloured as code, comment on a line, reply in a thread, read the description and the conversation under it, and send the review with a verdict. No gh, no browser. The token lives in the login keychain, never in a config file. And since 0.9.0 the same window reads what an agent just changed before there is a pull request at all: ⌘⇧R opens it on the session's uncommitted changes, staged, unstaged and new, to be read the way a pull request is read. Comment on lines, and ⌘↩ puts the comments on that session's prompt as file, line, the line quoted and what you said — pasted, not sent, so it is still your return that hands them to the agent. That part runs git, because what has changed in a working tree is a question only git answers. Since 0.13.0, when a command finishes or an agent like Claude Code finishes a turn, the corner of the session says which files it changed, and review opens the window on those files alone: what that one run did, not everything uncommitted, with commits, checkouts and ignored files left out.

A tab that wants you

When a long command finishes, or an agent rings the bell because it needs an answer, the tab says so and the Dock bounces if you are somewhere else. A build that finished four minutes ago should not be found by coming back to check. And since 0.8.13, come back to the window after half a minute or more elsewhere and one line under the session says what happened in the meantime — claude is waiting for you · cargo test failed with 101 5 min ago · cargo build finished 8 min ago — each a way to the pane it happened in, another tab included. Waiting first, failures next, then the rest; four at most and a count of the others; nothing that ended before you left. And since 0.8.17 a line under a tab breathes while its session is working (since 0.12.1 three pixels, the width of the tab, never below half its brightness, and the whole tab tinted in the accent colour), so a build in a tab you are not looking at can be seen to be going, and seen to have stopped. Working is not the same as running: an editor, a remote shell or a tail -f runs for hours doing nothing, and none of them pulse. A program that says it is busy counts; otherwise it is output nobody typed, and a full-screen program redrawing its clock is not work. Since 0.9.0 a command that has run here before fills that line towards how long it usually takes, and goes back to pulsing once it is late, because nobody knows how far along a late run is. The tab carries the ports its session serves on — :5173, a click to open — and a port turns red when the command stops, because a dead server that still looks alive is how an afternoon goes on the wrong bug. And ⌘J lists every session in the window in the order the questions come: waiting for you, failed, still going, idle, with what a waiting one last asked. Since 0.12.0, when proposals wait in Huskarl, the line on coming back starts with them and ⌘J lists Huskarl first, with the newest proposal; a click opens Huskarl on the queue. eterm asks Huskarl's daemon over its socket on this Mac, never approves anything, says nothing when Huskarl is not running, and [huskarl] enabled = false turns it off.

The command line, coloured as you type

Green when the first word is a command that exists, and silence when eTerm is not sure — a colour that is sometimes wrong teaches you to ignore it, so it is only ever shown when it is known. Since 0.8.13 there is one more colour: the forty-odd shapes of destruction AI mode holds a proposal back for now watch what you type, and a line that matches one — rm -rf, git push --force, DROP TABLE, curl piped to sh — is painted red as it is typed, with the footer saying which shape. Return still runs it; this is a look offered before rather than after. The red overrides a prompt framework's colours where the green never did, because a warning that gives way to somebody's yellow is not one.

Suggestions and completions

Two different things, answering two different questions. A suggestion is the rest of a command you have run before, in grey after the cursor: what did I type last time? A completion is a list of ways to finish the word under the cursor — commands, options, paths: what are my options here? Since 0.8.17 the list waits for you to stop typing instead of opening under every keystroke and covering the screen while you write a command you know, and a word that is already whole gets no list at all: git is not offered gitk and git-shell just as you reach for space. Commands need two letters, and escape stops a list that is on its way.

A palette that knows your project

npm run dev, composer test, make deploy — read from the manifest in the directory you are standing in, so the scripts your project actually has are one keystroke away without remembering what they were called.

Secrets drawn as a bar

Tokens and keys on screen are hidden behind a bar until the pointer is over them, so a screenshot or a pair of eyes over your shoulder gets nothing. And before any output reaches a model, the value of a SOMETHING_SECRET= line is replaced with [redacted]. The screen is local either way.

Tabs and splits that come back

Tabs and splits with draggable dividers, restored between runs — the layout you left is the layout you return to, sessions and all. Since 0.8.14 tabs drag into order along the strip, and the order is kept with the session — and since 0.8.15 a tab dragged is a tab moved and nothing else: the tabs live in the title bar, and for one release AppKit took the drag as a drag of the window. Since 0.9.0 a project can describe its layout in .eterm.toml — panes, each split off the one before, each running its command or none — and nothing runs because the file is there: when a session arrives in the directory, a strip names every command the layout would run, and Open in a new tab is what runs them. A cloned repository does not get to start processes by being opened. And since 0.9.2 a folder can be opened in eTerm from outside it — dropped on the Dock icon, chosen with Finder's Open With, passed as open -a eterm ~/Code/app, or sent from the eterm button beside each project in Elyra Conductor — and becomes a new tab that starts there, in the window in front. Until then eTerm came to the front in whatever directory it was already in, which is not what a browser's open project in terminal is asking for; Litr's developer tabs ask exactly that.

Pictures, not blocks of colour

Since 0.10.0 eTerm speaks the kitty graphics protocol, so icat, chafa, timg and the previews in yazi show images: PNG and raw RGB or RGBA, sent at once, in chunks or as a file, placed at the cursor in as many cells as they need or as many as the program asks for. Programs get the answers they wait for, including the query that tells them pictures work at all. A picture scrolls with the text around it and stays in the scrollback, and it is decoded off the thread that draws, so a large one does not stall the screen. Animation, cropping, shared memory and tmux's placeholders are not done yet, and the pictures page lists exactly what is.

A terminal that drops down, on a key you choose

The quick terminal drops from the top of the screen on a global key, from any application and over a full-screen one, and goes back up on the same key or as soon as you click elsewhere; its sessions keep running while it is away. There is no key by default, because a global key is taken from every other application — the Keys pane offers ⌃`, ⌥Space and ⌘⌥T, and [quick] hotkey takes any other. The rest of the keys can be changed too: [keys.bind] in settings.toml gives a key to an action by name or takes one away with none, the defaults a line does not name stay, and a line that cannot be used is named in the Keys pane while the rest of the file still counts.

Ligatures, and every glyph in its own cell

A Ligatures switch in the Appearance pane, font_features for stylistic sets and character variants by their tags (ss01, -calt), and font_fallback for the families to try before Menlo and Apple Color Emoji. Before 0.10.0 an emoji or a CJK character, which takes two cells, left everything after it on the row drawn one cell short of where the terminal had it — the letters in one place, the cursor, selection and colours in another — and a ligature did the same; every glyph now sits in the cell its character came from. And eterm -e command runs a command in place of the shell, as Ghostty and kitty do: the window ends with it, and neither restores nor saves the session.

Shell integration with nothing to install

zsh needs nothing done: eTerm starts every session with a ZDOTDIR of its own — four small files that source your own .zshenv, .zprofile, .zshrc and .zlogin and then hand ZDOTDIR back — so your configuration runs exactly as it would have, a shell started inside the session is an ordinary shell again, and nothing is written into your home directory.

22 palettes, eight on the GPU

Twenty-two colour palettes, eight of them drawn over a shader surface on the GPU. Pick one, or set your own in settings.toml.

PATH control, find, mouse, input methods

Tools stay reachable when eTerm is launched from the Dock rather than a shell, because PATH is under your control. Find in session, mouse reporting for the programs that use it, and input methods for the languages that need them. Since 0.8.14 a path or an address in ordinary output is a link too, marked or not: hold ⌘ and what a click would open is underlined — an address opens in the browser, src/main.rs:12:5 opens in the editor you chose, at that line and column. Editors are reached through the URL schemes they register, so nothing runs a program, only the ones found under /Applications are offered, and a path only counts when the file is really there — v1.2.3 is not a file.

The files beside the session

cmd-e opens a panel on the right with the directory the focused session is standing in, as a tree. It follows the session: cd somewhere else or focus another pane, and it shows that directory, with the folders that were open there still open. A click opens a folder, or a file in the editor chosen under Links, and the keyboard stays with the session. What an agent makes or deletes appears while you watch. Since 0.11.0; asking why the last command failed moved to cmd-shift-e to make room.

Cmd-click, even inside Claude Code

A cmd-click on a link opens it first, in the browser or the editor, as Ghostty and iTerm have it, even inside a program that has asked for the mouse, as Claude Code and almost every full-screen program do. Anywhere that is not a link, the program still gets the click. Before 0.11.0 the link was underlined there, and nothing opened.

e as an editor

Paths open in the editor you choose under Links, at the line and column the text named, through the editor's own URL scheme. e has no URL scheme, so it is started instead, as its own command line starts it, with the file as one argument and no shell in between; it is told the file and not the line, which is all it takes. Choosing it sends both the file panel's clicks and cmd-clicked paths there. Since 0.11.0.

T.02 — Three things worth knowing

Built with the decisions written down.

Three of them matter more than any feature, and they are the ones a terminal usually keeps to itself.

01

The core has no framework in it

The emulator, the key rules, settings, palettes and the AI client are plain Rust, and every line of gpui lives in one directory. gpui is young and moves fast — three of its APIs changed under this project in a single day — so the boundary keeps that cost in one place, where it can be paid once.

02

Nothing runs that you did not approve

AI mode shows the command and waits. Return runs it — unless it looks destructive, and then return does nothing at all and a different chord is required. A terminal often points at a machine reached over ssh, and the gap between proposing and running is where a person belongs. What that gap must not become is a habit.

03

The numbers were measured

A frame costs 0.15 ms of grid work against an 8.3 ms budget, 0.31 ms in the alternate screen. Where something has not been measured — a CJK input method, an Intel build, anything on Linux — the docs say so instead of guessing, because a number nobody measured is a number you will one day plan around.

Your keys, your account

The model is yours.
So is the bill.

AI mode talks to Anthropic or OpenAI with a key you paste in — there is no eTerm account and no proxy in the middle. Refresh asks the provider what your account actually serves today and lists that, rather than a model list that went stale the day it was written.

  • The model key and the GitHub token go into the login keychain, never into settings.toml — that file is plain text and the first thing anyone attaches when asking for help
  • Secrets on screen are replaced with [redacted] before any output reaches a model
  • The model is told the exit code and up to forty lines of output — what it needs to answer, not your whole scrollback
  • The screen itself is local either way; only what you ask about leaves the machine, and what a hook you point at an https address posts
How AI mode works
cmd-enter · ask over this session
› why is the deploy hanging on the migration step
The migration acquires a lock on orders while a long-running report holds a read on it. Find the holder:
$ SELECT pid, query FROM pg_stat_activity WHERE state != 'idle';
return runs it · cmd-return edits it · esc leaves
sent: exit code + 40 lines · DATABASE_URL=[redacted]
T.03 — Licence

Free to download.
Free to use. A licence is a thank-you.

Every feature in eTerm works without a licence, and always will. What a licence changes is one thing: now and then — not in the first twenty minutes of a session, and not more than once every three days — a strip appears under the session to say that eTerm is free, that a licence exists, and where. A licence turns that strip off. Nothing is held back from anyone who does not buy one.

The Licence pane arrived in 0.8.12. On 0.8.11 or earlier there is nothing to paste into yet — download the current build first; it updates itself from there.

A licence for eTerm

$49 one-time · never expires · no subscription

Pay once and a line of text beginning ETERM- is emailed to you within a minute. Then ⌘, → Licence → Set key…, paste, return. The status row says who it is licensed to, and the strip does not come back. You also get an account page where the key can always be found again.

How the licence works

Card payment by Stripe. No account is needed to buy; one is created for you so the key is never lost. Questions or a refund — reply to the licence email.

Checked where
On your machine, and nowhere else. Signed with a key only the shop has; eTerm carries the half that can verify and cannot sign.
Phones home
Never. A licence bought once works on a machine that has never been online. No account, no seat count, no activation server to go away.
Kept where
In the login keychain, beside the other secrets eTerm keeps — never in settings.toml.
Colophon — Get the terminal

Open a shell,
keep the receipts.

Download eTerm, open a project, and get a terminal that knows what each command did — with a model on a leash and a pull-request review one keystroke away.

eTerm · v0.13.0 Built in Norway · MMXXVI