Keymaps
Hardware keyboard shortcuts and the on-screen key bar for sophonz ADE — the built-in keymaps, the agent keys, the file format, and how to bring your own.
A keymap is a JSON file that binds hardware key combinations — cmd+t, ctrl+b then c, and so on — to app actions: new window, next pane, open Compose. Any key a keymap doesn't claim still reaches the terminal unchanged, so a keymap only ever adds shortcuts, never takes typing away. iOS and Android read the same file format, keymap v1, so one you export from a phone works on the other.
The same file can also describe the on-screen key bar: which rows it has, which keys are on them and what each one sends (The key bar). A file without that part keeps the app's built-in key bar.
The app ships with three built-in keymaps, read-only, and you can import as many of your own as you like.
The three templates
| Template | For | Main shortcuts | |
|---|---|---|---|
| Default | The Cmd-key set most Mac apps already use. | ⌘T new window · ⌘W close · ⌘[ ⌘] prev/next window · ⌘1–⌘9 go to a window · ⌘⇧[ ⌘⇧] prev/next session · ⌘↩ open and send Compose · ⌘= ⌘- ⌘0 font size · ⌘K clear · ⌥⇥ ⌥\`` switchers · for agents ⌘.interrupt,⌘Zrewind,⌘⇧Otranscript,⌘⇧Eeditor,⌘⇧A` agent keys | Download |
| tmux-style | Driving the app the way you drive tmux itself. | Prefix ctrl+b, then c new window · , rename · n p next/prev · 0–9 go to a window · x close · % " split · z zoom · o next pane · s sessions · w windows — plus ⌘↩ Compose, ⌘C/⌘V copy/paste, the switchers and Default's agent keys outside the prefix | Download |
| Minimal | Getting out of the terminal's way entirely. | ⌘↩ ⌘⇧C open Compose · ⌘↩ send · esc/⌘W/⌘. close · ⌘⇧M mic · ⌘C/⌘V copy/paste — nothing else is bound | Download |
Import and export
Settings → Keys and input → Hardware keyboard → Keymap lists the built-in three plus any you've added, and shows which one is active.
- Export writes a
.sophonz-keymap.jsonfile for the current keymap, so you can back it up or hand it to the other device. - Import reads a
.sophonz-keymap.jsonfile back in, key bar included. It's validated on the way in — a bad file is rejected with a reason and nothing changes — and, once it passes, added to the list and available to switch to.
The three built-in keymaps can't be edited or removed; anything you import is yours to change or delete.
The file format
{
"$schema": "https://www.sophonz.io/schemas/keymap-v1.json",
"format": "sophonz-keymap",
"version": 1,
"name": "My keys",
"platform": "any",
"options": { "optionAsMeta": true, "tmuxPrefix": { "enabled": false, "key": "ctrl+b" } },
"bindings": [
{ "action": "window.new", "keys": "cmd+t" },
{ "action": "compose.send", "keys": "cmd+enter", "when": "compose" }
],
"prefixBindings": [ { "action": "window.new", "key": "c" } ]
}| Field | |
|---|---|
format, version | Required: "sophonz-keymap" and 1. |
name | Required, 1–40 characters. |
platform | any (default), ios or android — records where it was made. Every app reads every keymap regardless. |
options.optionAsMeta | Option (⌥) sends Esc+key, the terminal convention for Meta. Default true; turn it off to let Option type accented characters instead. |
options.tmuxPrefix | { enabled, key }. On, the app itself catches the prefix key (ctrl+b or ctrl+a) and dispatches prefixBindings for the key that follows. Off, every key — prefix included — goes straight to tmux as typed. |
bindings[] | { action, keys, when? } — see below. |
prefixBindings[] | { action, key } — the single key (no modifiers) pressed right after the prefix. |
keyBar | The on-screen key bar — see The key bar. Optional. |
No properties beyond these are allowed, on the top-level object or on any binding.
Keys
Keys are written mod+mod+key, lower case:
- Modifiers, in this order, each at most once:
cmd,ctrl,opt(altalso works and is read asopt),shift.cmdis Meta on Android. - The key is one of
a–z,0–9,f1–f12, a named key —enter,esc,tab,space,backspace,delete,up,down,left,right,home,end,pageup,pagedown— or one of[ ] , . / ; ' \ - = `. - Shift is still a modifier, not part of the key: tmux's
%isshift+5,"isshift+',+isshift+=on a US layout.
Plain ctrl+letter, esc and tab are warned about if you bind them — the terminal wants those — and the templates never claim them. ctrl+space, 🌐 and Caps Lock are the system's own 한/영 switch and can never be bound; see below.
when
Every binding fires in one of two contexts:
terminal(the default) — the terminal has focus.compose— the Compose sheet is open. Required forcompose.send,compose.closeandcompose.mic, and not allowed on any other action.
prefixBindings have no when; they only make sense in the terminal, with the prefix already held.
Actions
| Action | Does |
|---|---|
compose.open | Opens the Compose sheet |
compose.send | Sends the Compose draft — when: compose only |
compose.close | Closes Compose without sending — when: compose only |
compose.mic | Starts voice dictation into the draft — when: compose only |
window.new | Opens a new window |
window.close | Closes the current window |
window.prev, window.next | Switches to the previous/next window |
window.goto.0 … window.goto.9 | Jumps straight to a window by number |
window.rename | Renames the current window |
window.list | Opens the pill's sheet (pick a session, window or pane) |
session.prev, session.next | Switches to the previous/next session |
session.list | Opens the same sheet as window.list |
pane.next | Moves to the next pane |
pane.zoom | Zooms or unzooms the current pane |
pane.split.right, pane.split.down | Splits the pane |
font.bigger, font.smaller, font.reset | Changes the terminal's font size |
edit.copy, edit.paste | Copies or pastes |
terminal.clear | Clears the terminal |
switcher.windows, switcher.sessions | Opens the window/session switcher — see below |
keybar.toggle | Opens or closes the key bar (with a hardware keyboard it starts closed) |
keyboard.onscreen | Shows or hides the on-screen keyboard |
window.goto.ask | Asks for a window number, then goes there |
session.detach | Detaches from the session |
agent.interrupt | Interrupts the agent (Esc) |
agent.rewind | Esc twice: Claude Code's rewind, Codex's edit-previous |
agent.mode | Switches the agent's mode (Shift+Tab) |
agent.newline | A new line without sending (Ctrl+J; Alt+Enter for an unknown agent) |
agent.transcript | The agent's transcript (Ctrl+O in Claude Code, Ctrl+T in Codex) |
agent.editor | Edits the prompt in $EDITOR (Ctrl+G) |
keybar.agent | Shows or hides the key bar's agent row |
The agent.* actions send what the agent in the current pane reads for that job, so one binding works for Claude Code, Codex and Gemini CLI alike. An app from before they existed refuses a file that uses them.
compose.send, compose.close and compose.mic can't be prefixBindings — they only exist inside Compose, and the prefix only fires in the terminal.
Limits and validation
A bad file changes nothing: the whole file is rejected, and every problem is listed with its JSON path, e.g. bindings[3].keys: "cmd+tt" is not a key. Checked, in the schema and again by each app:
- Valid JSON, an object, with
formatandversionexactly right. - No unknown property, action or key, and no
whenthe action doesn't allow. name1–40 characters.- No two bindings claim the same keys under the same
when; no twoprefixBindingsclaim the same key. - At most 200
bindingsand 100prefixBindings. - The file itself is at most 64 KB.
- In a
keyBar: row ids are unique, every key has exactly one ofsend,text,char,action,toggle, and every toggle key has a row to show and hide.
The key bar
The key bar's rows come from the active keymap. Settings → Keys and input → Key bar → Layout edits it: start from a preset, drag rows and keys into another order, hide or delete them, add your own, and change what any key sends. A built-in keymap is copied first. Hold any key on the bar to see what it sends — ⇧tab — Shift+Tab · Claude Code: switch permission mode — without pressing it; arrows and the other keys that repeat show it while they repeat, and the mic, which you hold to talk, has none.
| Preset | Rows | |
|---|---|---|
| Default | The first row (multiplexer and agent toggles, ctrl, alt, esc, tab, arrows), the agent row, the multiplexer row, the extras. What a keymap without a key bar gets. | Download |
| tmux | The multiplexer row always open, no agent row. | Download |
| Minimal | The first row and a short row of extras. | Download |
| Claude Code, Codex, Any agent | One agent's keys always shown, whatever runs in the pane. | Claude Code · Codex · Any agent |
On Android the extras sit right under the first row and the multiplexer row at the bottom, as they always have.
The agent keys
The agent row follows the agent st sees in the current pane — Claude Code, Codex or Gemini CLI — and shows nothing extra when there is none. The key at its front (codex ▾) or Settings → Keys and input → Key bar → Agent keys fixes it to one agent instead; Agent row sets whether it shows only while an agent runs, always, or never.
| Key | Sends | Claude Code | Codex | Gemini CLI |
|---|---|---|---|---|
esc | Esc | interrupt, close a dialog | interrupt the turn, decline | cancel |
esc esc | Esc, Esc | clear the draft, or rewind | edit the previous message | clear, or browse earlier turns |
⇧tab | Shift+Tab | permission mode | collaboration mode (Plan) | approval mode |
⇧← ⇧→ | Shift+Left, Shift+Right | answer its questions, next and previous question; edit the last queued message | ||
1 2 3 | the digit | pick an option | pick an option | pick an option |
^J | Ctrl+J | new line | new line | new line |
^O | Ctrl+O | transcript | expand or collapse | |
^T | Ctrl+T | task list | transcript | TODO list |
^B | Ctrl+B | run in the background (twice under tmux) | ||
^G | Ctrl+G | prompt in $EDITOR | prompt in $EDITOR | |
^Y | Ctrl+Y | YOLO |
With no agent the row has esc esc, ⇧tab, ⇧← ⇧→, 1 2 3 and ⌥⏎ (Alt+Enter). There is no Shift+Enter key; ^J does the same job.
keyBar
"keyBar": { "groups": [
{ "id": "main", "layout": "fit", "keys": [
{ "toggle": "mux" }, { "action": "mod.ctrl", "label": "ctrl" }, { "send": "left", "repeat": true } ] },
{ "id": "codex", "title": "Codex", "when": "agent:codex", "toggle": "agent", "keys": [
{ "action": "agent.pick" },
{ "label": "⇧←", "send": "shift+left", "hint": { "en": "Codex: answer its questions", "ko": "Codex: 질문에 답하기" } },
{ "label": "esc esc", "text": ["\u001b", "\u001b"] } ] }
] }| Field | |
|---|---|
groups[] | 1–12 rows, top to bottom. |
id | Required and unique: lower-case letters, digits and -, starting with a letter, up to 24. |
title | 1–24 characters. |
when | always (default), mux (only with a multiplexer), or agent:claude, agent:codex, agent:gemini, agent:generic (no agent st knows). |
toggle | A name. Toggle keys with that name show and hide every row that has it. Rows start shown, except agent rows, which start shown only while an agent runs. |
layout | scroll (default: keys keep their size and the row scrolls) or fit (keys share the width). |
hidden | Kept in the file but not shown. |
keys[] | 1–60 keys. Each has exactly one of send, text, char, action, toggle, and may have label (1–12 characters, else made from what it does), hint (a string, or one per language: {"en": …, "ko": …}), repeat and wide. |
send | A key as the hardware keyboard would send it — the keys above without cmd: shift+tab, shift+left, ctrl+b, opt+enter. The bar's ctrl and alt add to it. |
text | Sent exactly as written. A list is sent as separate writes a moment apart, so Esc, Esc arrives as two presses. |
char | One character; the bar's ctrl and alt apply to it. |
action | Any action above but compose.send and compose.close (compose.mic dictates), or mod.ctrl, mod.alt (the sticky modifiers), agent.pick (choose the agent). |
toggle | Shows or hides the rows with that toggle name; a row must have it. |
Keys for a multiplexer action the machine does not support are left out, as are toggles for rows that do not apply.
Editors validate it too
The $schema line at the top of the file points at keymap-v1.json, a JSON Schema (2020-12). VS Code and most editors with JSON Schema support read it automatically: red squiggles on a bad key or an unknown action, and autocomplete for both, right in the editor — before you ever import the file.
The 한/영 switch
Ctrl+Space, 🌐 (or the equivalent key) and Caps Lock are the system's own input-source switch on iOS; Ctrl+Space, Shift+Space or the 한/영 key do the same on Android. The app never intercepts any of them — they reach the system exactly as if the app weren't there, so switching between English and Hangul while typing always works.
The switchers
switcher.windows (⌥⇥ by default) and switcher.sessions (⌥` by default) hold their modifiers: the key itself opens the switcher, each further tap while the modifier is down moves the selection (backwards with Shift added), and releasing the modifiers switches to what's selected. Esc cancels.