@kolisachint/hoocode-agent 0.5.57 → 0.5.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +181 -0
- package/dist/core/agent-session.d.ts +2 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +4 -3
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +4 -1
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/keybindings.d.ts +162 -98
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +222 -106
- package/dist/core/keybindings.js.map +1 -1
- package/dist/modes/interactive/command-executor.d.ts.map +1 -1
- package/dist/modes/interactive/command-executor.js +95 -62
- package/dist/modes/interactive/command-executor.js.map +1 -1
- package/dist/modes/interactive/components/ask-options.d.ts +20 -6
- package/dist/modes/interactive/components/ask-options.d.ts.map +1 -1
- package/dist/modes/interactive/components/ask-options.js +35 -13
- package/dist/modes/interactive/components/ask-options.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts +9 -0
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +12 -0
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/task-panel.d.ts +2 -2
- package/dist/modes/interactive/components/task-panel.d.ts.map +1 -1
- package/dist/modes/interactive/components/task-panel.js +24 -11
- package/dist/modes/interactive/components/task-panel.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +29 -12
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +63 -8
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +189 -90
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/model-controller.d.ts +3 -0
- package/dist/modes/interactive/model-controller.d.ts.map +1 -1
- package/dist/modes/interactive/model-controller.js +1 -1
- package/dist/modes/interactive/model-controller.js.map +1 -1
- package/docs/keybindings.md +247 -80
- package/docs/terminal-setup.md +8 -3
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
package/dist/core/keybindings.js
CHANGED
|
@@ -3,39 +3,90 @@ import { existsSync, readFileSync } from "fs";
|
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { getAgentDir } from "../config.js";
|
|
5
5
|
/**
|
|
6
|
-
* The
|
|
6
|
+
* The keyboard map.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* ## Why it is grouped the way it is
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* - **overlays.** Inside a picker, the query line is a text field, so every
|
|
16
|
-
* `ctrl+<letter>` there belongs to the *text* (ctrl+a start of line, ctrl+u
|
|
17
|
-
* kill line, ctrl+w kill word). A picker's own verbs are therefore all on
|
|
18
|
-
* `alt+<letter>`, mnemonic to that picker; the picker captures keys while it
|
|
19
|
-
* is open, so reusing a global letter there is unambiguous.
|
|
10
|
+
* There are around sixty bindings here. Nobody holds sixty of anything, and the
|
|
11
|
+
* usual answer — sort them by mechanism, so everything that cycles sits together
|
|
12
|
+
* — makes a list that is tidy on the page and useless at the keyboard. "It
|
|
13
|
+
* cycles" is a fact about the widget. It is not what anyone is thinking when
|
|
14
|
+
* they reach for the key.
|
|
20
15
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* (ctrl+a/e/b/f/k/u/w/y/d/l/r/g, ctrl+s XOFF, ctrl+m == enter, ctrl+i == tab).
|
|
16
|
+
* So the grouping is by **intention**: the five things a person is ever doing
|
|
17
|
+
* here, in the order the loop runs.
|
|
24
18
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
19
|
+
* 1. **Flow** — get out, get back. esc, ctrl+c, ctrl+d, ctrl+z.
|
|
20
|
+
* 2. **Compose** — the message in your hands. alt+e, alt+r, alt+enter, alt+↑.
|
|
21
|
+
* 3. **Steer** — what the agent is before it runs. alt+a, alt+m, alt+t.
|
|
22
|
+
* 4. **Read** — what you see of what it did. alt+o, alt+l, ctrl+o, ctrl+t.
|
|
23
|
+
* 5. **Go** — sessions and places. alt+h, alt+w, alt+s, alt+k, alt+c.
|
|
27
24
|
*
|
|
25
|
+
* Five groups, none larger than five, which is the size a person can actually
|
|
26
|
+
* hold. Two of them cost nothing to learn: **Flow** is the set every terminal
|
|
27
|
+
* program already taught you, and the **overlays** (pickers, the tree, the
|
|
28
|
+
* options pane) print their own keys on their own hint lines — recognised, never
|
|
29
|
+
* recalled. That leaves three groups to genuinely know.
|
|
30
|
+
*
|
|
31
|
+
* The declaration order below *is* the grouping, and it is not cosmetic:
|
|
32
|
+
* `orderKeybindingsConfig` writes `keybindings.json` in this order, so the file
|
|
33
|
+
* a user opens to rebind something is grouped the same way this one is.
|
|
34
|
+
*
|
|
35
|
+
* ## What the chord tells you
|
|
36
|
+
*
|
|
37
|
+
* The modifier says what kind of thing will happen; the letter says to what.
|
|
38
|
+
*
|
|
39
|
+
* - **alt+<letter> sets a value.** Nothing takes the screen, nothing loses
|
|
40
|
+
* focus, you keep typing. Six of these are dials — an ordered set of stops
|
|
41
|
+
* with the current one painted where you can see it — and `shift+alt+<letter>`
|
|
42
|
+
* always steps back:
|
|
43
|
+
*
|
|
44
|
+
* **a**gent mode · **m**odel · **t**hinking · tool **o**utput
|
|
45
|
+
* task **l**ist · session **c**olour
|
|
46
|
+
*
|
|
47
|
+
* Reversibility is the point. A control you can undo invites you to try it; a
|
|
48
|
+
* one-way control makes you stop and think first, which is the wrong tax on a
|
|
49
|
+
* key you press all day.
|
|
50
|
+
*
|
|
51
|
+
* - **ctrl+<letter> acts on what is drawn right now**, and shares its letter
|
|
52
|
+
* with the alt key for the same subject. `alt+o` sets how much tool output
|
|
53
|
+
* there ever is, `ctrl+o` jumps to all of it and back. `alt+t` sets how much
|
|
54
|
+
* thinking there ever is, `ctrl+t` shows or hides what you have. Two subjects,
|
|
55
|
+
* two letters, four keys — half of what four unrelated chords would cost.
|
|
56
|
+
*
|
|
57
|
+
* - **A slash command chooses a stop outright.** `/mode`, `/model`, `/color`,
|
|
58
|
+
* `/tree`. The key steps, the command picks; that is why `app.model.select`
|
|
59
|
+
* and `app.session.tree` ship unbound, having given their letters to dials.
|
|
60
|
+
*
|
|
61
|
+
* One letter in the whole set names nothing: `alt+n` for the team roster. It
|
|
62
|
+
* survives because the task panel prints it in its own header, so it is read off
|
|
63
|
+
* the screen rather than remembered — which is the fallback for anything that
|
|
64
|
+
* cannot earn a mnemonic.
|
|
65
|
+
*
|
|
66
|
+
* ## The constraints that shaped it
|
|
67
|
+
*
|
|
68
|
+
* - No binding takes a key the editor or the terminal already owns
|
|
69
|
+
* (ctrl+a/e/b/f/k/u/w/y/d/l/r/g, ctrl+s XOFF, ctrl+m == enter, ctrl+i == tab).
|
|
70
|
+
* - Inside an overlay the query line is a text field, so every `ctrl+<letter>`
|
|
71
|
+
* there belongs to the *text*. Overlay verbs are all on `alt+<letter>`.
|
|
28
72
|
* - Only `alt+<letter>` and `alt+<digit>` survive a terminal without the Kitty
|
|
29
|
-
* keyboard protocol
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* either pair.
|
|
73
|
+
* keyboard protocol; `alt+[` and `alt+]` arrive as the CSI and OSC introducers.
|
|
74
|
+
* - `shift+<non-letter>` needs Kitty too, so a `shift+…` default is a
|
|
75
|
+
* convenience on top of a key that works everywhere. `shift+<letter>` is
|
|
76
|
+
* banned outright: without Kitty it *is* the uppercase letter, which no scope
|
|
77
|
+
* with a query line can tell from typing.
|
|
78
|
+
* - Legacy `alt+p` and `alt+n` are also read as `alt+up` and `alt+down`, so no
|
|
79
|
+
* scope may bind both halves of either pair.
|
|
80
|
+
*
|
|
81
|
+
* `test/keybinding-layout.test.ts` holds all of it, including that every
|
|
82
|
+
* binding belongs to exactly one family.
|
|
36
83
|
*/
|
|
37
84
|
export const KEYBINDINGS = {
|
|
38
85
|
...TUI_KEYBINDINGS,
|
|
86
|
+
// ── Flow — getting out, getting back ────────────────────────────────────
|
|
87
|
+
// Every terminal program you already use binds these, so they cost nothing to
|
|
88
|
+
// learn and must never move: whatever else is misconfigured, you can still
|
|
89
|
+
// stop the agent, clear the line, and leave.
|
|
39
90
|
"app.interrupt": { defaultKeys: "escape", description: "Cancel or abort" },
|
|
40
91
|
"app.clear": { defaultKeys: "ctrl+c", description: "Clear editor" },
|
|
41
92
|
"app.exit": { defaultKeys: "ctrl+d", description: "Exit when editor is empty" },
|
|
@@ -43,26 +94,82 @@ export const KEYBINDINGS = {
|
|
|
43
94
|
defaultKeys: process.platform === "win32" ? [] : "ctrl+z",
|
|
44
95
|
description: "Suspend to background",
|
|
45
96
|
},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
97
|
+
// ── Compose — the message in your hands ─────────────────────────────────
|
|
98
|
+
// Everything here acts on the text you are writing. Highest-frequency group,
|
|
99
|
+
// and the one your hands are already on.
|
|
100
|
+
// alt+e, not ctrl+g: ctrl+g is emacs/readline "abort" — the key you hit to
|
|
101
|
+
// get out of something. Having it launch $EDITOR inverts that reflex.
|
|
102
|
+
"app.editor.external": {
|
|
103
|
+
defaultKeys: "alt+e",
|
|
104
|
+
description: "Open external editor",
|
|
105
|
+
},
|
|
106
|
+
// alt+r, not ctrl+r: ctrl+r is reverse history search in every shell, and it
|
|
107
|
+
// was also the session picker's rename key — one chord, two meanings.
|
|
108
|
+
"app.input.voiceTranscribe": {
|
|
109
|
+
defaultKeys: "alt+r",
|
|
110
|
+
description: "Record voice and transcribe into the editor",
|
|
49
111
|
},
|
|
112
|
+
"app.clipboard.pasteImage": {
|
|
113
|
+
defaultKeys: process.platform === "win32" ? "alt+v" : "ctrl+v",
|
|
114
|
+
description: "Paste image from clipboard",
|
|
115
|
+
},
|
|
116
|
+
"app.message.followUp": {
|
|
117
|
+
defaultKeys: "alt+enter",
|
|
118
|
+
description: "Queue follow-up message",
|
|
119
|
+
},
|
|
120
|
+
"app.message.dequeue": {
|
|
121
|
+
defaultKeys: "alt+up",
|
|
122
|
+
description: "Restore queued messages",
|
|
123
|
+
},
|
|
124
|
+
// ── Steer — what the agent is before it runs ────────────────────────────
|
|
125
|
+
// The only three keys that change what happens next, and the only three that
|
|
126
|
+
// cost anything: latency, money, behaviour. The footer shows all three, which
|
|
127
|
+
// is why they are worth one deliberate chunk of memory.
|
|
128
|
+
// alt+a for "agent mode", and it is the dial the footer leads with. Not alt+m:
|
|
129
|
+
// mode and model are one letter apart and the model has the better claim on
|
|
130
|
+
// m, so this one takes the letter of what it selects — the agent's stance.
|
|
131
|
+
"app.mode.cycleForward": {
|
|
132
|
+
defaultKeys: "alt+a",
|
|
133
|
+
description: "Cycle agent mode (ask → plan → build → debug)",
|
|
134
|
+
},
|
|
135
|
+
"app.mode.cycleBackward": {
|
|
136
|
+
defaultKeys: "shift+alt+a",
|
|
137
|
+
description: "Cycle agent mode backward",
|
|
138
|
+
},
|
|
139
|
+
// alt+m, not ctrl+p: the model is a cockpit dial — it is what the agent *is*,
|
|
140
|
+
// not what is on screen — and p named nothing. The letter names the dial now,
|
|
141
|
+
// which is the whole rule for the six of them. `/model` opens the picker that
|
|
142
|
+
// used to be on this key, exactly as `/color` backs alt+c.
|
|
50
143
|
"app.model.cycleForward": {
|
|
51
|
-
defaultKeys: "
|
|
144
|
+
defaultKeys: "alt+m",
|
|
52
145
|
description: "Cycle to next model",
|
|
53
146
|
},
|
|
54
147
|
"app.model.cycleBackward": {
|
|
55
|
-
defaultKeys: "shift+
|
|
148
|
+
defaultKeys: "shift+alt+m",
|
|
56
149
|
description: "Cycle to previous model",
|
|
57
150
|
},
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
"app.model.select": { defaultKeys:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
151
|
+
// Unbound by default: a dial's key steps it, and its slash command chooses.
|
|
152
|
+
// alt+m stepping the model is worth more than alt+m opening a list of them,
|
|
153
|
+
// and `/model` is one keystroke further with completion on the name.
|
|
154
|
+
"app.model.select": { defaultKeys: [], description: "Open model selector" },
|
|
155
|
+
// alt+t pairs with ctrl+t the way alt+o pairs with ctrl+o: same letter, same
|
|
156
|
+
// subject, ctrl acting on what is drawn right now and alt on how much there
|
|
157
|
+
// ever is. shift+tab stays as a second key rather than the first — it is the
|
|
158
|
+
// cycle key every terminal agent has taught, and with no slash command for
|
|
159
|
+
// the thinking level it is the only way to reach this dial on a terminal
|
|
160
|
+
// that eats alt. Hints and /hotkeys name alt+t, which is the taught key.
|
|
161
|
+
"app.thinking.cycleForward": {
|
|
162
|
+
defaultKeys: ["alt+t", "shift+tab"],
|
|
163
|
+
description: "Cycle thinking level (off → … → high)",
|
|
164
|
+
},
|
|
165
|
+
"app.thinking.cycleBackward": {
|
|
166
|
+
defaultKeys: "shift+alt+t",
|
|
167
|
+
description: "Cycle thinking level backward",
|
|
168
|
+
},
|
|
169
|
+
// ── Read — what you see of what it did ──────────────────────────────────
|
|
170
|
+
// Free and reversible, every one of them: nothing here touches the work, only
|
|
171
|
+
// the window onto it. Press again or add shift and you are back where you
|
|
172
|
+
// were, which is what makes poking at them safe.
|
|
66
173
|
// The view dial pairs with ctrl+o on purpose: same letter, different ring.
|
|
67
174
|
// alt+o walks the dial a stop at a time and saves where it lands; ctrl+o
|
|
68
175
|
// jumps to the far end and back without moving your home stop. One value
|
|
@@ -75,65 +182,48 @@ export const KEYBINDINGS = {
|
|
|
75
182
|
defaultKeys: "shift+alt+o",
|
|
76
183
|
description: "Cycle tool output view backward",
|
|
77
184
|
},
|
|
185
|
+
"app.tools.expand": {
|
|
186
|
+
defaultKeys: "ctrl+o",
|
|
187
|
+
description: "Jump to the full view from wherever you are, and back again",
|
|
188
|
+
},
|
|
78
189
|
"app.thinking.toggle": {
|
|
79
190
|
defaultKeys: "ctrl+t",
|
|
80
191
|
description: "Toggle thinking blocks",
|
|
81
192
|
},
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
193
|
+
// alt+l for the task *ledger* — the pane's own name for itself. It was
|
|
194
|
+
// ctrl+n, which named nothing and was the last dial off the alt ring; moving
|
|
195
|
+
// it also gives the lens the reverse it could never have on ctrl, where
|
|
196
|
+
// shift+ctrl+n is Windows Terminal's "new window". ctrl+n is free now, so an
|
|
197
|
+
// emacs config can take it for cursorDown without colliding.
|
|
198
|
+
"app.tasks.cycleForward": {
|
|
199
|
+
defaultKeys: "alt+l",
|
|
85
200
|
description: "Cycle task panel view (tasks → subagents → teams, skips empty lenses)",
|
|
86
201
|
},
|
|
202
|
+
"app.tasks.cycleBackward": {
|
|
203
|
+
defaultKeys: "shift+alt+l",
|
|
204
|
+
description: "Cycle task panel view backward",
|
|
205
|
+
},
|
|
206
|
+
// The one letter in the set that names nothing. It survives on the fallback
|
|
207
|
+
// every unmemorable key needs: the task panel prints it in its own header
|
|
208
|
+
// when the teams lens is up, so it is read off the screen rather than
|
|
209
|
+
// remembered. alt+n steps INTO that lens and focuses the roster (--team only).
|
|
87
210
|
"app.team.focus": {
|
|
88
|
-
// Pairs with ctrl+n (cycle task panel view): alt+n steps INTO the teams
|
|
89
|
-
// lens and focuses the role roster (--team only). Not shift+ctrl+n —
|
|
90
|
-
// Windows Terminal intercepts that as its "new window" shortcut, the
|
|
91
|
-
// same trap that moved app.tasks.cycleView off ctrl+shift+t.
|
|
92
211
|
defaultKeys: "alt+n",
|
|
93
212
|
description: "Focus the team roster (navigate roles, n nudge, a attach)",
|
|
94
213
|
},
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
description: "Attach to the selected team role (team focus mode)",
|
|
104
|
-
},
|
|
105
|
-
// alt+e, not ctrl+g: ctrl+g is emacs/readline "abort" — the key you hit to
|
|
106
|
-
// get out of something. Having it launch $EDITOR inverts that reflex.
|
|
107
|
-
"app.editor.external": {
|
|
108
|
-
defaultKeys: "alt+e",
|
|
109
|
-
description: "Open external editor",
|
|
110
|
-
},
|
|
111
|
-
"app.message.followUp": {
|
|
112
|
-
defaultKeys: "alt+enter",
|
|
113
|
-
description: "Queue follow-up message",
|
|
114
|
-
},
|
|
115
|
-
"app.message.dequeue": {
|
|
116
|
-
defaultKeys: "alt+up",
|
|
117
|
-
description: "Restore queued messages",
|
|
118
|
-
},
|
|
119
|
-
"app.clipboard.pasteImage": {
|
|
120
|
-
defaultKeys: process.platform === "win32" ? "alt+v" : "ctrl+v",
|
|
121
|
-
description: "Paste image from clipboard",
|
|
122
|
-
},
|
|
123
|
-
// alt+r, not ctrl+r: ctrl+r is reverse history search in every shell, and it
|
|
124
|
-
// was also the session picker's rename key — one chord, two meanings.
|
|
125
|
-
"app.input.voiceTranscribe": {
|
|
126
|
-
defaultKeys: "alt+r",
|
|
127
|
-
description: "Record voice and transcribe into the editor",
|
|
128
|
-
},
|
|
214
|
+
// ── Go — sessions and places ────────────────────────────────────────────
|
|
215
|
+
// These take the screen and hand it back on escape. Each has a slash command
|
|
216
|
+
// that does the same thing, so none of them has to be remembered as a key.
|
|
217
|
+
"app.session.resume": { defaultKeys: "alt+h", description: "Resume a session from history" },
|
|
218
|
+
// Unbound since alt+t became the thinking dial: the letter is worth more to a
|
|
219
|
+
// dial pressed through the day than to a surface `/tree` opens with
|
|
220
|
+
// completion, the same trade `app.model.select` makes for `/model`.
|
|
221
|
+
"app.session.tree": { defaultKeys: [], description: "Open session tree" },
|
|
129
222
|
// The two destructive-ish session moves stay unbound by default. /new
|
|
130
223
|
// replaces the transcript and /fork needs a message picked out of it, so
|
|
131
224
|
// neither wants to be one stray chord away; both remain bindable by hand.
|
|
132
225
|
"app.session.new": { defaultKeys: [], description: "Start a new session" },
|
|
133
226
|
"app.session.fork": { defaultKeys: [], description: "Fork current session" },
|
|
134
|
-
// Navigation is non-destructive, so it gets keys: t for tree, h for history.
|
|
135
|
-
"app.session.tree": { defaultKeys: "alt+t", description: "Open session tree" },
|
|
136
|
-
"app.session.resume": { defaultKeys: "alt+h", description: "Resume a session from history" },
|
|
137
227
|
"app.session.changeDirectory": {
|
|
138
228
|
defaultKeys: "alt+w",
|
|
139
229
|
description: "Change working directory (move to another repo without quitting)",
|
|
@@ -152,26 +242,26 @@ export const KEYBINDINGS = {
|
|
|
152
242
|
},
|
|
153
243
|
"app.settings.open": { defaultKeys: "alt+s", description: "Open settings" },
|
|
154
244
|
"app.hotkeys.open": { defaultKeys: "alt+k", description: "Show keyboard shortcuts" },
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
"app.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
defaultKeys: ["ctrl+right", "alt+right"],
|
|
165
|
-
description: "Unfold tree branch or move down",
|
|
166
|
-
},
|
|
167
|
-
"app.tree.editLabel": {
|
|
168
|
-
defaultKeys: "shift+l",
|
|
169
|
-
description: "Edit tree label",
|
|
245
|
+
// ── Overlays — live only while their surface is open ────────────────────
|
|
246
|
+
// Never a memory burden: each surface prints its own keys on its own hint
|
|
247
|
+
// line, so these are recognised, not recalled. They may reuse a global
|
|
248
|
+
// letter, because the surface captures keys while it is up.
|
|
249
|
+
"app.team.nudge": {
|
|
250
|
+
// Plain letters are safe here: these fire only while the task panel holds
|
|
251
|
+
// focus (team-focus mode), never while typing in the editor.
|
|
252
|
+
defaultKeys: "n",
|
|
253
|
+
description: "Nudge the selected team role (team focus mode)",
|
|
170
254
|
},
|
|
171
|
-
"app.
|
|
172
|
-
defaultKeys: "
|
|
173
|
-
description: "
|
|
255
|
+
"app.team.attach": {
|
|
256
|
+
defaultKeys: "a",
|
|
257
|
+
description: "Attach to the selected team role (team focus mode)",
|
|
174
258
|
},
|
|
259
|
+
// The options pane reads as a horizontal wizard, so the arrows point the way
|
|
260
|
+
// the steps run: → commits the highlighted answer and moves on, ← goes back.
|
|
261
|
+
// On the free-text row they only mean that while the field is empty —
|
|
262
|
+
// otherwise they move the text cursor, and enter is what commits.
|
|
263
|
+
"app.options.next": { defaultKeys: "right", description: "Confirm and advance to the next question" },
|
|
264
|
+
"app.options.back": { defaultKeys: "left", description: "Go back to the previous question" },
|
|
175
265
|
// Session picker verbs. All on alt so the picker's query line keeps the
|
|
176
266
|
// emacs editing keys it used to lose: ctrl+a jumped to the start of the
|
|
177
267
|
// query *and* enabled every model, ctrl+d deleted a character *and* deleted
|
|
@@ -184,14 +274,14 @@ export const KEYBINDINGS = {
|
|
|
184
274
|
defaultKeys: "alt+o",
|
|
185
275
|
description: "Toggle session sort order",
|
|
186
276
|
},
|
|
187
|
-
"app.session.rename": {
|
|
188
|
-
defaultKeys: "alt+r",
|
|
189
|
-
description: "Rename session",
|
|
190
|
-
},
|
|
191
277
|
"app.session.toggleNamedFilter": {
|
|
192
278
|
defaultKeys: "alt+n",
|
|
193
279
|
description: "Toggle named session filter",
|
|
194
280
|
},
|
|
281
|
+
"app.session.rename": {
|
|
282
|
+
defaultKeys: "alt+r",
|
|
283
|
+
description: "Rename session",
|
|
284
|
+
},
|
|
195
285
|
"app.session.delete": {
|
|
196
286
|
defaultKeys: "alt+x",
|
|
197
287
|
description: "Delete session",
|
|
@@ -229,8 +319,29 @@ export const KEYBINDINGS = {
|
|
|
229
319
|
defaultKeys: "alt+down",
|
|
230
320
|
description: "Move model down in order",
|
|
231
321
|
},
|
|
232
|
-
"app.
|
|
233
|
-
|
|
322
|
+
"app.tree.foldOrUp": {
|
|
323
|
+
defaultKeys: ["ctrl+left", "alt+left"],
|
|
324
|
+
description: "Fold tree branch or move up",
|
|
325
|
+
},
|
|
326
|
+
"app.tree.unfoldOrDown": {
|
|
327
|
+
defaultKeys: ["ctrl+right", "alt+right"],
|
|
328
|
+
description: "Unfold tree branch or move down",
|
|
329
|
+
},
|
|
330
|
+
// alt+l ("label") and alt+t ("time"), not shift+l / shift+t. The tree has a
|
|
331
|
+
// search query that takes every printable key, and outside the Kitty protocol
|
|
332
|
+
// shift+<letter> *is* the plain uppercase letter — so typing "TODO" or
|
|
333
|
+
// "Logger" into the query opened the label editor instead of searching. Same
|
|
334
|
+
// rule as every other picker verb: the query owns the letters, the verbs take
|
|
335
|
+
// alt. Global alt+t opens the tree; inside the tree it toggles timestamps,
|
|
336
|
+
// which is unambiguous because the tree captures keys while it is open.
|
|
337
|
+
"app.tree.editLabel": {
|
|
338
|
+
defaultKeys: "alt+l",
|
|
339
|
+
description: "Edit tree label",
|
|
340
|
+
},
|
|
341
|
+
"app.tree.toggleLabelTimestamp": {
|
|
342
|
+
defaultKeys: "alt+t",
|
|
343
|
+
description: "Toggle tree label timestamps",
|
|
344
|
+
},
|
|
234
345
|
// Five lenses in a fixed order, so they are numbered rather than lettered:
|
|
235
346
|
// alt+1..alt+5 needs no mnemonic and collides with nothing. The old set
|
|
236
347
|
// (ctrl+d/t/u/l/a) collided with delete-char, thinking, kill-to-start,
|
|
@@ -300,11 +411,16 @@ const KEYBINDING_NAME_MIGRATIONS = {
|
|
|
300
411
|
selectPageDown: "tui.select.pageDown",
|
|
301
412
|
selectConfirm: "tui.select.confirm",
|
|
302
413
|
selectCancel: "tui.select.cancel",
|
|
414
|
+
// Renamed when each gained a backward half, so that every two-direction dial
|
|
415
|
+
// reads <dial>.cycleForward / <dial>.cycleBackward.
|
|
416
|
+
"app.thinking.cycle": "app.thinking.cycleForward",
|
|
417
|
+
"app.mode.cycle": "app.mode.cycleForward",
|
|
418
|
+
"app.tasks.cycleView": "app.tasks.cycleForward",
|
|
303
419
|
interrupt: "app.interrupt",
|
|
304
420
|
clear: "app.clear",
|
|
305
421
|
exit: "app.exit",
|
|
306
422
|
suspend: "app.suspend",
|
|
307
|
-
cycleThinkingLevel: "app.thinking.
|
|
423
|
+
cycleThinkingLevel: "app.thinking.cycleForward",
|
|
308
424
|
cycleModelForward: "app.model.cycleForward",
|
|
309
425
|
cycleModelBackward: "app.model.cycleBackward",
|
|
310
426
|
selectModel: "app.model.select",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keybindings.js","sourceRoot":"","sources":["../../src/core/keybindings.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Keybinding,\n\ttype KeybindingDefinitions,\n\ttype KeybindingsConfig,\n\ttype KeyId,\n\tTUI_KEYBINDINGS,\n\tKeybindingsManager as TuiKeybindingsManager,\n} from \"@kolisachint/hoocode-tui\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.js\";\n\ninterface AppKeybindings {\n\t\"app.interrupt\": true;\n\t\"app.clear\": true;\n\t\"app.exit\": true;\n\t\"app.suspend\": true;\n\t\"app.thinking.cycle\": true;\n\t\"app.model.cycleForward\": true;\n\t\"app.model.cycleBackward\": true;\n\t\"app.model.select\": true;\n\t\"app.tools.expand\": true;\n\t\"app.view.cycleForward\": true;\n\t\"app.view.cycleBackward\": true;\n\t\"app.thinking.toggle\": true;\n\t\"app.tasks.cycleView\": true;\n\t\"app.team.focus\": true;\n\t\"app.team.nudge\": true;\n\t\"app.team.attach\": true;\n\t\"app.session.toggleNamedFilter\": true;\n\t\"app.editor.external\": true;\n\t\"app.message.followUp\": true;\n\t\"app.message.dequeue\": true;\n\t\"app.clipboard.pasteImage\": true;\n\t\"app.input.voiceTranscribe\": true;\n\t\"app.session.new\": true;\n\t\"app.session.tree\": true;\n\t\"app.session.fork\": true;\n\t\"app.session.resume\": true;\n\t\"app.session.changeDirectory\": true;\n\t\"app.session.color.cycleForward\": true;\n\t\"app.session.color.cycleBackward\": true;\n\t\"app.settings.open\": true;\n\t\"app.hotkeys.open\": true;\n\t\"app.mode.cycle\": true;\n\t\"app.tree.foldOrUp\": true;\n\t\"app.tree.unfoldOrDown\": true;\n\t\"app.tree.editLabel\": true;\n\t\"app.tree.toggleLabelTimestamp\": true;\n\t\"app.session.togglePath\": true;\n\t\"app.session.toggleSort\": true;\n\t\"app.session.rename\": true;\n\t\"app.session.delete\": true;\n\t\"app.session.deleteNoninvasive\": true;\n\t\"app.models.save\": true;\n\t\"app.models.enableAll\": true;\n\t\"app.models.clearAll\": true;\n\t\"app.models.toggleProvider\": true;\n\t\"app.models.reorderUp\": true;\n\t\"app.models.reorderDown\": true;\n\t\"app.tree.filter.default\": true;\n\t\"app.tree.filter.noTools\": true;\n\t\"app.tree.filter.userOnly\": true;\n\t\"app.tree.filter.labeledOnly\": true;\n\t\"app.tree.filter.all\": true;\n\t\"app.tree.filter.cycleForward\": true;\n\t\"app.tree.filter.cycleBackward\": true;\n}\n\nexport type AppKeybinding = keyof AppKeybindings;\n\ndeclare module \"@kolisachint/hoocode-tui\" {\n\tinterface Keybindings extends AppKeybindings {}\n}\n\n/**\n * The cockpit layout.\n *\n * Three rings, and which ring a key belongs to is decided by its modifier:\n *\n * - **ctrl — the view.** What is on screen right now: expand, thinking blocks,\n * task panel, model cycling. Pressed many times a minute, so they sit under\n * the hand and never require a second modifier.\n * - **alt — the cockpit.** What the agent *is* and where it works: model,\n * mode, working directory, settings, sessions. Pressed a few times a session.\n * - **overlays.** Inside a picker, the query line is a text field, so every\n * `ctrl+<letter>` there belongs to the *text* (ctrl+a start of line, ctrl+u\n * kill line, ctrl+w kill word). A picker's own verbs are therefore all on\n * `alt+<letter>`, mnemonic to that picker; the picker captures keys while it\n * is open, so reusing a global letter there is unambiguous.\n *\n * Two rules keep the set learnable: shift reverses whatever the unshifted key\n * does, and no binding takes a key the editor or the terminal already owns\n * (ctrl+a/e/b/f/k/u/w/y/d/l/r/g, ctrl+s XOFF, ctrl+m == enter, ctrl+i == tab).\n *\n * Two constraints from the key parser shape which alt keys are usable at all,\n * and `test/keybinding-layout.test.ts` holds both:\n *\n * - Only `alt+<letter>` and `alt+<digit>` survive a terminal without the Kitty\n * keyboard protocol. `alt+[` and `alt+]` arrive as the CSI and OSC\n * introducers and are not parsed as keys; nor is any shift-modified key, so a\n * `shift+…` default is a Kitty-only convenience on top of an unshifted key\n * that works everywhere (as `shift+ctrl+p` has always been).\n * - Legacy `alt+p` and `alt+n` are also accepted as `alt+up` and `alt+down`\n * (the emacs previous/next aliases), so no scope may bind both halves of\n * either pair.\n */\nexport const KEYBINDINGS = {\n\t...TUI_KEYBINDINGS,\n\t\"app.interrupt\": { defaultKeys: \"escape\", description: \"Cancel or abort\" },\n\t\"app.clear\": { defaultKeys: \"ctrl+c\", description: \"Clear editor\" },\n\t\"app.exit\": { defaultKeys: \"ctrl+d\", description: \"Exit when editor is empty\" },\n\t\"app.suspend\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? [] : \"ctrl+z\",\n\t\tdescription: \"Suspend to background\",\n\t},\n\t\"app.thinking.cycle\": {\n\t\tdefaultKeys: \"shift+tab\",\n\t\tdescription: \"Cycle thinking level\",\n\t},\n\t\"app.model.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Cycle to next model\",\n\t},\n\t\"app.model.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+p\",\n\t\tdescription: \"Cycle to previous model\",\n\t},\n\t// alt+m, not ctrl+l: ctrl+l is \"clear the screen\" in every shell and every\n\t// full-screen terminal app, and users press it reflexively to tidy up. It\n\t// opening a model dialog is the single most surprising key in the old set.\n\t\"app.model.select\": { defaultKeys: \"alt+m\", description: \"Open model selector\" },\n\t\"app.tools.expand\": {\n\t\tdefaultKeys: \"ctrl+o\",\n\t\tdescription: \"Jump to the full view from wherever you are, and back again\",\n\t},\n\t// The view dial pairs with ctrl+o on purpose: same letter, different ring.\n\t// alt+o walks the dial a stop at a time and saves where it lands; ctrl+o\n\t// jumps to the far end and back without moving your home stop. One value\n\t// between them, so there is no second \"expanded\" state to keep in sync.\n\t\"app.view.cycleForward\": {\n\t\tdefaultKeys: \"alt+o\",\n\t\tdescription: \"Cycle tool output view (radar → peek → full)\",\n\t},\n\t\"app.view.cycleBackward\": {\n\t\tdefaultKeys: \"shift+alt+o\",\n\t\tdescription: \"Cycle tool output view backward\",\n\t},\n\t\"app.thinking.toggle\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Toggle thinking blocks\",\n\t},\n\t\"app.tasks.cycleView\": {\n\t\t// ctrl+n is free in the main editor (no emacs next-line binding here).\n\t\tdefaultKeys: \"ctrl+n\",\n\t\tdescription: \"Cycle task panel view (tasks → subagents → teams, skips empty lenses)\",\n\t},\n\t\"app.team.focus\": {\n\t\t// Pairs with ctrl+n (cycle task panel view): alt+n steps INTO the teams\n\t\t// lens and focuses the role roster (--team only). Not shift+ctrl+n —\n\t\t// Windows Terminal intercepts that as its \"new window\" shortcut, the\n\t\t// same trap that moved app.tasks.cycleView off ctrl+shift+t.\n\t\tdefaultKeys: \"alt+n\",\n\t\tdescription: \"Focus the team roster (navigate roles, n nudge, a attach)\",\n\t},\n\t\"app.team.nudge\": {\n\t\t// Plain letters are safe here: these fire only while the task panel holds\n\t\t// focus (team-focus mode), never while typing in the editor.\n\t\tdefaultKeys: \"n\",\n\t\tdescription: \"Nudge the selected team role (team focus mode)\",\n\t},\n\t\"app.team.attach\": {\n\t\tdefaultKeys: \"a\",\n\t\tdescription: \"Attach to the selected team role (team focus mode)\",\n\t},\n\t// alt+e, not ctrl+g: ctrl+g is emacs/readline \"abort\" — the key you hit to\n\t// get out of something. Having it launch $EDITOR inverts that reflex.\n\t\"app.editor.external\": {\n\t\tdefaultKeys: \"alt+e\",\n\t\tdescription: \"Open external editor\",\n\t},\n\t\"app.message.followUp\": {\n\t\tdefaultKeys: \"alt+enter\",\n\t\tdescription: \"Queue follow-up message\",\n\t},\n\t\"app.message.dequeue\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Restore queued messages\",\n\t},\n\t\"app.clipboard.pasteImage\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? \"alt+v\" : \"ctrl+v\",\n\t\tdescription: \"Paste image from clipboard\",\n\t},\n\t// alt+r, not ctrl+r: ctrl+r is reverse history search in every shell, and it\n\t// was also the session picker's rename key — one chord, two meanings.\n\t\"app.input.voiceTranscribe\": {\n\t\tdefaultKeys: \"alt+r\",\n\t\tdescription: \"Record voice and transcribe into the editor\",\n\t},\n\t// The two destructive-ish session moves stay unbound by default. /new\n\t// replaces the transcript and /fork needs a message picked out of it, so\n\t// neither wants to be one stray chord away; both remain bindable by hand.\n\t\"app.session.new\": { defaultKeys: [], description: \"Start a new session\" },\n\t\"app.session.fork\": { defaultKeys: [], description: \"Fork current session\" },\n\t// Navigation is non-destructive, so it gets keys: t for tree, h for history.\n\t\"app.session.tree\": { defaultKeys: \"alt+t\", description: \"Open session tree\" },\n\t\"app.session.resume\": { defaultKeys: \"alt+h\", description: \"Resume a session from history\" },\n\t\"app.session.changeDirectory\": {\n\t\tdefaultKeys: \"alt+w\",\n\t\tdescription: \"Change working directory (move to another repo without quitting)\",\n\t},\n\t// alt+c (\"colour\"), and the tree's filter cycle has the same letter on\n\t// purpose: the tree captures keys while it is open, so the two are never live\n\t// at once, and both mean \"step to the next one\" wherever you press them. The\n\t// backward half needs Kitty, like every other shift-modified default here.\n\t\"app.session.color.cycleForward\": {\n\t\tdefaultKeys: \"alt+c\",\n\t\tdescription: \"Cycle the session chip's color\",\n\t},\n\t\"app.session.color.cycleBackward\": {\n\t\tdefaultKeys: \"shift+alt+c\",\n\t\tdescription: \"Cycle the session chip's color backward\",\n\t},\n\t\"app.settings.open\": { defaultKeys: \"alt+s\", description: \"Open settings\" },\n\t\"app.hotkeys.open\": { defaultKeys: \"alt+k\", description: \"Show keyboard shortcuts\" },\n\t\"app.mode.cycle\": {\n\t\tdefaultKeys: \"alt+g\",\n\t\tdescription: \"Cycle agent mode (ask → plan → build → debug)\",\n\t},\n\t\"app.tree.foldOrUp\": {\n\t\tdefaultKeys: [\"ctrl+left\", \"alt+left\"],\n\t\tdescription: \"Fold tree branch or move up\",\n\t},\n\t\"app.tree.unfoldOrDown\": {\n\t\tdefaultKeys: [\"ctrl+right\", \"alt+right\"],\n\t\tdescription: \"Unfold tree branch or move down\",\n\t},\n\t\"app.tree.editLabel\": {\n\t\tdefaultKeys: \"shift+l\",\n\t\tdescription: \"Edit tree label\",\n\t},\n\t\"app.tree.toggleLabelTimestamp\": {\n\t\tdefaultKeys: \"shift+t\",\n\t\tdescription: \"Toggle tree label timestamps\",\n\t},\n\t// Session picker verbs. All on alt so the picker's query line keeps the\n\t// emacs editing keys it used to lose: ctrl+a jumped to the start of the\n\t// query *and* enabled every model, ctrl+d deleted a character *and* deleted\n\t// the highlighted session.\n\t\"app.session.togglePath\": {\n\t\tdefaultKeys: \"alt+p\",\n\t\tdescription: \"Toggle session path display\",\n\t},\n\t\"app.session.toggleSort\": {\n\t\tdefaultKeys: \"alt+o\",\n\t\tdescription: \"Toggle session sort order\",\n\t},\n\t\"app.session.rename\": {\n\t\tdefaultKeys: \"alt+r\",\n\t\tdescription: \"Rename session\",\n\t},\n\t\"app.session.toggleNamedFilter\": {\n\t\tdefaultKeys: \"alt+n\",\n\t\tdescription: \"Toggle named session filter\",\n\t},\n\t\"app.session.delete\": {\n\t\tdefaultKeys: \"alt+x\",\n\t\tdescription: \"Delete session\",\n\t},\n\t\"app.session.deleteNoninvasive\": {\n\t\tdefaultKeys: \"ctrl+backspace\",\n\t\tdescription: \"Delete session when query is empty\",\n\t},\n\t// Model picker verbs, same rule. ctrl+s in particular was XOFF: on a terminal\n\t// with flow control still on, \"save\" froze the session instead.\n\t\"app.models.save\": {\n\t\tdefaultKeys: \"alt+s\",\n\t\tdescription: \"Save model selection\",\n\t},\n\t\"app.models.enableAll\": {\n\t\tdefaultKeys: \"alt+a\",\n\t\tdescription: \"Enable all models\",\n\t},\n\t\"app.models.clearAll\": {\n\t\tdefaultKeys: \"alt+x\",\n\t\tdescription: \"Clear all models\",\n\t},\n\t// alt+g (\"group\"), not alt+p: on a terminal without the Kitty protocol, alt+p\n\t// arrives as ESC-p, which the key parser also accepts as alt+up — and alt+up\n\t// is this same picker's reorder key.\n\t\"app.models.toggleProvider\": {\n\t\tdefaultKeys: \"alt+g\",\n\t\tdescription: \"Toggle all models for provider\",\n\t},\n\t\"app.models.reorderUp\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Move model up in order\",\n\t},\n\t\"app.models.reorderDown\": {\n\t\tdefaultKeys: \"alt+down\",\n\t\tdescription: \"Move model down in order\",\n\t},\n\t\"app.options.next\": { defaultKeys: \"right\", description: \"Confirm and advance to the next question\" },\n\t\"app.options.back\": { defaultKeys: \"left\", description: \"Go back to the previous question\" },\n\t// Five lenses in a fixed order, so they are numbered rather than lettered:\n\t// alt+1..alt+5 needs no mnemonic and collides with nothing. The old set\n\t// (ctrl+d/t/u/l/a) collided with delete-char, thinking, kill-to-start,\n\t// clear-screen and start-of-line respectively.\n\t\"app.tree.filter.default\": {\n\t\tdefaultKeys: \"alt+1\",\n\t\tdescription: \"Tree filter: default view\",\n\t},\n\t\"app.tree.filter.noTools\": {\n\t\tdefaultKeys: \"alt+2\",\n\t\tdescription: \"Tree filter: hide tool results\",\n\t},\n\t\"app.tree.filter.userOnly\": {\n\t\tdefaultKeys: \"alt+3\",\n\t\tdescription: \"Tree filter: user messages only\",\n\t},\n\t\"app.tree.filter.labeledOnly\": {\n\t\tdefaultKeys: \"alt+4\",\n\t\tdescription: \"Tree filter: labeled entries only\",\n\t},\n\t\"app.tree.filter.all\": {\n\t\tdefaultKeys: \"alt+5\",\n\t\tdescription: \"Tree filter: show all entries\",\n\t},\n\t// alt+c for \"cycle\". Not a bracket pair, tempting as `alt+[` / `alt+]` are:\n\t// without the Kitty protocol those arrive as ESC-[ and ESC-], which are the\n\t// CSI and OSC introducers and so are not parsed as keys at all. The backward\n\t// half needs Kitty, like every other shift-modified default here.\n\t\"app.tree.filter.cycleForward\": {\n\t\tdefaultKeys: \"alt+c\",\n\t\tdescription: \"Tree filter: cycle forward\",\n\t},\n\t\"app.tree.filter.cycleBackward\": {\n\t\tdefaultKeys: \"shift+alt+c\",\n\t\tdescription: \"Tree filter: cycle backward\",\n\t},\n} as const satisfies KeybindingDefinitions;\n\nconst KEYBINDING_NAME_MIGRATIONS = {\n\tcursorUp: \"tui.editor.cursorUp\",\n\tcursorDown: \"tui.editor.cursorDown\",\n\tcursorLeft: \"tui.editor.cursorLeft\",\n\tcursorRight: \"tui.editor.cursorRight\",\n\tcursorWordLeft: \"tui.editor.cursorWordLeft\",\n\tcursorWordRight: \"tui.editor.cursorWordRight\",\n\tcursorLineStart: \"tui.editor.cursorLineStart\",\n\tcursorLineEnd: \"tui.editor.cursorLineEnd\",\n\tjumpForward: \"tui.editor.jumpForward\",\n\tjumpBackward: \"tui.editor.jumpBackward\",\n\tpageUp: \"tui.editor.pageUp\",\n\tpageDown: \"tui.editor.pageDown\",\n\tdeleteCharBackward: \"tui.editor.deleteCharBackward\",\n\tdeleteCharForward: \"tui.editor.deleteCharForward\",\n\tdeleteWordBackward: \"tui.editor.deleteWordBackward\",\n\tdeleteWordForward: \"tui.editor.deleteWordForward\",\n\tdeleteToLineStart: \"tui.editor.deleteToLineStart\",\n\tdeleteToLineEnd: \"tui.editor.deleteToLineEnd\",\n\tyank: \"tui.editor.yank\",\n\tyankPop: \"tui.editor.yankPop\",\n\tundo: \"tui.editor.undo\",\n\tnewLine: \"tui.input.newLine\",\n\tsubmit: \"tui.input.submit\",\n\ttab: \"tui.input.tab\",\n\tcopy: \"tui.input.copy\",\n\tselectUp: \"tui.select.up\",\n\tselectDown: \"tui.select.down\",\n\tselectPageUp: \"tui.select.pageUp\",\n\tselectPageDown: \"tui.select.pageDown\",\n\tselectConfirm: \"tui.select.confirm\",\n\tselectCancel: \"tui.select.cancel\",\n\tinterrupt: \"app.interrupt\",\n\tclear: \"app.clear\",\n\texit: \"app.exit\",\n\tsuspend: \"app.suspend\",\n\tcycleThinkingLevel: \"app.thinking.cycle\",\n\tcycleModelForward: \"app.model.cycleForward\",\n\tcycleModelBackward: \"app.model.cycleBackward\",\n\tselectModel: \"app.model.select\",\n\texpandTools: \"app.tools.expand\",\n\ttoggleThinking: \"app.thinking.toggle\",\n\ttoggleSessionNamedFilter: \"app.session.toggleNamedFilter\",\n\texternalEditor: \"app.editor.external\",\n\tfollowUp: \"app.message.followUp\",\n\tdequeue: \"app.message.dequeue\",\n\tpasteImage: \"app.clipboard.pasteImage\",\n\tnewSession: \"app.session.new\",\n\ttree: \"app.session.tree\",\n\tfork: \"app.session.fork\",\n\tresume: \"app.session.resume\",\n\ttreeFoldOrUp: \"app.tree.foldOrUp\",\n\ttreeUnfoldOrDown: \"app.tree.unfoldOrDown\",\n\ttreeEditLabel: \"app.tree.editLabel\",\n\ttreeToggleLabelTimestamp: \"app.tree.toggleLabelTimestamp\",\n\ttoggleSessionPath: \"app.session.togglePath\",\n\ttoggleSessionSort: \"app.session.toggleSort\",\n\trenameSession: \"app.session.rename\",\n\tdeleteSession: \"app.session.delete\",\n\tdeleteSessionNoninvasive: \"app.session.deleteNoninvasive\",\n} as const satisfies Record<string, Keybinding>;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isLegacyKeybindingName(key: string): key is keyof typeof KEYBINDING_NAME_MIGRATIONS {\n\treturn key in KEYBINDING_NAME_MIGRATIONS;\n}\n\nfunction toKeybindingsConfig(value: unknown): KeybindingsConfig {\n\tif (!isRecord(value)) return {};\n\n\tconst config: KeybindingsConfig = {};\n\tfor (const [key, binding] of Object.entries(value)) {\n\t\tif (typeof binding === \"string\") {\n\t\t\tconfig[key] = binding as KeyId;\n\t\t\tcontinue;\n\t\t}\n\t\tif (Array.isArray(binding) && binding.every((entry) => typeof entry === \"string\")) {\n\t\t\tconfig[key] = binding as KeyId[];\n\t\t}\n\t}\n\treturn config;\n}\n\nexport function migrateKeybindingsConfig(rawConfig: Record<string, unknown>): {\n\tconfig: Record<string, unknown>;\n\tmigrated: boolean;\n} {\n\tconst config: Record<string, unknown> = {};\n\tlet migrated = false;\n\n\tfor (const [key, value] of Object.entries(rawConfig)) {\n\t\tconst nextKey = isLegacyKeybindingName(key) ? KEYBINDING_NAME_MIGRATIONS[key] : key;\n\t\tif (nextKey !== key) {\n\t\t\tmigrated = true;\n\t\t}\n\t\tif (key !== nextKey && Object.hasOwn(rawConfig, nextKey)) {\n\t\t\tmigrated = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconfig[nextKey] = value;\n\t}\n\n\treturn { config: orderKeybindingsConfig(config), migrated };\n}\n\nfunction orderKeybindingsConfig(config: Record<string, unknown>): Record<string, unknown> {\n\tconst ordered: Record<string, unknown> = {};\n\tfor (const keybinding of Object.keys(KEYBINDINGS)) {\n\t\tif (Object.hasOwn(config, keybinding)) {\n\t\t\tordered[keybinding] = config[keybinding];\n\t\t}\n\t}\n\n\tconst extras = Object.keys(config)\n\t\t.filter((key) => !Object.hasOwn(ordered, key))\n\t\t.sort();\n\tfor (const key of extras) {\n\t\tordered[key] = config[key];\n\t}\n\n\treturn ordered;\n}\n\nfunction loadRawConfig(path: string): Record<string, unknown> | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\")) as unknown;\n\t\treturn isRecord(parsed) ? parsed : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport class KeybindingsManager extends TuiKeybindingsManager {\n\tprivate configPath: string | undefined;\n\n\tconstructor(userBindings: KeybindingsConfig = {}, configPath?: string) {\n\t\tsuper(KEYBINDINGS, userBindings);\n\t\tthis.configPath = configPath;\n\t}\n\n\tstatic create(agentDir: string = getAgentDir()): KeybindingsManager {\n\t\tconst configPath = join(agentDir, \"keybindings.json\");\n\t\tconst userBindings = KeybindingsManager.loadFromFile(configPath);\n\t\treturn new KeybindingsManager(userBindings, configPath);\n\t}\n\n\treload(): void {\n\t\tif (!this.configPath) return;\n\t\tthis.setUserBindings(KeybindingsManager.loadFromFile(this.configPath));\n\t}\n\n\tgetEffectiveConfig(): KeybindingsConfig {\n\t\treturn this.getResolvedBindings();\n\t}\n\n\tprivate static loadFromFile(path: string): KeybindingsConfig {\n\t\tconst rawConfig = loadRawConfig(path);\n\t\tif (!rawConfig) return {};\n\t\treturn toKeybindingsConfig(migrateKeybindingsConfig(rawConfig).config);\n\t}\n}\n\nexport type { KeyId, KeybindingsConfig };\n"]}
|
|
1
|
+
{"version":3,"file":"keybindings.js","sourceRoot":"","sources":["../../src/core/keybindings.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Keybinding,\n\ttype KeybindingDefinitions,\n\ttype KeybindingsConfig,\n\ttype KeyId,\n\tTUI_KEYBINDINGS,\n\tKeybindingsManager as TuiKeybindingsManager,\n} from \"@kolisachint/hoocode-tui\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.js\";\n\ninterface AppKeybindings {\n\t\"app.interrupt\": true;\n\t\"app.clear\": true;\n\t\"app.exit\": true;\n\t\"app.suspend\": true;\n\t\"app.thinking.cycleForward\": true;\n\t\"app.thinking.cycleBackward\": true;\n\t\"app.model.cycleForward\": true;\n\t\"app.model.cycleBackward\": true;\n\t\"app.model.select\": true;\n\t\"app.tools.expand\": true;\n\t\"app.view.cycleForward\": true;\n\t\"app.view.cycleBackward\": true;\n\t\"app.thinking.toggle\": true;\n\t\"app.tasks.cycleForward\": true;\n\t\"app.tasks.cycleBackward\": true;\n\t\"app.team.focus\": true;\n\t\"app.team.nudge\": true;\n\t\"app.team.attach\": true;\n\t\"app.session.toggleNamedFilter\": true;\n\t\"app.editor.external\": true;\n\t\"app.message.followUp\": true;\n\t\"app.message.dequeue\": true;\n\t\"app.clipboard.pasteImage\": true;\n\t\"app.input.voiceTranscribe\": true;\n\t\"app.session.new\": true;\n\t\"app.session.tree\": true;\n\t\"app.session.fork\": true;\n\t\"app.session.resume\": true;\n\t\"app.session.changeDirectory\": true;\n\t\"app.session.color.cycleForward\": true;\n\t\"app.session.color.cycleBackward\": true;\n\t\"app.settings.open\": true;\n\t\"app.hotkeys.open\": true;\n\t\"app.mode.cycleForward\": true;\n\t\"app.mode.cycleBackward\": true;\n\t\"app.options.next\": true;\n\t\"app.options.back\": true;\n\t\"app.tree.foldOrUp\": true;\n\t\"app.tree.unfoldOrDown\": true;\n\t\"app.tree.editLabel\": true;\n\t\"app.tree.toggleLabelTimestamp\": true;\n\t\"app.session.togglePath\": true;\n\t\"app.session.toggleSort\": true;\n\t\"app.session.rename\": true;\n\t\"app.session.delete\": true;\n\t\"app.session.deleteNoninvasive\": true;\n\t\"app.models.save\": true;\n\t\"app.models.enableAll\": true;\n\t\"app.models.clearAll\": true;\n\t\"app.models.toggleProvider\": true;\n\t\"app.models.reorderUp\": true;\n\t\"app.models.reorderDown\": true;\n\t\"app.tree.filter.default\": true;\n\t\"app.tree.filter.noTools\": true;\n\t\"app.tree.filter.userOnly\": true;\n\t\"app.tree.filter.labeledOnly\": true;\n\t\"app.tree.filter.all\": true;\n\t\"app.tree.filter.cycleForward\": true;\n\t\"app.tree.filter.cycleBackward\": true;\n}\n\nexport type AppKeybinding = keyof AppKeybindings;\n\ndeclare module \"@kolisachint/hoocode-tui\" {\n\tinterface Keybindings extends AppKeybindings {}\n}\n\n/**\n * The keyboard map.\n *\n * ## Why it is grouped the way it is\n *\n * There are around sixty bindings here. Nobody holds sixty of anything, and the\n * usual answer — sort them by mechanism, so everything that cycles sits together\n * — makes a list that is tidy on the page and useless at the keyboard. \"It\n * cycles\" is a fact about the widget. It is not what anyone is thinking when\n * they reach for the key.\n *\n * So the grouping is by **intention**: the five things a person is ever doing\n * here, in the order the loop runs.\n *\n * 1. **Flow** — get out, get back. esc, ctrl+c, ctrl+d, ctrl+z.\n * 2. **Compose** — the message in your hands. alt+e, alt+r, alt+enter, alt+↑.\n * 3. **Steer** — what the agent is before it runs. alt+a, alt+m, alt+t.\n * 4. **Read** — what you see of what it did. alt+o, alt+l, ctrl+o, ctrl+t.\n * 5. **Go** — sessions and places. alt+h, alt+w, alt+s, alt+k, alt+c.\n *\n * Five groups, none larger than five, which is the size a person can actually\n * hold. Two of them cost nothing to learn: **Flow** is the set every terminal\n * program already taught you, and the **overlays** (pickers, the tree, the\n * options pane) print their own keys on their own hint lines — recognised, never\n * recalled. That leaves three groups to genuinely know.\n *\n * The declaration order below *is* the grouping, and it is not cosmetic:\n * `orderKeybindingsConfig` writes `keybindings.json` in this order, so the file\n * a user opens to rebind something is grouped the same way this one is.\n *\n * ## What the chord tells you\n *\n * The modifier says what kind of thing will happen; the letter says to what.\n *\n * - **alt+<letter> sets a value.** Nothing takes the screen, nothing loses\n * focus, you keep typing. Six of these are dials — an ordered set of stops\n * with the current one painted where you can see it — and `shift+alt+<letter>`\n * always steps back:\n *\n * **a**gent mode · **m**odel · **t**hinking · tool **o**utput\n * task **l**ist · session **c**olour\n *\n * Reversibility is the point. A control you can undo invites you to try it; a\n * one-way control makes you stop and think first, which is the wrong tax on a\n * key you press all day.\n *\n * - **ctrl+<letter> acts on what is drawn right now**, and shares its letter\n * with the alt key for the same subject. `alt+o` sets how much tool output\n * there ever is, `ctrl+o` jumps to all of it and back. `alt+t` sets how much\n * thinking there ever is, `ctrl+t` shows or hides what you have. Two subjects,\n * two letters, four keys — half of what four unrelated chords would cost.\n *\n * - **A slash command chooses a stop outright.** `/mode`, `/model`, `/color`,\n * `/tree`. The key steps, the command picks; that is why `app.model.select`\n * and `app.session.tree` ship unbound, having given their letters to dials.\n *\n * One letter in the whole set names nothing: `alt+n` for the team roster. It\n * survives because the task panel prints it in its own header, so it is read off\n * the screen rather than remembered — which is the fallback for anything that\n * cannot earn a mnemonic.\n *\n * ## The constraints that shaped it\n *\n * - No binding takes a key the editor or the terminal already owns\n * (ctrl+a/e/b/f/k/u/w/y/d/l/r/g, ctrl+s XOFF, ctrl+m == enter, ctrl+i == tab).\n * - Inside an overlay the query line is a text field, so every `ctrl+<letter>`\n * there belongs to the *text*. Overlay verbs are all on `alt+<letter>`.\n * - Only `alt+<letter>` and `alt+<digit>` survive a terminal without the Kitty\n * keyboard protocol; `alt+[` and `alt+]` arrive as the CSI and OSC introducers.\n * - `shift+<non-letter>` needs Kitty too, so a `shift+…` default is a\n * convenience on top of a key that works everywhere. `shift+<letter>` is\n * banned outright: without Kitty it *is* the uppercase letter, which no scope\n * with a query line can tell from typing.\n * - Legacy `alt+p` and `alt+n` are also read as `alt+up` and `alt+down`, so no\n * scope may bind both halves of either pair.\n *\n * `test/keybinding-layout.test.ts` holds all of it, including that every\n * binding belongs to exactly one family.\n */\nexport const KEYBINDINGS = {\n\t...TUI_KEYBINDINGS,\n\n\t// ── Flow — getting out, getting back ────────────────────────────────────\n\t// Every terminal program you already use binds these, so they cost nothing to\n\t// learn and must never move: whatever else is misconfigured, you can still\n\t// stop the agent, clear the line, and leave.\n\t\"app.interrupt\": { defaultKeys: \"escape\", description: \"Cancel or abort\" },\n\t\"app.clear\": { defaultKeys: \"ctrl+c\", description: \"Clear editor\" },\n\t\"app.exit\": { defaultKeys: \"ctrl+d\", description: \"Exit when editor is empty\" },\n\t\"app.suspend\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? [] : \"ctrl+z\",\n\t\tdescription: \"Suspend to background\",\n\t},\n\n\t// ── Compose — the message in your hands ─────────────────────────────────\n\t// Everything here acts on the text you are writing. Highest-frequency group,\n\t// and the one your hands are already on.\n\t// alt+e, not ctrl+g: ctrl+g is emacs/readline \"abort\" — the key you hit to\n\t// get out of something. Having it launch $EDITOR inverts that reflex.\n\t\"app.editor.external\": {\n\t\tdefaultKeys: \"alt+e\",\n\t\tdescription: \"Open external editor\",\n\t},\n\t// alt+r, not ctrl+r: ctrl+r is reverse history search in every shell, and it\n\t// was also the session picker's rename key — one chord, two meanings.\n\t\"app.input.voiceTranscribe\": {\n\t\tdefaultKeys: \"alt+r\",\n\t\tdescription: \"Record voice and transcribe into the editor\",\n\t},\n\t\"app.clipboard.pasteImage\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? \"alt+v\" : \"ctrl+v\",\n\t\tdescription: \"Paste image from clipboard\",\n\t},\n\t\"app.message.followUp\": {\n\t\tdefaultKeys: \"alt+enter\",\n\t\tdescription: \"Queue follow-up message\",\n\t},\n\t\"app.message.dequeue\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Restore queued messages\",\n\t},\n\n\t// ── Steer — what the agent is before it runs ────────────────────────────\n\t// The only three keys that change what happens next, and the only three that\n\t// cost anything: latency, money, behaviour. The footer shows all three, which\n\t// is why they are worth one deliberate chunk of memory.\n\t// alt+a for \"agent mode\", and it is the dial the footer leads with. Not alt+m:\n\t// mode and model are one letter apart and the model has the better claim on\n\t// m, so this one takes the letter of what it selects — the agent's stance.\n\t\"app.mode.cycleForward\": {\n\t\tdefaultKeys: \"alt+a\",\n\t\tdescription: \"Cycle agent mode (ask → plan → build → debug)\",\n\t},\n\t\"app.mode.cycleBackward\": {\n\t\tdefaultKeys: \"shift+alt+a\",\n\t\tdescription: \"Cycle agent mode backward\",\n\t},\n\t// alt+m, not ctrl+p: the model is a cockpit dial — it is what the agent *is*,\n\t// not what is on screen — and p named nothing. The letter names the dial now,\n\t// which is the whole rule for the six of them. `/model` opens the picker that\n\t// used to be on this key, exactly as `/color` backs alt+c.\n\t\"app.model.cycleForward\": {\n\t\tdefaultKeys: \"alt+m\",\n\t\tdescription: \"Cycle to next model\",\n\t},\n\t\"app.model.cycleBackward\": {\n\t\tdefaultKeys: \"shift+alt+m\",\n\t\tdescription: \"Cycle to previous model\",\n\t},\n\t// Unbound by default: a dial's key steps it, and its slash command chooses.\n\t// alt+m stepping the model is worth more than alt+m opening a list of them,\n\t// and `/model` is one keystroke further with completion on the name.\n\t\"app.model.select\": { defaultKeys: [], description: \"Open model selector\" },\n\t// alt+t pairs with ctrl+t the way alt+o pairs with ctrl+o: same letter, same\n\t// subject, ctrl acting on what is drawn right now and alt on how much there\n\t// ever is. shift+tab stays as a second key rather than the first — it is the\n\t// cycle key every terminal agent has taught, and with no slash command for\n\t// the thinking level it is the only way to reach this dial on a terminal\n\t// that eats alt. Hints and /hotkeys name alt+t, which is the taught key.\n\t\"app.thinking.cycleForward\": {\n\t\tdefaultKeys: [\"alt+t\", \"shift+tab\"],\n\t\tdescription: \"Cycle thinking level (off → … → high)\",\n\t},\n\t\"app.thinking.cycleBackward\": {\n\t\tdefaultKeys: \"shift+alt+t\",\n\t\tdescription: \"Cycle thinking level backward\",\n\t},\n\n\t// ── Read — what you see of what it did ──────────────────────────────────\n\t// Free and reversible, every one of them: nothing here touches the work, only\n\t// the window onto it. Press again or add shift and you are back where you\n\t// were, which is what makes poking at them safe.\n\t// The view dial pairs with ctrl+o on purpose: same letter, different ring.\n\t// alt+o walks the dial a stop at a time and saves where it lands; ctrl+o\n\t// jumps to the far end and back without moving your home stop. One value\n\t// between them, so there is no second \"expanded\" state to keep in sync.\n\t\"app.view.cycleForward\": {\n\t\tdefaultKeys: \"alt+o\",\n\t\tdescription: \"Cycle tool output view (radar → peek → full)\",\n\t},\n\t\"app.view.cycleBackward\": {\n\t\tdefaultKeys: \"shift+alt+o\",\n\t\tdescription: \"Cycle tool output view backward\",\n\t},\n\t\"app.tools.expand\": {\n\t\tdefaultKeys: \"ctrl+o\",\n\t\tdescription: \"Jump to the full view from wherever you are, and back again\",\n\t},\n\t\"app.thinking.toggle\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Toggle thinking blocks\",\n\t},\n\t// alt+l for the task *ledger* — the pane's own name for itself. It was\n\t// ctrl+n, which named nothing and was the last dial off the alt ring; moving\n\t// it also gives the lens the reverse it could never have on ctrl, where\n\t// shift+ctrl+n is Windows Terminal's \"new window\". ctrl+n is free now, so an\n\t// emacs config can take it for cursorDown without colliding.\n\t\"app.tasks.cycleForward\": {\n\t\tdefaultKeys: \"alt+l\",\n\t\tdescription: \"Cycle task panel view (tasks → subagents → teams, skips empty lenses)\",\n\t},\n\t\"app.tasks.cycleBackward\": {\n\t\tdefaultKeys: \"shift+alt+l\",\n\t\tdescription: \"Cycle task panel view backward\",\n\t},\n\t// The one letter in the set that names nothing. It survives on the fallback\n\t// every unmemorable key needs: the task panel prints it in its own header\n\t// when the teams lens is up, so it is read off the screen rather than\n\t// remembered. alt+n steps INTO that lens and focuses the roster (--team only).\n\t\"app.team.focus\": {\n\t\tdefaultKeys: \"alt+n\",\n\t\tdescription: \"Focus the team roster (navigate roles, n nudge, a attach)\",\n\t},\n\n\t// ── Go — sessions and places ────────────────────────────────────────────\n\t// These take the screen and hand it back on escape. Each has a slash command\n\t// that does the same thing, so none of them has to be remembered as a key.\n\t\"app.session.resume\": { defaultKeys: \"alt+h\", description: \"Resume a session from history\" },\n\t// Unbound since alt+t became the thinking dial: the letter is worth more to a\n\t// dial pressed through the day than to a surface `/tree` opens with\n\t// completion, the same trade `app.model.select` makes for `/model`.\n\t\"app.session.tree\": { defaultKeys: [], description: \"Open session tree\" },\n\t// The two destructive-ish session moves stay unbound by default. /new\n\t// replaces the transcript and /fork needs a message picked out of it, so\n\t// neither wants to be one stray chord away; both remain bindable by hand.\n\t\"app.session.new\": { defaultKeys: [], description: \"Start a new session\" },\n\t\"app.session.fork\": { defaultKeys: [], description: \"Fork current session\" },\n\t\"app.session.changeDirectory\": {\n\t\tdefaultKeys: \"alt+w\",\n\t\tdescription: \"Change working directory (move to another repo without quitting)\",\n\t},\n\t// alt+c (\"colour\"), and the tree's filter cycle has the same letter on\n\t// purpose: the tree captures keys while it is open, so the two are never live\n\t// at once, and both mean \"step to the next one\" wherever you press them. The\n\t// backward half needs Kitty, like every other shift-modified default here.\n\t\"app.session.color.cycleForward\": {\n\t\tdefaultKeys: \"alt+c\",\n\t\tdescription: \"Cycle the session chip's color\",\n\t},\n\t\"app.session.color.cycleBackward\": {\n\t\tdefaultKeys: \"shift+alt+c\",\n\t\tdescription: \"Cycle the session chip's color backward\",\n\t},\n\t\"app.settings.open\": { defaultKeys: \"alt+s\", description: \"Open settings\" },\n\t\"app.hotkeys.open\": { defaultKeys: \"alt+k\", description: \"Show keyboard shortcuts\" },\n\n\t// ── Overlays — live only while their surface is open ────────────────────\n\t// Never a memory burden: each surface prints its own keys on its own hint\n\t// line, so these are recognised, not recalled. They may reuse a global\n\t// letter, because the surface captures keys while it is up.\n\t\"app.team.nudge\": {\n\t\t// Plain letters are safe here: these fire only while the task panel holds\n\t\t// focus (team-focus mode), never while typing in the editor.\n\t\tdefaultKeys: \"n\",\n\t\tdescription: \"Nudge the selected team role (team focus mode)\",\n\t},\n\t\"app.team.attach\": {\n\t\tdefaultKeys: \"a\",\n\t\tdescription: \"Attach to the selected team role (team focus mode)\",\n\t},\n\t// The options pane reads as a horizontal wizard, so the arrows point the way\n\t// the steps run: → commits the highlighted answer and moves on, ← goes back.\n\t// On the free-text row they only mean that while the field is empty —\n\t// otherwise they move the text cursor, and enter is what commits.\n\t\"app.options.next\": { defaultKeys: \"right\", description: \"Confirm and advance to the next question\" },\n\t\"app.options.back\": { defaultKeys: \"left\", description: \"Go back to the previous question\" },\n\t// Session picker verbs. All on alt so the picker's query line keeps the\n\t// emacs editing keys it used to lose: ctrl+a jumped to the start of the\n\t// query *and* enabled every model, ctrl+d deleted a character *and* deleted\n\t// the highlighted session.\n\t\"app.session.togglePath\": {\n\t\tdefaultKeys: \"alt+p\",\n\t\tdescription: \"Toggle session path display\",\n\t},\n\t\"app.session.toggleSort\": {\n\t\tdefaultKeys: \"alt+o\",\n\t\tdescription: \"Toggle session sort order\",\n\t},\n\t\"app.session.toggleNamedFilter\": {\n\t\tdefaultKeys: \"alt+n\",\n\t\tdescription: \"Toggle named session filter\",\n\t},\n\t\"app.session.rename\": {\n\t\tdefaultKeys: \"alt+r\",\n\t\tdescription: \"Rename session\",\n\t},\n\t\"app.session.delete\": {\n\t\tdefaultKeys: \"alt+x\",\n\t\tdescription: \"Delete session\",\n\t},\n\t\"app.session.deleteNoninvasive\": {\n\t\tdefaultKeys: \"ctrl+backspace\",\n\t\tdescription: \"Delete session when query is empty\",\n\t},\n\t// Model picker verbs, same rule. ctrl+s in particular was XOFF: on a terminal\n\t// with flow control still on, \"save\" froze the session instead.\n\t\"app.models.save\": {\n\t\tdefaultKeys: \"alt+s\",\n\t\tdescription: \"Save model selection\",\n\t},\n\t\"app.models.enableAll\": {\n\t\tdefaultKeys: \"alt+a\",\n\t\tdescription: \"Enable all models\",\n\t},\n\t\"app.models.clearAll\": {\n\t\tdefaultKeys: \"alt+x\",\n\t\tdescription: \"Clear all models\",\n\t},\n\t// alt+g (\"group\"), not alt+p: on a terminal without the Kitty protocol, alt+p\n\t// arrives as ESC-p, which the key parser also accepts as alt+up — and alt+up\n\t// is this same picker's reorder key.\n\t\"app.models.toggleProvider\": {\n\t\tdefaultKeys: \"alt+g\",\n\t\tdescription: \"Toggle all models for provider\",\n\t},\n\t\"app.models.reorderUp\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Move model up in order\",\n\t},\n\t\"app.models.reorderDown\": {\n\t\tdefaultKeys: \"alt+down\",\n\t\tdescription: \"Move model down in order\",\n\t},\n\t\"app.tree.foldOrUp\": {\n\t\tdefaultKeys: [\"ctrl+left\", \"alt+left\"],\n\t\tdescription: \"Fold tree branch or move up\",\n\t},\n\t\"app.tree.unfoldOrDown\": {\n\t\tdefaultKeys: [\"ctrl+right\", \"alt+right\"],\n\t\tdescription: \"Unfold tree branch or move down\",\n\t},\n\t// alt+l (\"label\") and alt+t (\"time\"), not shift+l / shift+t. The tree has a\n\t// search query that takes every printable key, and outside the Kitty protocol\n\t// shift+<letter> *is* the plain uppercase letter — so typing \"TODO\" or\n\t// \"Logger\" into the query opened the label editor instead of searching. Same\n\t// rule as every other picker verb: the query owns the letters, the verbs take\n\t// alt. Global alt+t opens the tree; inside the tree it toggles timestamps,\n\t// which is unambiguous because the tree captures keys while it is open.\n\t\"app.tree.editLabel\": {\n\t\tdefaultKeys: \"alt+l\",\n\t\tdescription: \"Edit tree label\",\n\t},\n\t\"app.tree.toggleLabelTimestamp\": {\n\t\tdefaultKeys: \"alt+t\",\n\t\tdescription: \"Toggle tree label timestamps\",\n\t},\n\t// Five lenses in a fixed order, so they are numbered rather than lettered:\n\t// alt+1..alt+5 needs no mnemonic and collides with nothing. The old set\n\t// (ctrl+d/t/u/l/a) collided with delete-char, thinking, kill-to-start,\n\t// clear-screen and start-of-line respectively.\n\t\"app.tree.filter.default\": {\n\t\tdefaultKeys: \"alt+1\",\n\t\tdescription: \"Tree filter: default view\",\n\t},\n\t\"app.tree.filter.noTools\": {\n\t\tdefaultKeys: \"alt+2\",\n\t\tdescription: \"Tree filter: hide tool results\",\n\t},\n\t\"app.tree.filter.userOnly\": {\n\t\tdefaultKeys: \"alt+3\",\n\t\tdescription: \"Tree filter: user messages only\",\n\t},\n\t\"app.tree.filter.labeledOnly\": {\n\t\tdefaultKeys: \"alt+4\",\n\t\tdescription: \"Tree filter: labeled entries only\",\n\t},\n\t\"app.tree.filter.all\": {\n\t\tdefaultKeys: \"alt+5\",\n\t\tdescription: \"Tree filter: show all entries\",\n\t},\n\t// alt+c for \"cycle\". Not a bracket pair, tempting as `alt+[` / `alt+]` are:\n\t// without the Kitty protocol those arrive as ESC-[ and ESC-], which are the\n\t// CSI and OSC introducers and so are not parsed as keys at all. The backward\n\t// half needs Kitty, like every other shift-modified default here.\n\t\"app.tree.filter.cycleForward\": {\n\t\tdefaultKeys: \"alt+c\",\n\t\tdescription: \"Tree filter: cycle forward\",\n\t},\n\t\"app.tree.filter.cycleBackward\": {\n\t\tdefaultKeys: \"shift+alt+c\",\n\t\tdescription: \"Tree filter: cycle backward\",\n\t},\n} as const satisfies KeybindingDefinitions;\n\nconst KEYBINDING_NAME_MIGRATIONS = {\n\tcursorUp: \"tui.editor.cursorUp\",\n\tcursorDown: \"tui.editor.cursorDown\",\n\tcursorLeft: \"tui.editor.cursorLeft\",\n\tcursorRight: \"tui.editor.cursorRight\",\n\tcursorWordLeft: \"tui.editor.cursorWordLeft\",\n\tcursorWordRight: \"tui.editor.cursorWordRight\",\n\tcursorLineStart: \"tui.editor.cursorLineStart\",\n\tcursorLineEnd: \"tui.editor.cursorLineEnd\",\n\tjumpForward: \"tui.editor.jumpForward\",\n\tjumpBackward: \"tui.editor.jumpBackward\",\n\tpageUp: \"tui.editor.pageUp\",\n\tpageDown: \"tui.editor.pageDown\",\n\tdeleteCharBackward: \"tui.editor.deleteCharBackward\",\n\tdeleteCharForward: \"tui.editor.deleteCharForward\",\n\tdeleteWordBackward: \"tui.editor.deleteWordBackward\",\n\tdeleteWordForward: \"tui.editor.deleteWordForward\",\n\tdeleteToLineStart: \"tui.editor.deleteToLineStart\",\n\tdeleteToLineEnd: \"tui.editor.deleteToLineEnd\",\n\tyank: \"tui.editor.yank\",\n\tyankPop: \"tui.editor.yankPop\",\n\tundo: \"tui.editor.undo\",\n\tnewLine: \"tui.input.newLine\",\n\tsubmit: \"tui.input.submit\",\n\ttab: \"tui.input.tab\",\n\tcopy: \"tui.input.copy\",\n\tselectUp: \"tui.select.up\",\n\tselectDown: \"tui.select.down\",\n\tselectPageUp: \"tui.select.pageUp\",\n\tselectPageDown: \"tui.select.pageDown\",\n\tselectConfirm: \"tui.select.confirm\",\n\tselectCancel: \"tui.select.cancel\",\n\t// Renamed when each gained a backward half, so that every two-direction dial\n\t// reads <dial>.cycleForward / <dial>.cycleBackward.\n\t\"app.thinking.cycle\": \"app.thinking.cycleForward\",\n\t\"app.mode.cycle\": \"app.mode.cycleForward\",\n\t\"app.tasks.cycleView\": \"app.tasks.cycleForward\",\n\tinterrupt: \"app.interrupt\",\n\tclear: \"app.clear\",\n\texit: \"app.exit\",\n\tsuspend: \"app.suspend\",\n\tcycleThinkingLevel: \"app.thinking.cycleForward\",\n\tcycleModelForward: \"app.model.cycleForward\",\n\tcycleModelBackward: \"app.model.cycleBackward\",\n\tselectModel: \"app.model.select\",\n\texpandTools: \"app.tools.expand\",\n\ttoggleThinking: \"app.thinking.toggle\",\n\ttoggleSessionNamedFilter: \"app.session.toggleNamedFilter\",\n\texternalEditor: \"app.editor.external\",\n\tfollowUp: \"app.message.followUp\",\n\tdequeue: \"app.message.dequeue\",\n\tpasteImage: \"app.clipboard.pasteImage\",\n\tnewSession: \"app.session.new\",\n\ttree: \"app.session.tree\",\n\tfork: \"app.session.fork\",\n\tresume: \"app.session.resume\",\n\ttreeFoldOrUp: \"app.tree.foldOrUp\",\n\ttreeUnfoldOrDown: \"app.tree.unfoldOrDown\",\n\ttreeEditLabel: \"app.tree.editLabel\",\n\ttreeToggleLabelTimestamp: \"app.tree.toggleLabelTimestamp\",\n\ttoggleSessionPath: \"app.session.togglePath\",\n\ttoggleSessionSort: \"app.session.toggleSort\",\n\trenameSession: \"app.session.rename\",\n\tdeleteSession: \"app.session.delete\",\n\tdeleteSessionNoninvasive: \"app.session.deleteNoninvasive\",\n} as const satisfies Record<string, Keybinding>;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isLegacyKeybindingName(key: string): key is keyof typeof KEYBINDING_NAME_MIGRATIONS {\n\treturn key in KEYBINDING_NAME_MIGRATIONS;\n}\n\nfunction toKeybindingsConfig(value: unknown): KeybindingsConfig {\n\tif (!isRecord(value)) return {};\n\n\tconst config: KeybindingsConfig = {};\n\tfor (const [key, binding] of Object.entries(value)) {\n\t\tif (typeof binding === \"string\") {\n\t\t\tconfig[key] = binding as KeyId;\n\t\t\tcontinue;\n\t\t}\n\t\tif (Array.isArray(binding) && binding.every((entry) => typeof entry === \"string\")) {\n\t\t\tconfig[key] = binding as KeyId[];\n\t\t}\n\t}\n\treturn config;\n}\n\nexport function migrateKeybindingsConfig(rawConfig: Record<string, unknown>): {\n\tconfig: Record<string, unknown>;\n\tmigrated: boolean;\n} {\n\tconst config: Record<string, unknown> = {};\n\tlet migrated = false;\n\n\tfor (const [key, value] of Object.entries(rawConfig)) {\n\t\tconst nextKey = isLegacyKeybindingName(key) ? KEYBINDING_NAME_MIGRATIONS[key] : key;\n\t\tif (nextKey !== key) {\n\t\t\tmigrated = true;\n\t\t}\n\t\tif (key !== nextKey && Object.hasOwn(rawConfig, nextKey)) {\n\t\t\tmigrated = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconfig[nextKey] = value;\n\t}\n\n\treturn { config: orderKeybindingsConfig(config), migrated };\n}\n\nfunction orderKeybindingsConfig(config: Record<string, unknown>): Record<string, unknown> {\n\tconst ordered: Record<string, unknown> = {};\n\tfor (const keybinding of Object.keys(KEYBINDINGS)) {\n\t\tif (Object.hasOwn(config, keybinding)) {\n\t\t\tordered[keybinding] = config[keybinding];\n\t\t}\n\t}\n\n\tconst extras = Object.keys(config)\n\t\t.filter((key) => !Object.hasOwn(ordered, key))\n\t\t.sort();\n\tfor (const key of extras) {\n\t\tordered[key] = config[key];\n\t}\n\n\treturn ordered;\n}\n\nfunction loadRawConfig(path: string): Record<string, unknown> | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\")) as unknown;\n\t\treturn isRecord(parsed) ? parsed : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport class KeybindingsManager extends TuiKeybindingsManager {\n\tprivate configPath: string | undefined;\n\n\tconstructor(userBindings: KeybindingsConfig = {}, configPath?: string) {\n\t\tsuper(KEYBINDINGS, userBindings);\n\t\tthis.configPath = configPath;\n\t}\n\n\tstatic create(agentDir: string = getAgentDir()): KeybindingsManager {\n\t\tconst configPath = join(agentDir, \"keybindings.json\");\n\t\tconst userBindings = KeybindingsManager.loadFromFile(configPath);\n\t\treturn new KeybindingsManager(userBindings, configPath);\n\t}\n\n\treload(): void {\n\t\tif (!this.configPath) return;\n\t\tthis.setUserBindings(KeybindingsManager.loadFromFile(this.configPath));\n\t}\n\n\tgetEffectiveConfig(): KeybindingsConfig {\n\t\treturn this.getResolvedBindings();\n\t}\n\n\tprivate static loadFromFile(path: string): KeybindingsConfig {\n\t\tconst rawConfig = loadRawConfig(path);\n\t\tif (!rawConfig) return {};\n\t\treturn toKeybindingsConfig(migrateKeybindingsConfig(rawConfig).config);\n\t}\n}\n\nexport type { KeyId, KeybindingsConfig };\n"]}
|