@brimveyn/aimux-config 0.3.1 → 0.3.3

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/README.md CHANGED
@@ -1,160 +1,213 @@
1
1
  # @brimveyn/aimux-config
2
2
 
3
- TypeScript configuration API for [aimux](https://github.com/BrimVeyn/aimux) — the terminal multiplexer for AI CLIs.
3
+ Typed configuration package for [`@brimveyn/aimux`](https://github.com/BrimVeyn/aimux).
4
4
 
5
- Write your keymaps, theme, and backends in a typed TypeScript file. Method-chaining builder inspired by nvim, with a prefix-trie resolver that supports leader keys and multi-key sequences.
5
+ Use this package to author `aimux.config.ts` or `aimux.config.js` inside an
6
+ `aimux` profile directory.
7
+
8
+ ## What This Package Does
9
+
10
+ `@brimveyn/aimux-config` provides:
11
+
12
+ - `defineConfig()` for typed config authoring
13
+ - the keymap builder API
14
+ - the built-in action catalog
15
+ - theme helpers and built-in theme IDs
16
+ - exported types for config, modes, state, and tooling
17
+
18
+ It does not launch the app. The `aimux` runtime loads and consumes the resolved
19
+ config.
6
20
 
7
21
  ## Install
8
22
 
23
+ Install it inside an `aimux` profile directory:
24
+
9
25
  ```bash
10
- mkdir -p ~/.config/aimux && cd ~/.config/aimux
26
+ mkdir -p ~/.config/aimux/default
27
+ cd ~/.config/aimux/default
11
28
  bun init -y
12
29
  bun add -d @brimveyn/aimux-config
13
30
  ```
14
31
 
15
- Then create `~/.config/aimux/aimux.config.ts`:
32
+ Then create:
33
+
34
+ ```text
35
+ ~/.config/aimux/default/aimux.config.ts
36
+ ```
37
+
38
+ If you use another profile, replace `default` with that profile name.
39
+
40
+ ## Minimal Example
16
41
 
17
42
  ```ts
18
- import { defineConfig, actions, themes } from '@brimveyn/aimux-config'
43
+ import { defineConfig, actions } from '@brimveyn/aimux-config'
19
44
 
20
45
  export default defineConfig({
21
- theme: themes.extend('tokyo-night', { accent: '#ff9e64' }),
46
+ sessionBar: {
47
+ position: 'top',
48
+ visible: true,
49
+ },
22
50
 
23
51
  keymaps: (k) =>
24
- k
25
- .leader('<Space>')
26
- .timeout(300)
27
- .mode('navigation', (m) =>
28
- m
29
- .map('j', actions.nextTab)
30
- .map('k', actions.prevTab)
31
- .map('<leader>g', actions.sessionPicker)
32
- .group('<leader>t', 'tabs', (g) =>
33
- g.map('n', actions.newTab).map('r', actions.renameTab).map('x', actions.closeTab)
34
- )
35
- ),
52
+ k.mode('navigation', (m) => m.map('<C-p>', actions.sessionPicker, 'Session picker')),
36
53
  })
37
54
  ```
38
55
 
39
- ## Key notation
56
+ This example only uses surfaces that are wired into the runtime today.
40
57
 
41
- | Notation | Matches |
42
- | ----------------- | ------------------------------ |
43
- | `j` | Bare character `j` |
44
- | `J` | Shift+J (uppercase letter) |
45
- | `<C-n>` | Ctrl+N |
46
- | `<M-x>` / `<A-x>` | Meta/Alt+X |
47
- | `<C-M-a>` | Ctrl+Alt+A |
48
- | `<CR>` | Return/Enter |
49
- | `<Esc>` | Escape |
50
- | `<Space>` | Spacebar |
51
- | `<Tab>` | Tab |
52
- | `<BS>` | Backspace |
53
- | `<Up>` `<Down>` | Arrow keys |
54
- | `<leader>` | Configured leader chord |
55
- | `dd` | Multi-key sequence (d, then d) |
56
- | `<leader>tn` | Leader, then t, then n |
58
+ ## Key Notation
57
59
 
58
- Ambiguous prefixes (e.g., `d` is bound AND `dd` is bound) are resolved after a configurable timeout (default 300ms).
60
+ | Notation | Meaning |
61
+ | ---------------------------------- | -------------------------- |
62
+ | `j` | bare character |
63
+ | `J` | shifted letter |
64
+ | `<C-n>` | Ctrl+N |
65
+ | `<M-x>` or `<A-x>` | Meta or Alt + X |
66
+ | `<C-M-a>` | Ctrl+Alt+A |
67
+ | `<CR>` | Enter |
68
+ | `<Esc>` | Escape |
69
+ | `<Tab>` | Tab |
70
+ | `<BS>` | Backspace |
71
+ | `<Space>` | Space |
72
+ | `<Up>` `<Down>` `<Left>` `<Right>` | arrow keys |
73
+ | `<leader>` | configured leader chord |
74
+ | `dd` | multi-key sequence |
75
+ | `<leader>tn` | leader, then `t`, then `n` |
59
76
 
60
- ## Builder API
77
+ Ambiguous prefixes are resolved after the configured timeout.
61
78
 
62
- ### Top-level config
79
+ ## Keymap Builder
63
80
 
64
81
  ```ts
65
- defineConfig({
66
- theme?: ThemeId | ThemeDefinition
67
- keymaps?: (k: KeymapBuilder) => KeymapBuilder
68
- backends?: Record<string, BackendConfig> // stub for future use
69
- sidebar?: SidebarConfig // stub
70
- hooks?: HooksConfig // stub
71
- snippets?: SnippetDef[] // stub
72
- })
82
+ k.leader(key)
83
+ .timeout(ms)
84
+ .mode(id | ids[], configure)
73
85
  ```
74
86
 
75
- ### Keymap builder
87
+ Mode builder methods:
76
88
 
77
89
  ```ts
78
- k.leader(keys) // default: '<Space>'
79
- .timeout(ms) // default: 300
80
- .mode(id | ids[], configure) // define bindings for a mode (or several at once)
90
+ m.map(keys, action, description?)
91
+ .unmap(keys)
92
+ .group(prefix, name, configure)
93
+ .passthrough()
81
94
  ```
82
95
 
83
- Pass an array of `ModeId`s to register the same bindings in every listed mode — handy for actions that should fire in both `navigation` and `terminal-input`, for example:
96
+ Example with groups and multi-mode bindings:
84
97
 
85
98
  ```ts
86
- k.mode(['navigation', 'terminal-input'], (m) => m.map('<C-s>', actions.snippetPicker))
99
+ import { defineConfig, actions } from '@brimveyn/aimux-config'
100
+
101
+ export default defineConfig({
102
+ keymaps: (k) =>
103
+ k
104
+ .mode('navigation', (m) =>
105
+ m
106
+ .group('<leader>t', 'tabs', (g) =>
107
+ g
108
+ .map('n', actions.newTab, 'New tab')
109
+ .map('r', actions.renameTab, 'Rename tab')
110
+ .map('x', actions.closeTab, 'Close tab')
111
+ )
112
+ .unmap('r')
113
+ )
114
+ .mode(['navigation', 'terminal-input'], (m) =>
115
+ m.map('<C-s>', actions.snippetPicker, 'Snippet picker')
116
+ ),
117
+ })
87
118
  ```
88
119
 
89
- ### Mode builder
120
+ ## Important Runtime Note About the Leader Key
90
121
 
91
- ```ts
92
- m.map(keys, action) // bind a key/sequence to an action
93
- .unmap(keys) // remove a default binding
94
- .group(prefix, name, g) // sugar for leader-prefixed sub-trees
95
- .passthrough() // for text-input modes: unmatched keys route to text input
96
- ```
122
+ The shipped runtime defaults use `Ctrl+W` as the leader key.
97
123
 
98
- ### Groups
124
+ The builder itself starts from `<Space>` internally, but the app merges user
125
+ config on top of shipped defaults from `@brimveyn/aimux`. In practice:
99
126
 
100
- Groups organize leader-key sub-trees. `.group('<leader>t', 'tabs', g => g.map('n', ...))` is sugar for `.map('<leader>tn', ...)` with a `name` label used by the help modal.
127
+ - if you omit `.leader(...)`, the shipped leader stays `Ctrl+W`
128
+ - if you set another leader such as `<C-a>`, it overrides the shipped leader
129
+ - do not rely on `.leader('<Space>')` to switch the runtime leader to Space
101
130
 
102
- ```ts
103
- .group('<leader>t', 'tabs', (g) => g
104
- .map('n', actions.newTab)
105
- .map('r', actions.renameTab)
106
- .map('x', actions.closeTab))
107
- ```
131
+ See [`../../docs/guide/keymaps.md`](../../docs/guide/keymaps.md) for the full
132
+ explanation.
108
133
 
109
134
  ## Actions
110
135
 
111
- Pre-built actions cover every built-in aimux operation:
136
+ Pre-built actions include:
112
137
 
113
- **Tab control** `nextTab`, `prevTab`, `newTab`, `renameTab`, `closeTab`, `restartTab`, `moveTab(n)`, `reorderTab(n)`
138
+ - tabs: `nextTab`, `prevTab`, `newTab`, `renameTab`, `closeTab`, `restartTab`
139
+ - sessions: `sessionPicker`, `switchSessionByIndex(n)` and session modal actions
140
+ - snippets: `snippetPicker`, snippet editor and filter actions
141
+ - themes: `themePicker`, `previewTheme`, `confirmTheme`, `restoreTheme`
142
+ - panes: `splitVertical`, `splitHorizontal`, `focusPane`, `resizePane`, `closePane`
143
+ - UI: `toggleSidebar`, `toggleSessionBar`, `toggleGitPane`,
144
+ `resizeGitPane(delta)`, `setGitPaneMode(mode)`, `setGitPanePosition(position)`
145
+ - modes: `enterInsert`, `leaveTerminalInput`, `closeModal`, `helpModal`
146
+ - git: enter git mode, stage, delete or unstage, commit, push
114
147
 
115
- **Modals** `sessionPicker`, `snippetPicker`, `themePicker`, `helpModal`, `closeModal`
148
+ You can also bind a custom `ActionFn` for dynamic behavior.
116
149
 
117
- **Sidebar / panels** — `toggleSidebar`, `resizeSidebar(n)`, `toggleGitPanel`, `resizeGitPanel(n)`
150
+ ## Themes
118
151
 
119
- **Layout / splits** — `splitVertical`, `splitHorizontal`, `focusPane('left'|'right'|'up'|'down')`, `resizePane(n, 'horizontal'|'vertical')`, `closePane`
152
+ Built-in theme IDs:
120
153
 
121
- **Mode transitions** — `enterInsert`, `enterLayoutMode`, `leaveTerminalInput`, `quit`
154
+ - `aimux`
155
+ - `dracula`
156
+ - `dracula-at-night`
157
+ - `everforest`
158
+ - `tokyo-night`
159
+ - `gruvbox-dark`
160
+ - `catppuccin-mocha`
161
+ - `nord`
162
+ - `solarized-dark`
163
+ - `one-dark`
164
+ - `kanagawa`
122
165
 
123
- **Custom actions** — write an `ActionFn` for dynamic logic:
166
+ Helpers:
124
167
 
125
168
  ```ts
126
- .mode('navigation', (m) => m
127
- .map('gT', (ctx) => {
128
- const tabId = ctx.state.activeTabId
129
- if (!tabId) return null
130
- return {
131
- actions: [{ type: 'close-active-tab' }],
132
- effects: [{ type: 'close-tab', tabId }],
133
- }
134
- }))
169
+ themes.extend(baseThemeId, overrides)
170
+ themes.create(colors)
135
171
  ```
136
172
 
137
- ## Themes
173
+ Typed theme config is currently only `Partially supported` by the runtime.
138
174
 
139
- ```ts
140
- import { themes } from '@brimveyn/aimux-config'
175
+ ## Support Status
141
176
 
142
- // Extend a built-in theme
143
- themes.extend('tokyo-night', {
144
- accent: '#ff9e64',
145
- background: '#1a1b26',
146
- })
177
+ | Surface | Status | Notes |
178
+ | ------------ | ------------------- | --------------------------------------------------------------------- |
179
+ | `keymaps` | Supported | Fully registered by the runtime |
180
+ | `sessionBar` | Supported | Used during app initialization |
181
+ | `gitPane` | Supported | Placement and rendering of the git file list (see docs reference) |
182
+ | `theme` | Partially supported | Package surface exists, but runtime startup uses `aimux.json.themeId` |
183
+ | `backends` | Typed surface only | Runtime wiring deferred |
184
+ | `sidebar` | Typed surface only | Type exists, runtime not currently driven by this field |
185
+ | `hooks` | Typed surface only | Type exists, runtime use not currently wired |
186
+ | `snippets` | Typed surface only | Runtime currently uses `aimux-snippets.json` |
147
187
 
148
- // Create a custom theme
149
- themes.create({
150
- accent: '#...',
151
- accentAlt: '#...',
152
- background: '#...',
153
- // ... all ThemeColors keys required
154
- })
188
+ ## Backends Subpath
189
+
190
+ The package also exports:
191
+
192
+ ```ts
193
+ @brimveyn/aimux-config/backends
155
194
  ```
156
195
 
157
- Built-in themes: `aimux`, `tokyo-night`, `dracula`, `dracula-at-night`, `everforest`, `gruvbox-dark`, `catppuccin-mocha`, `nord`, `solarized-dark`, `one-dark`, `kanagawa`.
196
+ Current helpers:
197
+
198
+ - `claudeBackend()`
199
+ - `codexBackend()`
200
+
201
+ These helpers are documented as stubs. Do not treat them as a fully supported
202
+ runtime backend override system yet.
203
+
204
+ ## More Documentation
205
+
206
+ - [`../../docs/reference/config-reference.md`](../../docs/reference/config-reference.md)
207
+ - [`../../docs/guide/keymaps.md`](../../docs/guide/keymaps.md)
208
+ - [`../../docs/guide/themes.md`](../../docs/guide/themes.md)
209
+ - [`../../docs/concepts/config-and-state.md`](../../docs/concepts/config-and-state.md)
210
+ - [`../../docs/concepts/profiles.md`](../../docs/concepts/profiles.md)
158
211
 
159
212
  ## License
160
213
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brimveyn/aimux-config",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "TypeScript configuration API for aimux — keymaps, themes, and backends with a fluent builder.",
5
5
  "keywords": [
6
6
  "aimux",
package/src/actions.ts CHANGED
@@ -35,8 +35,16 @@ export const themePicker: KeyResult = r(
35
35
  export const helpModal: KeyResult = r([{ type: 'open-help-modal' }], [], 'modal.help')
36
36
 
37
37
  export const toggleSidebar: KeyResult = r([{ type: 'toggle-sidebar' }])
38
- export const toggleGitPanel: KeyResult = r([{ type: 'toggle-git-panel' }])
38
+ export const toggleGitPane: KeyResult = r([{ type: 'toggle-git-pane' }])
39
39
  export const toggleSessionBar: KeyResult = r([{ type: 'toggle-session-bar' }])
40
+
41
+ export function setGitPaneMode(mode: 'embedded' | 'pane'): KeyResult {
42
+ return r([{ mode, type: 'set-git-pane-mode' }])
43
+ }
44
+
45
+ export function setGitPanePosition(position: 'top' | 'bottom' | 'left' | 'right'): KeyResult {
46
+ return r([{ position, type: 'set-git-pane-position' }])
47
+ }
40
48
  export const enterGitMode: KeyResult = r([{ type: 'enter-git-mode' }], [], 'git-mode')
41
49
 
42
50
  export function switchSessionByIndex(index: number): KeyResult {
@@ -97,8 +105,8 @@ export function resizeSidebar(delta: number): KeyResult {
97
105
  return r([{ delta, type: 'resize-sidebar' }])
98
106
  }
99
107
 
100
- export function resizeGitPanel(delta: number): KeyResult {
101
- return r([{ delta, type: 'resize-git-panel' }])
108
+ export function resizeGitPane(delta: number): KeyResult {
109
+ return r([{ delta, type: 'resize-git-pane' }])
102
110
  }
103
111
 
104
112
  export function focusPane(direction: 'left' | 'right' | 'up' | 'down'): KeyResult {
@@ -200,6 +208,12 @@ export const beginSnippetFilter: KeyResult = r(
200
208
  'modal.snippet-picker.filtering'
201
209
  )
202
210
 
211
+ export const beginHelpFilter: KeyResult = r(
212
+ [{ type: 'begin-help-filter' }],
213
+ [],
214
+ 'modal.help.filtering'
215
+ )
216
+
203
217
  export const confirmSplit: KeyResult = r([], [{ type: 'confirm-split' }])
204
218
 
205
219
  export const restoreTheme: KeyResult = r(
@@ -252,21 +266,6 @@ export const confirmUpdateSelection: KeyResult = r(
252
266
  'navigation'
253
267
  )
254
268
 
255
- // Layout-specific
256
- export const exitLayoutToInput: KeyResult = r(
257
- [{ focusMode: 'terminal-input', type: 'set-focus-mode' }],
258
- [],
259
- 'terminal-input'
260
- )
261
-
262
- export const exitLayoutToNavigation: ActionFn = (ctx: ModeContext) => {
263
- const actions: KeyResult['actions'] = [{ focusMode: 'navigation', type: 'set-focus-mode' }]
264
- if (!ctx.state.sidebar.visible) {
265
- actions.push({ type: 'toggle-sidebar' })
266
- }
267
- return r(actions, [], 'navigation')
268
- }
269
-
270
269
  export const closePane: ActionFn = (ctx: ModeContext) => {
271
270
  const tabId = ctx.state.activeTabId
272
271
  if (!tabId) return null
@@ -299,12 +298,6 @@ export const leaveTerminalInput: KeyResult = r(
299
298
  'navigation'
300
299
  )
301
300
 
302
- export const enterLayoutMode: KeyResult = r(
303
- [{ focusMode: 'layout', type: 'set-focus-mode' }],
304
- [],
305
- 'layout'
306
- )
307
-
308
301
  export const toggleSidebarFromInput: KeyResult = r([{ type: 'toggle-sidebar' }])
309
302
 
310
303
  // Session name modal
package/src/defaults.ts CHANGED
@@ -27,10 +27,10 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
27
27
  .map('<C-t>', actions.themePicker, 'Theme picker')
28
28
  .map('<C-h>', actions.resizeSidebar(-2), 'Sidebar narrower')
29
29
  .map('<C-l>', actions.resizeSidebar(2), 'Sidebar wider')
30
- .map('G', actions.toggleGitPanel, 'Toggle git panel')
30
+ .map('G', actions.toggleGitPane, 'Toggle git pane')
31
31
  .map('<C-d>', actions.enterGitMode, 'Enter git mode')
32
- .map('<C-j>', actions.resizeGitPanel(-0.05), 'Git panel smaller')
33
- .map('<C-k>', actions.resizeGitPanel(0.05), 'Git panel larger')
32
+ .map('<C-j>', actions.resizeGitPane(-0.05), 'Git pane smaller')
33
+ .map('<C-k>', actions.resizeGitPane(0.05), 'Git pane larger')
34
34
  .map('J', actions.reorderTab(1), 'Move tab right')
35
35
  .map('j', actions.nextTab, 'Next tab')
36
36
  .map('K', actions.reorderTab(-1), 'Move tab left')
@@ -46,8 +46,18 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
46
46
  .mode('terminal-input', (m) =>
47
47
  m
48
48
  .map('<C-z>', actions.leaveTerminalInput, 'Leave insert')
49
- .map('<Leader>', actions.enterLayoutMode, 'Layout mode')
50
49
  .map('<C-b>', actions.toggleSidebarFromInput, 'Toggle sidebar')
50
+ .map('<Leader>h', actions.focusPane('left'), 'Focus left')
51
+ .map('<Leader>j', actions.focusPane('down'), 'Focus down')
52
+ .map('<Leader>k', actions.focusPane('up'), 'Focus up')
53
+ .map('<Leader>l', actions.focusPane('right'), 'Focus right')
54
+ .map('<Leader>H', actions.resizePane(-1, 'vertical'), 'Shrink ←', { repeatable: true })
55
+ .map('<Leader>L', actions.resizePane(1, 'vertical'), 'Grow →', { repeatable: true })
56
+ .map('<Leader>K', actions.resizePane(-1, 'horizontal'), 'Shrink ↑', { repeatable: true })
57
+ .map('<Leader>J', actions.resizePane(1, 'horizontal'), 'Grow ↓', { repeatable: true })
58
+ .map('<Leader>|', actions.splitVertical, 'Split vertical')
59
+ .map('<Leader>-', actions.splitHorizontal, 'Split horizontal')
60
+ .map('<Leader>q', actions.closePane, 'Close pane')
51
61
  )
52
62
 
53
63
  // -----------------------------------------------------------------------
@@ -67,27 +77,6 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
67
77
  .map('<Leader>9', actions.switchSessionByIndex(9), 'Session 9')
68
78
  )
69
79
 
70
- // -----------------------------------------------------------------------
71
- // Layout mode
72
- // -----------------------------------------------------------------------
73
- .mode('layout', (m) =>
74
- m
75
- .map('<Esc>', actions.exitLayoutToInput, 'Back to insert')
76
- .map('<Leader>', actions.exitLayoutToInput, 'Back to insert')
77
- .map('<C-z>', actions.exitLayoutToNavigation, 'Back to nav')
78
- .map('H', actions.resizePane(-1, 'vertical'), 'Shrink ←')
79
- .map('L', actions.resizePane(1, 'vertical'), 'Grow →')
80
- .map('K', actions.resizePane(-1, 'horizontal'), 'Shrink ↑')
81
- .map('J', actions.resizePane(1, 'horizontal'), 'Grow ↓')
82
- .map('h', actions.focusPane('left'), 'Focus left')
83
- .map('j', actions.focusPane('down'), 'Focus down')
84
- .map('k', actions.focusPane('up'), 'Focus up')
85
- .map('l', actions.focusPane('right'), 'Focus right')
86
- .map('|', actions.splitVertical, 'Split vertical')
87
- .map('-', actions.splitHorizontal, 'Split horizontal')
88
- .map('q', actions.closePane, 'Close pane')
89
- )
90
-
91
80
  // -----------------------------------------------------------------------
92
81
  // Git mode
93
82
  // -----------------------------------------------------------------------
@@ -109,7 +98,30 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
109
98
  // -----------------------------------------------------------------------
110
99
  // Modal: help
111
100
  // -----------------------------------------------------------------------
112
- .mode('modal.help', (m) => m.map('<Esc>', actions.closeModal, 'Close'))
101
+ .mode('modal.help', (m) =>
102
+ m
103
+ .map('<Esc>', actions.closeModal, 'Close')
104
+ .map('j', actions.moveModalSelection(1), 'Next')
105
+ .map('k', actions.moveModalSelection(-1), 'Prev')
106
+ .map('<Down>', actions.moveModalSelection(1))
107
+ .map('<Up>', actions.moveModalSelection(-1))
108
+ .map('<C-n>', actions.moveModalSelection(1))
109
+ .map('<C-p>', actions.moveModalSelection(-1))
110
+ .map('/', actions.beginHelpFilter, 'Filter')
111
+ )
112
+
113
+ // -----------------------------------------------------------------------
114
+ // Modal: help filtering
115
+ // -----------------------------------------------------------------------
116
+ .mode('modal.help.filtering', (m) =>
117
+ m
118
+ .map('<Esc>', actions.cancelCommandEdit('modal.help'), 'Cancel')
119
+ .map('<C-n>', actions.moveModalSelection(1), 'Next')
120
+ .map('<C-p>', actions.moveModalSelection(-1), 'Prev')
121
+ .map('<Down>', actions.moveModalSelection(1))
122
+ .map('<Up>', actions.moveModalSelection(-1))
123
+ .passthrough()
124
+ )
113
125
 
114
126
  // -----------------------------------------------------------------------
115
127
  // Modal: theme-picker
@@ -121,6 +133,8 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
121
133
  .map('k', actions.previewTheme(-1), 'Prev')
122
134
  .map('<Down>', actions.previewTheme(1))
123
135
  .map('<Up>', actions.previewTheme(-1))
136
+ .map('<C-n>', actions.previewTheme(1))
137
+ .map('<C-p>', actions.previewTheme(-1))
124
138
  .map('<CR>', actions.confirmTheme, 'Confirm')
125
139
  )
126
140
 
@@ -138,6 +152,8 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
138
152
  .map('<Left>', actions.moveModalSelection(-1))
139
153
  .map('<Down>', actions.moveModalSelection(1))
140
154
  .map('<Up>', actions.moveModalSelection(-1))
155
+ .map('<C-n>', actions.moveModalSelection(1))
156
+ .map('<C-p>', actions.moveModalSelection(-1))
141
157
  .map('<CR>', actions.confirmUpdateSelection, 'Confirm')
142
158
  )
143
159
 
@@ -161,6 +177,8 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
161
177
  .map('k', actions.moveModalSelection(-1), 'Prev')
162
178
  .map('<Down>', actions.moveModalSelection(1))
163
179
  .map('<Up>', actions.moveModalSelection(-1))
180
+ .map('<C-n>', actions.moveModalSelection(1))
181
+ .map('<C-p>', actions.moveModalSelection(-1))
164
182
  .map('<CR>', actions.launchSelectedAssistant, 'Launch')
165
183
  .map('e', actions.beginCommandEdit, 'Edit command')
166
184
  )
@@ -174,6 +192,8 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
174
192
  .map('<CR>', actions.commitCommandEdit, 'Save')
175
193
  .map('<C-n>', actions.moveModalSelection(1), 'Next')
176
194
  .map('<C-p>', actions.moveModalSelection(-1), 'Prev')
195
+ .map('<Down>', actions.moveModalSelection(1))
196
+ .map('<Up>', actions.moveModalSelection(-1))
177
197
  .passthrough()
178
198
  )
179
199
 
@@ -187,6 +207,8 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
187
207
  .map('k', actions.moveModalSelection(-1), 'Prev')
188
208
  .map('<Down>', actions.moveModalSelection(1))
189
209
  .map('<Up>', actions.moveModalSelection(-1))
210
+ .map('<C-n>', actions.moveModalSelection(1))
211
+ .map('<C-p>', actions.moveModalSelection(-1))
190
212
  .map('<CR>', actions.confirmSelectedSession, 'Open')
191
213
  .map('n', actions.openCreateSessionModal, 'New')
192
214
  .map('r', actions.openRenameSelectedSession, 'Rename')
@@ -203,6 +225,8 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
203
225
  .map('<CR>', actions.confirmSelectedSession, 'Open')
204
226
  .map('<C-n>', actions.moveModalSelection(1), 'Next')
205
227
  .map('<C-p>', actions.moveModalSelection(-1), 'Prev')
228
+ .map('<Down>', actions.moveModalSelection(1))
229
+ .map('<Up>', actions.moveModalSelection(-1))
206
230
  .passthrough()
207
231
  )
208
232
 
@@ -226,6 +250,8 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
226
250
  .map('<CR>', actions.confirmCreateSession, 'Confirm')
227
251
  .map('<C-n>', actions.moveModalSelection(1), 'Next')
228
252
  .map('<C-p>', actions.moveModalSelection(-1), 'Prev')
253
+ .map('<Down>', actions.moveModalSelection(1))
254
+ .map('<Up>', actions.moveModalSelection(-1))
229
255
  .passthrough()
230
256
  )
231
257
 
@@ -239,6 +265,8 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
239
265
  .map('k', actions.moveModalSelection(-1), 'Prev')
240
266
  .map('<Down>', actions.moveModalSelection(1))
241
267
  .map('<Up>', actions.moveModalSelection(-1))
268
+ .map('<C-n>', actions.moveModalSelection(1))
269
+ .map('<C-p>', actions.moveModalSelection(-1))
242
270
  .map('<CR>', actions.pasteSelectedSnippet, 'Send')
243
271
  .map('a', actions.pasteSnippetToGroup, 'Send to group')
244
272
  .map('n', actions.openSnippetEditor, 'New')
@@ -258,6 +286,8 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
258
286
  .map('<C-a>', actions.snippetFilterPasteToGroup, 'Send to group')
259
287
  .map('<C-n>', actions.moveModalSelection(1), 'Next')
260
288
  .map('<C-p>', actions.moveModalSelection(-1), 'Prev')
289
+ .map('<Down>', actions.moveModalSelection(1))
290
+ .map('<Up>', actions.moveModalSelection(-1))
261
291
  .passthrough()
262
292
  )
263
293
 
@@ -271,6 +301,8 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
271
301
  .map('<CR>', actions.saveSnippetEditor, 'Save')
272
302
  .map('<C-n>', actions.moveModalSelection(1), 'Next')
273
303
  .map('<C-p>', actions.moveModalSelection(-1), 'Prev')
304
+ .map('<Down>', actions.moveModalSelection(1))
305
+ .map('<Up>', actions.moveModalSelection(-1))
274
306
  .passthrough()
275
307
  )
276
308
 
@@ -284,6 +316,8 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
284
316
  .map('k', actions.moveModalSelection(-1), 'Prev')
285
317
  .map('<Down>', actions.moveModalSelection(1))
286
318
  .map('<Up>', actions.moveModalSelection(-1))
319
+ .map('<C-n>', actions.moveModalSelection(1))
320
+ .map('<C-p>', actions.moveModalSelection(-1))
287
321
  .map('<CR>', actions.confirmSplit, 'Confirm')
288
322
  )
289
323
 
package/src/index.ts CHANGED
@@ -19,6 +19,12 @@ export type {
19
19
  BackendConfig,
20
20
  BindingDef,
21
21
  FocusMode,
22
+ GitPaneConfig,
23
+ GitPaneDiffCountConfig,
24
+ GitPaneEmbeddedConfig,
25
+ GitPanePaneConfig,
26
+ GitPanePathConfig,
27
+ GitPaneState,
22
28
  GroupBuilderApi,
23
29
  HooksConfig,
24
30
  KeyInput,
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  Action,
3
3
  BindingDef,
4
+ BindingOptions,
4
5
  GroupBuilderApi,
5
6
  KeymapBuilderApi,
6
7
  ModeBindingBuilderApi,
@@ -21,11 +22,12 @@ export class GroupBuilder implements GroupBuilderApi {
21
22
  private readonly groupName: string
22
23
  ) {}
23
24
 
24
- map(keys: string, action: Action, description?: string): this {
25
+ map(keys: string, action: Action, description?: string, opts?: BindingOptions): this {
25
26
  this.bindings.push({
26
27
  description,
27
28
  group: this.groupName,
28
29
  keys: `${this.prefix}${keys}`,
30
+ repeatable: opts?.repeatable,
29
31
  result: action,
30
32
  })
31
33
  return this
@@ -48,8 +50,8 @@ export class ModeBindingBuilder implements ModeBindingBuilderApi {
48
50
  private readonly removals: string[] = []
49
51
  private _passthrough = false
50
52
 
51
- map(keys: string, action: Action, description?: string): this {
52
- this.bindings.push({ description, keys, result: action })
53
+ map(keys: string, action: Action, description?: string, opts?: BindingOptions): this {
54
+ this.bindings.push({ description, keys, repeatable: opts?.repeatable, result: action })
53
55
  return this
54
56
  }
55
57
 
package/src/resolver.ts CHANGED
@@ -18,6 +18,7 @@ export function resolveConfig(userConfig: AimuxUserConfig): ResolvedConfig {
18
18
 
19
19
  return {
20
20
  backends: userConfig.backends ?? {},
21
+ gitPane: userConfig.gitPane ?? {},
21
22
  hooks: userConfig.hooks ?? {},
22
23
  keymaps,
23
24
  sessionBar: userConfig.sessionBar ?? {},
package/src/types.ts CHANGED
@@ -11,7 +11,6 @@
11
11
  export type ModeId =
12
12
  | 'navigation'
13
13
  | 'terminal-input'
14
- | 'layout'
15
14
  | 'git-mode'
16
15
  | 'modal.new-tab'
17
16
  | 'modal.new-tab.command-edit'
@@ -25,6 +24,7 @@ export type ModeId =
25
24
  | 'modal.snippet-editor'
26
25
  | 'modal.theme-picker'
27
26
  | 'modal.help'
27
+ | 'modal.help.filtering'
28
28
  | 'modal.split-picker'
29
29
  | 'modal.git-commit'
30
30
  | 'modal.update-available'
@@ -35,13 +35,7 @@ export type BuiltinAssistantId = 'claude' | 'codex' | 'opencode' | 'terminal'
35
35
  export type AssistantId = BuiltinAssistantId | (string & {})
36
36
  export type TabStatus = 'starting' | 'running' | 'disconnected' | 'exited' | 'error'
37
37
  export type TabActivity = 'busy' | 'idle'
38
- export type FocusMode =
39
- | 'navigation'
40
- | 'terminal-input'
41
- | 'modal'
42
- | 'command-edit'
43
- | 'layout'
44
- | 'git'
38
+ export type FocusMode = 'navigation' | 'terminal-input' | 'modal' | 'command-edit' | 'git'
45
39
  export type SplitDirection = 'horizontal' | 'vertical'
46
40
 
47
41
  // ─── Terminal data shapes ─────────────────────────────────────────────────────
@@ -109,8 +103,6 @@ export interface WorkspaceSnapshotV1 {
109
103
  sidebar: {
110
104
  visible: boolean
111
105
  width: number
112
- gitPanelVisible?: boolean
113
- gitPanelRatio?: number
114
106
  }
115
107
  tabs: PersistedTabSnapshot[]
116
108
  layoutTree?: LayoutNode
@@ -163,8 +155,15 @@ export interface SidebarState {
163
155
  width: number
164
156
  minWidth: number
165
157
  maxWidth: number
166
- gitPanelVisible: boolean
167
- gitPanelRatio: number
158
+ }
159
+
160
+ export interface GitPaneState {
161
+ visible: boolean
162
+ mode: 'embedded' | 'pane'
163
+ position: 'top' | 'bottom' | 'left' | 'right'
164
+ ratio: number
165
+ path: GitPanePathConfig
166
+ diffCount: GitPaneDiffCountConfig
168
167
  }
169
168
 
170
169
  export type GitFileStatus = 'M' | 'A' | 'D' | 'R' | 'C' | 'U' | '?'
@@ -242,6 +241,7 @@ export interface ModalThemePicker extends ModalBase {
242
241
  }
243
242
  export interface ModalHelp extends ModalBase {
244
243
  type: 'help'
244
+ entryCount: number
245
245
  }
246
246
  export interface ModalSplitPicker extends ModalBase {
247
247
  type: 'split-picker'
@@ -310,6 +310,7 @@ export interface AppState {
310
310
  snippets: SnippetRecord[]
311
311
  focusMode: FocusMode
312
312
  sidebar: SidebarState
313
+ gitPane: GitPaneState
313
314
  modal: ModalState
314
315
  layout: LayoutState
315
316
  customCommands: Record<AssistantId, string>
@@ -341,6 +342,8 @@ export type ModalAction =
341
342
  | { type: 'open-snippet-picker' }
342
343
  | { type: 'open-snippet-editor'; snippetId?: string }
343
344
  | { type: 'begin-snippet-filter' }
345
+ | { type: 'begin-help-filter' }
346
+ | { type: 'set-help-entry-count'; count: number }
344
347
  | { type: 'open-theme-picker' }
345
348
  | { type: 'open-update-available-modal'; currentVersion: string; latestVersion: string }
346
349
 
@@ -410,8 +413,10 @@ export type UIAction =
410
413
  | { type: 'resize-sidebar'; delta: number }
411
414
  | { type: 'set-focus-mode'; focusMode: FocusMode }
412
415
  | { type: 'set-terminal-size'; cols: number; rows: number }
413
- | { type: 'toggle-git-panel' }
414
- | { type: 'resize-git-panel'; delta: number }
416
+ | { type: 'toggle-git-pane' }
417
+ | { type: 'resize-git-pane'; delta: number }
418
+ | { type: 'set-git-pane-mode'; mode: 'embedded' | 'pane' }
419
+ | { type: 'set-git-pane-position'; position: 'top' | 'bottom' | 'left' | 'right' }
415
420
  | { type: 'set-pending-chords'; chords: string[] | null }
416
421
  | { type: 'toggle-session-bar' }
417
422
  | { type: 'set-session-bar-position'; position: SessionBarPosition }
@@ -587,8 +592,12 @@ export type Action = KeyResult | ActionFn
587
592
 
588
593
  // ─── Keymap builder API types ─────────────────────────────────────────────────
589
594
 
595
+ export interface BindingOptions {
596
+ repeatable?: boolean
597
+ }
598
+
590
599
  export interface GroupBuilderApi {
591
- map(keys: string, action: Action, description?: string): GroupBuilderApi
600
+ map(keys: string, action: Action, description?: string, opts?: BindingOptions): GroupBuilderApi
592
601
  group(
593
602
  prefix: string,
594
603
  name: string,
@@ -597,7 +606,12 @@ export interface GroupBuilderApi {
597
606
  }
598
607
 
599
608
  export interface ModeBindingBuilderApi {
600
- map(keys: string, action: Action, description?: string): ModeBindingBuilderApi
609
+ map(
610
+ keys: string,
611
+ action: Action,
612
+ description?: string,
613
+ opts?: BindingOptions
614
+ ): ModeBindingBuilderApi
601
615
  unmap(keys: string): ModeBindingBuilderApi
602
616
  group(
603
617
  prefix: string,
@@ -623,12 +637,40 @@ export interface SessionBarConfig {
623
637
  position?: SessionBarPosition
624
638
  }
625
639
 
640
+ // ─── Git pane config (discriminated union) ────────────────────────────────────
641
+
642
+ export type GitPanePathConfig =
643
+ | { enabled: false }
644
+ | { enabled: true; pathFn?: (path: string) => string }
645
+
646
+ export type GitPaneDiffCountConfig = { enabled: boolean }
647
+
648
+ interface GitPaneBaseConfig {
649
+ visible?: boolean
650
+ ratio?: number
651
+ path?: GitPanePathConfig
652
+ diffCount?: GitPaneDiffCountConfig
653
+ }
654
+
655
+ export interface GitPaneEmbeddedConfig extends GitPaneBaseConfig {
656
+ mode?: 'embedded'
657
+ position?: 'top' | 'bottom'
658
+ }
659
+
660
+ export interface GitPanePaneConfig extends GitPaneBaseConfig {
661
+ mode: 'pane'
662
+ position?: 'left' | 'right'
663
+ }
664
+
665
+ export type GitPaneConfig = GitPaneEmbeddedConfig | GitPanePaneConfig
666
+
626
667
  export interface AimuxUserConfig {
627
668
  theme?: ThemeId | ThemeDefinition
628
669
  keymaps?: (k: KeymapBuilderApi) => KeymapBuilderApi
629
670
  backends?: Record<string, BackendConfig>
630
671
  sidebar?: SidebarConfig
631
672
  sessionBar?: SessionBarConfig
673
+ gitPane?: GitPaneConfig
632
674
  hooks?: HooksConfig
633
675
  snippets?: SnippetDef[]
634
676
  }
@@ -640,6 +682,7 @@ export interface BindingDef {
640
682
  result: Action
641
683
  group?: string
642
684
  description?: string
685
+ repeatable?: boolean
643
686
  }
644
687
 
645
688
  export interface ModeKeymapDef {
@@ -660,6 +703,7 @@ export interface ResolvedConfig {
660
703
  backends: Record<string, BackendConfig>
661
704
  sidebar: SidebarConfig
662
705
  sessionBar: SessionBarConfig
706
+ gitPane: GitPaneConfig
663
707
  hooks: HooksConfig
664
708
  snippets: SnippetDef[]
665
709
  }