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

TemplateForMain shortcuts
DefaultThe 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 keysDownload
tmux-styleDriving 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 prefixDownload
MinimalGetting out of the terminal's way entirely.⌘↩ ⌘⇧C open Compose · ⌘↩ send · esc/⌘W/⌘. close · ⌘⇧M mic · ⌘C/⌘V copy/paste — nothing else is boundDownload

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.json file for the current keymap, so you can back it up or hand it to the other device.
  • Import reads a .sophonz-keymap.json file 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, versionRequired: "sophonz-keymap" and 1.
nameRequired, 1–40 characters.
platformany (default), ios or android — records where it was made. Every app reads every keymap regardless.
options.optionAsMetaOption (⌥) 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.
keyBarThe 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 (alt also works and is read as opt), shift. cmd is 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 % is shift+5, " is shift+', + is shift+= 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 for compose.send, compose.close and compose.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

ActionDoes
compose.openOpens the Compose sheet
compose.sendSends the Compose draft — when: compose only
compose.closeCloses Compose without sending — when: compose only
compose.micStarts voice dictation into the draft — when: compose only
window.newOpens a new window
window.closeCloses the current window
window.prev, window.nextSwitches to the previous/next window
window.goto.0 … window.goto.9Jumps straight to a window by number
window.renameRenames the current window
window.listOpens the pill's sheet (pick a session, window or pane)
session.prev, session.nextSwitches to the previous/next session
session.listOpens the same sheet as window.list
pane.nextMoves to the next pane
pane.zoomZooms or unzooms the current pane
pane.split.right, pane.split.downSplits the pane
font.bigger, font.smaller, font.resetChanges the terminal's font size
edit.copy, edit.pasteCopies or pastes
terminal.clearClears the terminal
switcher.windows, switcher.sessionsOpens the window/session switcher — see below
keybar.toggleOpens or closes the key bar (with a hardware keyboard it starts closed)
keyboard.onscreenShows or hides the on-screen keyboard
window.goto.askAsks for a window number, then goes there
session.detachDetaches from the session
agent.interruptInterrupts the agent (Esc)
agent.rewindEsc twice: Claude Code's rewind, Codex's edit-previous
agent.modeSwitches the agent's mode (Shift+Tab)
agent.newlineA new line without sending (Ctrl+J; Alt+Enter for an unknown agent)
agent.transcriptThe agent's transcript (Ctrl+O in Claude Code, Ctrl+T in Codex)
agent.editorEdits the prompt in $EDITOR (Ctrl+G)
keybar.agentShows 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 format and version exactly right.
  • No unknown property, action or key, and no when the action doesn't allow.
  • name 1–40 characters.
  • No two bindings claim the same keys under the same when; no two prefixBindings claim the same key.
  • At most 200 bindings and 100 prefixBindings.
  • The file itself is at most 64 KB.
  • In a keyBar: row ids are unique, every key has exactly one of send, 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.

PresetRows
DefaultThe 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
tmuxThe multiplexer row always open, no agent row.Download
MinimalThe first row and a short row of extras.Download
Claude Code, Codex, Any agentOne 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.

KeySendsClaude CodeCodexGemini CLI
escEscinterrupt, close a dialoginterrupt the turn, declinecancel
esc escEsc, Escclear the draft, or rewindedit the previous messageclear, or browse earlier turns
⇧tabShift+Tabpermission modecollaboration mode (Plan)approval mode
⇧← ⇧→Shift+Left, Shift+Rightanswer its questions, next and previous question; edit the last queued message
1 2 3the digitpick an optionpick an optionpick an option
^JCtrl+Jnew linenew linenew line
^OCtrl+Otranscriptexpand or collapse
^TCtrl+Ttask listtranscriptTODO list
^BCtrl+Brun in the background (twice under tmux)
^GCtrl+Gprompt in $EDITORprompt in $EDITOR
^YCtrl+YYOLO

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.
idRequired and unique: lower-case letters, digits and -, starting with a letter, up to 24.
title1–24 characters.
whenalways (default), mux (only with a multiplexer), or agent:claude, agent:codex, agent:gemini, agent:generic (no agent st knows).
toggleA 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.
layoutscroll (default: keys keep their size and the row scrolls) or fit (keys share the width).
hiddenKept 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.
sendA 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.
textSent exactly as written. A list is sent as separate writes a moment apart, so Esc, Esc arrives as two presses.
charOne character; the bar's ctrl and alt apply to it.
actionAny action above but compose.send and compose.close (compose.mic dictates), or mod.ctrl, mod.alt (the sticky modifiers), agent.pick (choose the agent).
toggleShows 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.