@brimveyn/aimux-config 0.1.0

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/src/types.ts ADDED
@@ -0,0 +1,568 @@
1
+ // -----------------------------------------------------------------------------
2
+ // Self-contained types for the aimux keymap/config surface.
3
+ // These are duplicated from the aimux CLI's internal types to keep this
4
+ // package dependency-free. They must stay structurally identical with the
5
+ // CLI types in `aimux`'s `src/state/types.ts`, `src/input/modes/types.ts`,
6
+ // `src/ui/themes.ts`, and `src/state/layout-tree.ts`.
7
+ // -----------------------------------------------------------------------------
8
+
9
+ // ─── Mode identifiers ─────────────────────────────────────────────────────────
10
+
11
+ export type ModeId =
12
+ | 'navigation'
13
+ | 'terminal-input'
14
+ | 'layout'
15
+ | 'modal.new-tab'
16
+ | 'modal.new-tab.command-edit'
17
+ | 'modal.session-picker'
18
+ | 'modal.session-picker.filtering'
19
+ | 'modal.session-name'
20
+ | 'modal.create-session'
21
+ | 'modal.rename-tab'
22
+ | 'modal.snippet-picker'
23
+ | 'modal.snippet-picker.filtering'
24
+ | 'modal.snippet-editor'
25
+ | 'modal.theme-picker'
26
+ | 'modal.help'
27
+ | 'modal.split-picker'
28
+
29
+ // ─── Primitive app types ──────────────────────────────────────────────────────
30
+
31
+ export type BuiltinAssistantId = 'claude' | 'codex' | 'opencode' | 'terminal'
32
+ export type AssistantId = BuiltinAssistantId | (string & {})
33
+ export type TabStatus = 'starting' | 'running' | 'disconnected' | 'exited' | 'error'
34
+ export type TabActivity = 'busy' | 'idle'
35
+ export type FocusMode = 'navigation' | 'terminal-input' | 'modal' | 'command-edit' | 'layout'
36
+ export type SplitDirection = 'horizontal' | 'vertical'
37
+
38
+ // ─── Terminal data shapes ─────────────────────────────────────────────────────
39
+
40
+ export interface TerminalSpan {
41
+ text: string
42
+ fg?: string
43
+ bg?: string
44
+ bold?: boolean
45
+ italic?: boolean
46
+ underline?: boolean
47
+ cursor?: boolean
48
+ }
49
+
50
+ export interface TerminalLine {
51
+ spans: TerminalSpan[]
52
+ }
53
+
54
+ export interface TerminalSnapshot {
55
+ lines: TerminalLine[]
56
+ viewportY: number
57
+ baseY: number
58
+ cursorVisible: boolean
59
+ }
60
+
61
+ export interface TerminalModeState {
62
+ mouseTrackingMode: 'none' | 'x10' | 'vt200' | 'drag' | 'any'
63
+ sendFocusMode: boolean
64
+ alternateScrollMode: boolean
65
+ isAlternateBuffer: boolean
66
+ bracketedPasteMode: boolean
67
+ }
68
+
69
+ // ─── Layout ───────────────────────────────────────────────────────────────────
70
+
71
+ export type LayoutLeaf = { type: 'leaf'; tabId: string }
72
+ export type LayoutSplit = {
73
+ type: 'split'
74
+ direction: SplitDirection
75
+ ratio: number
76
+ first: LayoutNode
77
+ second: LayoutNode
78
+ }
79
+ export type LayoutNode = LayoutLeaf | LayoutSplit
80
+
81
+ // ─── Records ──────────────────────────────────────────────────────────────────
82
+
83
+ export interface PersistedTabSnapshot {
84
+ id: string
85
+ assistant: AssistantId
86
+ title: string
87
+ command: string
88
+ status: Exclude<TabStatus, 'disconnected'>
89
+ buffer: string
90
+ viewport?: TerminalSnapshot
91
+ terminalModes: TerminalModeState
92
+ errorMessage?: string
93
+ exitCode?: number
94
+ }
95
+
96
+ export interface WorkspaceSnapshotV1 {
97
+ version: 1
98
+ savedAt: string
99
+ activeTabId: string | null
100
+ sidebar: {
101
+ visible: boolean
102
+ width: number
103
+ gitPanelVisible?: boolean
104
+ gitPanelRatio?: number
105
+ }
106
+ tabs: PersistedTabSnapshot[]
107
+ layoutTree?: LayoutNode
108
+ layoutTrees?: Record<string, LayoutNode>
109
+ tabGroupMap?: Record<string, string>
110
+ }
111
+
112
+ export interface SessionRecord {
113
+ id: string
114
+ name: string
115
+ projectPath?: string
116
+ createdAt: string
117
+ updatedAt: string
118
+ lastOpenedAt: string
119
+ workspaceSnapshot?: WorkspaceSnapshotV1
120
+ }
121
+
122
+ export interface TabSession {
123
+ id: string
124
+ assistant: AssistantId
125
+ title: string
126
+ status: TabStatus
127
+ activity?: TabActivity
128
+ buffer: string
129
+ viewport?: TerminalSnapshot
130
+ terminalModes: TerminalModeState
131
+ command: string
132
+ errorMessage?: string
133
+ exitCode?: number
134
+ }
135
+
136
+ export interface SnippetRecord {
137
+ id: string
138
+ name: string
139
+ content: string
140
+ }
141
+
142
+ export type DirectoryResultType = 'git-repo' | 'worktree' | 'workspace'
143
+
144
+ export interface DirectoryResult {
145
+ path: string
146
+ type: DirectoryResultType
147
+ }
148
+
149
+ // ─── Sidebar / git panel / modal state (for ModeContext) ──────────────────────
150
+
151
+ export interface SidebarState {
152
+ visible: boolean
153
+ width: number
154
+ minWidth: number
155
+ maxWidth: number
156
+ gitPanelVisible: boolean
157
+ gitPanelRatio: number
158
+ }
159
+
160
+ export type GitFileStatus = 'M' | 'A' | 'D' | 'R' | 'C' | 'U' | '?'
161
+ export type GitFileSection = 'staged' | 'unstaged' | 'untracked'
162
+
163
+ export interface GitFileEntry {
164
+ path: string
165
+ renamedFrom?: string
166
+ section: GitFileSection
167
+ status: GitFileStatus
168
+ added: number | null
169
+ removed: number | null
170
+ }
171
+
172
+ export type GitPanelError = 'not-a-repo' | 'unknown'
173
+
174
+ export interface GitPanelState {
175
+ branch: string | null
176
+ ahead: number
177
+ behind: number
178
+ files: GitFileEntry[]
179
+ error: GitPanelError | null
180
+ }
181
+
182
+ interface ModalBase {
183
+ selectedIndex: number
184
+ editBuffer: string | null
185
+ sessionTargetId: string | null
186
+ }
187
+
188
+ export interface ModalClosed extends ModalBase {
189
+ type: null
190
+ editBuffer: null
191
+ sessionTargetId: null
192
+ }
193
+
194
+ export interface ModalNewTab extends ModalBase {
195
+ type: 'new-tab'
196
+ }
197
+ export interface ModalSessionPicker extends ModalBase {
198
+ type: 'session-picker'
199
+ }
200
+ export interface ModalSessionName extends ModalBase {
201
+ type: 'session-name'
202
+ }
203
+ export interface ModalRenameTab extends ModalBase {
204
+ type: 'rename-tab'
205
+ }
206
+ export interface ModalSnippetPicker extends ModalBase {
207
+ type: 'snippet-picker'
208
+ }
209
+ export interface ModalThemePicker extends ModalBase {
210
+ type: 'theme-picker'
211
+ }
212
+ export interface ModalHelp extends ModalBase {
213
+ type: 'help'
214
+ }
215
+ export interface ModalSplitPicker extends ModalBase {
216
+ type: 'split-picker'
217
+ splitDirection: SplitDirection
218
+ }
219
+ export interface ModalCreateSession extends ModalBase {
220
+ type: 'create-session'
221
+ directoryResults: DirectoryResult[]
222
+ pendingProjectPath: string | null
223
+ activeField: 'directory' | 'name'
224
+ nameBuffer: string
225
+ }
226
+ export interface ModalSnippetEditor extends ModalBase {
227
+ type: 'snippet-editor'
228
+ activeField: 'name' | 'content'
229
+ contentBuffer: string
230
+ }
231
+
232
+ export type ModalState =
233
+ | ModalClosed
234
+ | ModalNewTab
235
+ | ModalSessionPicker
236
+ | ModalSessionName
237
+ | ModalRenameTab
238
+ | ModalSnippetPicker
239
+ | ModalThemePicker
240
+ | ModalHelp
241
+ | ModalSplitPicker
242
+ | ModalCreateSession
243
+ | ModalSnippetEditor
244
+
245
+ export interface LayoutState {
246
+ terminalCols: number
247
+ terminalRows: number
248
+ }
249
+
250
+ export interface AppState {
251
+ tabs: TabSession[]
252
+ activeTabId: string | null
253
+ layoutTrees: Record<string, LayoutNode>
254
+ tabGroupMap: Record<string, string>
255
+ sessions: SessionRecord[]
256
+ currentSessionId: string | null
257
+ snippets: SnippetRecord[]
258
+ focusMode: FocusMode
259
+ sidebar: SidebarState
260
+ modal: ModalState
261
+ layout: LayoutState
262
+ customCommands: Record<AssistantId, string>
263
+ gitPanel: GitPanelState
264
+ pendingChords: string[] | null
265
+ }
266
+
267
+ // ─── AppAction union ──────────────────────────────────────────────────────────
268
+
269
+ export type ModalAction =
270
+ | { type: 'open-new-tab-modal' }
271
+ | { type: 'open-help-modal' }
272
+ | { type: 'open-split-picker'; direction: SplitDirection }
273
+ | { type: 'open-session-picker' }
274
+ | { type: 'open-session-name-modal'; sessionTargetId?: string; initialName?: string }
275
+ | { type: 'close-modal' }
276
+ | { type: 'move-modal-selection'; delta: number }
277
+ | { type: 'begin-command-edit' }
278
+ | { type: 'update-command-edit'; char: string }
279
+ | { type: 'commit-command-edit' }
280
+ | { type: 'cancel-command-edit' }
281
+ | { type: 'open-create-session-modal' }
282
+ | { type: 'set-directory-results'; results: DirectoryResult[] }
283
+ | { type: 'switch-create-session-field' }
284
+ | { type: 'select-directory' }
285
+ | { type: 'begin-session-filter' }
286
+ | { type: 'open-rename-tab-modal' }
287
+ | { type: 'open-snippet-picker' }
288
+ | { type: 'open-snippet-editor'; snippetId?: string }
289
+ | { type: 'begin-snippet-filter' }
290
+ | { type: 'open-theme-picker' }
291
+
292
+ export type SessionAction =
293
+ | { type: 'load-session'; sessionId: string; workspaceSnapshot?: WorkspaceSnapshotV1 }
294
+ | { type: 'set-sessions'; sessions: SessionRecord[] }
295
+ | { type: 'create-session-record'; session: SessionRecord }
296
+ | { type: 'rename-session-record'; sessionId: string; name: string }
297
+ | { type: 'delete-session-record'; sessionId: string }
298
+
299
+ export type TabAction =
300
+ | { type: 'add-tab'; tab: TabSession }
301
+ | {
302
+ type: 'hydrate-workspace'
303
+ tabs: TabSession[]
304
+ activeTabId: string | null
305
+ layoutTree?: LayoutNode | null
306
+ layoutTrees?: Record<string, LayoutNode>
307
+ tabGroupMap?: Record<string, string>
308
+ }
309
+ | { type: 'close-tab'; tabId: string }
310
+ | { type: 'close-active-tab' }
311
+ | { type: 'set-active-tab'; tabId: string }
312
+ | { type: 'move-active-tab'; delta: number }
313
+ | { type: 'reorder-active-tab'; delta: number }
314
+ | { type: 'reset-tab-session'; tabId: string }
315
+ | { type: 'rename-tab'; tabId: string; title: string }
316
+ | { type: 'append-tab-buffer'; tabId: string; chunk: string }
317
+ | {
318
+ type: 'replace-tab-viewport'
319
+ tabId: string
320
+ viewport: TerminalSnapshot
321
+ terminalModes: TerminalModeState
322
+ }
323
+ | { type: 'set-tab-activity'; tabId: string; activity?: TabActivity }
324
+ | { type: 'set-tab-status'; tabId: string; status: TabStatus; exitCode?: number }
325
+ | { type: 'set-tab-error'; tabId: string; message: string }
326
+
327
+ export type LayoutAction =
328
+ | {
329
+ type: 'split-pane'
330
+ direction: SplitDirection
331
+ newTab: TabSession
332
+ }
333
+ | { type: 'close-pane'; tabId: string }
334
+ | {
335
+ type: 'focus-pane-direction'
336
+ direction: 'left' | 'right' | 'up' | 'down'
337
+ }
338
+ | {
339
+ type: 'resize-pane'
340
+ tabId: string
341
+ delta: number
342
+ axis?: SplitDirection
343
+ }
344
+ | {
345
+ type: 'set-split-ratio'
346
+ tabId: string
347
+ ratio: number
348
+ axis?: SplitDirection
349
+ }
350
+
351
+ export type UIAction =
352
+ | { type: 'toggle-sidebar' }
353
+ | { type: 'resize-sidebar'; delta: number }
354
+ | { type: 'set-focus-mode'; focusMode: FocusMode }
355
+ | { type: 'set-terminal-size'; cols: number; rows: number }
356
+ | { type: 'toggle-git-panel' }
357
+ | { type: 'resize-git-panel'; delta: number }
358
+ | { type: 'set-pending-chords'; chords: string[] | null }
359
+
360
+ export interface GitRefreshPayload {
361
+ branch: string | null
362
+ ahead: number
363
+ behind: number
364
+ files: GitFileEntry[]
365
+ }
366
+
367
+ export type GitPanelAction =
368
+ | { type: 'git-refresh-success'; payload: GitRefreshPayload }
369
+ | { type: 'git-refresh-error'; kind: GitPanelError }
370
+ | { type: 'git-panel-reset' }
371
+
372
+ export type DataAction =
373
+ | { type: 'set-snippets'; snippets: SnippetRecord[] }
374
+ | { type: 'delete-snippet'; snippetId: string }
375
+
376
+ export type AppAction =
377
+ | ModalAction
378
+ | SessionAction
379
+ | TabAction
380
+ | LayoutAction
381
+ | UIAction
382
+ | DataAction
383
+ | GitPanelAction
384
+
385
+ // ─── Side effects ─────────────────────────────────────────────────────────────
386
+
387
+ export type SideEffect =
388
+ | { type: 'quit'; state: AppState }
389
+ | { type: 'launch-selected-assistant' }
390
+ | { type: 'confirm-selected-session' }
391
+ | { type: 'delete-selected-session' }
392
+ | { type: 'open-rename-selected-session' }
393
+ | { type: 'create-session'; name: string; projectPath?: string }
394
+ | { type: 'close-tab'; tabId: string }
395
+ | { type: 'restart-tab'; tab: TabSession }
396
+ | { type: 'paste-selected-snippet' }
397
+ | { type: 'paste-snippet-to-group' }
398
+ | { type: 'edit-selected-snippet' }
399
+ | { type: 'delete-selected-snippet' }
400
+ | { type: 'save-snippet-editor' }
401
+ | { type: 'save-custom-command' }
402
+ | { type: 'apply-theme'; action: 'open' }
403
+ | { type: 'apply-theme'; action: 'restore' }
404
+ | { type: 'apply-theme'; action: 'confirm' }
405
+ | { type: 'apply-theme'; action: 'preview'; delta: 1 | -1 }
406
+ | { type: 'rename-session'; sessionId: string; name: string }
407
+ | { type: 'split-pane'; direction: SplitDirection }
408
+ | { type: 'confirm-split' }
409
+
410
+ // ─── Key input / KeyResult / ModeContext ──────────────────────────────────────
411
+
412
+ /** Structural shape for a parsed key event. Matches @opentui/core's KeyEvent. */
413
+ export interface KeyInput {
414
+ name: string
415
+ ctrl: boolean
416
+ meta: boolean
417
+ shift: boolean
418
+ sequence: string
419
+ }
420
+
421
+ export interface KeyResult {
422
+ actions: AppAction[]
423
+ effects: SideEffect[]
424
+ transition?: ModeId
425
+ }
426
+
427
+ export interface ModeContext {
428
+ readonly state: AppState
429
+ }
430
+
431
+ // ─── Themes ───────────────────────────────────────────────────────────────────
432
+
433
+ export interface ThemeColors {
434
+ background: string
435
+ panel: string
436
+ panelMuted: string
437
+ panelHighlight: string
438
+ overlay: string
439
+ border: string
440
+ borderActive: string
441
+ text: string
442
+ textMuted: string
443
+ accent: string
444
+ accentAlt: string
445
+ warning: string
446
+ danger: string
447
+ success: string
448
+ dim: string
449
+ }
450
+
451
+ export type ThemeId =
452
+ | 'aimux'
453
+ | 'dracula'
454
+ | 'dracula-at-night'
455
+ | 'everforest'
456
+ | 'tokyo-night'
457
+ | 'gruvbox-dark'
458
+ | 'catppuccin-mocha'
459
+ | 'nord'
460
+ | 'solarized-dark'
461
+ | 'one-dark'
462
+ | 'kanagawa'
463
+
464
+ export interface ThemeDefinition {
465
+ base?: ThemeId
466
+ colors: ThemeColors
467
+ }
468
+
469
+ // ─── Backend config (stub) ────────────────────────────────────────────────────
470
+
471
+ export interface BackendConfig {
472
+ command: string
473
+ args?: string[]
474
+ }
475
+
476
+ // ─── Sidebar config (stub) ────────────────────────────────────────────────────
477
+
478
+ export interface SidebarConfig {
479
+ widgets?: string[]
480
+ width?: number
481
+ }
482
+
483
+ // ─── Hooks config (stub) ──────────────────────────────────────────────────────
484
+
485
+ export interface HooksConfig {
486
+ onSessionCreate?: (session: { name: string; projectPath?: string }) => void | Promise<void>
487
+ }
488
+
489
+ // ─── Snippet config (stub) ────────────────────────────────────────────────────
490
+
491
+ export interface SnippetDef {
492
+ name: string
493
+ trigger?: string
494
+ text: string
495
+ }
496
+
497
+ // ─── Action value types ───────────────────────────────────────────────────────
498
+
499
+ export type ActionFn = (ctx: ModeContext) => KeyResult | null
500
+ export type Action = KeyResult | ActionFn
501
+
502
+ // ─── Keymap builder API types ─────────────────────────────────────────────────
503
+
504
+ export interface GroupBuilderApi {
505
+ map(keys: string, action: Action): GroupBuilderApi
506
+ group(
507
+ prefix: string,
508
+ name: string,
509
+ configure: (g: GroupBuilderApi) => GroupBuilderApi
510
+ ): GroupBuilderApi
511
+ }
512
+
513
+ export interface ModeBindingBuilderApi {
514
+ map(keys: string, action: Action): ModeBindingBuilderApi
515
+ unmap(keys: string): ModeBindingBuilderApi
516
+ group(
517
+ prefix: string,
518
+ name: string,
519
+ configure: (g: GroupBuilderApi) => GroupBuilderApi
520
+ ): ModeBindingBuilderApi
521
+ passthrough(): ModeBindingBuilderApi
522
+ }
523
+
524
+ export interface KeymapBuilderApi {
525
+ leader(key: string): KeymapBuilderApi
526
+ timeout(ms: number): KeymapBuilderApi
527
+ mode(id: ModeId, configure: (m: ModeBindingBuilderApi) => ModeBindingBuilderApi): KeymapBuilderApi
528
+ }
529
+
530
+ // ─── Top-level user config ────────────────────────────────────────────────────
531
+
532
+ export interface AimuxUserConfig {
533
+ theme?: ThemeId | ThemeDefinition
534
+ keymaps?: (k: KeymapBuilderApi) => KeymapBuilderApi
535
+ backends?: Record<string, BackendConfig>
536
+ sidebar?: SidebarConfig
537
+ hooks?: HooksConfig
538
+ snippets?: SnippetDef[]
539
+ }
540
+
541
+ // ─── Resolved config (internal) ───────────────────────────────────────────────
542
+
543
+ export interface BindingDef {
544
+ keys: string
545
+ result: Action
546
+ group?: string
547
+ }
548
+
549
+ export interface ModeKeymapDef {
550
+ bindings: BindingDef[]
551
+ removals: string[]
552
+ isPassthrough: boolean
553
+ }
554
+
555
+ export interface ResolvedKeymapConfig {
556
+ leader: string
557
+ timeout: number
558
+ modes: Map<ModeId, ModeKeymapDef>
559
+ }
560
+
561
+ export interface ResolvedConfig {
562
+ theme: ThemeId | ThemeDefinition | undefined
563
+ keymaps: ResolvedKeymapConfig
564
+ backends: Record<string, BackendConfig>
565
+ sidebar: SidebarConfig
566
+ hooks: HooksConfig
567
+ snippets: SnippetDef[]
568
+ }