@brimveyn/aimux-config 0.4.6 → 0.5.1

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
@@ -44,17 +44,21 @@ import { defineConfig, actions } from '@brimveyn/aimux-config'
44
44
 
45
45
  export default defineConfig({
46
46
  sessionBar: {
47
- position: 'top',
48
- visible: true,
47
+ initialPosition: 'top',
48
+ initialVisible: true,
49
49
  },
50
50
 
51
51
  keymaps: (k) =>
52
- k.mode('navigation', (m) => m.map('<C-p>', actions.sessionPicker, 'Session picker')),
52
+ k.mode('navigation', (m) => m.map('<C-p>', actions.sessionPicker, 'Workspace picker')),
53
53
  })
54
54
  ```
55
55
 
56
56
  This example only uses surfaces that are wired into the runtime today.
57
57
 
58
+ Important: typed config expresses startup intent. Fields such as
59
+ `sessionBar.initialVisible` or `gitPane.initialMode` are reapplied on every
60
+ launch; runtime UI changes do not write back into `aimux.config.ts`.
61
+
58
62
  ## Key Notation
59
63
 
60
64
  | Notation | Meaning |
@@ -136,7 +140,7 @@ explanation.
136
140
  Pre-built actions include:
137
141
 
138
142
  - tabs: `nextTab`, `prevTab`, `newTab`, `renameTab`, `closeTab`, `restartTab`
139
- - sessions: `sessionPicker`, `switchSessionByIndex(n)` and session modal actions
143
+ - workspaces: `sessionPicker`, `switchSessionByIndex(n)` and workspace modal actions
140
144
  - snippets: `snippetPicker`, snippet editor and filter actions
141
145
  - themes: `themePicker`, `previewTheme`, `confirmTheme`, `restoreTheme`
142
146
  - panes: `splitVertical`, `splitHorizontal`, `focusPane`, `resizePane`, `closePane`
@@ -149,58 +153,44 @@ You can also bind a custom `ActionFn` for dynamic behavior.
149
153
 
150
154
  ## Themes
151
155
 
152
- aimux themes are [Shiki](https://shiki.style/themes) themes. Two house themes
153
- (`aimux`, `dracula-at-night`) ship alongside the full Shiki catalog — 67
154
- themes total. The same `Theme` object powers UI colors and code highlighting.
156
+ `theme` is a startup override surface, not the persisted selected theme.
155
157
 
156
- Declare your own themes in config:
158
+ Use it to choose the built-in light or dark family when there is no persisted
159
+ `aimux.json.themeId`, and to apply palette overrides on top of the chosen base
160
+ theme:
157
161
 
158
162
  ```ts
159
- import { defineConfig, themes } from '@brimveyn/aimux-config'
163
+ import { defineConfig } from '@brimveyn/aimux-config'
160
164
 
161
165
  export default defineConfig({
162
- theme: 'my-neon',
163
- themes: {
164
- // Palette shortcut — patch VSCode color keys on top of a base theme.
165
- 'my-neon': themes.define('My Neon', 'aimux', {
166
- 'textLink.foreground': '#ff00aa',
167
- 'terminal.ansiMagenta': '#00ffcc',
168
- }),
169
- // Or drop a full VSCode theme JSON verbatim:
170
- 'custom': themes.full({
171
- name: 'custom',
172
- displayName: 'Custom',
173
- type: 'dark',
174
- fg: '#eee',
175
- bg: '#1a1a1a',
176
- colors: {
177
- /* VSCode workbench keys */
178
- },
179
- settings: [
180
- /* TextMate token rules */
181
- ],
182
- }),
166
+ theme: {
167
+ initialMode: 'dark',
168
+ paletteOverrides: {
169
+ primary: '#7dd3fc',
170
+ warning: '#fbbf24',
171
+ },
183
172
  },
184
173
  })
185
174
  ```
186
175
 
187
- See [`../../docs/guide/themes.md`](../../docs/guide/themes.md) for picker
188
- shortcuts, the VSCode color key reference, and migration notes if you're
189
- coming from the old palette alias API.
176
+ The runtime theme picker persists the selected theme id separately in
177
+ `aimux.json`, and that persisted choice wins on restart. See
178
+ [`../../docs/guide/themes.md`](../../docs/guide/themes.md) for picker behavior,
179
+ palette guidance, and precedence details.
190
180
 
191
181
  ## Support Status
192
182
 
193
- | Surface | Status | Notes |
194
- | ------------ | ------------------ | -------------------------------------------------------------------- |
195
- | `keymaps` | Supported | Fully registered by the runtime |
196
- | `sessionBar` | Supported | Used during app initialization |
197
- | `gitPane` | Supported | Placement and rendering of the git file list (see docs reference) |
198
- | `theme` | Supported | Initial theme id; `aimux.json.themeId` wins if still known |
199
- | `themes` | Supported | User themes; appear in the picker and power synthesized highlighting |
200
- | `backends` | Typed surface only | Runtime wiring deferred |
201
- | `sidebar` | Typed surface only | Type exists, runtime not currently driven by this field |
202
- | `hooks` | Typed surface only | Type exists, runtime use not currently wired |
203
- | `snippets` | Typed surface only | Runtime currently uses `aimux-snippets.json` |
183
+ | Surface | Status | Notes |
184
+ | ------------ | ------------------ | ------------------------------------------------------------------------------------ |
185
+ | `keymaps` | Supported | Fully registered by the runtime |
186
+ | `sessionBar` | Supported | Startup overrides; app-managed runtime state still persists separately |
187
+ | `gitPane` | Supported | Startup overrides for placement and rendering; runtime state persists separately |
188
+ | `theme` | Supported | `theme.initialMode` is a startup override; persisted `aimux.json.themeId` still wins |
189
+ | `themes` | Supported | User themes; appear in the picker and power synthesized highlighting |
190
+ | `backends` | Typed surface only | Runtime wiring deferred |
191
+ | `sidebar` | Typed surface only | Type exists, runtime not currently driven by this field |
192
+ | `hooks` | Typed surface only | Type exists, runtime use not currently wired |
193
+ | `snippets` | Typed surface only | Runtime currently uses `aimux-snippets.json` |
204
194
 
205
195
  ## Backends Subpath
206
196
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brimveyn/aimux-config",
3
- "version": "0.4.6",
3
+ "version": "0.5.1",
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
@@ -1,5 +1,10 @@
1
1
  import type { ActionFn, AppAction, KeyResult, ModeContext, ModeId } from './types'
2
2
 
3
+ import { isAutoCommitEnabled } from './auto-commit-runtime'
4
+
5
+ const AUTO_COMMIT_DISABLED_MESSAGE =
6
+ 'auto-commit is disabled — set autoCommit: { enabled: true } in aimux.config.ts'
7
+
3
8
  function r(
4
9
  actions: KeyResult['actions'] = [],
5
10
  effects: KeyResult['effects'] = [],
@@ -43,6 +48,12 @@ export function helpModal(scope?: ModeId): KeyResult {
43
48
  export const toggleSidebar: KeyResult = r([{ type: 'toggle-sidebar' }])
44
49
  export const toggleGitPane: KeyResult = r([{ type: 'toggle-git-pane' }])
45
50
  export const toggleSessionBar: KeyResult = r([{ type: 'toggle-session-bar' }])
51
+ export const toggleAIUsage: ActionFn = (ctx: ModeContext) => {
52
+ if (ctx.state.modal.type === 'ai-usage') {
53
+ return r([{ type: 'close-modal' }], [], 'navigation')
54
+ }
55
+ return r([{ type: 'open-ai-usage-modal' }], [], 'modal.ai-usage')
56
+ }
46
57
 
47
58
  export function setGitPaneMode(mode: 'embedded' | 'pane'): KeyResult {
48
59
  return r([{ mode, type: 'set-git-pane-mode' }])
@@ -545,7 +556,12 @@ export const gitCommitOpen: ActionFn = (ctx: ModeContext) => {
545
556
  },
546
557
  ])
547
558
  }
548
- return r([...clearPendingDelete(ctx), { type: 'open-git-commit-modal' }], [], 'modal.git-commit')
559
+ const sessionId = ctx.state.currentSessionId ?? undefined
560
+ return r(
561
+ [...clearPendingDelete(ctx), { sessionId, type: 'open-git-commit-modal' }],
562
+ [],
563
+ 'modal.git-commit'
564
+ )
549
565
  }
550
566
 
551
567
  export const gitPush: ActionFn = (ctx: ModeContext) => {
@@ -564,18 +580,88 @@ export const gitPush: ActionFn = (ctx: ModeContext) => {
564
580
  // Modal: git-commit
565
581
  // ---------------------------------------------------------------------------
566
582
 
567
- export const gitCommitCancel: KeyResult = r([{ type: 'close-modal' }], [], 'git-mode')
568
-
569
- export const gitCommitSubmit: ActionFn = (ctx: ModeContext) => {
570
- const modal = ctx.state.modal
571
- if (modal.type !== 'git-commit') {
572
- return r([{ type: 'close-modal' }], [], 'git-mode')
573
- }
583
+ function extractGitCommitFields(modal: ModeContext['state']['modal']): {
584
+ title: string
585
+ body: string
586
+ } | null {
587
+ if (modal.type !== 'git-commit') return null
574
588
  const editBuffer = modal.editBuffer ?? ''
575
589
  const activeIsTitle = modal.activeField === 'title'
576
590
  const title = (activeIsTitle ? editBuffer : modal.contentBuffer).trim()
577
591
  const body = (activeIsTitle ? modal.contentBuffer : editBuffer).trim()
578
- return r([{ type: 'close-modal' }], [{ body, title, type: 'git-commit' }], 'git-mode')
592
+ return { body, title }
593
+ }
594
+
595
+ export const gitCommitCancel: ActionFn = (ctx: ModeContext) => {
596
+ const modal = ctx.state.modal
597
+ if (modal.type === 'git-commit' && modal.stage === 'confirm') {
598
+ return r([{ type: 'git-commit-leave-confirm' }], [], 'modal.git-commit')
599
+ }
600
+ return r([{ type: 'close-modal' }], [], 'git-mode')
601
+ }
602
+
603
+ export const gitCommitEnterConfirm: ActionFn = (ctx: ModeContext) => {
604
+ const modal = ctx.state.modal
605
+ if (modal.type !== 'git-commit') return null
606
+ if (!isAutoCommitEnabled()) {
607
+ return r([{ message: AUTO_COMMIT_DISABLED_MESSAGE, type: 'git-mode-set-message' }])
608
+ }
609
+ const fields = extractGitCommitFields(modal)
610
+ const hasTitle = !!fields && fields.title.length > 0
611
+ if (hasTitle) {
612
+ return r([{ type: 'git-commit-enter-confirm' }], [], 'modal.git-commit.confirm')
613
+ }
614
+ const sessionId = ctx.state.currentSessionId
615
+ if (!sessionId) {
616
+ return r([{ type: 'git-commit-enter-confirm' }], [], 'modal.git-commit.confirm')
617
+ }
618
+ return r(
619
+ [{ sessionId, type: 'git-commit-enter-generating' }],
620
+ [{ sessionId, type: 'generate-auto-commit-now' }],
621
+ 'modal.git-commit.generating'
622
+ )
623
+ }
624
+
625
+ export const gitCommitLeaveConfirm: KeyResult = r(
626
+ [{ type: 'git-commit-leave-confirm' }],
627
+ [],
628
+ 'modal.git-commit'
629
+ )
630
+
631
+ export const gitCommitLeaveGenerating: ActionFn = (ctx: ModeContext) => {
632
+ const sessionId = ctx.state.currentSessionId
633
+ const actionsList: AppAction[] = [{ type: 'git-commit-leave-generating' }]
634
+ if (sessionId) {
635
+ actionsList.push({ sessionId, type: 'auto-commit-clear' })
636
+ }
637
+ return r(actionsList, [], 'modal.git-commit')
638
+ }
639
+
640
+ export const gitCommitAutoAccept: ActionFn = (ctx: ModeContext) => {
641
+ if (!isAutoCommitEnabled()) {
642
+ return r([{ message: AUTO_COMMIT_DISABLED_MESSAGE, type: 'git-mode-set-message' }])
643
+ }
644
+ const fields = extractGitCommitFields(ctx.state.modal)
645
+ if (!fields) {
646
+ return r([{ type: 'close-modal' }], [], 'git-mode')
647
+ }
648
+ return r(
649
+ [{ type: 'close-modal' }],
650
+ [{ body: fields.body, title: fields.title, type: 'git-commit-auto' }],
651
+ 'git-mode'
652
+ )
653
+ }
654
+
655
+ export const gitCommitSubmit: ActionFn = (ctx: ModeContext) => {
656
+ const fields = extractGitCommitFields(ctx.state.modal)
657
+ if (!fields) {
658
+ return r([{ type: 'close-modal' }], [], 'git-mode')
659
+ }
660
+ return r(
661
+ [{ type: 'close-modal' }],
662
+ [{ body: fields.body, title: fields.title, type: 'git-commit' }],
663
+ 'git-mode'
664
+ )
579
665
  }
580
666
 
581
667
  export const gitCommitReturnKey: ActionFn = (ctx: ModeContext) => {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Module-level flag that mirrors `autoCommit.enabled` from the resolved config.
3
+ *
4
+ * Actions (which run outside React and can't subscribe to a React context) read
5
+ * this via `isAutoCommitEnabled()`. The app sets it once at startup via
6
+ * `setAutoCommitEnabled(resolvedConfig.autoCommit.enabled)` before any children
7
+ * render, so downstream readers always see the correct value.
8
+ */
9
+
10
+ let enabled = false
11
+
12
+ export function setAutoCommitEnabled(value: boolean): void {
13
+ enabled = value
14
+ }
15
+
16
+ export function isAutoCommitEnabled(): boolean {
17
+ return enabled
18
+ }
package/src/defaults.ts CHANGED
@@ -1,8 +1,17 @@
1
- import type { ResolvedKeymapConfig } from './types'
1
+ import type { AutoCommitConfig, ResolvedKeymapConfig } from './types'
2
2
 
3
3
  import * as actions from './actions'
4
4
  import { KeymapBuilder } from './keymap-builder'
5
5
 
6
+ export const DEFAULT_AUTO_COMMIT_CONFIG: AutoCommitConfig = {
7
+ enabled: false,
8
+ models: {
9
+ claude: 'claude-haiku-4-5',
10
+ codex: 'gpt-5-mini',
11
+ },
12
+ timeoutMs: 60_000,
13
+ }
14
+
6
15
  /**
7
16
  * Build the default keymap — 1:1 transcription of all existing handlers.
8
17
  */
@@ -66,6 +75,7 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
66
75
  .mode(['navigation', 'terminal-input'], (m) =>
67
76
  m
68
77
  .map('<Leader>b', actions.toggleSessionBar, 'Toggle session bar')
78
+ .map('<Leader>u', actions.toggleAIUsage, 'Toggle AI usage')
69
79
  .map('<Leader>1', actions.switchSessionByIndex(1), 'Session 1')
70
80
  .map('<Leader>2', actions.switchSessionByIndex(2), 'Session 2')
71
81
  .map('<Leader>3', actions.switchSessionByIndex(3), 'Session 3')
@@ -138,6 +148,13 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
138
148
  .passthrough()
139
149
  )
140
150
 
151
+ // -----------------------------------------------------------------------
152
+ // Modal: ai-usage (info-only)
153
+ // -----------------------------------------------------------------------
154
+ .mode('modal.ai-usage', (m) =>
155
+ m.map('<Esc>', actions.closeModal, 'Close').map('<Leader>u', actions.toggleAIUsage, 'Close')
156
+ )
157
+
141
158
  // -----------------------------------------------------------------------
142
159
  // Modal: update-available
143
160
  // -----------------------------------------------------------------------
@@ -276,10 +293,30 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
276
293
  m
277
294
  .map('<Esc>', actions.gitCommitCancel, 'Cancel')
278
295
  .map('<C-CR>', actions.gitCommitSubmit, 'Commit')
296
+ .map('<C-a>', actions.gitCommitEnterConfirm, 'Auto-commit (stage all)')
279
297
  .map('<Tab>', actions.switchField, 'Next field')
280
298
  .map('<CR>', actions.gitCommitReturnKey, 'Newline / confirm')
281
299
  .passthrough()
282
300
  )
283
301
 
302
+ // -----------------------------------------------------------------------
303
+ // Modal: git-commit confirm (auto-commit stage all + commit)
304
+ // -----------------------------------------------------------------------
305
+ .mode('modal.git-commit.confirm', (m) =>
306
+ m
307
+ .map('<Esc>', actions.gitCommitLeaveConfirm, 'Cancel auto-commit')
308
+ .map('<CR>', actions.gitCommitAutoAccept, 'Stage all + commit')
309
+ .map('<C-CR>', actions.gitCommitAutoAccept, 'Stage all + commit')
310
+ .map('<Tab>', actions.switchField, 'Next field')
311
+ .passthrough()
312
+ )
313
+
314
+ // -----------------------------------------------------------------------
315
+ // Modal: git-commit generating (waiting for LLM)
316
+ // -----------------------------------------------------------------------
317
+ .mode('modal.git-commit.generating', (m) =>
318
+ m.map('<Esc>', actions.gitCommitLeaveGenerating, 'Cancel generation')
319
+ )
320
+
284
321
  return kb._build()
285
322
  }
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ export { paletteToShikiTheme } from './palette-to-shiki'
22
22
  export { GroupBuilder, KeymapBuilder, ModeBindingBuilder } from './keymap-builder'
23
23
  export { getDefaultKeymapConfig } from './defaults'
24
24
  export { resolveConfig } from './resolver'
25
+ export { isAutoCommitEnabled, setAutoCommitEnabled } from './auto-commit-runtime'
25
26
 
26
27
  // Type exports
27
28
  export type {
@@ -33,8 +34,11 @@ export type {
33
34
  AimuxThemeConfig,
34
35
  // User-facing config
35
36
  AimuxUserConfig,
37
+ AIUsageTool,
38
+ AIUsageToolConfig,
36
39
  AppAction,
37
40
  AppState,
41
+ AutoCommitConfig,
38
42
  BackendConfig,
39
43
  BindingDef,
40
44
  FocusMode,
@@ -68,6 +72,7 @@ export type {
68
72
  SnippetDef,
69
73
  SnippetRecord,
70
74
  SplitDirection,
75
+ StatusBarConfig,
71
76
  TabSession,
72
77
  Theme,
73
78
  ThemeId,
package/src/resolver.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import type {
2
2
  AimuxUserConfig,
3
+ AutoCommitConfig,
3
4
  ModeId,
4
5
  ModeKeymapDef,
5
6
  ResolvedConfig,
6
7
  ResolvedKeymapConfig,
7
8
  } from './types'
8
9
 
9
- import { getDefaultKeymapConfig } from './defaults'
10
+ import { DEFAULT_AUTO_COMMIT_CONFIG, getDefaultKeymapConfig } from './defaults'
10
11
  import { KeymapBuilder } from './keymap-builder'
11
12
 
12
13
  /**
@@ -16,15 +17,97 @@ import { KeymapBuilder } from './keymap-builder'
16
17
  export function resolveConfig(userConfig: AimuxUserConfig): ResolvedConfig {
17
18
  const keymaps = resolveKeymaps(userConfig)
18
19
 
20
+ const autoCommit: AutoCommitConfig = {
21
+ enabled: userConfig.autoCommit?.enabled ?? DEFAULT_AUTO_COMMIT_CONFIG.enabled,
22
+ models: { ...DEFAULT_AUTO_COMMIT_CONFIG.models, ...userConfig.autoCommit?.models },
23
+ timeoutMs: userConfig.autoCommit?.timeoutMs ?? DEFAULT_AUTO_COMMIT_CONFIG.timeoutMs,
24
+ }
25
+
19
26
  return {
27
+ autoCommit,
20
28
  backends: userConfig.backends ?? {},
21
- gitPane: userConfig.gitPane ?? {},
29
+ gitPane: resolveGitPane(userConfig.gitPane),
22
30
  hooks: userConfig.hooks ?? {},
23
31
  keymaps,
24
- sessionBar: userConfig.sessionBar ?? {},
32
+ sessionBar: resolveSessionBar(userConfig.sessionBar),
25
33
  sidebar: userConfig.sidebar ?? {},
26
34
  snippets: userConfig.snippets ?? [],
27
- theme: userConfig.theme,
35
+ statusBar: userConfig.statusBar ?? {},
36
+ theme: resolveTheme(userConfig.theme),
37
+ }
38
+ }
39
+
40
+ function resolveTheme(userConfig: AimuxUserConfig['theme']): ResolvedConfig['theme'] {
41
+ if (!userConfig) return undefined
42
+ return {
43
+ initialMode: userConfig.initialMode ?? userConfig.mode,
44
+ paletteOverrides: userConfig.paletteOverrides,
45
+ }
46
+ }
47
+
48
+ function resolveSessionBar(
49
+ userConfig: AimuxUserConfig['sessionBar']
50
+ ): ResolvedConfig['sessionBar'] {
51
+ if (!userConfig) return {}
52
+ return {
53
+ initialPosition: userConfig.initialPosition ?? userConfig.position,
54
+ initialVisible: userConfig.initialVisible ?? userConfig.visible,
55
+ }
56
+ }
57
+
58
+ function resolvePaneInitialPosition(
59
+ userConfig: NonNullable<AimuxUserConfig['gitPane']>
60
+ ): 'left' | 'right' | undefined {
61
+ if (userConfig.initialPosition === 'left' || userConfig.initialPosition === 'right') {
62
+ return userConfig.initialPosition
63
+ }
64
+ if (userConfig.position === 'left' || userConfig.position === 'right') {
65
+ return userConfig.position
66
+ }
67
+ return undefined
68
+ }
69
+
70
+ function resolveEmbeddedInitialPosition(
71
+ userConfig: NonNullable<AimuxUserConfig['gitPane']>
72
+ ): 'top' | 'bottom' | undefined {
73
+ if (userConfig.initialPosition === 'top' || userConfig.initialPosition === 'bottom') {
74
+ return userConfig.initialPosition
75
+ }
76
+ if (userConfig.position === 'top' || userConfig.position === 'bottom') {
77
+ return userConfig.position
78
+ }
79
+ return undefined
80
+ }
81
+
82
+ function resolveGitPane(userConfig: AimuxUserConfig['gitPane']): ResolvedConfig['gitPane'] {
83
+ if (!userConfig) return {}
84
+
85
+ const initialMode = userConfig.initialMode ?? userConfig.mode
86
+ const paneInitialPosition = resolvePaneInitialPosition(userConfig)
87
+ const embeddedInitialPosition = resolveEmbeddedInitialPosition(userConfig)
88
+ const shared = {
89
+ diffCount: userConfig.diffCount,
90
+ initialDiffModeRatio: userConfig.initialDiffModeRatio ?? userConfig.diffModeRatio,
91
+ initialFileListMode: userConfig.initialFileListMode ?? userConfig.fileListMode,
92
+ initialRatio: userConfig.initialRatio ?? userConfig.ratio,
93
+ initialTreeCompaction: userConfig.initialTreeCompaction ?? userConfig.treeCompaction,
94
+ initialVisible: userConfig.initialVisible ?? userConfig.visible,
95
+ path: userConfig.path,
96
+ prefetchRadius: userConfig.prefetchRadius,
97
+ }
98
+
99
+ if (initialMode === 'pane') {
100
+ return {
101
+ ...shared,
102
+ initialMode: 'pane',
103
+ initialPosition: paneInitialPosition,
104
+ }
105
+ }
106
+
107
+ return {
108
+ ...shared,
109
+ ...(initialMode === 'embedded' ? { initialMode: 'embedded' as const } : {}),
110
+ initialPosition: embeddedInitialPosition,
28
111
  }
29
112
  }
30
113
 
package/src/types.ts CHANGED
@@ -24,7 +24,10 @@ export type ModeId =
24
24
  | 'modal.help.filtering'
25
25
  | 'modal.split-picker'
26
26
  | 'modal.git-commit'
27
+ | 'modal.git-commit.confirm'
28
+ | 'modal.git-commit.generating'
27
29
  | 'modal.update-available'
30
+ | 'modal.ai-usage'
28
31
 
29
32
  // ─── Primitive app types ──────────────────────────────────────────────────────
30
33
 
@@ -63,6 +66,7 @@ export interface TerminalLine {
63
66
 
64
67
  export interface TerminalSnapshot {
65
68
  lines: TerminalLine[]
69
+ tailLines?: TerminalLine[]
66
70
  viewportY: number
67
71
  baseY: number
68
72
  cursorVisible: boolean
@@ -291,6 +295,7 @@ export interface ModalGitCommit extends ModalBase {
291
295
  type: 'git-commit'
292
296
  activeField: 'title' | 'body'
293
297
  contentBuffer: string
298
+ stage: 'edit' | 'generating' | 'confirm'
294
299
  }
295
300
  export interface ModalSnippetEditor extends ModalBase {
296
301
  type: 'snippet-editor'
@@ -304,6 +309,10 @@ export interface ModalUpdateAvailable extends ModalBase {
304
309
  latestVersion: string
305
310
  }
306
311
 
312
+ export interface ModalAIUsage extends ModalBase {
313
+ type: 'ai-usage'
314
+ }
315
+
307
316
  export type ModalState =
308
317
  | ModalClosed
309
318
  | ModalNewTab
@@ -318,6 +327,7 @@ export type ModalState =
318
327
  | ModalSnippetEditor
319
328
  | ModalGitCommit
320
329
  | ModalUpdateAvailable
330
+ | ModalAIUsage
321
331
 
322
332
  export interface LayoutState {
323
333
  terminalCols: number
@@ -331,6 +341,28 @@ export interface SessionBarState {
331
341
  position: SessionBarPosition
332
342
  }
333
343
 
344
+ export type AutoCommitSuggestion =
345
+ | { kind: 'idle' }
346
+ | {
347
+ kind: 'generating'
348
+ tabId: string
349
+ workingTreeHash: string
350
+ abortController: AbortController
351
+ startedAt: number
352
+ }
353
+ | {
354
+ kind: 'ready'
355
+ tabId: string
356
+ workingTreeHash: string
357
+ title: string
358
+ body: string
359
+ generatedAt: number
360
+ }
361
+
362
+ export interface AutoCommitState {
363
+ bySession: Record<string, AutoCommitSuggestion>
364
+ }
365
+
334
366
  export interface AppState {
335
367
  tabs: TabSession[]
336
368
  activeTabId: string | null
@@ -349,6 +381,7 @@ export interface AppState {
349
381
  customCommands: Record<AssistantId, string>
350
382
  gitPanel: GitPanelState
351
383
  gitMode: GitModeState
384
+ autoCommit: AutoCommitState
352
385
  pendingChords: string[] | null
353
386
  }
354
387
 
@@ -380,13 +413,14 @@ export type ModalAction =
380
413
  | { type: 'set-theme-entry-count'; count: number }
381
414
  | { type: 'open-theme-picker' }
382
415
  | { type: 'open-update-available-modal'; currentVersion: string; latestVersion: string }
416
+ | { type: 'open-ai-usage-modal' }
383
417
 
384
418
  export type SessionAction =
385
419
  | { type: 'load-session'; sessionId: string; workspaceSnapshot?: WorkspaceSnapshotV1 }
386
420
  | { type: 'set-sessions'; sessions: SessionRecord[] }
387
421
  | { type: 'create-session-record'; session: SessionRecord }
388
422
  | { type: 'rename-session-record'; sessionId: string; name: string }
389
- | { type: 'delete-session-record'; sessionId: string }
423
+ | { type: 'delete-session-record'; sessionId: string; openSessionPicker?: boolean }
390
424
  | { type: 'reorder-sessions'; orderedIds: string[] }
391
425
  | { type: 'set-session-status'; sessionId: string; status: SessionStatus }
392
426
 
@@ -503,12 +537,35 @@ export type GitModeAction =
503
537
  fromSection: GitFileSection
504
538
  toSection: GitFileSection | null
505
539
  }
506
- | { type: 'open-git-commit-modal' }
540
+ | { type: 'open-git-commit-modal'; sessionId?: string }
541
+ | { type: 'git-commit-enter-confirm' }
542
+ | { type: 'git-commit-leave-confirm' }
543
+ | { type: 'git-commit-enter-generating'; sessionId: string }
544
+ | { type: 'git-commit-leave-generating' }
507
545
 
508
546
  export type DataAction =
509
547
  | { type: 'set-snippets'; snippets: SnippetRecord[] }
510
548
  | { type: 'delete-snippet'; snippetId: string }
511
549
 
550
+ export type AutoCommitAction =
551
+ | {
552
+ type: 'auto-commit-generation-started'
553
+ sessionId: string
554
+ tabId: string
555
+ workingTreeHash: string
556
+ abortController: AbortController
557
+ startedAt: number
558
+ }
559
+ | {
560
+ type: 'auto-commit-generation-ready'
561
+ sessionId: string
562
+ workingTreeHash: string
563
+ title: string
564
+ body: string
565
+ generatedAt: number
566
+ }
567
+ | { type: 'auto-commit-clear'; sessionId: string }
568
+
512
569
  export type AppAction =
513
570
  | ModalAction
514
571
  | SessionAction
@@ -518,6 +575,7 @@ export type AppAction =
518
575
  | DataAction
519
576
  | GitPanelAction
520
577
  | GitModeAction
578
+ | AutoCommitAction
521
579
 
522
580
  // ─── Side effects ─────────────────────────────────────────────────────────────
523
581
 
@@ -552,6 +610,8 @@ export type SideEffect =
552
610
  | { type: 'git-restore'; path: string }
553
611
  | { type: 'git-rm'; path: string }
554
612
  | { type: 'git-commit'; title: string; body: string }
613
+ | { type: 'git-commit-auto'; title: string; body: string }
614
+ | { type: 'generate-auto-commit-now'; sessionId: string }
555
615
  | { type: 'git-push' }
556
616
  | { type: 'confirm-update-selection' }
557
617
  | { type: 'switch-session-by-index'; index: number }
@@ -692,7 +752,13 @@ export interface KeymapBuilderApi {
692
752
  // ─── Top-level user config ────────────────────────────────────────────────────
693
753
 
694
754
  export interface SessionBarConfig {
755
+ /** Startup override for the session bar visibility. Reapplied on each launch. */
756
+ initialVisible?: boolean
757
+ /** Startup override for the session bar position. Reapplied on each launch. */
758
+ initialPosition?: SessionBarPosition
759
+ /** @deprecated Use `initialVisible` instead. */
695
760
  visible?: boolean
761
+ /** @deprecated Use `initialPosition` instead. */
696
762
  position?: SessionBarPosition
697
763
  }
698
764
 
@@ -705,32 +771,78 @@ export type GitPanePathConfig =
705
771
  export type GitPaneDiffCountConfig = { enabled: boolean }
706
772
 
707
773
  interface GitPaneBaseConfig {
774
+ /** Startup override for git pane visibility. Reapplied on each launch. */
775
+ initialVisible?: boolean
776
+ /** Startup override for the pane split ratio. Reapplied on each launch. */
777
+ initialRatio?: number
778
+ /** Startup override for fullscreen diff ratio. Reapplied on each launch. */
779
+ initialDiffModeRatio?: number
780
+ /** Startup override for tree/flat file list mode. Reapplied on each launch. */
781
+ initialFileListMode?: GitFileListMode
782
+ /** Startup override for tree compaction. Reapplied on each launch. */
783
+ initialTreeCompaction?: boolean
784
+ path?: GitPanePathConfig
785
+ diffCount?: GitPaneDiffCountConfig
786
+ /** Prefetch N neighbouring diffs around the cursor; 0 disables. */
787
+ prefetchRadius?: number
788
+ /** @deprecated Use `initialVisible` instead. */
708
789
  visible?: boolean
790
+ /** @deprecated Use `initialRatio` instead. */
709
791
  ratio?: number
792
+ /** @deprecated Use `initialDiffModeRatio` instead. */
710
793
  diffModeRatio?: number
794
+ /** @deprecated Use `initialFileListMode` instead. */
711
795
  fileListMode?: GitFileListMode
796
+ /** @deprecated Use `initialTreeCompaction` instead. */
712
797
  treeCompaction?: boolean
713
- path?: GitPanePathConfig
714
- diffCount?: GitPaneDiffCountConfig
715
- /** Prefetch N neighbouring diffs around the cursor; 0 disables. */
716
- prefetchRadius?: number
717
798
  }
718
799
 
719
800
  export interface GitPaneEmbeddedConfig extends GitPaneBaseConfig {
801
+ initialMode?: 'embedded'
802
+ initialPosition?: 'top' | 'bottom'
803
+ /** @deprecated Use `initialMode` instead. */
720
804
  mode?: 'embedded'
805
+ /** @deprecated Use `initialPosition` instead. */
721
806
  position?: 'top' | 'bottom'
722
807
  }
723
808
 
724
809
  export interface GitPanePaneConfig extends GitPaneBaseConfig {
810
+ initialMode: 'pane'
811
+ initialPosition?: 'left' | 'right'
812
+ /** @deprecated Use `initialMode` instead. */
725
813
  mode: 'pane'
814
+ /** @deprecated Use `initialPosition` instead. */
726
815
  position?: 'left' | 'right'
727
816
  }
728
817
 
729
818
  export type GitPaneConfig = GitPaneEmbeddedConfig | GitPanePaneConfig
730
819
 
820
+ export interface AutoCommitConfig {
821
+ enabled: boolean
822
+ timeoutMs: number
823
+ models: Partial<Record<string, string>>
824
+ }
825
+
731
826
  export interface AimuxThemeConfig {
732
- mode?: ThemeMode
827
+ /** Startup override for light/dark theme family. Reapplied on each launch. */
828
+ initialMode?: ThemeMode
733
829
  paletteOverrides?: Partial<AimuxPalette>
830
+ /** @deprecated Use `initialMode` instead. */
831
+ mode?: ThemeMode
832
+ }
833
+
834
+ export type AIUsageTool = 'claude' | 'codex'
835
+
836
+ export interface AIUsageToolConfig {
837
+ enabled?: boolean
838
+ pollSeconds?: number
839
+ claudePlan?: 'auto' | 'pro' | 'max5' | 'max20'
840
+ codexWeeklyLimit?: number
841
+ tools?: AIUsageTool[]
842
+ }
843
+
844
+ export interface StatusBarConfig {
845
+ aiUsage?: AIUsageToolConfig
734
846
  }
735
847
 
736
848
  export interface AimuxUserConfig {
@@ -742,6 +854,8 @@ export interface AimuxUserConfig {
742
854
  gitPane?: GitPaneConfig
743
855
  hooks?: HooksConfig
744
856
  snippets?: SnippetDef[]
857
+ autoCommit?: Partial<AutoCommitConfig>
858
+ statusBar?: StatusBarConfig
745
859
  }
746
860
 
747
861
  // ─── Resolved config (internal) ───────────────────────────────────────────────
@@ -767,12 +881,46 @@ export interface ResolvedKeymapConfig {
767
881
  }
768
882
 
769
883
  export interface ResolvedConfig {
770
- theme: AimuxThemeConfig | undefined
884
+ theme:
885
+ | {
886
+ initialMode?: ThemeMode
887
+ paletteOverrides?: Partial<AimuxPalette>
888
+ }
889
+ | undefined
771
890
  keymaps: ResolvedKeymapConfig
772
891
  backends: Record<string, BackendConfig>
773
892
  sidebar: SidebarConfig
774
- sessionBar: SessionBarConfig
775
- gitPane: GitPaneConfig
893
+ sessionBar: {
894
+ initialVisible?: boolean
895
+ initialPosition?: SessionBarPosition
896
+ }
897
+ gitPane:
898
+ | {
899
+ initialMode?: 'embedded'
900
+ initialPosition?: 'top' | 'bottom'
901
+ initialVisible?: boolean
902
+ initialRatio?: number
903
+ initialDiffModeRatio?: number
904
+ initialFileListMode?: GitFileListMode
905
+ initialTreeCompaction?: boolean
906
+ path?: GitPanePathConfig
907
+ diffCount?: GitPaneDiffCountConfig
908
+ prefetchRadius?: number
909
+ }
910
+ | {
911
+ initialMode: 'pane'
912
+ initialPosition?: 'left' | 'right'
913
+ initialVisible?: boolean
914
+ initialRatio?: number
915
+ initialDiffModeRatio?: number
916
+ initialFileListMode?: GitFileListMode
917
+ initialTreeCompaction?: boolean
918
+ path?: GitPanePathConfig
919
+ diffCount?: GitPaneDiffCountConfig
920
+ prefetchRadius?: number
921
+ }
776
922
  hooks: HooksConfig
777
923
  snippets: SnippetDef[]
924
+ autoCommit: AutoCommitConfig
925
+ statusBar: StatusBarConfig
778
926
  }