Configuration
Settings live in ~/.config/eterm/settings.toml and are written on every change
made in the settings window (cmd-,). The file can also be edited by hand;
eterm reads it at startup.
The file is not inside the application, so upgrading never touches it. It also survives being unreadable, which matters more than it sounds: settings are saved on every change, so defaults loaded into memory would otherwise become defaults on disk the moment you touched a toggle.
- A file that cannot be parsed is moved to
settings.toml.brokenand the settings window says so along the bottom, with the reason and the line. Nothing is lost; copy back what you want. - The previous file is kept as
settings.toml.bakbefore each save. - Saving writes a temporary file and renames it over the old one, so a crash or a full disk halfway through cannot leave a half-written file.
- Anything the file does not mention takes its default, and anything eterm does not recognise is ignored — so a file written by an older or a newer version still loads.
Everything below is optional. An empty file is a valid file.
appearance
[appearance]
palette = "palenight" # one of the 22 built-in palettes
font_family = "" # empty picks the best installed monospace font
font_size = 14.0
line_height = 1.4 # multiplier, not pixels
letter_spacing = 0.0 # extra pixels between cells
padding = 18.0 # around the grid
cursor_style = "block" # block, bar, underline
show_footer = true # the directory and title strip at the bottom
highlight_input = true # colour the command line as you type it
highlight_input turns the first word green when it is a command that exists.
It needs shell integration, which is what reports where
the prompt ends, and it never touches a cell the shell has already coloured —
so a prompt framework or zsh-syntax-highlighting keeps its own work.
A word we do not recognise is left alone rather than marked wrong, because it may be an alias or a shell function, and neither of those is visible from here.
The palette list is in the Appearance pane. Eight of them are designed to sit on a shader surface; four of those are light.
font_family is resolved against what is installed. If the named font is
missing, eterm falls back through JetBrains Mono, SF Mono, Berkeley Mono, IBM
Plex Mono, Fira Code, Menlo and Monaco, and the Appearance pane offers only the
ones actually present.
motion
[motion]
cursor_blink = true
blink_interval = 530 # milliseconds per half cycle
smooth_scroll = true
animate_surface = false # drift the shader surface
animate_surface is off by default because a moving surface means the window
never idles: about 5% of a core at 30fps. The surface itself costs 0.10ms a
frame when it does move.
shell
[shell]
program = "" # empty means your login shell
args = []
working_directory = "" # empty means home
scrollback = 10000
# Directories put in front of the inherited PATH. ~ is expanded.
path = [
"~/.local/bin",
"~/.cargo/bin",
"/opt/homebrew/bin",
"/opt/homebrew/sbin",
"/usr/local/bin",
]
# Environment variables for the session. These win over everything eterm sets,
# the shell integration included. A leading ~/ is expanded.
[shell.env]
EDITOR = "hx"
path matters more than it looks. A process launched from the Dock inherits
launchd's PATH, which on a stock Mac is four system directories and nothing
else, so cargo and brew are missing even though they work in another
terminal. The Shell pane shows the result: every directory marked inherited,
added or missing, and which of 21 known tools are reachable.
A login shell can still rewrite PATH in its own rc files afterwards. What eterm sets is the starting point, not the last word.
LANG is set for you when nothing else did. An application launched from the
Dock inherits no locale — launchd has none to give — and a shell without one
works in bytes rather than characters: æøå is three characters and six bytes,
and the line editor counts six. Backspace then takes half a letter. eterm sets
LANG from the region macOS is set to, falling back to en_US.UTF-8 when that
region has no locale installed, and leaves it alone when LANG or LC_ALL
already says something.
shell.env is also the way out of the automatic
shell integration: eterm points zsh at a ZDOTDIR of
its own, and setting ZDOTDIR here is what overrules it.
Secrets on screen
cat .env, and the tokens are on the screen — when somebody is looking over
your shoulder, when the screen is shared, and when the screen is about to be
handed to a model. With hide_secrets on, anything shaped like a secret is
drawn as a bar in the accent colour, and shown only while the pointer is over
it. Copying still copies the real text: this is for the shoulder, not for you.
What counts is decided by shape, chosen for precision: GitHub, Anthropic, OpenAI,
AWS, Slack, Stripe and Google tokens by their prefixes and lengths, JWTs, the
header of a private key, and the value of any SOMETHING_KEY=, _SECRET=,
_TOKEN= or _PASSWORD= line of eight characters or more. A commit hash is not
a secret and is left alone.
The same shapes are replaced with [redacted] in everything that leaves for a
model — the screen sent with a first question, the output of every step after
it — and in a block handed to another pane with cmd-shift-s, since what is in
that pane is usually a model too. See AI mode.
A tab that wants you
Agents sit and wait in panes you are not looking at. When a program in a session
asks for you — with the bell, with iTerm2's OSC 9, urxvt's OSC 777 or
kitty's OSC 99, or by saying through OSC 9;4 that it has stopped being busy,
which is how Elyra reports progress — the dot on its tab grows to full size and
full colour, and the Dock bounces if eterm is not the app in front. The mark
clears the moment you type or click in that session.
Claude Code rings the bell by default when it needs an answer; set
preferredNotifChannel to iterm2 and it sends OSC 9 instead, which is heard
the same way.
keys
[keys]
option_as_meta = false
Off leaves the option key to the system, so dead keys and input methods compose:
⌥U then A is ä. On sends ESC-prefixed keys, which is what readline expects
from ⌥B. It cannot be both.
ai
[ai]
enabled = false
provider = "anthropic" # anthropic or open_ai
model = "claude-opus-5"
context_lines = 40 # lines of screen sent with the question
fetched_models = [] # filled in by Refresh in the settings pane
The API key is deliberately not here. See AI mode.
input
[input]
suggestions = true # the rest of a command you have run before, in grey
completions = true # a list of commands, options or paths while typing
Both need shell integration. See Suggestions for what they read and what they leave alone.
review
[review]
enabled = true # cmd-r opens a pull request for reading
Off means the command does not exist and the window cannot be opened. The token is not here — it is in the login keychain, for the same reason the model key is. See Review.
renderer
[renderer]
vsync = true
max_fps = 120
updates
[updates]
check = true # ask elyracode.com once a day whether there is a newer eterm
Nothing but the request goes out, and nothing is fetched or installed until you press the button. See Updates.
other
[other]
notify_after = 20 # bounce the Dock icon for commands this slow; 0 is off
confirm_on_quit = false
copy_on_select = false # copy a selection as soon as it is made
bell = true # a program ringing the bell is asking for you
hide_secrets = true # tokens and keys drawn as a bar, and kept from models
notify_after only applies when eterm is not the application in front — you
are already looking at it otherwise. A command that succeeded bounces the Dock
icon once; one that failed keeps bouncing until you come back.
Session state
~/.config/eterm/session.toml is written by eterm and holds the window size,
the tabs, how each was split, the ratios and the directory each pane was in.
Deleting it gives you a plain window. Only the window a run starts with restores
and records it; a window opened with cmd-n is a new window.