The Apex space bunny.

Apex User's Guide

This guide describes how to use Apex. It assumes some familiarity with acme; Rob Pike's Acme: A User Interface for Programmers is still the best introduction to the ideas, and nearly all of it applies here. Russ Cox's tour of acme shows them in use. What follows covers what Apex does the same, what it does differently, and what it adds.

Getting started

Build the app as described on the front page, and move target/Apex.app to wherever you keep applications. Then choose Apex ▸ Install apex Command…, so that the apex command is on your path; you will want it.

When Apex starts, it attaches to the daemon on this machine, starting one if none is running, and reopens the sessions you had open last time. The first time, that is a single session called default. Quitting Apex leaves the daemon and its sessions running: start Apex again and everything is where you left it, including programs running in terminals.

To make another session, press ⌘T, type a name, and pick Create. From a shell:

% apex new-session work ~/src/project
% apex attach work

The screen

Apex's window is a set of columns, and each column a stack of windows, tiled as acme tiles them. Every window has a tag, a line of text above its body. Columns have tags too, and so does the whole window: its top row sits in the Mac's title bar. Tags are ordinary text: you can type in them, and anything in them can be executed.

A window's tag begins with what the window is, drawn from its state rather than its text: the path (folders dim, the name strong), a label when it has one (a terminal's title, say), and a few of Apex's own commands drawn as icons (Del, Snarf, Undo, Put, and so on), faint until the pointer is on the tag. After that comes the tag's own text, which initially is just Look . A column's tag holds New Cut Paste Snarf Sort Zerox Delcol, and the top row Newcol Newterm Win Web Kill Putall Exit End.

At the left of each tag is the window's handle (acme's layout box), drawn as a dot: hollow when the window is clean, filled when it has unsaved changes, gold when the file has changed on disk underneath it. A ring in the accent colour means a program is live in the window; a spinning arc means it is working. A window that wants your attention has its tag tinted.

The tag of a clean window: a hollow dot, the path crates/apex-core/src/state.rs, icons for Del, Snarf and Redo, then Look.The same tag with unsaved changes: a filled dot, and icons for Del, Snarf, Undo and Put.A terminal's tag: a ringed dot, the directory, the label airy, and icons for Del, Snarf and Send.
A clean file, the same file with unsaved changes (now offering Undo and Put), and a terminal with a live shell.

Window bodies come in three kinds: text, terminals, and pages (HTML, drawn by a web view). They are tiled, moved, and closed in exactly the same way.

Apex with two columns. The title bar holds the session's name, its directory, pills for the running lsp, agent and preview tools, and the top row's commands. Each column has a tag of commands; the left window shows DESIGN.md, the right a Preview of it.
The title bar: the session, its directory, the running tools, and the top row. Below, two columns, each with its tag; on the left a file, on the right its Preview.

The sidebar (⌃⌘S, or the button by the window's traffic lights) lists your sessions, grouped by host, and the windows of the one showing.

The mouse

As in acme, the three buttons have fixed meanings everywhere, in tags and bodies alike:

B1select text; drag to sweep
B2execute the text clicked or swept
B3look: open or find the text clicked or swept

A click with B2 or B3 on unselected text expands to a word: for B2 the run of characters that can appear in a command, for B3 a file name with an optional address, such as main.rs:120. Clicking inside the selection takes the whole selection. B2 and B3 sweeps are drawn in their own colours (orange for B2, blue for B3), and what B2 ran blinks briefly so that you can see it.

Chords work as in acme. While holding B1 after a sweep, click B2 to cut, or B3 to paste. While sweeping with B2, click B1 to run the command with the most recent selection as its argument.

On a laptop

Apex follows plan9port's mapping onto a single button:

clickB1
⌥-clickB2
⌘-clickB3
⇧-clickB4 (see below)
force clickB3

To chord while sweeping with B1, press ⌥ to cut or ⌘ to paste. Holding ⌘ or ⌥ alone over text shows, on a small pill, exactly what a click would look at or run there.

B4 and B5

Apex adds a fourth button. B4 (or ⇧-click) on a window opens a menu of the commands that tools and plumbing rules offer for that window, such as Preview on a Markdown file or Def once a language server is running. The menu opens with the last choice under the pointer, so a plain B4 click repeats it. B4 on a handle minimizes the window or column instead. B5, a mouse's forward button, runs Back.

Commands

B2 executes. If the text is one of Apex's built-in commands, Apex does it; if a tool or plumbing rule offers the word for this window, that tool does it; otherwise the text is run as a shell command in the window's directory, with acme's $winid, $% and $samfile set. Shell commands run through rc, which is bundled. Their output goes to the directory's +Errors window.

As in acme, a command may begin with |, < or >: |cmd pipes the selection through cmd, replacing it; <cmd replaces it with cmd's output; and >cmd sends the selection to cmd, its output to +Errors.

Built-in commands

Cut Paste Snarfthe usual; snarf is the system clipboard
Undo Redoundo or redo in the window
Put [file]write the window's file; Putall writes every dirty file
Getreload the file (or reload a page)
Delclose the window; warns once if unsaved. Delete does not warn
New [name]a new window, empty or on a file
Newcol Delcoladd or remove a column
Zeroxanother window on the same text
Sortsort a column's windows by name
Look [text]search the body for text, or the selection
Edit cmdrun a sam command (see below)
Tab [n]set or show the tab width
Indent on|offautomatic indentation
Fonttoggle between the proportional and fixed fonts
IDprint the window's id
Sendappend the selection (or snarf) to the body; in a terminal, type it
Stashput the window away (see Arranging windows)
Newterm [cmd]a new terminal, running a shell or cmd
Win [cmd]acme's win: a shell in a text window
Web [url]a web page
Kill namekill running commands by name
Exitclose Apex's window; the session keeps running
Endend the session; warns once if anything is unsaved

Edit takes the sam command language, as in acme, with Plan 9 regular expressions: Edit ,x/foo/c/bar/. The file and pipe commands (e, w, |, and so on) are not supported yet; neither are X and Y.

⌘⇧P opens a palette of every command you might run in the window under the pointer: the words in its tag, the commands its tools offer, Apex's built-ins, and what was run recently anywhere in the session. Return runs one there, as B2 would.

Looking and plumbing

B3 is acme's look, made programmable. The text clicked is offered to the session's plumbing rules in turn, and the first rule that takes it decides what happens. Out of the box:

⌘-B3 (⌃⌘-click on a laptop) asks for the definition of what was clicked, when a language server is running; ⇧⌘-B3 goes back. ⌘[ and ⌘] move back and forth through the places you've jumped from.

Look as you type

Typing in a tag's Look argument searches as you type if it is written with a slash: Look/word. A closing slash lets it contain spaces: Look/two words/. Each keystroke searches again from where you began; when nothing is found, the argument is struck through. Escape leaves the caret at the end of the match. ⌘F does all of this for you: it puts the caret in the window's Look argument, ready to type. ⌘G and ⌘⇧G find the next and previous matches. While a search is going on, every match in the window is faintly highlighted.

A window whose tag reads Look/acme/ with the caret in the argument. In the body the first acme is selected, and the later ones are faintly highlighted.
Typing Look/acme/: the match is selected, and the others are marked.

Looking works in terminals and pages too, searching what they show (and a terminal's scrollback).

Arranging windows

Windows and columns are arranged with their handles, as in acme:

B1 clickgrow the window a little; on a maximized window, restore the others
B2 clickmaximize: the column's other windows shrink to their tags
B3 clickthe window takes the whole column; the others hide behind it until B1 or B3 again
⇧-click (B4)minimize the window to its tag
dragmove the window, within its column or to another

Column handles do the same for columns across the row. A minimized column becomes a slim strip; click it to bring it back. Dragging a window to the outer edge of a column splits off a new column for it, and the line between two columns can be dragged to resize them. While you drag, where the window or column would land is shaded.

The stash is a place to put windows away without closing them. ⌘M (or the command Stash) stashes the window under the pointer. Stashed windows appear as a small hand of cards at the right of the title bar. Hover over them to fan them out and see one, live: you can work in it right there. Click a card to bring the window back where it was. Anything that goes to a stashed window, such as a look or a notification, brings it back as well.

Two stashed windows, Errors and rust-analyzer, as cards at the right of the title bar. Below them, the Errors window is shown live, with two lines selected.
The stash fanned out: Errors and rust-analyzer's diagnostics, the first shown below the cards, live.

The keyboard

As in acme, typing goes to the text under the pointer, not to a focused window. The caret that keys will go to is drawn in the accent colour and blinks; other carets are plain.

^U ^W ^Herase the line, word, or character before the caret
^A ^Estart or end of line
^Fcomplete the file name before the caret
Escapeselect what was just typed
↑ ↓scroll the body; in a tag, expand or collapse it
Home Endreturn to where you were typing, or go to the top or end
⌘Z ⌘⇧Zundo, redo
⌘X ⌘C ⌘V ⌘Acut, copy, paste, select all

Menu commands act on the window under the pointer:

⌘S ⌘R ⌘WPut, Get, Del
⌘Na new window
⌘Oopen a file or folder in the session's directory
⌘Pgo to an open window or recently closed file
⌘⇧Othe same, across all sessions
⌘⇧Prun a command
⌘F ⌘G ⌘⇧Glook, look again forwards, backwards
⌘[ ⌘]back, forward
⌘Mstash the window
⌘Jgo to the next notification
⌘T ⌘⇧Wopen a session, close this session's tab
⌘1 … ⌘9go to a session by position
⌘⇧[ ⌘⇧]previous, next session
⌘⇧Kthe session you were last in
⌃Tabwalk sessions, most recent first
⌘⇧\show all sessions
⌃⌘Sshow or hide the sidebar
⌘+ ⌘− ⌘0bigger, smaller, actual size
⌘,edit your profile
⌘⇧Rreconnect

Finding things

⌘P, ⌘O and ⌘⇧P open palettes that match what you type fuzzily. ⌘O lists every file under the session's directory; the listing and the matching are done on the session's host, so it is fast on large trees and on remote machines alike.

A window's path in its tag is a breadcrumb: click a folder, or the name, to list what is next to it, and type to narrow the list. Return opens the choice in a new window; ⌥Return opens it in place. Double-click the path to rename the window's file. Similarly, the session's directory is shown in the title bar; click it to change directory.

^F completes the file name before the caret, listing the choices when there is more than one.

Errors and notifications

A command's output and errors go to its directory's +Errors window, as in acme. In Apex, though, that window is created in the stash rather than over your work, and whatever is new in it appears briefly in a toast at the lower right. B3 on a file:line in the toast goes there; Show All shows the window. Language servers and other tools report the same way.

A toast headed Errors /Users/marius/, with Show All and a close button, listing file:line:col lines from a search.
A toast with what is new in +Errors. Each file:line can be clicked with B3.

A window can ask for your attention: an agent waiting for an answer, say, or a script running apex notify. Its tag is tinted, the Dock icon shows a count, and ⌘J takes you to the oldest such window, in whichever session it is. Other sessions wanting attention are marked in the sidebar and the session menu.

The commands a session is running are shown as pills in the title bar. Hover to see the full command; click × to kill it, B1 on its name to see its output, B3 to go to the window it was run from. apex ps and apex kill do the same from a shell.

Terminals

Newterm opens a terminal in a new window, running your shell in the directory it was run from; Newterm cmd runs a command instead. The terminal is a full emulator (built on libghostty), so anything that runs in Terminal runs here. It is also a window like any other: B1 selects, B2 executes the text (or types a word nothing else takes into the program), B3 looks, so a file:line in a compiler's output opens the file.

Sendtype the snarf buffer into the terminal
Snarfoutsnarf the last command's output
Clearclear the scrollback
⌘↑ ⌘↓previous, next prompt
⌘⇧Ccopy the last command's output

The prompt commands need shell integration. Add this to your .zshrc (or bash to your .bashrc):

eval "$(apex shell-integration zsh)"

A failed command is then also marked in the margin by its prompt.

A terminal's window is named after its directory and title, as win names its window. Programs inside a session have $EDITOR set so that, for example, git commit opens the message in a window over the terminal; delete the window to finish. Programs that open a URL or file through xdg-open or $BROWSER have it plumbed into the session. And apex itself, run in a terminal, acts on the session it is in.

Win runs acme's win: a shell in an ordinary text window, which some still prefer.

Pages

Page windows show HTML. They are fetched through the session's host, so a page on a remote session sees that machine's files and network.

Preview, offered in the B4 menu on Markdown, HTML and SVG files, opens a live rendering next to the file. It is redrawn as you type and follows the caret. Other formats can be added with a converter, a command that reads the file and writes HTML:

apex set Preview.rst 'pandoc -f rst -t html5'

Web url opens a web page, with back and forward buttons and an address field in place of the tag. apex diff reads a unified diff and shows it side by side, each line a link into its file:

git diff | apex diff

Sessions

A session is a whole workspace: its columns, windows, terminals, and the programs running in them. It lives in the daemon, not in the app. The app shows one session at a time, and keeps others open as tabs, listed in the sidebar. They stay attached in the background, so switching is instant. ⌘T opens the session picker, which lists the sessions on every host you know, lets you make new ones, and adds hosts.

Click the session's name in the title bar to rename it. Closing a tab (⌘⇧W, or × in the sidebar) leaves the session running on its host; End, or apex end-session, ends it.

Only one app leads a session at a time. If you attach to a session from another machine, the first app becomes a watcher: it still shows the session, live, but typing does nothing, and a banner offers to Take over. If the connection drops, the app reconnects by itself; the title bar says stalled or offline meanwhile.

Remote hosts

A session can live on another machine. In the session picker, choose Add a host… and give it an ssh destination, such as me@box. Or from a shell:

% apex attach me@box/work

Apex copies its own apex and rc (built for Linux, and bundled in the app) to ~/.apex/bin on the host when needed, starts the daemon there, and talks to it over ssh. Nothing else needs installing, and no files are synchronized: you are using Apex on that machine.

Machines reached by something other than ssh use a provider, a program named apex-remote-NAME on your path that runs a command on the destination, as ssh does. Destinations are then written NAME:dest. The providers directory in the source has examples.

Configuration

The View menu chooses the theme (six palettes, each light and dark), the fonts, and a few preferences. Everything else is configured with scripts of apex commands, as acme is configured with shell scripts.

~/.apex/profile, on the session's host, is run (by rc) whenever a session is made, in the session's directory. Its environment when it finishes becomes the environment of everything in the session. ⌘, opens it. A profile might look like this:

apex set Newterm.shell zsh
apex set Preview.dot 'dot -Tsvg'
apex plumb rule add -text='#(\d+)' -client=open -args='https://github.com/me/repo/issues/$1'
apex tool lsp &
apex tool agent &
GOFLAGS=-mod=mod

examples/profile in the source is a fuller example; among other things it names terminal windows after their directories for zsh, bash and fish.

~/.apex/attach, on the machine running the app, is run on the session's host each time the app attaches; settings it makes belong to that app alone.

apex set KEY VALUE makes a setting. These are the ones Apex reads:

Newterm.shellthe shell terminals run
Newterm.scrollbacklines of terminal history (10000)
Preview.EXTa converter to HTML for files ending .EXT
lsp.LANGthe language server for go, rust, python, typescript or c
lsp.roota file marking a project's root, for language servers

Plumbing rules

The plumbing rules are the session's, and apex plumb rule adds, lists and removes them. A rule matches text (anchored at both ends), and optionally the window it is in: by file name, kind, or owning tool. It then opens a file (-edit), runs a command (-run), or asks a tool (-tool). The groups of its pattern are $1, $2, …; $file, $dir and $win describe the window. A rule with -verb=Word instead of matching B3 adds Word to the B4 menu of the windows it applies to:

apex plumb rule add -verb=Test -file='_test\.go$' -run='go test ./...'

apex plumb -dry-run text shows which rules would take the text, and why the others didn't. apex help plumb describes every flag.

Language servers and agents

apex tool lsp runs language servers for the session: gopls, rust-analyzer, pyright, typescript-language-server and clangd by default, one per project root. Once a server is ready, files in its language get Def, Type, Refs, Hov, Sig, Fmt and Rn name in their B4 menus; ⌘-B3 is Def. Diagnostics go to a stashed window per server, and show up as toasts.

Coding agents (Claude Code, Codex, and Muse) run in terminals like anything else. apex tool agent makes them easier to live with. Install its hooks once, then run it in a session (from its profile, say):

% apex tool agent install
% apex tool agent

Each agent's terminal is then marked while it works, and notifies you when it needs an answer. Its B4 menu offers Transcript, Preview (its last answer as a page), Changes (a diff of what it changed), and Allow, Deny and Ask for permission requests.

Scripting

The apex command does everything the app does, and works whether or not the app is running. Inside a session it acts on that session; elsewhere, name one with -session. Windows are named by id, path, or label.

apex open file…open files
apex new [path]a new window, with stdin in it
apex win listlist the windows
apex text read winprint a window's text
apex edit win cmdrun an Edit command
apex exec win cmdrun a command, as B2 would
apex term new|send|readterminals
apex plumb textplumb text, as B3 would
apex eventsstream every change to the session
apex lslist sessions

For example, to replace every TODO in a file and save it:

apex edit notes.md ',x/TODO/c/DONE/'
apex exec notes.md Put

apex help lists every command, and apex help cmd describes one. Programs can also attach to a session directly with the Rust or Go libraries, or through apex tool bridge, which speaks JSON; the wiki describes how.