gori's keyboard shortcuts are rebindable from the Hotkeys editor. Reach it from Preferences (Ctrl-, β Editor & Keys β Hotkeys, then β΅), or jump straight there with settings:hotkeys in the command palette (Ctrl-P). The editor lists every rebindable action grouped by where it fires (GLOBAL, HISTORY, REPEATER, FUZZER, INTERCEPT, β¦); pick a row, press a new key, done.
Ctrl-, β Editor & Keys β Hotkeys
Ctrl-P β settings:hotkeys
Key budget (how new shortcuts earn a key)
Bare letter keys are scarce. New actions should pick a price tier before taking a chord:
| Tier | Price | When | Examples |
|---|---|---|---|
| L0 Structural | Esc Enter Tab arrows Space (leader) |
Always | focus, open/close, READ/INS, space menu |
| L1 Loop | bare letter or sticky family (^R) |
many times / minute | History/Issues j/k / y t (mark) v (view), sub-tab strip t (mark), Repeater send |
| L2 Session breath | Global bare (cap: c i s only) |
many times / session | capture, intercept, scope lens |
| L3 Contextual | Space then mnemonic |
occasional, pane-local | compare, mine, send-group, copy-as |
| L4 Rare / config | palette (Ctrl-P) or Preferences (Ctrl-,) |
rare | settings, Match & Replace, notifications |
Rules of thumb:
- Default for new pane actions is L3 (space menu only). Promote to a direct key only after the loop proves it.
- Ctrl is for actions that must work while typing (INS), and for run/stop on a workbench (
Ctrl-R/Ctrl-X). It is not a general upgrade from bare. - Shift carries the whole-tab wipes.
β§XisClearin every tab that has one (History, Probe, Authorize, Issues, and the Project ACTIVITY feed), withXas the space-menu letter beside it. The letter isxand notcbecause of what sits under the shift: barexis bound in none of those five scopes, while barecis live in all of them (capture.toggle, anddismisson the Probe list), and a project wipe does not belong one shift above a key an operator presses all day. A destructive chord must also be named where it can be read before it is pressed (the Help sheet and the tab's own body hint, not the space menu alone), and it must ask first. ddestroys. Bareddeletes or dismisses the selected row in every scope that binds it β sixteen of them. The Repeater's response diff was the one exception, and it now sits onβ§Dso the reflex never lands on a display toggle. Its space-menu letter isβ§Dtoo:dis Duplicate sub-tab on all nine sub-tab strips (see the space menu), and that bucket is drawn in every pane, so the plain letter was no longer the diff toggle's to keep β which leaves the row and the keyboard spelling it the same way. A new pane action that is not a delete does not getd.- Copy is the worked example of that rule.
ycopies in READ, andCtrl-Ycopies in INS as well, in every text box. In INS a bareyis a literal character, and typing it over aShift+arrows selection replaces the selection, so the copy reflex needs a chord that survives typing. Both are the same verb (*.copy), so a rebind moves the READ letter andCtrl-Ystays where it is: it is pinned, in every scope, including through an explicit unbind. Unbindingyis a statement about READ mode, and it must not quietly leave a text pane with no way at all to copy what you just selected. - Every list that holds something worth copying binds
y. A pane that shows bytes, a row, or a line of record and answers nothing toyis a gap, not a design: the Intercept queue, the Evidence archive, the Project ACTIVITY feed and the OAST callback detail each had one and each now answers the letter. Where two copies live in one place and mean opposite things β OAST's list copies the payload gori sent, its detail copies what came back β only one can hold the chord (a scope has no focus dimension in the keymap), and the other keeps its space-menu letter. /filters the list you are looking at. Every list long enough to scroll answers it, including the three rule lists that did not: Colormarker, Match & Replace and the Probe RULES sub-tab (~40 built-in rules across three sections). The bar is a lens β it hides rows, it never disables one β andEscclears it. The one thing it changes is reordering: on the two lists where order decides which rule wins, a move is refused while a query is held, because a filtered list is not the order the rule engine holds.fhas two tiers and one exception. It is freeze in every evidence context (an Issue's RELATED card, the evidence viewer) and find on the sub-tab strip β a different tier, which cannot collide. History's follow and the Comparer's fold-unchanged areSpacemenu entries instead; both are session-rare toggles, which is what L3 is for. The exception is the Intercept queue, wherefforwards the held request andβ§Fforwards them all: that is the tab's own loop key and itsf/β§Ffamily is internally coherent, so it is documented rather than moved β the same callCtrl-Rgets for History β Repeater.xselects a line;tflips a row's flag.xmeans "select this line" in fourteen scopes, and it was "enable/disable this rule" in four (Colormarker, Match & Replace, the Probe RULES list and the OAST providers). Those four now answert, which is whattalready means as mark in History, Issues, the Sitemap and the Intercept queue β a rule list has no marks, so nothing collides, and the Rewriter's toggle stops being a hand-rolled controller key and becomes an ordinary rebindable chord.sgoes to the source, or flips the scope lens. Those are its only two meanings.sopens the tab a row lives in β the Evidence archive, an Issue's RELATED card, and the Probe list and detail β and everywhere it is not one of those, it is the Global scope lens. What it stopped meaning: swap A β B on the Comparer and the Diff (noww), and global β project on the Colormarker and Match & Replace rule lists (nowSpacemenu entries, so the lens is no longer shadowed there). One shadow is left and named: the Project ACTIVITY feed'sscycles the source chip, because that pane's/bar is a free-text query and does not parsesource:/level:/actor:to fold the three chips into.- The space menu is not an INS fallback: text editors swallow keys upstream, so
Spacestays a literal character there. An action that has to be reachable while typing needs a Ctrl chord, and a mnemonic alone is not enough. (This is whyCtrl-Q, not the space menu alone, carries the Repeater/Fuzzer decoder-chain editor after it gaveCtrl-Yup to Copy.) - History β Repeater and Repeater send stay on
Ctrl-R(same muscle memory). Do not move HistoryβRepeater to barer. rsends to the Repeater;Ctrl-Rruns. Bareris "send this row to the Repeater" in the five scopes that have a flow to send, and nothing elsewhere: the Diff's Run moved toCtrl-R(which already owns Run in nine scopes), OAST's Resume listener moved toShift-R, and the Project ACTIVITY feed's Refresh is aSpacemenu entry β a feed with a refresh key probably wants none at all, since it already re-reads on entry, on a peer's write and on the poll. The sub-tab strip'sr= rename is a different tier and is unaffected.- Match & Replace and Notifications ship keyless (palette / badge); rebind them if you want a Global chord.
One bare letter, one question
Bare letters are settled per question, not per tab. An action takes one of these only if it answers that letter's question; anything else starts at L3 (the space menu).
| Key | Means |
|---|---|
Enter |
show this row in place |
o |
open this row's own detail (Enter's alias; on the Sitemap Enter expands, so o is the only one) |
s |
go to the tab this row lives in β else the scope lens |
d |
delete / dismiss the selected row |
y |
copy |
t |
flip this row's flag (mark, or a rule's on/off) |
a |
add a row here |
e |
edit the selected row |
/ |
filter this list |
f |
freeze (evidence) Β· find (the sub-tab strip) |
r |
send this to the Repeater |
Ctrl-R |
run |
w |
swap A β B |
x |
select this line |
Shift-X |
clear this tab (asks first) |
Space |
this tab's command menu |
Three exceptions are deliberate, each a tab's own loop key: Intercept f forwards (and
Shift-F forwards all), History β Repeater and Repeater send stay on Ctrl-R, and the
Project ACTIVITY feed's s cycles the source chip.
Editing
The editor opens a working copy. Nothing is saved until you press Enter, and Esc discards every change.
| Key | Action |
|---|---|
β / β (or j / k), wheel |
Move the selection |
/ |
Search the action list |
e or Space |
Rebind the selected action, then press the new key |
x or Backspace |
Unbind the selected action |
r |
Reset the selected action to its default |
Shift-R |
Reset every action to its defaults |
β / β |
Cycle the OS default profile (see below) |
Enter |
Save + apply (live, no restart) |
Esc |
Discard and close |
When you start a rebind the footer shows "press a key to bind". Press the chord you want, modifiers included, except the ones listed under Reserved keys below. If the key is reserved or already used by another action in the same place, the editor refuses it and tells you why; capture stays open so you can try another key.
A row's chord shows (unbound) when nothing is bound. The β marker means you've changed it from the default; Β· means it's at the default.
Conflicts
Two actions may share a key only if they fire in different places. That's by design (s is "scope lens" almost everywhere but "go to source" on Probe, c is "toggle capture" everywhere except the Intercept queue where it cycles the catch direction). The editor blocks only a same-place collision, because there the keymap could keep just one of them.
The Digit Family
The tab bar is nine numbered slots, and the numbers are the primary way to move:
| Key | Action |
|---|---|
1β9 |
Jump to slot N on the tab bar |
0 |
Go to tabβ¦ β a type-to-filter list of all 21 tabs, each with a line on what it is for |
β§1ββ§9 |
Jump to sub-tab N of the active tab |
β§0 |
Find sub-tabβ¦ β the same picker f opens from the strip |
0 opens the whole catalog. The nine on the bar wear the digit that reaches them; the rest wear none β that is the only difference being off the bar makes.These work from everywhere β the tab bar, the sub-tab strip, a list body, a drill-in
detail, a read-only pane β with one exception: while a field is taking text (an editor in
INS, a / query bar or search, a line prompt, a picker's filter, the CVSS scorer, the
Decoder's CHAIN field), a digit is a character. It is the same rule Space follows: where
Space types a space, 3 types a 3 β which is what makes base64, sha256 and rot13
typeable into a conversion chain.
The bar paints the numbers by default (Preferences β Layout β Tab numbers, settings:layout).
The pill just past the last tab reads 0:Tabs β the key, and what it opens. It sits two
columns after the ninth slot rather than pinned to the right edge, so the order β walks and
the order you read are the same one; only when the strip stops fitting beside it does it pin
right and let the tabs scroll. It is there whatever your layout is: 0 reaches the whole
catalog, the nine on the bar included, so there is nothing for it to count and no layout that
makes it disappear. The run to the right of it is left free on purpose β a place for a readout
that is not a tab.
Nine slots, and the tenth tab
settings:tabs refuses a tenth tab and says so β β§K a row up across the seam instead, which
trades it onto the bar and the last slot off. A layout saved by an older
build (the bar used to be unbounded) is truncated to its first nine, in your own order,
and gori names the folded tabs once on the launch that does it.
One tab can still ride past the ninth slot: an off-bar tab you jumped to with 0 sits at the
far right of the bar, without a number, until you leave it. It is where you are standing,
not a slot you arranged β and no digit points at it.
If you want the old unbounded bar back, turn off Preferences β Layout β Tab bar slots.
The bar then scrolls with βΉ βΊ again, 1β9 still reach its first nine tabs, and 0
still reaches every tab.
Keyboard layouts
A terminal speaking the kitty keyboard protocol reports β§3 as 3 plus a shift flag,
and gori binds that. Every other terminal sends the shifted digit as a character β # on
a US layout β which gori folds back onto β§3. On a non-US layout that character is a
different one, so β§1ββ§9 work where your terminal reports the shift modifier and not
otherwise. Both fallbacks are always live: f on the sub-tab strip opens the same picker
β§0 does, and Ctrl-1β¦Ctrl-9 is the alias for β§1ββ§9 on terminals that deliver it.
The space menu
Space in a navigable pane opens the action menu for where you are standing β the
pane's own verbs, grouped under COMMON and the focused area's label, each fronted by one
mnemonic letter. It is not the palette: there is no typing and no filter, just one keypress
per row.
One menu per tab, whatever has focus
Nine tabs carry a sub-tab strip β Repeater, Fuzzer, Miner, Sequencer, Decoder, JWT,
Cookie, Comparer and Notes. The strip's own actions used to be a context section like any
other: they appeared only while the strip had focus, so from a body pane you had to walk
focus up a level before Space would even offer "close this sub-tab".
They are now their own SUB-TABS bucket, and it is in the menu from every level of a tab
that has a strip β the body panes, the strip itself and the tab bar. With β§1ββ§9 dropping
you anywhere, what Space offers must not depend on which row the cursor happens to be on.
The same nine letters on all nine strips
The bucket is one table to learn, not nine. A tab that does not have an action simply omits the row; it never spends that letter on something else.
| Key | Action | Direct chord |
|---|---|---|
n |
New sub-tab | Ctrl-N |
w |
Close sub-tab (or every marked one) | Ctrl-W |
d |
Duplicate sub-tab | |
e |
Rename sub-tab | r on the strip |
t |
Tag sub-tab (Repeater) | |
f |
Search sub-tabs β the β picker |
f on the strip, β§0 anywhere |
/ |
Filter the strip (name / host / method / tag) | |
T |
Mark every sub-tab the filter shows | |
N |
Clear the sub-tab marks | Esc on the strip |
Ctrl-N and Ctrl-W are shown beside their rows, and they work from any pane on all nine
tabs β the menu teaches the faster key rather than hiding it. (Miner and Sequencer seed
their sessions from a run, so they have no n.)
Because the bucket rides along with every pane, its nine letters are reserved in every view
of those tabs. A pane action that wanted one had to move: the rule is that the pane letter
yields, since the strip's letter has to read the same on all nine strips. Space W marks a
word in the Repeater/Fuzzer editors, Space D toggles the response diff, and the JWT and
Cookie lens toggles moved to m (Mode β the Decoder's letter for the same gesture).
Editor Keysets
gori's text panes are modal: Esc and i move between READ and INSERT, and in READ the bare letters are commands. The shipped grammar is helix-shaped β x selects the line, then y copies it β which is one gesture away from vim, where the same thing is V then y. That one difference is what a vim-trained hand fights all day.
Preferences β Editor & Keys β Keys β Editor keyset (Ctrl-,), or settings:keys in the palette, switches it:
| Keyset | Select line | Undo | Find | Append | Top / bottom |
|---|---|---|---|---|---|
| helix-ish (default) | x |
Ctrl-Z |
Ctrl-F |
β | β |
| vim-ish | β§V |
u |
/ |
a |
g / β§G |
Everything not in that table is the same under both keysets, because gori already spells it the way vim does: i enters INSERT, Esc leaves it, y copies, Ctrl-G goes to a line, and arrows plus Shift extend a selection.
A keyset is a mapping, not an emulation
It is a named bundle of key overrides for a small, fixed set of editor actions β exactly the mechanism the OS default profiles are, one layer up. It does not add an operator-pending grammar, registers, counts, text objects, or any editing operation gori's panes do not already have. A READ-mode pane is a caret, a selection and a copy; naming keys for operations that do not exist is how "vim mode" becomes a promise the editor breaks.
So some vim spellings are deliberately not offered, each for a reason you can check:
gg,dd,yyand every other two-key sequence. A gori chord is one keystroke.galone is the top of the pane, andywith nothing selected already copies the whole pane β which is the useful half of whatyymeans.:commands, including:42. A bare:is reserved for gori's own command line, so go-to-line staysCtrl-Gunder both keysets.- Delete. There is no delete-line in a READ-mode pane to put on
ddorD. Editing happens in INSERT. - The enable/disable
xon a rule list (Colormarker, Probe rules, OAST providers, Rewriter). Thatxturns a rule on and off; it is not a selection, and a keyset does not move a state change ontoβ§V. - The Intercept queue's select-line, which ships keyless on purpose β that pane reaches it from the space menu, and a keyset respells keys rather than handing one to a pane whose author decided against it.
The override order
Four layers, most specific first:
your own rebinding > the keyset > the OS default profile > the shipped chord
A keyset is therefore a better default, never a ceiling: pick vim-ish and then rebind one action in the Hotkeys editor, and that one stays where you put it. "Reset to default" on a row puts it back to the active keyset's spelling, not to the shipped one.
Every surface follows the active keyset with no extra step, because they all read the effective keymap: the status hint strips, the Help tab, the command palette, the space menu and the Hotkeys editor's own conflict messages. Under vim-ish the Notes footer reads / find where it read ^F find, and binding something to β§V is refused by name because Select line is there.
It does not touch the space menu's letters
A keyset moves the key you press in the pane, not the letter the space menu puts in front of a row β the same rule any per-action rebind follows, and the reason those letters are stable identities. Under vim-ish the menu still fronts Select line with x, and prints the live chord beside it:
β x Select line β§V β
so the card teaches both halves rather than making you guess which one it means. The nine SUB-TABS letters (n w d e t f / T N) likewise mean the same thing on all nine strips whichever keyset you pick. The two namespaces cannot collide: the menu is modal, and a keyset only ever writes to the keymap.
That includes /, which is a SUB-TABS letter and vim-ish's find key. They are different tiers β the menu letter acts on the strip while the card is up, the chord searches the text pane you are standing in β and gori checks the chord half at boot: validate_chords! sweeps every (OS profile Γ keyset) cell, and a keyset letter that shadowed a pane's own key would fail the build rather than ship.
What still works whatever you pick
Ctrl-Z keeps undoing inside INSERT under vim-ish β that guard runs before the keymap in all nine text editors, which is where a typing hand wants it. Ctrl-F likewise still opens the find prompt, INSERT included; / is an addition in READ, not a replacement. And i is refused with a message on a read-only pane that sits beside an editor (the Repeater response, the Fuzzer results, the Decoder output), under both keysets.
Reserved Keys
Some keys can't be rebound because the terminal or gori needs them:
-
Quit:
Ctrl-C,Ctrl-D. -
Indistinguishable from named keys:
Ctrl-M/Ctrl-J(Enter),Ctrl-I(Tab),Ctrl-H(Backspace),Ctrl-[(Escape). -
Structural:
Enter,Esc,Tab,Backspace,Space(the space-menu leader), and a bare:(the command line). -
gori shortcuts claimed before the keymap:
Ctrl-G(go to line),Ctrl-F(find, thenTabfor find & replace),Ctrl-B(reveal whitespace),Ctrl-E(external editor),Ctrl-P(command palette),Ctrl-N(new repeater/fuzz/note),Ctrl-W(close the sub-tab, or every marked one),Ctrl-Z(undo, consumed by every text editor: Repeater, Fuzzer, Notes, Issues, Intercept, Decoder, JWT, Rewriter and the Project description),Ctrl-,(Preferences), andCtrl-1β¦Ctrl-9(switch sub-tab β the alias forβ§1ββ§9, see The digit family). These are handled by a hardcoded guard before the keymap, so a binding on them would never fire. For the same reason Command palette, Reveal whitespace, New repeater request, and New fuzz session aren't listed in the editor. Their key is fixed.You can't move an individual key out of that family, but you can give the whole family a second modifier; see Command modifier below.
Ctrl-G/Ctrl-Fact on whichever multi-line pane has focus: the Repeater's request and response, the History detail, the Intercept editor, Notes, the Project description, the Decoder's INPUT and OUTPUT, and the Fuzzer's template and result detail.Tabswitches find to find & replace on the six that are editable; everything else is read-only, and the prompt says so rather than offering a swap it cannot make.
Flow-control/signal chords like Ctrl-S are not reserved; gori runs the terminal in raw mode, so they reach the app (Repeater's SNI toggle ships on Ctrl-S).
OS Default Profiles
The β / β profile selector picks which default key set a fresh (un-overridden) binding uses: auto (tracks the platform gori was built for), macOS, Linux, or Windows. Your own rebindings always sit on top of the chosen profile, regardless of OS.
Today the per-OS defaults are identical: in a terminal, Ctrl+letter chords reach the application on macOS, Linux, and Windows alike, and the genuinely hazardous keys are the reserved control characters above (blocked everywhere). The profile mechanism is in place so a real per-terminal clash can be fixed without touching dispatch. For now, auto is the right choice for everyone.
Command Modifier
The chord family listed under Reserved keys is fixed because a hardcoded guard runs before the keymap. That's a problem when your terminal never delivers the Ctrl form at all:
Ctrl-1β¦Ctrl-9is undeliverable on many terminals: there is no control character for it, so the sub-tab jumps simply never arrive. You never need it:β§1ββ§9is the primary sub-tab jump (see The digit family), and on a sub-tab stripflists and searches every open sub-tab, from whichever chip you are standing on. (Theβat the strip's left edge opens the same list; click it, or pressβfrom the first chip.)- A multiplexer eats the chord first. tmux's default prefix is
Ctrl-B, which gori also uses for reveal-whitespace.
Preferences β Editor & Keys β Keys β Command modifier (Ctrl-,), or settings:keys in the palette, switches that family between Ctrl and Option (β₯). It is an alias, not a swap: with Option selected, β₯P opens the palette and ^P still does. Only the advertised form changes: status hints, the Help tab and the palette all start showing β₯P, β₯N, β₯1-9.
| Modifier | Effect |
|---|---|
Ctrl (default) |
^P ^N ^W ^G ^F ^B ^E ^Z ^, ^1-^9 |
Option (β₯) |
the above plus β₯P β₯N β₯W β₯G β₯F β₯B β₯E β₯Z β₯, β₯1-β₯9 |
Because Ctrl keeps working, picking Option can never lock you out of the palette. That is worth knowing before you flip it, since on macOS your terminal must be set to send Option as Meta/Esc+ or β₯P arrives as Ο and nothing happens:
- Terminal.app: Settings β Profiles β Keyboard β Use Option as Meta key
- iTerm2: Settings β Profiles β Keys β Left/Right Option key β Esc+
Two things it does not do. It doesn't touch chords the editor can already rebind (^R send, ^S SNI, β¦); rebind those per action instead. And if you had bound an action to an Option chord in the family (alt-n, say), turning the alias on shadows it: the guard wins, that action reverts to its default, and the save toast names it.
The first-run wizard recaps this on its Review step, so you can pick a modifier before ever reaching the app.
Where It's Stored
Saved to ~/.gori/settings.json (override the directory with $GORI_HOME) under a sparse hotkeys block. Only the bindings you changed are written, as a list of chord labels per action id; an empty list is an explicit unbind:
{
"hotkeys": {
"os": "auto",
"command_modifier": "alt",
"keyset": "vim",
"bindings": {
"rules.edit": ["g"],
"scope.edit": []
}
}
}
command_modifier is "ctrl" (the default) or "alt", and keyset is "helix" (the default) or "vim" β see Editor keysets. An unknown value for either falls back to the default rather than to no keys. An untouched install writes no hotkeys block at all.
An absent action uses the profile default. Unknown ids and unparseable chords are ignored on load, so hand-edits and version drift degrade gracefully.
Limitations
- Only an action's primary chord is shown/edited; navigation aliases (e.g. the arrow-key duplicates of
j/k) aren't listed. - Every surface that names a rebindable chord reads it from the effective keymap: the command palette, the space menu, the Help tab and its popup, the status-bar hint strips, and the empty-state cards. What stays literal is not a verb: the claimed
^P/^N/^W/^1-9family (the sub-tab alias) and structural keys (esc,β΅, arrows,βΉ). - Space-menu mnemonic letters are stable action identities (Helix-like); rebinding changes the direct chord, not the space-menu letter.
- Where the sub-tab strip already binds a letter for an action, the menu spells that action with the same letter where it can:
flists and searches the sub-tabs,tmarks a chip andβ§Tmarks the strip. Rename is the one it cannot match β the strip bindsr, andrisRun/Send(the menu echo ofCtrl-R) in the Repeater, Fuzzer, Miner and Sequencer, where a rename does not get to displace it. One action must not have two spellings across the nine strips, so rename iseon all of them and the strip'srstays a raw chord. See the space menu for the whole table. - The editor actions are rebindable individually, and as a set via Editor keysets. What the rebind editor will not move is the handful whose chord a hardcoded guard answers first:
Esc(back to READ),Ctrl-Z,Ctrl-FandCtrl-G. They are listed in the Help sheet so you can read them, and a keyset can give them a second, bare spelling β which is howvim-ishreachesuand/. - Press
?from a navigable context to jump to the Help tab (mitmproxy-style cheat-sheet).
Next Steps
- Settings: the Preferences modal and every section in it
- Themes: switch or create colour themes the same way
- Configuration Reference: the
hotkeyskey insettings.json