@brimveyn/aimux-config 0.10.8 → 0.11.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 CHANGED
@@ -1,119 +1,58 @@
1
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`.
2
+ // The aimux keymap/config surface: everything a user's `aimux.config.ts` and a
3
+ // plugin author write against.
4
+ //
5
+ // The application state, mode and layout shapes it builds on live in
6
+ // `./app-types` and are re-exported here, so this module stays the one import
7
+ // a config file needs. Both halves are SOURCE definitions — `src/` re-exports
8
+ // from this package rather than declaring its own copies, which is what makes
9
+ // the two impossible to drift apart.
7
10
  // -----------------------------------------------------------------------------
8
11
 
9
- // ─── Mode identifiers ─────────────────────────────────────────────────────────
12
+ export type * from './app-types'
10
13
 
11
- export type ModeId =
12
- | 'navigation'
13
- | 'terminal-input'
14
- | 'git-mode'
15
- | 'modal.new-tab.command-edit'
16
- | 'modal.new-tab.editing-command'
17
- | 'modal.workspace-delete-confirm'
18
- | 'modal.project-picker.filtering'
19
- | 'modal.project-name'
20
- | 'modal.create-project'
21
- | 'modal.create-workspace'
22
- | 'modal.rename-tab'
23
- | 'modal.rename-workspace'
24
- | 'modal.setting-text'
25
- | 'modal.settings-search.filtering'
26
- | 'modal.snippet-picker.filtering'
27
- | 'modal.snippet-editor'
28
- | 'modal.theme-picker.filtering'
29
- | 'modal.help.filtering'
30
- | 'modal.split-picker'
31
- | 'modal.git-commit'
32
- | 'modal.git-commit.confirm'
33
- | 'modal.git-commit.generating'
34
- | 'modal.update-available'
35
- | 'modal.workspace-move'
36
- | 'modal.workspace-move-confirm'
37
- | 'modal.flash-jump'
38
- | 'modal.quotas'
39
- | 'settings'
40
- | 'stats'
14
+ import type {
15
+ GitFileListMode,
16
+ GitPaneDiffCountConfig,
17
+ GitPanePathConfig,
18
+ KeyResult,
19
+ ModeContext,
20
+ ModeId,
21
+ } from './app-types'
41
22
 
42
- // ─── Primitive app types ──────────────────────────────────────────────────────
23
+ // ─── Mode identifiers ─────────────────────────────────────────────────────────
43
24
 
44
- export type BuiltinAssistantId =
45
- | 'claude'
46
- | 'codex'
47
- | 'opencode'
48
- | 'grok'
49
- | 'kimi'
50
- | 'terminal'
51
- | 'antigravity'
52
- export type AssistantId = BuiltinAssistantId | (string & {})
53
- export type TabStatus = 'starting' | 'running' | 'disconnected' | 'error'
25
+ // ─── Primitive app types ──────────────────────────────────────────────────────
54
26
 
55
- /**
56
- * Status values that may appear in legacy on-disk project snapshots but are
57
- * not produced by the running app anymore.
58
- */
59
- export type LegacyPersistedTabStatus = TabStatus | 'exited'
60
- export type TabActivity = 'working' | 'waiting-input' | 'idle'
61
- export interface ProjectStatus {
62
- working: boolean
63
- waiting: boolean
64
- }
65
- export interface WorkspaceActivity {
66
- working: boolean
67
- waiting: boolean
68
- done: boolean
69
- }
70
- export type FocusMode =
71
- | 'navigation'
72
- | 'terminal-input'
73
- | 'modal'
74
- | 'command-edit'
75
- | 'git'
76
- | 'settings'
77
- | 'stats'
78
27
  export type SplitDirection = 'horizontal' | 'vertical'
79
28
 
80
29
  // ─── Terminal data shapes ─────────────────────────────────────────────────────
81
30
 
82
- export interface TerminalSpan {
83
- text: string
84
- fg?: string
85
- bg?: string
86
- bold?: boolean
87
- italic?: boolean
88
- underline?: boolean
89
- cursor?: boolean
90
- }
91
-
92
- export interface TerminalLine {
93
- spans: TerminalSpan[]
94
- }
95
-
96
- export interface TerminalSnapshot {
97
- lines: TerminalLine[]
98
- tailLines?: TerminalLine[]
99
- viewportY: number
100
- baseY: number
101
- cursorVisible: boolean
102
- }
103
-
104
- export interface TerminalModeState {
105
- mouseTrackingMode: 'none' | 'x10' | 'vt200' | 'drag' | 'any'
106
- sendFocusMode: boolean
107
- alternateScrollMode: boolean
108
- isAlternateBuffer: boolean
109
- bracketedPasteMode: boolean
110
- }
111
-
112
31
  // ─── Layout ───────────────────────────────────────────────────────────────────
113
32
 
33
+ /**
34
+ * What occupies a pane. `tab` is a terminal; `plugin` is a renderer a plugin
35
+ * registered, and has no PTY behind it.
36
+ */
37
+ export type LayoutLeafKind = 'tab' | 'plugin'
38
+
114
39
  export interface LayoutLeaf {
115
40
  type: 'leaf'
41
+ /**
42
+ * The tab id — or, when `kind` is `'plugin'`, the qualified pane id
43
+ * (`<pluginId>.<paneId>`).
44
+ *
45
+ * Still called `tabId` because it is the key in every layout ever written to
46
+ * `aimux.json`, and a rename would be a migration for the sake of a name.
47
+ * Code that means "whatever is in this pane" should say so by using
48
+ * `allPaneIds`; code that means "a terminal" uses `allTabIds`.
49
+ */
116
50
  tabId: string
51
+ /**
52
+ * Absent means `'tab'`. Every persisted layout omits it, and every pane that
53
+ * existed before plugins could hold one is a terminal.
54
+ */
55
+ kind?: LayoutLeafKind
117
56
  }
118
57
  export interface LayoutSplit {
119
58
  type: 'split'
@@ -124,895 +63,10 @@ export interface LayoutSplit {
124
63
  }
125
64
  export type LayoutNode = LayoutLeaf | LayoutSplit
126
65
 
127
- // ─── Records ──────────────────────────────────────────────────────────────────
128
-
129
- export interface PersistedTabSnapshot {
130
- id: string
131
- assistant: AssistantId
132
- title: string
133
- command: string
134
- status: Exclude<LegacyPersistedTabStatus, 'disconnected'>
135
- buffer: string
136
- viewport?: TerminalSnapshot
137
- terminalModes: TerminalModeState
138
- errorMessage?: string
139
- exitCode?: number
140
- workspaceId?: string
141
- autoRenameStatus?: 'eligible' | 'attempted'
142
- }
143
-
144
- export interface ProjectSnapshotV1 {
145
- version: 1
146
- savedAt: string
147
- activeTabId: string | null
148
- sidebar: {
149
- visible: boolean
150
- width: number
151
- }
152
- tabs: PersistedTabSnapshot[]
153
- layoutTree?: LayoutNode
154
- layoutTrees?: Record<string, LayoutNode>
155
- tabGroupMap?: Record<string, string>
156
- /**
157
- * Last viewed tab per workspace, keyed by workspace id. Optional and additive
158
- * (no version bump): older builds ignore it, newer builds tolerate its
159
- * absence. Written now so the data accrues, but restoring it at startup is
160
- * gated off until a future change flips RESTORE_LAST_ACTIVE_TAB_BY_WORKSPACE.
161
- */
162
- lastActiveTabByWorkspace?: Record<string, string>
163
- }
164
-
165
- export type WorkspaceSource = 'primary' | 'aimux-temp' | 'external'
166
-
167
- export interface WorkspaceRecord {
168
- id: string
169
- name: string
170
- path: string
171
- repoRoot: string
172
- branch?: string
173
- baseRef?: string
174
- commitSha?: string
175
- source: WorkspaceSource
176
- createdByAimux: boolean
177
- color?: string
178
- createdAt: string
179
- updatedAt: string
180
- /** Last setup-script run for this workspace. Display only — the auto-run gate
181
- * is "this session has not seen this workspace id before". */
182
- setupRanAt?: string
183
- setupExitCode?: number
184
- }
185
-
186
- export interface ProjectRecord {
187
- id: string
188
- name: string
189
- projectPath?: string
190
- createdAt: string
191
- updatedAt: string
192
- lastOpenedAt: string
193
- order?: number
194
- projectSnapshot?: ProjectSnapshotV1
195
- workspaces?: WorkspaceRecord[]
196
- activeWorkspaceId?: string
197
- }
198
-
199
- export interface TabSession {
200
- id: string
201
- assistant: AssistantId
202
- title: string
203
- status: TabStatus
204
- activity?: TabActivity
205
- buffer: string
206
- viewport?: TerminalSnapshot
207
- terminalModes: TerminalModeState
208
- command: string
209
- errorMessage?: string
210
- exitCode?: number
211
- workspaceId?: string
212
- /** This tab finished a turn while you were elsewhere. See `src/state/types.ts`. */
213
- unseen?: boolean
214
- autoRenameStatus?: 'eligible' | 'attempted'
215
- /** Real PTY tab that no chrome enumerates. See `src/state/types.ts`. */
216
- hidden?: boolean
217
- /** Who owns this tab, independent of visibility. See `src/state/types.ts`. */
218
- role?: 'setup'
219
- }
220
-
221
- export interface SnippetRecord {
222
- id: string
223
- name: string
224
- content: string
225
- trigger?: string
226
- vars?: Record<string, SnippetVar>
227
- }
228
-
229
- export type DirectoryResultType = 'git-repo' | 'workspace' | 'project'
230
-
231
- export interface DirectoryResult {
232
- path: string
233
- type: DirectoryResultType
234
- }
235
-
236
- // ─── Bars / git panel / modal state (for ModeContext) ─────────────────────────
237
-
238
- export type BarSide = 'left' | 'right'
239
-
240
- export interface BarWidget {
241
- id: string
242
- grow: number
243
- visible: boolean
244
- }
245
-
246
- export interface BarState {
247
- visible: boolean
248
- width: number
249
- /** Ordered top → bottom. */
250
- widgets: BarWidget[]
251
- }
252
-
253
- export type BarsState = Record<BarSide, BarState>
254
-
255
- export interface GitPaneState {
256
- diffModeRatio: number
257
- fileListMode: GitFileListMode
258
- treeCompaction: boolean
259
- path: GitPanePathConfig
260
- diffCount: GitPaneDiffCountConfig
261
- prefetchRadius: number
262
- }
263
-
264
- export type GitFileStatus = 'M' | 'A' | 'D' | 'R' | 'C' | 'U' | '?'
265
- export type GitFileSection = 'staged' | 'unstaged' | 'untracked' | 'historical'
266
-
267
- export interface GitFileEntry {
268
- path: string
269
- renamedFrom?: string
270
- section: GitFileSection
271
- status: GitFileStatus
272
- added: number | null
273
- removed: number | null
274
- repoPath?: string
275
- }
276
-
277
- export type GitPanelError = 'not-a-repo' | 'unknown'
278
-
279
- export interface GitPanelState {
280
- branch: string | null
281
- ahead: number
282
- behind: number
283
- files: GitFileEntry[]
284
- error: GitPanelError | null
285
- }
286
-
287
- export type DiffFileStatus =
288
- | 'modified'
289
- | 'new'
290
- | 'deleted'
291
- | 'binary'
292
- | 'renamed'
293
- | 'image'
294
- | 'too-large'
295
-
296
- export interface DiffData {
297
- path: string
298
- status: DiffFileStatus
299
- oldPath?: string
300
- rawDiff: string
301
- binarySizeBefore?: number
302
- binarySizeAfter?: number
303
- errorMessage?: string
304
- imageBytesBefore?: Uint8Array
305
- imageBytesAfter?: Uint8Array
306
- imageMime?: string
307
- imageFormatLabel?: string
308
- }
309
-
310
- export type GitDiffView = 'split' | 'stacked'
311
- export type GitFileListMode = 'tree' | 'flat'
312
-
313
- export interface FoldState {
314
- top: number
315
- bottom: number
316
- }
317
-
318
- export interface GitModeState {
319
- selectedEntryKey: string | null
320
- collapsedFolders: Record<string, true>
321
- diffs: Record<string, DiffData>
322
- /** Parsed diff cache (internal); detailed shape lives in src/state/types. */
323
- parsedFiles: Record<string, { hash: string; file: unknown }>
324
- /** Syntax-highlight cache (internal). */
325
- highlights: Record<string, { hash: string; themeId: string; add: unknown; del: unknown }>
326
- loading: Record<string, boolean>
327
- pendingDeletePath: string | null
328
- actionMessage: string | null
329
- diffView: GitDiffView
330
- folds: Record<string, Record<string, FoldState>>
331
- headOffset: number
332
- reviewBase: boolean
333
- }
334
-
335
- interface ModalBase {
336
- selectedIndex: number
337
- editBuffer: string | null
338
- projectTargetId: string | null
339
- cursorPos?: number
340
- /**
341
- * Where the focus goes when this modal closes, when it is not back to the
342
- * panes. Set by whoever opened it, because that is who knows what is behind it
343
- * — the settings screen opens five different modals, and none of them should
344
- * have to grow a case in the reducer to find their way home.
345
- */
346
- returnTo?: FocusMode
347
- }
348
-
349
- export interface ModalClosed extends ModalBase {
350
- type: null
351
- editBuffer: null
352
- projectTargetId: null
353
- }
354
-
355
- export interface ModalNewTab extends ModalBase {
356
- type: 'new-tab'
357
- editingCommand: AssistantId | null
358
- /** Set when `create-workspace` chained into this picker. */
359
- pendingWorkspace?: PendingWorkspaceLaunch
360
- /** A prompt for the picked assistant, without the workspace pinning/rename. */
361
- pendingPrompt?: string
362
- }
363
- export interface PendingWorkspaceLaunch {
364
- projectId: string
365
- workspaceId: string
366
- prompt: string
367
- }
368
- export interface ModalProjectPicker extends ModalBase {
369
- type: 'project-picker'
370
- }
371
- export interface ModalProjectName extends ModalBase {
372
- type: 'project-name'
373
- returnToProjectPicker: boolean
374
- }
375
- export interface ModalRenameTab extends ModalBase {
376
- type: 'rename-tab'
377
- }
378
- /** Fuzzy-ish search across every setting, from inside the settings screen. */
379
- export interface ModalSettingsSearch extends ModalBase {
380
- type: 'settings-search'
381
- }
382
- export interface ModalSettingText extends ModalBase {
383
- type: 'setting-text'
384
- settingId: string
385
- settingLabel: string
386
- }
387
- export interface ModalRenameWorkspace extends ModalBase {
388
- type: 'rename-workspace'
389
- workspaceProjectId: string
390
- }
391
- export interface ModalSnippetPicker extends ModalBase {
392
- type: 'snippet-picker'
393
- actionMessage?: string | null
394
- }
395
- export interface ModalThemePicker extends ModalBase {
396
- type: 'theme-picker'
397
- entryCount: number
398
- }
399
- export interface ModalHelp extends ModalBase {
400
- type: 'help'
401
- entryCount: number
402
- scope: ModeId | null
403
- }
404
- export interface ModalSplitPicker extends ModalBase {
405
- type: 'split-picker'
406
- splitDirection: SplitDirection
407
- }
408
- export interface ModalCreateProject extends ModalBase {
409
- type: 'create-project'
410
- directoryResults: DirectoryResult[]
411
- pendingProjectPath: string | null
412
- activeField: 'directory' | 'name'
413
- nameBuffer: string
414
- returnToProjectPicker: boolean
415
- }
416
- export interface ModalCreateWorkspace extends ModalBase {
417
- type: 'create-workspace'
418
- activeField: 'prompt' | 'base'
419
- /** "What do you want to work on?" — names the workspace and its branch. */
420
- prompt: string
421
- branchError: string | null
422
- baseQuery: string
423
- baseRef: string
424
- baseBranches: string[]
425
- }
426
- export interface ModalGitCommit extends ModalBase {
427
- type: 'git-commit'
428
- activeField: 'title' | 'body'
429
- contentBuffer: string
430
- stage: 'edit' | 'generating' | 'confirm'
431
- }
432
- export interface ModalSnippetEditor extends ModalBase {
433
- type: 'snippet-editor'
434
- activeField: 'name' | 'trigger' | 'content'
435
- nameBuffer: string
436
- triggerBuffer: string
437
- contentBuffer: string
438
- }
439
-
440
- export interface ModalUpdateAvailable extends ModalBase {
441
- type: 'update-available'
442
- currentVersion: string
443
- latestVersion: string
444
- }
445
-
446
- /** The status bar's usage indicator, expanded. Carries nothing: it is a readout. */
447
- export interface ModalQuotas extends ModalBase {
448
- type: 'quotas'
449
- }
450
-
451
- export interface ModalWorkspaceMove extends ModalBase {
452
- type: 'workspace-move'
453
- /** The workspace being moved (may differ from the active one, e.g. a tab menu). */
454
- sourceWorkspaceId: string
455
- deleteSource: boolean
456
- /** Per-workspace dirty file counts, loaded async when the modal opens. */
457
- stats: { kind: 'loading' } | { kind: 'ready'; dirtyFiles: Record<string, number> }
458
- }
459
-
460
- /**
461
- * Confirmation for a recoverable move failure: the target's dirty files
462
- * overlap the incoming changes (stash-target) or the squash conflicts
463
- * (keep-conflicts). Both workspaces are already restored; confirming re-runs
464
- * move-workspace with the matching flag.
465
- */
466
- export interface ModalWorkspaceMoveConfirm extends ModalBase {
467
- type: 'workspace-move-confirm'
468
- variant: 'stash-target' | 'keep-conflicts'
469
- files: string[]
470
- projectId: string
471
- sourceWorkspaceId: string
472
- targetWorkspaceId: string
473
- deleteSource: boolean
474
- sourceLabel: string
475
- targetLabel: string
476
- }
477
-
478
- /**
479
- * Standalone confirmation for a recoverable workspace delete failure triggered
480
- * outside the new-tab picker (e.g. the sidebar's "Remove workspace"). Carries the
481
- * params needed to re-run the delete with force once confirmed.
482
- */
483
- export interface ModalWorkspaceDeleteConfirm extends ModalBase {
484
- type: 'workspace-delete-confirm'
485
- projectId: string
486
- workspaceId: string
487
- workspaceLabel: string
488
- reason: string
489
- closeTabs: boolean
490
- /** Whether confirming force-deletes — true only after a recoverable failure. */
491
- force: boolean
492
- }
493
-
494
- export type FlashJumpTargetKind = 'project' | 'workspace' | 'tab'
495
-
496
- export interface FlashJumpTarget {
497
- kind: FlashJumpTargetKind
498
- projectIndex: number
499
- projectId: string
500
- workspaceId?: string
501
- tabId?: string
502
- }
503
-
504
- export interface FlashLabel {
505
- key: string
506
- label: string
507
- target: FlashJumpTarget
508
- }
509
-
510
- export interface ModalFlashJump extends ModalBase {
511
- type: 'flash-jump'
512
- labels: FlashLabel[]
513
- buffer: string
514
- pendingJump: FlashJumpTarget | null
515
- }
516
-
517
- export type ModalState =
518
- | ModalClosed
519
- | ModalNewTab
520
- | ModalProjectPicker
521
- | ModalProjectName
522
- | ModalRenameTab
523
- | ModalRenameWorkspace
524
- | ModalSnippetPicker
525
- | ModalThemePicker
526
- | ModalHelp
527
- | ModalSplitPicker
528
- | ModalCreateProject
529
- | ModalCreateWorkspace
530
- | ModalSnippetEditor
531
- | ModalGitCommit
532
- | ModalUpdateAvailable
533
- | ModalQuotas
534
- | ModalWorkspaceMove
535
- | ModalWorkspaceMoveConfirm
536
- | ModalWorkspaceDeleteConfirm
537
- | ModalFlashJump
538
- | ModalSettingText
539
- | ModalSettingsSearch
540
-
541
- export interface LayoutState {
542
- terminalCols: number
543
- terminalRows: number
544
- }
545
-
546
- export interface ProjectBarState {
547
- visible: boolean
548
- }
549
-
550
- export type AutoCommitSuggestion =
551
- | { kind: 'idle' }
552
- | {
553
- kind: 'generating'
554
- tabId: string
555
- workingTreeHash: string
556
- abortController: AbortController
557
- startedAt: number
558
- }
559
- | {
560
- kind: 'ready'
561
- tabId: string
562
- workingTreeHash: string
563
- title: string
564
- body: string
565
- generatedAt: number
566
- }
567
-
568
- export interface AutoCommitState {
569
- byProject: Record<string, AutoCommitSuggestion>
570
- }
571
-
572
- export interface DiscoveredRepo {
573
- path: string
574
- name: string
575
- isRoot: boolean
576
- }
577
-
578
- export interface MultiRepoState {
579
- repos: DiscoveredRepo[]
580
- prefixes: Record<string, string>
581
- }
582
-
583
- /** Where the cursor is in the settings screen — the cursor only, not the values. */
584
- export interface SettingsUIState {
585
- rowIndex: number
586
- }
587
-
588
- /** Mirror of the CLI's `StatsUIState`. */
589
- export interface StatsUIState {
590
- pageIndex: number
591
- scrollTop: number
592
- }
593
-
594
- export interface AppState {
595
- tabs: TabSession[]
596
- activeTabId: string | null
597
- layoutTrees: Record<string, LayoutNode>
598
- tabGroupMap: Record<string, string>
599
- projects: ProjectRecord[]
600
- currentProjectId: string | null
601
- projectStatuses: Record<string, ProjectStatus>
602
- projectBar: ProjectBarState
603
- snippets: SnippetRecord[]
604
- focusMode: FocusMode
605
- bars: BarsState
606
- gitPane: GitPaneState
607
- modal: ModalState
608
- layout: LayoutState
609
- customCommands: Record<AssistantId, string>
610
- gitPanel: GitPanelState
611
- gitMode: GitModeState
612
- autoCommit: AutoCommitState
613
- multiRepo: MultiRepoState
614
- settings: SettingsUIState
615
- stats: StatsUIState
616
- workspaceDivergence: Record<string, BranchDivergence>
617
- workspaceActivity: Record<string, WorkspaceActivity>
618
- lastActiveTabByWorkspace: Record<string, string>
619
- pendingChords: string[] | null
620
- }
621
-
622
- // ─── AppAction union ──────────────────────────────────────────────────────────
623
-
624
- export type ModalAction =
625
- | {
626
- type: 'open-new-tab-modal'
627
- pendingWorkspace?: PendingWorkspaceLaunch
628
- pendingPrompt?: string
629
- }
630
- | { type: 'open-help-modal'; scope?: ModeId }
631
- | { type: 'open-split-picker'; direction: SplitDirection }
632
- | { type: 'open-project-picker' }
633
- | {
634
- type: 'open-project-name-modal'
635
- projectTargetId?: string
636
- initialName?: string
637
- returnToProjectPicker?: boolean
638
- }
639
- | { type: 'close-modal' }
640
- | { type: 'move-modal-selection'; delta: number }
641
- | {
642
- type: 'move-modal-cursor'
643
- delta?: number
644
- to?: 'end' | 'home' | 'line-down' | 'line-up' | 'word-left' | 'word-right'
645
- }
646
- | { type: 'update-command-edit'; char: string }
647
- | { type: 'cancel-command-edit' }
648
- | { type: 'open-create-project-modal'; returnToProjectPicker: boolean }
649
- | { type: 'open-create-workspace-modal' }
650
- | { type: 'set-create-workspace-base-branches'; branches: string[]; defaultBranch?: string }
651
- | { type: 'set-create-workspace-branch-error'; message: string | null }
652
- | { type: 'switch-create-workspace-field' }
653
- | { type: 'set-directory-results'; results: DirectoryResult[] }
654
- | { type: 'switch-create-project-field' }
655
- | { type: 'select-directory' }
656
- | { type: 'open-rename-tab-modal' }
657
- | {
658
- type: 'open-rename-workspace-modal'
659
- projectId: string
660
- workspaceId: string
661
- initialName: string
662
- }
663
- | { type: 'open-snippet-picker'; returnTo?: FocusMode }
664
- | { type: 'open-snippet-editor'; snippetId?: string }
665
- | { type: 'set-help-entry-count'; count: number }
666
- | { type: 'set-theme-entry-count'; count: number }
667
- | { type: 'open-theme-picker'; returnTo?: FocusMode }
668
- | { type: 'open-update-available-modal'; currentVersion: string; latestVersion: string }
669
- | { type: 'open-quotas-modal' }
670
- | { type: 'set-modal-selection-index'; index: number }
671
- | { type: 'open-edit-custom-command'; assistantId: AssistantId }
672
- | { type: 'open-workspace-move-modal'; sourceWorkspaceId: string }
673
- | { type: 'toggle-workspace-move-delete' }
674
- | { type: 'set-workspace-move-stats'; dirtyFiles: Record<string, number> }
675
- | {
676
- type: 'open-workspace-move-confirm'
677
- variant: 'stash-target' | 'keep-conflicts'
678
- files: string[]
679
- projectId: string
680
- sourceWorkspaceId: string
681
- targetWorkspaceId: string
682
- deleteSource: boolean
683
- sourceLabel: string
684
- targetLabel: string
685
- }
686
- | {
687
- type: 'open-workspace-delete-confirm'
688
- projectId: string
689
- workspaceId: string
690
- workspaceLabel: string
691
- reason: string
692
- closeTabs: boolean
693
- force: boolean
694
- }
695
- | { type: 'open-flash-jump-modal' }
696
- | { type: 'clear-flash-jump-pending' }
697
-
698
- export type ProjectAction =
699
- | { type: 'load-project'; projectId: string; projectSnapshot?: ProjectSnapshotV1 }
700
- | { type: 'set-projects'; projects: ProjectRecord[] }
701
- | { type: 'create-project-record'; project: ProjectRecord }
702
- | { type: 'rename-project-record'; projectId: string; name: string }
703
- | { type: 'delete-project-record'; projectId: string; openProjectPicker?: boolean }
704
- | { type: 'reorder-projects'; orderedIds: string[] }
705
- | { type: 'reorder-active-project'; delta: number }
706
- | { type: 'set-project-status'; projectId: string; status: ProjectStatus }
707
- | { type: 'set-workspace-activity'; workspaceId: string; working: boolean; waiting: boolean }
708
- | { type: 'mark-workspace-done'; workspaceId: string }
709
- | {
710
- type: 'add-workspace-record'
711
- projectId: string
712
- workspace: WorkspaceRecord
713
- activate?: boolean
714
- }
715
- | { type: 'set-active-workspace'; projectId: string; workspaceId: string }
716
- | {
717
- type: 'update-workspace-record'
718
- projectId: string
719
- workspaceId: string
720
- patch: Partial<WorkspaceRecord>
721
- }
722
-
723
- export type TabAction =
724
- | { type: 'add-tab'; tab: TabSession }
725
- | {
726
- type: 'hydrate-project'
727
- tabs: TabSession[]
728
- activeTabId: string | null
729
- layoutTree?: LayoutNode | null
730
- layoutTrees?: Record<string, LayoutNode>
731
- tabGroupMap?: Record<string, string>
732
- }
733
- | { type: 'close-tab'; tabId: string }
734
- | { type: 'close-active-tab' }
735
- | { type: 'set-active-tab'; tabId: string }
736
- | { type: 'move-active-tab'; delta: number }
737
- | { type: 'reorder-active-tab'; delta: number }
738
- | { type: 'reset-tab-project'; tabId: string }
739
- | { type: 'rename-tab'; tabId: string; title: string }
740
- | { type: 'append-tab-buffer'; tabId: string; chunk: string }
741
- | {
742
- type: 'replace-tab-viewport'
743
- tabId: string
744
- viewport: TerminalSnapshot
745
- terminalModes: TerminalModeState
746
- }
747
- | { type: 'set-tab-activity'; tabId: string; activity?: TabActivity }
748
- | { type: 'set-tab-error'; tabId: string; message: string }
749
-
750
- export type LayoutAction =
751
- | {
752
- type: 'split-pane'
753
- direction: SplitDirection
754
- newTab: TabSession
755
- }
756
- | { type: 'close-pane'; tabId: string }
757
- | {
758
- type: 'focus-pane-direction'
759
- direction: 'left' | 'right' | 'up' | 'down'
760
- }
761
- | {
762
- type: 'resize-pane'
763
- tabId: string
764
- delta: number
765
- axis?: SplitDirection
766
- }
767
- | {
768
- type: 'set-split-ratio'
769
- tabId: string
770
- ratio: number
771
- axis?: SplitDirection
772
- }
773
-
774
- export type UIAction =
775
- | { type: 'toggle-bar'; side: BarSide }
776
- | { type: 'resize-bar'; side: BarSide; delta: number }
777
- | { type: 'set-bar-width'; side: BarSide; width: number }
778
- | { type: 'toggle-widget'; widgetId: string }
779
- | { type: 'move-widget'; widgetId: string; side: BarSide; index: number }
780
- | { type: 'set-bar-boundary'; side: BarSide; index: number; ratio: number }
781
- | { type: 'resize-widget'; widgetId: string; delta: number }
782
- | { type: 'set-focus-mode'; focusMode: FocusMode }
783
- | { type: 'set-terminal-size'; cols: number; rows: number }
784
- | { type: 'resize-git-diff-pane'; delta: number }
785
- | { type: 'set-pending-chords'; chords: string[] | null }
786
- | { type: 'toggle-project-bar' }
787
-
788
- export type SettingsAction =
789
- | { type: 'enter-settings' }
790
- | { type: 'exit-settings' }
791
- | { type: 'settings-move-selection'; delta: -1 | 1 }
792
- | { type: 'settings-jump-section'; delta: -1 | 1 }
793
- | { type: 'settings-select-row'; rowIndex: number }
794
- | { type: 'open-settings-search' }
795
- | { type: 'open-setting-text-modal'; settingId: string; label: string; value: string }
796
-
797
- export type StatsAction =
798
- | { type: 'enter-stats' }
799
- | { type: 'exit-stats' }
800
- | { type: 'stats-move-page'; delta: -1 | 1 }
801
- | { type: 'stats-select-page'; pageIndex: number }
802
- /** Rows, not pixels: the view holds a scroll offset the page applies to its box. */
803
- | { type: 'stats-scroll'; delta: number }
804
- /** The offset the scrollbox actually accepted, sent back so the state cannot run past the page. */
805
- | { type: 'stats-scroll-settled'; scrollTop: number }
806
-
807
- export interface GitRefreshPayload {
808
- branch: string | null
809
- ahead: number
810
- behind: number
811
- files: GitFileEntry[]
812
- }
813
-
814
- /** Commits a workspace branch is ahead/behind the ref it forked from. */
815
- export interface BranchDivergence {
816
- ahead: number
817
- behind: number
818
- /** Lines changed since the fork point, working tree included. */
819
- added?: number
820
- removed?: number
821
- }
822
-
823
- export type GitPanelAction =
824
- | { type: 'git-refresh-success'; payload: GitRefreshPayload }
825
- | { type: 'git-refresh-error'; kind: GitPanelError }
826
- | { type: 'git-panel-reset' }
827
- | { type: 'set-workspace-divergence'; divergence: Record<string, BranchDivergence> }
828
- | { type: 'set-git-pane'; patch: Partial<GitPaneState> }
829
-
830
- export type GitModeAction =
831
- | { type: 'enter-git-mode' }
832
- | { type: 'exit-git-mode' }
833
- | { type: 'git-mode-move-selection'; delta: -1 | 1 }
834
- | { type: 'git-mode-move-file-selection'; delta: -1 | 1 }
835
- | { type: 'git-mode-select-entry-by-key'; key: string }
836
- | { type: 'git-mode-toggle-folder'; key: string }
837
- | { type: 'git-mode-toggle-selected-folder' }
838
- | { type: 'git-mode-collapse-selection' }
839
- | { type: 'git-mode-expand-selection' }
840
- | { type: 'git-mode-toggle-file-list-mode' }
841
- | { type: 'git-mode-toggle-tree-compaction' }
842
- | { type: 'git-mode-set-diff'; key: string; diff: DiffData; hash: string }
843
- | { type: 'git-mode-set-loading'; key: string; loading: boolean }
844
- | { type: 'git-mode-set-pending-delete'; path: string | null }
845
- | { type: 'git-mode-clear-diff-cache'; path: string }
846
- | { type: 'git-mode-set-message'; message: string | null }
847
- | { type: 'snippet-picker-set-message'; message: string | null }
848
- | { type: 'git-mode-toggle-diff-view' }
849
- | { type: 'git-mode-toggle-review-base' }
850
- | { type: 'git-mode-shift-head-offset'; delta: number }
851
- | { type: 'git-mode-set-head-offset'; offset: number }
852
- | {
853
- type: 'git-mode-fold-adjust'
854
- key: string
855
- foldId: string
856
- side: 'top' | 'bottom'
857
- delta: number
858
- }
859
- | { type: 'git-mode-fold-set'; key: string; foldId: string; top: number; bottom: number }
860
- | { type: 'git-mode-fold-toggle-all'; key: string }
861
- | {
862
- type: 'git-mode-optimistic-move'
863
- path: string
864
- fromSection: GitFileSection
865
- toSection: GitFileSection | null
866
- }
867
- | { type: 'open-git-commit-modal'; projectId?: string }
868
- | { type: 'git-commit-enter-confirm' }
869
- | { type: 'git-commit-leave-confirm' }
870
- | { type: 'git-commit-enter-generating'; projectId: string }
871
- | { type: 'git-commit-leave-generating' }
872
-
873
- export type DataAction =
874
- | { type: 'set-snippets'; snippets: SnippetRecord[] }
875
- | { type: 'delete-snippet'; snippetId: string }
876
-
877
- export type AutoCommitAction =
878
- | {
879
- type: 'auto-commit-generation-started'
880
- projectId: string
881
- tabId: string
882
- workingTreeHash: string
883
- abortController: AbortController
884
- startedAt: number
885
- }
886
- | {
887
- type: 'auto-commit-generation-ready'
888
- projectId: string
889
- workingTreeHash: string
890
- title: string
891
- body: string
892
- generatedAt: number
893
- }
894
- | { type: 'auto-commit-clear'; projectId: string }
895
-
896
- export type AppAction =
897
- | ModalAction
898
- | ProjectAction
899
- | TabAction
900
- | LayoutAction
901
- | UIAction
902
- | SettingsAction
903
- | StatsAction
904
- | DataAction
905
- | GitPanelAction
906
- | GitModeAction
907
- | AutoCommitAction
908
-
909
66
  // ─── Side effects ─────────────────────────────────────────────────────────────
910
67
 
911
- export type SideEffect =
912
- | { type: 'quit'; state: AppState }
913
- | { type: 'open-new-tab' }
914
- | { type: 'launch-selected-assistant' }
915
- | { type: 'edit-selected-assistant' }
916
- | { type: 'confirm-selected-project' }
917
- | { type: 'delete-selected-project' }
918
- | { type: 'open-rename-selected-project' }
919
- | { type: 'create-project'; name: string; projectPath?: string }
920
- | { type: 'create-workspace' }
921
- | { type: 'load-create-workspace-base-branches' }
922
- | { type: 'close-tab'; tabId: string }
923
- | { type: 'restart-tab'; tab: TabSession }
924
- | { type: 'paste-selected-snippet' }
925
- | { type: 'paste-snippet-to-group' }
926
- | { type: 'edit-selected-snippet' }
927
- | { type: 'delete-selected-snippet' }
928
- | { type: 'save-snippet-editor' }
929
- | { type: 'save-custom-command' }
930
- | { type: 'apply-theme'; action: 'open' }
931
- | { type: 'apply-theme'; action: 'restore' }
932
- | { type: 'apply-theme'; action: 'confirm' }
933
- | { type: 'apply-theme'; action: 'preview'; delta: 1 | -1 }
934
- | { type: 'rename-project'; projectId: string; name: string }
935
- | { type: 'rename-tab'; tabId: string; title: string }
936
- | { type: 'split-pane'; direction: SplitDirection; sourceTabId?: string }
937
- | { type: 'confirm-split' }
938
- | { type: 'scroll-git-diff'; delta: number }
939
- | { type: 'persist-git-diff-mode-ratio'; ratio: number }
940
- | { type: 'persist-git-file-list-mode'; mode: GitFileListMode }
941
- | { type: 'persist-git-tree-compaction'; enabled: boolean }
942
- | { type: 'git-stage'; path: string }
943
- | { type: 'git-unstage'; path: string }
944
- | { type: 'git-stage-all' }
945
- | { type: 'git-unstage-all' }
946
- | { type: 'git-restore'; path: string }
947
- | { type: 'git-rm'; path: string }
948
- | { type: 'git-commit'; title: string; body: string }
949
- | { type: 'git-commit-auto'; title: string; body: string }
950
- | { type: 'generate-auto-commit-now'; projectId: string }
951
- | { type: 'git-push' }
952
- | { type: 'confirm-update-selection' }
953
- | { type: 'switch-project-by-index'; index: number }
954
- | { type: 'cycle-sidebar-item'; direction: 1 | -1 }
955
- | { type: 'switch-tab-by-index'; index: number }
956
- | { type: 'delete-project'; projectId: string }
957
- | {
958
- type: 'delete-workspace'
959
- projectId: string
960
- workspaceId: string
961
- // Force the git worktree removal (discards uncommitted changes in the
962
- // workspace). Also implies closing the workspace's tabs.
963
- force?: boolean
964
- // Close the workspace's tabs without forcing the git removal. Lets the
965
- // sidebar "Remove workspace" clean up tabs (avoiding orphans) while still
966
- // refusing to discard uncommitted work in a temp workspace.
967
- closeTabs?: boolean
968
- }
969
- | {
970
- type: 'move-workspace'
971
- projectId: string
972
- sourceWorkspaceId: string
973
- targetWorkspaceId: string
974
- deleteSource?: boolean
975
- // Retry flags set by the workspace-move-confirm dialog.
976
- stashTarget?: boolean
977
- keepConflicts?: boolean
978
- }
979
- | { type: 'load-workspace-move-stats' }
980
- | { type: 'toggle-transparent' }
981
- | { type: 'toggle-mode' }
982
- | { type: 'open-file-in-editor'; path: string }
983
- | { type: 'open-selected-snippet-source-in-editor' }
984
- | { type: 'run-setup' }
985
- | { type: 'stop-setup' }
986
- | { type: 'configure-setup-script'; projectId?: string }
987
- | { type: 'ask-agent-for-setup-script' }
988
- | { type: 'promote-setup-tab' }
989
- | { type: 'activate-settings-row' }
990
- | { type: 'adjust-settings-row'; delta: 1 | -1 }
991
- | { type: 'reset-settings-row' }
992
- | { type: 'confirm-settings-search' }
993
- | { type: 'commit-setting-text'; settingId: string; value: string }
994
-
995
68
  // ─── Key input / KeyResult / ModeContext ──────────────────────────────────────
996
69
 
997
- /** Structural shape for a parsed key event. Matches @opentui/core's KeyEvent. */
998
- export interface KeyInput {
999
- name: string
1000
- ctrl: boolean
1001
- meta: boolean
1002
- shift: boolean
1003
- sequence: string
1004
- }
1005
-
1006
- export interface KeyResult {
1007
- actions: AppAction[]
1008
- effects: SideEffect[]
1009
- transition?: ModeId
1010
- }
1011
-
1012
- export interface ModeContext {
1013
- readonly state: AppState
1014
- }
1015
-
1016
70
  // ─── Themes ───────────────────────────────────────────────────────────────────
1017
71
 
1018
72
  // Theme types now live in `./tui` (1:1 port of opencode TUI). The aliases
@@ -1040,6 +94,35 @@ export interface HooksConfig {
1040
94
  onProjectCreate?: (project: { name: string; projectPath?: string }) => void | Promise<void>
1041
95
  }
1042
96
 
97
+ // ─── Plugin config ────────────────────────────────────────────────────────────
98
+
99
+ /**
100
+ * A plugin declared from `aimux.config.ts` rather than through
101
+ * `aimux plugin link`. Either form works; this one is the version-controllable
102
+ * half, and it outranks `aimux-plugins.json` on every key it sets.
103
+ */
104
+ export interface PluginConfigEntry {
105
+ /**
106
+ * Directory holding `aimux-plugin.json`. Relative paths resolve against the
107
+ * profile config directory — the one `aimux.config.ts` itself lives in.
108
+ * Omit it to configure a plugin that is already linked or installed.
109
+ */
110
+ path?: string
111
+ /**
112
+ * Plugin id. Required when there is no `path`; otherwise it is checked
113
+ * against the manifest so a moved directory fails loudly instead of loading
114
+ * something else.
115
+ */
116
+ id?: string
117
+ /** Default true. `false` keeps the plugin registered but never loads it. */
118
+ enabled?: boolean
119
+ /** Merged over the manifest defaults and the registry, and wins over both. */
120
+ config?: Record<string, unknown>
121
+ }
122
+
123
+ /** A bare string is shorthand for `{ path }`. */
124
+ export type PluginConfigDecl = string | PluginConfigEntry
125
+
1043
126
  // ─── Snippet config (stub) ────────────────────────────────────────────────────
1044
127
 
1045
128
  /**
@@ -1111,6 +194,20 @@ export interface KeymapBuilderApi {
1111
194
  id: ModeId | readonly ModeId[],
1112
195
  configure: (m: ModeBindingBuilderApi) => ModeBindingBuilderApi
1113
196
  ): KeymapBuilderApi
197
+ /**
198
+ * Binds an action a plugin contributes, by its qualified `<pluginId>.<verb>`
199
+ * name:
200
+ *
201
+ * ```ts
202
+ * k.mode('navigation', (m) => m.map('<leader>r', k.plugin('acme.review.open')))
203
+ * ```
204
+ *
205
+ * A name, not a function, because the keymap is resolved at startup and
206
+ * plugins load after it. Requiring an import would mean a config file could
207
+ * only reference plugins it could reach — the coupling a plugin system
208
+ * exists to remove. An unresolved name is inert, the way an unbound key is.
209
+ */
210
+ plugin(name: string): Action
1114
211
  }
1115
212
 
1116
213
  // ─── Top-level user config ────────────────────────────────────────────────────
@@ -1124,14 +221,6 @@ export interface ProjectBarConfig {
1124
221
 
1125
222
  // ─── Git pane config (discriminated union) ────────────────────────────────────
1126
223
 
1127
- export type GitPanePathConfig =
1128
- | { enabled: false }
1129
- | { enabled: true; pathFn?: (path: string) => string }
1130
-
1131
- export interface GitPaneDiffCountConfig {
1132
- enabled: boolean
1133
- }
1134
-
1135
224
  interface GitPaneBaseConfig {
1136
225
  /** Startup override for git pane visibility. Reapplied on each launch. */
1137
226
  initialVisible?: boolean
@@ -1243,7 +332,12 @@ export interface AimuxThemeConfig {
1243
332
  }
1244
333
  }
1245
334
 
1246
- export type AIUsageTool = 'claude' | 'codex'
335
+ /**
336
+ * A tool the usage indicator can poll. Widened with `(string & {})` because a
337
+ * plugin assistant can declare a `usage` adapter and its id is not knowable
338
+ * here; the literal half survives, so the built-in two still autocomplete.
339
+ */
340
+ export type AIUsageTool = 'claude' | 'codex' | (string & {})
1247
341
 
1248
342
  export interface AIUsageToolConfig {
1249
343
  enabled?: boolean
@@ -1332,6 +426,11 @@ export interface AimuxUserConfig {
1332
426
  statusBar?: StatusBarConfig
1333
427
  externalEditor?: ExternalEditorConfig
1334
428
  integrations?: AimuxIntegrationsConfig
429
+ /**
430
+ * Plugins to load, in addition to anything `aimux plugin link/install`
431
+ * registered. See `docs/developer/plugins.md`.
432
+ */
433
+ plugins?: PluginConfigDecl[]
1335
434
  /**
1336
435
  * @deprecated Removed. Workspace provisioning is a per-project setup script
1337
436
  * now — see `docs/guide/workspaces.md#setup`. Declared here only so the strike
@@ -1406,4 +505,6 @@ export interface ResolvedConfig {
1406
505
  integrations: {
1407
506
  claudeHooks: boolean
1408
507
  }
508
+ /** Normalised to the object form; a bare string became `{ path }`. */
509
+ plugins: PluginConfigEntry[]
1409
510
  }