A native macOS terminal with a chat-app shell: a conversation-style sidebar of workspaces and tabs, a rounded composer bar, sliding side panels for an embedded web browser and a file browser (local or remote over SFTP), split-pane layouts that survive relaunch, and a local scripting API so agents and scripts can drive it.
Built in Swift with SwiftUI/AppKit on top of SwiftTerm — a real VT100/xterm terminal (vim, htop, and ssh all work), not a command runner. No Electron.
Just want to run it? Download the app — the newest build is at the top of that page. For the newest version rather than the newest build, releases/latest. The green Code button above gives you the source tree, not the app.
- Real terminal emulation — PTY-backed login shell, full ANSI/xterm support, alternate-screen apps, configurable scrollback and cursor style, and an opt-in Metal GPU renderer.
- Chat-style shell — sidebar with New terminal / Automations / Skills rows
and workspaces whose tabs read like conversations (blue activity dots for
live background sessions); a landing screen with quick-start cards and
recent commands from your shell history; and a floating composer that types
into whichever terminal has focus — drag it anywhere, minimise it to a pill,
and recall earlier commands with the arrow keys. It colours what you type —
commands, flags, strings, variables, pipes — in your terminal theme's colours,
switching to a palette tuned for the input wherever a theme's own colours
would not read on it (4.5:1 contrast, checked in CI). A command word is
coloured if it is a shell builtin, an executable on this app's
PATHor in~/.local/bin,~/.cargo/bin,~/binor~/go/bin, or a path such as./scriptthat points at an executable file (checked against the terminal's directory, for local terminals only). Anything else — an alias or a shell function, say — is left plain, never marked wrong. As you type, the rest of a command you have run before shows in grey after the caret (→ or ⇥ takes it, esc waves it away); ⌃R searches your history, ↑↓ choose and ↩ puts the choice in the input without running it; and ⇥ completes file names — filling in what the candidates share first, then listing the rest — for terminals on this Mac, never for a remote session. Suggestions come from what the composer has run and from the tail of your shell's history file (~/.zsh_historyor~/.bash_history, up to 512 KB, read locally); they are held in memory, never saved, exported or sent anywhere, and a setting turns the file off. Only single-line commands are suggested. The chip above the input names the terminal it will run in, and switches it to a private shell of its own if you want one. - Framed window with an icon rail — one top bar and an icon rail down the
left edge frame the content, which sits in a rounded card. The rail holds
Terminal, Workspaces and Connect, with a
···menu for the rest (Tasks, Automations, Skills): each has a pin that puts its icon on the rail. The Workspaces panel opens beside the content and can be pinned there, or left unpinned to float over it until you pick something or click away (⌃⌘S shows or hides it). A button at the bottom left of the frame toggles the terminal dock. Settings → Appearance sets the frame's tint, corner radius and edge thickness, whether it is translucent or outlined, and the interface typeface; a preview there shows the change as you make it. - Dockable panels — a terminal dock along the bottom and a browser, file, notes or HTTP panel down the right, opened from the one button at the top right (each can also open in full view, across the whole content area), with draggable dividers whose sizes persist. A dock tab can be a local shell, a saved connection, or a running shell moved down from a pane, and it reopens as whatever it was.
- Task manager — every shell the app is running, wherever it lives: tab panes, the terminal dock, and the composer. Uptime while alive, exit code once it isn't, and one click to jump to it or stop it.
- System monitor — live CPU, memory, GPU and network for the machine along the bottom right of the window; hover for the detail, including the app's own footprint. Switch it off in Settings → Appearance and the sampling stops too.
- Workspaces → tabs → splits — arbitrary horizontal/vertical split trees per tab, snapshotted to Application Support and restored on launch. A tab with one pane renders flush; focus rings appear only once it is split.
- Scriptable browser — an embedded WKWebView usable as a tabbed right panel or a split pane, with page zoom, print and a scoped "clear browsing data", and drivable from the API (navigate, click, fill, read text, wait for a selector, screenshot) for testing a web UI from an agent.
- Remote sessions and files — SSH/Mosh terminal sessions to saved hosts (with reconnect), plus a Finder-style file pane that browses this Mac or any saved host over SFTP, with upload, download, and drag-and-drop.
- Quick connect — the sidebar's Connect menu lists saved hosts, saved screen-sharing endpoints, and whatever the local network is advertising right now. VNC and RDP open through the system's own client; SSH, SFTP and web open in the app.
- Notes panel — a scratchpad beside the terminal (⌥⌘N), saved as you type and kept out of exported snapshots.
- HTTP client — a request builder and response viewer, as a split pane or a right-hand panel (⌥⌘R): method, URL, headers, body; pretty-printed JSON; ⌘Return sends from anywhere in the pane; "Copy as cURL"; per-pane history. Requests only ever go out over HTTPS, or plain HTTP to this Mac itself — never a relaxed ATS exception for an arbitrary host. History is run through this app's best-effort secret masking (values that look like credentials, not a guarantee) before it touches disk, and stays out of exported snapshots.
- Site explorer — the HTTP pane's second mode (Request | Explore): type an
address and walk what the site publishes like a directory, with
ls,cd,tree,find,getand Tab completion. See Site explorer for what it reads and what it won't. - superfile and btop, built in — two icons at the top right, left of the panel toggles, open superfile (a terminal file manager, ⌥⌘S) and btop (a resource monitor, ⌥⌘P) in the side panel. Both ship inside the app, nothing to install, and both wear the terminal's colours — light, dark, and every change between. See Bundled tools.
- Proxmox VE — list the VMs and containers on a cluster over its API, open a guest's console in the browser panel, and save a guest as an SSH host. The API token's secret half stays in the keychain; a self-signed certificate is handled by pinning one you confirm, never by relaxing trust.
- Local scripting API + CLI — a Unix-socket JSON API and the
iterminalctlcommand for creating workspaces, splitting panes, sending input, and driving the browser, plus an event stream plugins and agents can subscribe to. - Command palette — ⌘K, fuzzy search over every action.
- Gentle motion — panels, the dock, banners and the sidebar's sections move on springs, and hover and selection in the sidebar, toolbar and dock fade instead of flashing. With Reduce Motion on (System Settings → Accessibility → Display) the sliding panels, dock and banners fade in place and the springs behind them become a short ease. The app follows the setting live, so changing it applies without a relaunch. Splitting or closing a pane is not animated.
- Settings for everything — General, Appearance, Terminal (theme, font, cursor, scrollback, GPU), Panels, Connections, Security, AI, HTTP Client, Backup, Shortcuts, and Advanced, all applying live.
- AI assistant — type
@ai …in the composer to ask an OpenAI-compatible endpoint (OpenAI, Ollama, or any/v1proxy). Keys stay in the keychain; replies appear above the input and are never auto-run in a PTY. When a command you ran from the composer prints something that reads like an error, an Explain / Fix it offer appears; a suggested command goes into the input for you to review, with a note if it is the kind that is easy to regret.
- macOS 14 (Sonoma) or later, Intel or Apple Silicon (universal binary)
- Xcode 16 or later (SwiftTerm's manifest uses Swift tools 6.0)
- XcodeGen
It's a native macOS app, so it needs a Mac (14+, Intel or Apple Silicon). There are two ways to get it running.
From Releases, where the
newest is at the top. Every green build of main publishes its own
Build n — an optimised Release build, the same configuration a shipped
copy would be — and version tags get a release of their own with a changelog.
Version releases are the only ones with a fixed link:
releases/latest
resolves to the newest of them, since they are the releases not marked as
prereleases. Per-build downloads have no rolling link — this repository has
immutable releases enabled, so a tag can back exactly one release for all time
and cannot be reused, which is why each build gets a tag of its own.
unzip iTERMiNAL-*.zip
xattr -dr com.apple.quarantine iTERMiNAL.app # see note below
open iTERMiNAL.appThese builds are unsigned, and macOS quarantines anything downloaded
through a browser — so without that xattr line Gatekeeper refuses to open
it ("damaged or can't be verified"). Right-click → Open works too. Signing
properly needs an Apple Developer ID, which this project doesn't have yet.
To try a branch that hasn't merged, the same app is attached to every CI run as the iTERMiNAL-app artifact under the Actions tab. Prefer a release where one exists: artifacts expire after 90 days and are buried inside a workflow run, while a release keeps its link forever and sits in the repo sidebar.
While this repository is private, both routes need a GitHub account with access to it. Making the repository public is what turns the release link into one anybody can open — artifacts would still require an account even then.
brew install xcodegen
xcodegen generate
open iTERMiNAL.xcodeprojThen build and run the iTERMiNAL scheme. CI builds every push on a macOS
runner (.github/workflows/build.yml).
Two routes, and neither needs MARKETING_VERSION bumped in project.yml — CI
overrides it from the version being released, so the About box always agrees
with the release it came from.
From the Actions tab. Run the Build workflow with a version input
(0.112 or v0.112 — the v is optional). It builds first and only then
creates the tag and the release, using GITHUB_TOKEN from inside the run.
This exists because pushing a tag is not something every client driving this
repository can do. The Claude Code GitHub relay refuses refs/tags/* writes
and the releases API outright, so an agent can open and merge pull requests but
cannot cut a release. GITHUB_TOKEN inside Actions is not subject to that.
Or push a tag, if you have a checkout and the access:
git tag v0.112 && git push origin v0.112Either way CI publishes the zip with a generated changelog, and the changelog
runs from the previous v* tag — stated explicitly, because the default
baseline would be the build-* prerelease published minutes earlier at the
same commit, which is no range at all.
The version is checked before anything is built: the workflow refuses a tag
that already exists or is not shaped like a version. That check is not
politeness. This repository has immutable releases enabled, so a tag name that
has ever backed a published release is reserved permanently — a typo cannot be
deleted and retried, which is how latest was burned here for good.
The sidebar and file panel follow your working directory via OSC 7. zsh
doesn't emit it by default — add this to ~/.zshrc:
# Report the working directory to iTERMiNAL (OSC 7)
if [[ "$TERM_PROGRAM" == "iTERMiNAL" ]]; then
_iterminal_cwd() { print -Pn "\e]7;file://%m%d\a" }
precmd_functions+=(_iterminal_cwd)
fiThe same twenty palettes, exported for Ghostty, with a config that carries the rest of the design — muted slate-green background over the usual black, Nerd Font, padding, translucency, tinted split dividers — plus matching btop, starship, yazi, fzf and fastfetch configs.
cd contrib/ghostty && ./install.sh --extrasThe themes are generated from TerminalTheme.swift, so a scheme looks the same
in both terminals — and --theme re-themes the companions too, so btop and
starship follow the terminal rather than staying one palette behind:
./install.sh --extras --theme iterminal-tokyo-nightSee contrib/ghostty/README.md.
Enable it in Settings → Security (it's off by default — it can type into live shells), then install the CLI from the same panel and run:
iterminalctl ping
iterminalctl tab.create directory=~/code
iterminalctl terminal.send text="git status" newline=true
iterminalctl terminal.capture
iterminalctl browser.open url=localhost:3000
iterminalctl browser.fill selector="#email" value=me@example.com
iterminalctl browser.click selector="#submit"
iterminalctl browser.wait selector=".result" timeout=15
iterminalctl browser.screenshot path=~/shot.png| Command | Parameters |
|---|---|
help, ping, app.info |
— |
workspace.list, workspace.create |
name |
tab.list, tab.create, tab.select, tab.close |
id, workspace, directory |
pane.list, pane.split, pane.close |
direction, kind (terminal, browser, files, http) |
terminal.send, terminal.capture |
text, newline, session |
browser.open / newTab / navigate / eval / click / fill / text / html / wait / screenshot |
url, selector, value, script, timeout, path, pane |
files.list |
path, connection, hidden |
connection.list |
— |
terminal.reconnect |
session |
subscribe / unsubscribe |
events |
The wire protocol is newline-delimited JSON over a Unix socket, so any language can speak it:
Subscribe and the connection stays open, pushing a frame per event — this is what plugins and agents hook into:
iterminalctl subscribe
iterminalctl subscribe events=session.exited,tab.created{"event":"session.exited","data":{"session":"…","exitCode":0,"remote":true}}| Event | Fires when |
|---|---|
session.started / session.exited |
a session launches or its process ends |
session.directory / session.title |
the shell reports a new cwd or title |
session.activity |
a session repaints (debounced to 4/sec) |
session.attention |
bell / OSC 9 / 777 from a pane (debounced ~1s; separate from activity) |
session.link |
the user clicks a link in a terminal |
tab.created / tab.closed / tab.selected |
tab lifecycle |
workspace.created, pane.split, pane.closed |
layout changes |
browser.navigated |
a browser pane finishes loading |
browser.tab.created / browser.tab.closed |
browser panel tab lifecycle |
dock.session.created / dock.session.closed |
terminal dock lifecycle (connection set on a remote tab) |
dock.session.moved |
a running shell was moved from a pane into the dock |
http.sent |
an HTTP client pane's request finished — status on success, error on failure |
From an unfocused pane (or while the app is in the background), try:
printf '\a' # bell
printf '\033]9;build done\007' # OSC 9
printf '\033]777;notify;title;body\007' # OSC 777With Settings → Terminal → Notifications set to in-app (default), the sidebar shows a blue attention mark. System banners require the in-app + system mode.
Add hosts in Settings → Connections, then open one from the sidebar's
Connect row, the composer's + menu, the terminal dock's + menu, or the
palette. Transport can be
ssh, mosh, or a custom command (which is how Tailscale SSH or Eternal
Terminal fit — %h, %p, %u, %d expand to host, port, user, and
user@host). The same hosts appear in the Files panel's source menu for SFTP.
Both features run the system's own clients, reusing your ~/.ssh/config,
known_hosts, agent, and keys — this app never stores, prompts for, or
transmits an SSH password. A terminal session has a real TTY, so ssh can ask
you for a password or 2FA code itself; the file browser runs sftp
non-interactively and therefore requires key-based authentication.
Settings → Connections has two sections beyond saved SSH hosts.
On this network browses Bonjour for _rfb._tcp (VNC / Apple Screen
Sharing), _ssh._tcp, _sftp-ssh._tcp and _http._tcp, so a machine already
advertising itself can be reached without typing an address. It browses only:
nothing about this Mac is advertised, and no connection is opened until you
pick a result. A Bonjour listing names a service, not a host, so the address
behind it is looked up at the moment you connect rather than while listening.
macOS asks for local network access the first time; declining leaves the list
empty and changes nothing else. Discovery starts when you open that section or
the sidebar's Connect menu, so a user who never does is never asked.
Screen sharing and remote desktop holds endpoints saved by hand. iTERMiNAL
implements neither VNC nor RDP — it hands the address to whichever app has
registered the scheme (Screen Sharing for vnc://, Microsoft Remote Desktop or
similar for rdp://), so that client is what authenticates and no password is
stored here. If nothing has registered the scheme, you are told, rather than
the button appearing to do nothing.
SSH, SFTP and web entries open inside the app instead — a terminal tab, the Files panel, and the browser panel.
Settings → Connections → Proxmox takes an endpoint and an API token id
(user@realm!tokenid); the token's secret half goes to the keychain, and the
host record itself carries no secret — the same rule saved SSH hosts follow.
Discovery is API-driven, not a port scan. Proxmox does not leave a VNC port
listening per VM — a console is created on demand by vncproxy behind a
one-time ticket — and it never exposes a guest's RDP at all, since that is a
service inside the guest on the guest's own address. A scan would find almost
nothing and miss every VM worth listing, so the app reads /nodes, then the
qemu and lxc guests per node, and asks the guest agent for addresses. No
agent means no address, not a failed refresh.
From the list, a guest's console opens in the app's browser panel (Proxmox already serves noVNC over its own web UI, so there is no VNC client involved), and a guest with a reported address can be saved as an SSH host in one click.
Certificates. A default Proxmox install serves a self-signed certificate, and this app's ATS exemption covers web content only — which relaxes transport policy, not certificate trust — so neither the API calls nor the console page would load. Rather than disabling validation or widening the exemption, which would weaken every connection the app makes, the system's verdict is tried first and you confirm a SHA-256 fingerprint once; exactly that certificate is then accepted, for that host and port alone. A host with a real certificate needs no pin. The pin is honoured by both the API client and the browser panel, because WKWebView runs its own trust evaluation and never consults URLSession's delegate.
- The scripting API is opt-in. It ships disabled, with separate toggles for sending terminal input and controlling the browser.
- The socket is user-only: mode 0600 inside a 0700 directory, and every request must carry a 256-bit token held in your keychain and compared in constant time.
- Secrets live only in the keychain — never in preferences, the saved layout, or exported snapshots.
- The assistant sends only what you switch on. Working directory, git branch and workspace name travel by default; the visible terminal screen does not, and has to be turned on in Settings → AI — except that Explain / Fix it, which you ask for each time, ask first when it is off. Terminal text is sent with control sequences stripped and values that look like API keys, tokens, passwords, private keys and URL credentials masked. That is best effort, not a promise: a secret in a form it does not recognise still goes, which is why sending the screen stays off until you say otherwise.
- App Transport Security stays on, with two narrow exemptions: web-view
content, so the browser pane can preview a plain-http dev server, and local
networking, so the assistant and the HTTP client panel can reach a server on
loopback. Anything routable still has to be HTTPS — the HTTP client refuses
a plain
http://request to any other host outright, rather than letting it fail as a confusing network error. - Certificate validation is never disabled. A self-signed Proxmox host is reached by pinning the one certificate you confirmed by fingerprint, for that host and port alone — the system's own verdict is tried first, so pinning can only ever add an accepted certificate, never subtract a check.
- No sandbox, but Hardened Runtime is on. A terminal exists to launch your
programs, and sandboxed children inherit the sandbox — a sandboxed build
could not read
~/.ssh, Homebrew tools, or repos outside its container. No general-purpose terminal ships sandboxed. Hardened Runtime is the part notarization actually requires, and it doesn't restrict spawned processes.
| Action | Keys |
|---|---|
| Command palette | ⌘K |
| New terminal tab | ⌘T |
| New workspace | ⇧⌘N |
| Split right / down | ⌘D / ⇧⌘D |
| Split with browser | ⇧⌘B |
| Close pane / tab | ⇧⌘W / ⌥⌘W |
| Terminal dock | ⌘J |
| Show / hide sidebar | ⌃⌘S |
| Browser / Files / Notes panel | ⌥⌘B / ⌥⌘F / ⌥⌘N |
| HTTP Client panel | ⌥⌘R |
| superfile / btop | ⌥⌘S / ⌥⌘P |
| Focus composer | ⇧⌘R |
| Minimise / expand composer | ⇧⌘M |
| Task manager | ⌥⌘T |
| Settings | ⌘, |
Sources/
├── iTERMiNAL/
│ ├── App/ entry point, window scene, menu commands
│ ├── Chrome/ window frame (top bar, icon rail), workspaces panel,
│ │ composer, command palette, themes
│ ├── Terminal/ TerminalEngine protocol + SwiftTerm implementation,
│ │ TerminalSession (PTY lifecycle, cwd/title/git metadata)
│ ├── Workspace/ Workspace → Tab → PaneNode split tree, persistence
│ ├── Panels/ scriptable browser pane, file pane, HTTP client pane
│ ├── HTTP/ request/response types, formatting, site-explorer parsers and
│ │ path tree — Foundation-only
│ ├── Files/ FileSystemProvider protocol, local + SFTP providers
│ ├── Remote/ Proxmox VE client, Bonjour discovery, remote services
│ ├── API/ Unix-socket server, message envelope, command router
│ ├── Security/ keychain wrapper
│ ├── Backup/ workspace snapshot archive, export/import
│ ├── Settings/ preferences store + settings window
│ └── AI/ AssistantService + OpenAI-compatible client
└── iterminalctl/ command-line client, bundled into the app
The terminal backend sits behind TerminalEngine
(Sources/iTERMiNAL/Terminal/TerminalEngine.swift) and the file backend behind
FileSystemProvider (Sources/iTERMiNAL/Files/FileSystemProvider.swift), so
either can be swapped (libghostty, an in-process SSH stack) without touching
the UI.
Configure a provider in Settings → AI, then type @ai … in the composer.
Replies appear in a banner above the input — suggested commands are never
executed automatically.
After a command you ran from the composer prints something that reads like
an error — "command not found", "permission denied", "no such file or
directory", a Python traceback, fatal:, npm ERR!, a failed make or build —
a slim row appears: That looked like an error · Explain · Fix it. It is a
guess from the words on screen, made on this Mac, and says only that it
looked like one: real exit codes would need the shell's help, and a command
that merely printed "permission denied" while searching has not failed.
Turn the offers off in Settings → AI.
The same two actions work any time, for any terminal, without any detection: Explain last output (⌥⌘E) and Suggest a fix (⌥⌘X) in the Terminal menu and the composer's + menu.
- What is sent. From the offer: the command and only the new text that appeared after it was sent. From the menu: what is on screen, and the command last sent to that terminal from the composer if it is still there. Either way, control sequences are stripped, values that look like secrets are masked, and it is cut to the last 6,000 characters starting on a whole line.
- Asking first. If "Include recent terminal output" is off, you are asked each time, told where it would go (the host's name, or "stays on this Mac" for a loopback model), and can read exactly what would be sent. Pressing Send does not turn the setting on.
- Nothing runs. A fenced command in a reply gets Insert, which puts it
in the input to review — one undo step, nothing sent until you press Return —
and Copy. Code in other languages gets Copy only. A command that deletes
recursively, uses
sudo, pipes a download into a shell, overwrites a file, force-pushes, or similar carries a short plain warning. No warning does not mean safe; it only reads for a few well-known dangers.
- Preset OpenAI (base URL
https://api.openai.com/v1). - Pick a model (default
gpt-4o-mini). - Paste an API key and click Save Key (stored in the keychain as
assistant.apiKey, never in preferences or export snapshots).
- Run Ollama and pull a model, e.g.
ollama pull llama3.2. - In Settings → AI, choose Ollama (local) or set the base URL to
http://127.0.0.1:11434/v1. - Set the model name to match (e.g.
llama3.2). No API key is required for localhost. Plain HTTP works here only because ATS is given theNSAllowsLocalNetworkingexemption, which covers loopback and local-link addresses alone — a LAN hostname over plain HTTP is still refused.
Switch an HTTP pane from Request to Explore and it becomes a command
line over one website's published paths. Type an address (example.com, or
http://localhost:3000 for a local server) and press Return — or, from
Request, press the list icon beside Send to map the address in the URL field.
An illustrative session:
$ example.com
mapping example.com — reading what the site publishes
robots.txt 200 14 paths, 1 sitemaps
sitemap /sitemap.xml 1,204 paths
reading the page you entered for links
page 200 37 links added
mapped 1,251 paths — 1,204 from sitemaps, 14 from robots.txt, 37 from links
example.com:/ $ ls -l
--D- admin/
S--- blog/ 312 inside
---L about
example.com:/ $ cd blog
example.com:/blog $ get 2024/post-1
| Command | Does |
|---|---|
ls [-l] [path] |
list a directory; -l shows where each entry came from — S sitemap, A robots Allow, D robots Disallow, L found as a link |
cd [path], pwd |
move around (.., /, ~, relative and absolute paths) |
tree [-L n] [path], find <text> |
the map below a directory; known paths containing some text |
get [path] (or cat) |
fetch one address and print the response; links in it are added to the map |
req [path] |
load an address into the request builder, without sending |
open <address>, refresh, info, clear, help |
map another site; read the declarations again; what was read and from where |
Tab completes names from the map, ↑ ↓ recall earlier lines, Esc cancels.
What it reads, and what it never does. The map is made only of what the
site itself declares or you fetched: its robots.txt (every Allow and
Disallow path, as written), the sitemaps that file names (an index is
followed a few levels, a few at a time), and the page you entered. ls,
cd, tree and find read that map and send nothing.
- No guessing. It never probes for common directories, never tries a
wordlist, and does not fall back to
/sitemap.xmlwhenrobots.txtdoesn't declare one.getfetches an address you typed, one at a time. - One site. A sitemap or
Sitemap:line that names another host (or another scheme) is reported and not requested, and a redirect off the site is shown, not followed — otherwise a site could aim this app at an address of its choosing. Different hosts are never merged:www.example.comis notexample.com. The one exception is for the address you typed: when the site's own redirect says its front door is itswww.twin (or the reverse), both over https on the same port,openmaps that host and says so —google.comsends you towww.google.com, and that is where the map is. - Bounded. Four sitemaps at a time, at most 40 files, three levels deep,
25,000 paths per
open; responses stop at the HTTP client's size limit..gzsitemaps are not read. - Lossy where it says so. Links come from
href/srcin HTML and from URL-shaped strings in JSON — not scripts, and<base>is ignored. A server with directory listings on shows up as ordinary links. Settings → HTTP Client can switch link-reading off, leaving only what is declared. - robots.txt rules are prefixes, shown as the site wrote them;
patterns containing
*or$name nothing and are left out.
The map lives in memory for the pane and is not saved with a layout or in a
snapshot. A get is recorded in the pane's history and on the http.sent
event like any other request; the requests open makes on its own are listed
in the transcript instead.
Workspaces and preferences live on this Mac, under Application Support.
Settings → Backup shows where, and moves them: Export Workspaces… writes a
single .iterminal JSON snapshot, Import Workspaces… reads one back and
replaces the current layout.
A snapshot carries workspaces, tabs, the split layout, and non-secret
preferences. It deliberately leaves out secrets — the API token stays in the
keychain, SSH has no passwords to carry — and machine-local paths such as
composerShell, so importing on another Mac cannot point the app at a shell
that isn't there. Terminal scrollback is excluded too: transcripts stay in
their own 0600 files on this Mac.
There is no background sync and nothing leaves the machine unless you export it. An earlier version offered iCloud through CloudKit; it needed an Apple Developer iCloud container to bind, so it was visible in every build and usable only in a signed one, and it has been removed along with the app's entitlements file. App Sandbox stays off; Hardened Runtime stays on.
Two third-party programs ship inside iTERMiNAL.app, in
Contents/Resources/Tools, and open from the icons at the top right:
| Tool | What it is | Licence | How it gets into the app |
|---|---|---|---|
superfile (spf) |
Terminal file manager | MIT | Upstream's macOS release binaries for both chips, checked against pinned SHA-256 hashes that were matched to upstream's own checksums file, merged into one universal binary |
| btop | Resource monitor | Apache-2.0 | Built by CI from a pinned commit — upstream publishes no macOS binaries — with Homebrew's GCC 15, once per chip on a native runner |
Selecting an icon starts the tool in the side panel; turning it off, or closing its panel, ends the process. Putting the whole panel region away leaves it running. superfile opens in the directory of the terminal you were using. The composer never types into either of them: with one focused, it sends commands to the tab behind it instead.
Both need room, and fall back to a "too small" message when squeezed, so the panel widens to at least 640pt while one is in front — capped so your terminal keeps its minimum — and returns to your usual width when you switch back to Browser, Files or Notes.
They wear the terminal's colours, and change with it — a new theme in Settings, or the Mac switching between light and dark:
- superfile is given a theme written in the terminal's ANSI palette, with its background and body text left to the terminal, so what it draws is the terminal's palette and restyles the moment that changes. Code previews are the exception: the highlighter needs literal colours, so the preview style (matched to the terminal theme by name where one exists) updates the next time superfile starts.
- btop only accepts literal colours, so it gets a theme generated from the
palette — background left to the terminal — and is sent
SIGUSR2, btop's own reload signal, whenever the palette changes.
Their settings live in ~/Library/Application Support/iTERMiNAL/Tools and are
passed on the command line, so a copy of either tool you installed yourself
keeps its own configuration. The one file written elsewhere is superfile's
iterminal.toml, in superfile's theme folder, since that is the only place it
reads themes from. The bundled superfile also doesn't check GitHub for updates:
its version moves with this repository.
Icons render with any font. superfile draws file and folder icons from Nerd Font code points, which no macOS font has, so they showed as boxes. The app bundles Symbols Nerd Font Mono (MIT; the icon sets' own credits ship beside it) and puts it behind the terminal font as a fallback, the way Ghostty and WezTerm do. It is used only for characters your font has no glyph for, in every terminal — prompts like Starship benefit too — and is registered for this app alone: nothing is installed on your Mac. CI checks, on a real Mac, that the icons resolve to it, bold included.
The default font needed one more step. macOS hands the system SF Mono out as a UI font, and a UI font's fallback always ends in a box-drawing font before any fallback the app adds. So by default the terminal draws with SF Mono from the complete family macOS ships inside Terminal.app, read in place — the same typeface, as Terminal.app draws it. Measured in CI at 13 pt: identical width, lines 0.2 pt taller (both round to the same row), and bold is SF Mono Bold rather than Semibold. If that family is ever missing, or would not keep a real bold and italic, the system font is used as before. Any other font you pick is used as is.
What makes bundling safe. btop links GCC's runtime statically —
STATIC=true, plus CXX_IS_CLANG=false to get past a bug in btop's Makefile
that otherwise makes that setting a no-op on macOS — and CI refuses any build
that links a library outside /usr/lib and /System/Library: a binary that
loads Homebrew's libstdc++ would run in CI and on no one else's Mac. CI then checks
both tools in the finished bundle: present, executable, both architectures,
system-only linkage, and that they actually run.
Versions live in scripts/tools.env and move only by a PR that edits it;
nothing updates itself or is downloaded at runtime. Settings → Advanced lists
the bundled versions and opens each licence. The tools take the download from
about 5 MB to about 35 MB; btop accounts for about 3 MB of that, superfile the
rest. The symbols font adds about 1.7 MB more.
Deliberately not done: btop's README recommends setting it suid-root so it can show every user's processes. It does not run that way here — a suid-root binary inside an app bundle is a privilege-escalation risk — so btop shows full detail for your own processes and less for others'.
A local Xcode build works without the tools and the font, and says so in their panels and in Settings. To include them, on a Mac:
scripts/fetch-superfile.sh # needs curl, lipo, codesign
brew install gcc@15 make && scripts/build-btop.sh Vendor/Tools # this Mac's arch only
scripts/fetch-symbols-font.sh # needs curl; runs anywhere- AI assistant behind the
@aicomposer prefix (OpenAI-compatible; no tool calling) - Pane attention notifications (OSC 9/777) via the event bus
- Editable key bindings
- Workspace snapshots: export/import with no account required
- HTTP client pane: request builder, response viewer, history
- HTTP client: declared-site explorer (
robots.txt/sitemap.xml/ links in fetched pages), command-line navigation - HTTP client: saved
.httpcollections - Optional libghostty engine
- Signed/notarized releases
MIT — see LICENSE.