@brimveyn/aimux-config 0.5.13 → 0.6.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/package.json +1 -1
- package/src/actions.ts +137 -19
- package/src/backends.ts +1 -1
- package/src/defaults.ts +24 -1
- package/src/index.ts +2 -0
- package/src/resolver.ts +5 -0
- package/src/tui/registry.ts +1 -1
- package/src/tui/resolve.ts +1 -7
- package/src/types.ts +126 -5
package/package.json
CHANGED
package/src/actions.ts
CHANGED
|
@@ -80,7 +80,7 @@ export const splitHorizontal: KeyResult = r(
|
|
|
80
80
|
)
|
|
81
81
|
|
|
82
82
|
export const enterInsert: ActionFn = (ctx: ModeContext) => {
|
|
83
|
-
if (!ctx.state.activeTabId) return null
|
|
83
|
+
if (!(ctx.state.activeTabId != null && ctx.state.activeTabId !== '')) return null
|
|
84
84
|
return r([{ focusMode: 'terminal-input', type: 'set-focus-mode' }], [], 'terminal-input')
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -88,13 +88,15 @@ export const closeModal: KeyResult = r([{ type: 'close-modal' }], [], 'navigatio
|
|
|
88
88
|
|
|
89
89
|
export const closeOverlayModal: KeyResult = r([{ type: 'close-modal' }])
|
|
90
90
|
|
|
91
|
+
export const cancelNewTabModal: KeyResult = r([{ type: 'cancel-command-edit' }])
|
|
92
|
+
|
|
91
93
|
// ---------------------------------------------------------------------------
|
|
92
94
|
// Dynamic actions (need ctx at runtime — ActionFn)
|
|
93
95
|
// ---------------------------------------------------------------------------
|
|
94
96
|
|
|
95
97
|
export const closeTab: ActionFn = (ctx: ModeContext) => {
|
|
96
98
|
const tabId = ctx.state.activeTabId
|
|
97
|
-
if (!tabId) return null
|
|
99
|
+
if (!(tabId != null && tabId !== '')) return null
|
|
98
100
|
return r([{ type: 'close-active-tab' }], [{ tabId, type: 'close-tab' }])
|
|
99
101
|
}
|
|
100
102
|
|
|
@@ -153,7 +155,7 @@ export function focusPane(direction: 'left' | 'right' | 'up' | 'down'): KeyResul
|
|
|
153
155
|
export function resizePane(delta: number, axis: 'horizontal' | 'vertical'): ActionFn {
|
|
154
156
|
return (ctx: ModeContext) => {
|
|
155
157
|
const tabId = ctx.state.activeTabId
|
|
156
|
-
if (!tabId) return null
|
|
158
|
+
if (!(tabId != null && tabId !== '')) return null
|
|
157
159
|
return r([{ axis, delta, tabId, type: 'resize-pane' }])
|
|
158
160
|
}
|
|
159
161
|
}
|
|
@@ -173,7 +175,45 @@ export function moveModalSelectionWithPreview(
|
|
|
173
175
|
// Modal-specific actions
|
|
174
176
|
// ---------------------------------------------------------------------------
|
|
175
177
|
|
|
176
|
-
export const launchSelectedAssistant:
|
|
178
|
+
export const launchSelectedAssistant: ActionFn = (ctx: ModeContext) => {
|
|
179
|
+
if (ctx.state.modal.type === 'new-tab' && ctx.state.modal.step === 'assistant') {
|
|
180
|
+
return r([{ type: 'select-new-tab-assistant' }])
|
|
181
|
+
}
|
|
182
|
+
if (
|
|
183
|
+
ctx.state.modal.type === 'new-tab' &&
|
|
184
|
+
ctx.state.modal.step === 'worktree' &&
|
|
185
|
+
ctx.state.modal.createWorktree
|
|
186
|
+
) {
|
|
187
|
+
return r([{ type: 'enter-new-tab-worktree-create' }])
|
|
188
|
+
}
|
|
189
|
+
return r([], [{ type: 'launch-selected-assistant' }])
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export const toggleNewTabWorktree: KeyResult = r([{ type: 'toggle-new-tab-worktree' }])
|
|
193
|
+
|
|
194
|
+
export const deleteSelectedWorktree: ActionFn = (ctx: ModeContext) => {
|
|
195
|
+
const modal = ctx.state.modal
|
|
196
|
+
const sessionId = ctx.state.currentSessionId
|
|
197
|
+
if (modal.type !== 'new-tab' || modal.step !== 'worktree' || modal.createWorktree) return null
|
|
198
|
+
if (!(sessionId != null && sessionId !== '')) return null
|
|
199
|
+
const session = ctx.state.sessions.find((entry) => entry.id === sessionId)
|
|
200
|
+
const worktree = session?.worktrees?.[modal.selectedIndex]
|
|
201
|
+
if (!worktree) return null
|
|
202
|
+
return r(
|
|
203
|
+
[
|
|
204
|
+
{ index: modal.selectedIndex, type: 'set-modal-selection-index' },
|
|
205
|
+
{ message: null, type: 'set-new-tab-worktree-delete-state' },
|
|
206
|
+
],
|
|
207
|
+
[
|
|
208
|
+
{
|
|
209
|
+
force: modal.worktreeDeleteConfirmId === worktree.id,
|
|
210
|
+
sessionId,
|
|
211
|
+
type: 'delete-worktree',
|
|
212
|
+
worktreeId: worktree.id,
|
|
213
|
+
},
|
|
214
|
+
]
|
|
215
|
+
)
|
|
216
|
+
}
|
|
177
217
|
|
|
178
218
|
export const cancelCommandEdit = (returnTo: KeyResult['transition']): KeyResult =>
|
|
179
219
|
r([{ type: 'cancel-command-edit' }], [], returnTo)
|
|
@@ -201,6 +241,10 @@ export const openSnippetEditor: KeyResult = r(
|
|
|
201
241
|
|
|
202
242
|
export const editSelectedSnippet: KeyResult = r([], [{ type: 'edit-selected-snippet' }])
|
|
203
243
|
export const deleteSelectedSnippet: KeyResult = r([], [{ type: 'delete-selected-snippet' }])
|
|
244
|
+
export const openSelectedSnippetSourceInEditor: KeyResult = r(
|
|
245
|
+
[],
|
|
246
|
+
[{ type: 'open-selected-snippet-source-in-editor' }]
|
|
247
|
+
)
|
|
204
248
|
|
|
205
249
|
export const pasteSelectedSnippet: KeyResult = r(
|
|
206
250
|
[{ type: 'close-modal' }],
|
|
@@ -278,6 +322,12 @@ export const cancelEditCustomCommand: KeyResult = r(
|
|
|
278
322
|
'modal.new-tab.command-edit'
|
|
279
323
|
)
|
|
280
324
|
|
|
325
|
+
export const editSelectedAssistant: KeyResult = r(
|
|
326
|
+
[],
|
|
327
|
+
[{ type: 'edit-selected-assistant' }],
|
|
328
|
+
'modal.new-tab.editing-command'
|
|
329
|
+
)
|
|
330
|
+
|
|
281
331
|
// ---------------------------------------------------------------------------
|
|
282
332
|
// Update-available modal
|
|
283
333
|
// ---------------------------------------------------------------------------
|
|
@@ -288,9 +338,66 @@ export const confirmUpdateSelection: KeyResult = r(
|
|
|
288
338
|
'navigation'
|
|
289
339
|
)
|
|
290
340
|
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
// Worktree-move modal
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
export const openWorktreeMove: ActionFn = (ctx: ModeContext) => {
|
|
346
|
+
if (ctx.state.gitMode.headOffset > 0) {
|
|
347
|
+
return r([
|
|
348
|
+
{
|
|
349
|
+
message: 'disabled while viewing HEAD~N (press 0 or [ to return)',
|
|
350
|
+
type: 'git-mode-set-message',
|
|
351
|
+
},
|
|
352
|
+
])
|
|
353
|
+
}
|
|
354
|
+
const session = ctx.state.sessions.find((entry) => entry.id === ctx.state.currentSessionId)
|
|
355
|
+
const worktrees = session?.worktrees ?? []
|
|
356
|
+
// From git mode, the source is the active worktree (the one being reviewed).
|
|
357
|
+
const source = worktrees.find((w) => w.id === session?.activeWorktreeId) ?? worktrees[0]
|
|
358
|
+
const hasBranch = source != null && source.branch != null && source.branch !== ''
|
|
359
|
+
const others = worktrees.filter((w) => w.id !== source?.id)
|
|
360
|
+
if (!hasBranch || source == null || others.length < 1) {
|
|
361
|
+
return r([{ message: 'no other worktree to move into', type: 'git-mode-set-message' }])
|
|
362
|
+
}
|
|
363
|
+
// Overlay: no mode transition — deriveModeId routes input to the picker and
|
|
364
|
+
// focusMode stays 'git' so the git view remains mounted underneath.
|
|
365
|
+
return r([{ sourceWorktreeId: source.id, type: 'open-worktree-move-modal' }])
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export const toggleWorktreeMoveDelete: KeyResult = r([{ type: 'toggle-worktree-move-delete' }])
|
|
369
|
+
|
|
370
|
+
export const confirmWorktreeMove: ActionFn = (ctx: ModeContext) => {
|
|
371
|
+
const modal = ctx.state.modal
|
|
372
|
+
const session = ctx.state.sessions.find((entry) => entry.id === ctx.state.currentSessionId)
|
|
373
|
+
const worktrees = session?.worktrees ?? []
|
|
374
|
+
const sourceId = modal.type === 'worktree-move' ? modal.sourceWorktreeId : undefined
|
|
375
|
+
const source = worktrees.find((w) => w.id === sourceId)
|
|
376
|
+
const targets = worktrees.filter((w) => w.id !== sourceId)
|
|
377
|
+
const selectedIndex = modal.type === 'worktree-move' ? modal.selectedIndex : 0
|
|
378
|
+
const target = targets[selectedIndex]
|
|
379
|
+
if (!target || !session || !source || modal.type !== 'worktree-move') {
|
|
380
|
+
return r([{ type: 'close-modal' }])
|
|
381
|
+
}
|
|
382
|
+
// Overlay close keeps focusMode 'git' (see close-modal reducer), so the move
|
|
383
|
+
// result lands back in the git view that was underneath the picker.
|
|
384
|
+
return r(
|
|
385
|
+
[{ type: 'close-modal' }],
|
|
386
|
+
[
|
|
387
|
+
{
|
|
388
|
+
deleteSource: modal.deleteSource,
|
|
389
|
+
sessionId: session.id,
|
|
390
|
+
sourceWorktreeId: source.id,
|
|
391
|
+
targetWorktreeId: target.id,
|
|
392
|
+
type: 'move-worktree',
|
|
393
|
+
},
|
|
394
|
+
]
|
|
395
|
+
)
|
|
396
|
+
}
|
|
397
|
+
|
|
291
398
|
export const closePane: ActionFn = (ctx: ModeContext) => {
|
|
292
399
|
const tabId = ctx.state.activeTabId
|
|
293
|
-
if (!tabId) return null
|
|
400
|
+
if (!(tabId != null && tabId !== '')) return null
|
|
294
401
|
return r(
|
|
295
402
|
[
|
|
296
403
|
{ tabId, type: 'close-pane' },
|
|
@@ -335,7 +442,9 @@ export const confirmSessionRename: ActionFn = (ctx: ModeContext) => {
|
|
|
335
442
|
? 'modal.session-picker.filtering'
|
|
336
443
|
: 'navigation'
|
|
337
444
|
const effects: KeyResult['effects'] =
|
|
338
|
-
trimmed && sessionId
|
|
445
|
+
trimmed && sessionId != null && sessionId !== ''
|
|
446
|
+
? [{ name: trimmed, sessionId, type: 'rename-session' }]
|
|
447
|
+
: []
|
|
339
448
|
return r([closeAction], effects, transition)
|
|
340
449
|
}
|
|
341
450
|
|
|
@@ -344,7 +453,7 @@ export const confirmRenameTab: ActionFn = (ctx: ModeContext) => {
|
|
|
344
453
|
const trimmed = (ctx.state.modal.editBuffer ?? '').trim()
|
|
345
454
|
const tabId = ctx.state.modal.sessionTargetId
|
|
346
455
|
const actions: KeyResult['actions'] = []
|
|
347
|
-
if (trimmed && tabId) {
|
|
456
|
+
if (trimmed && tabId != null && tabId !== '') {
|
|
348
457
|
actions.push({ tabId, title: trimmed, type: 'rename-tab' })
|
|
349
458
|
}
|
|
350
459
|
actions.push({ type: 'close-modal' })
|
|
@@ -377,14 +486,14 @@ export const confirmCreateSession: ActionFn = (ctx: ModeContext) => {
|
|
|
377
486
|
}
|
|
378
487
|
|
|
379
488
|
function getDefaultSessionName(projectPath?: string): string {
|
|
380
|
-
if (!projectPath) return ''
|
|
489
|
+
if (!(projectPath != null && projectPath !== '')) return ''
|
|
381
490
|
const segments = projectPath.split('/').filter(Boolean)
|
|
382
491
|
return segments.at(-1) ?? ''
|
|
383
492
|
}
|
|
384
493
|
|
|
385
494
|
// Session picker escape (conditional)
|
|
386
495
|
export const sessionPickerEscape: ActionFn = (ctx: ModeContext) => {
|
|
387
|
-
if (!ctx.state.currentSessionId) return null
|
|
496
|
+
if (!(ctx.state.currentSessionId != null && ctx.state.currentSessionId !== '')) return null
|
|
388
497
|
return r([{ type: 'close-modal' }], [], 'navigation')
|
|
389
498
|
}
|
|
390
499
|
|
|
@@ -410,15 +519,19 @@ function clearPendingDelete(ctx: ModeContext): AppAction[] {
|
|
|
410
519
|
return [{ path: null, type: 'git-mode-set-pending-delete' }]
|
|
411
520
|
}
|
|
412
521
|
|
|
413
|
-
function gitFileKey(section: string, path: string): string {
|
|
414
|
-
return
|
|
522
|
+
function gitFileKey(section: string, path: string, repoPath?: string): string {
|
|
523
|
+
return repoPath != null && repoPath !== ''
|
|
524
|
+
? `${section}:${repoPath}:${path}`
|
|
525
|
+
: `${section}:${path}`
|
|
415
526
|
}
|
|
416
527
|
|
|
417
528
|
function selectedGitFile(ctx: ModeContext) {
|
|
418
529
|
const key = ctx.state.gitMode.selectedEntryKey
|
|
419
|
-
if (!key) return null
|
|
530
|
+
if (!(key != null && key !== '')) return null
|
|
420
531
|
return (
|
|
421
|
-
ctx.state.gitPanel.files.find(
|
|
532
|
+
ctx.state.gitPanel.files.find(
|
|
533
|
+
(file) => gitFileKey(file.section, file.path, file.repoPath) === key
|
|
534
|
+
) ?? null
|
|
422
535
|
)
|
|
423
536
|
}
|
|
424
537
|
|
|
@@ -439,17 +552,20 @@ export function selectGitFileOnly(delta: -1 | 1): ActionFn {
|
|
|
439
552
|
}
|
|
440
553
|
|
|
441
554
|
export const toggleSelectedGitFolder: ActionFn = (ctx: ModeContext) => {
|
|
442
|
-
if (!ctx.state.gitMode.selectedEntryKey
|
|
555
|
+
if (!(ctx.state.gitMode.selectedEntryKey != null && ctx.state.gitMode.selectedEntryKey !== ''))
|
|
556
|
+
return r([])
|
|
443
557
|
return r([{ type: 'git-mode-toggle-selected-folder' }])
|
|
444
558
|
}
|
|
445
559
|
|
|
446
560
|
export const collapseGitSelection: ActionFn = (ctx: ModeContext) => {
|
|
447
|
-
if (!ctx.state.gitMode.selectedEntryKey
|
|
561
|
+
if (!(ctx.state.gitMode.selectedEntryKey != null && ctx.state.gitMode.selectedEntryKey !== ''))
|
|
562
|
+
return r([])
|
|
448
563
|
return r([{ type: 'git-mode-collapse-selection' }])
|
|
449
564
|
}
|
|
450
565
|
|
|
451
566
|
export const expandGitSelection: ActionFn = (ctx: ModeContext) => {
|
|
452
|
-
if (!ctx.state.gitMode.selectedEntryKey
|
|
567
|
+
if (!(ctx.state.gitMode.selectedEntryKey != null && ctx.state.gitMode.selectedEntryKey !== ''))
|
|
568
|
+
return r([])
|
|
453
569
|
return r([{ type: 'git-mode-expand-selection' }])
|
|
454
570
|
}
|
|
455
571
|
|
|
@@ -475,6 +591,8 @@ export function scrollGitDiff(delta: number): KeyResult {
|
|
|
475
591
|
|
|
476
592
|
export const toggleGitDiffView: KeyResult = r([{ type: 'git-mode-toggle-diff-view' }])
|
|
477
593
|
|
|
594
|
+
export const toggleGitReviewBase: KeyResult = r([{ type: 'git-mode-toggle-review-base' }])
|
|
595
|
+
|
|
478
596
|
export function shiftGitHeadOffset(delta: number): KeyResult {
|
|
479
597
|
return r([
|
|
480
598
|
{ delta, type: 'git-mode-shift-head-offset' },
|
|
@@ -551,7 +669,7 @@ export const gitDestructiveSelected: ActionFn = (ctx: ModeContext) => {
|
|
|
551
669
|
export const gitToggleFoldAll: ActionFn = (ctx: ModeContext) => {
|
|
552
670
|
const file = selectedGitFile(ctx)
|
|
553
671
|
if (!file) return r([])
|
|
554
|
-
const key = gitFileKey(file.section, file.path)
|
|
672
|
+
const key = gitFileKey(file.section, file.path, file.repoPath)
|
|
555
673
|
return r([{ key, type: 'git-mode-fold-toggle-all' }])
|
|
556
674
|
}
|
|
557
675
|
|
|
@@ -677,7 +795,7 @@ export const gitCommitEnterConfirm: ActionFn = (ctx: ModeContext) => {
|
|
|
677
795
|
return r([{ type: 'git-commit-enter-confirm' }], [], 'modal.git-commit.confirm')
|
|
678
796
|
}
|
|
679
797
|
const sessionId = ctx.state.currentSessionId
|
|
680
|
-
if (!sessionId) {
|
|
798
|
+
if (!(sessionId != null && sessionId !== '')) {
|
|
681
799
|
return r([{ type: 'git-commit-enter-confirm' }], [], 'modal.git-commit.confirm')
|
|
682
800
|
}
|
|
683
801
|
return r(
|
|
@@ -696,7 +814,7 @@ export const gitCommitLeaveConfirm: KeyResult = r(
|
|
|
696
814
|
export const gitCommitLeaveGenerating: ActionFn = (ctx: ModeContext) => {
|
|
697
815
|
const sessionId = ctx.state.currentSessionId
|
|
698
816
|
const actionsList: AppAction[] = [{ type: 'git-commit-leave-generating' }]
|
|
699
|
-
if (sessionId) {
|
|
817
|
+
if (sessionId != null && sessionId !== '') {
|
|
700
818
|
actionsList.push({ sessionId, type: 'auto-commit-clear' })
|
|
701
819
|
}
|
|
702
820
|
return r(actionsList, [], 'modal.git-commit')
|
package/src/backends.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { BackendConfig } from './types'
|
|
|
6
6
|
*/
|
|
7
7
|
export function claudeBackend(opts?: { model?: string }): BackendConfig {
|
|
8
8
|
return {
|
|
9
|
-
args: opts?.model ? ['--model', opts.model] : [],
|
|
9
|
+
args: opts?.model != null && opts.model !== '' ? ['--model', opts.model] : [],
|
|
10
10
|
command: 'claude',
|
|
11
11
|
}
|
|
12
12
|
}
|
package/src/defaults.ts
CHANGED
|
@@ -104,8 +104,10 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
|
|
|
104
104
|
.map('e', actions.gitToggleFoldAll, 'Expand/collapse all folds')
|
|
105
105
|
.map('o', actions.openSelectedGitFileInEditor, 'Open in editor')
|
|
106
106
|
.map('c', actions.gitCommitOpen, 'Commit')
|
|
107
|
+
.map('m', actions.openWorktreeMove, 'Move worktree')
|
|
107
108
|
.map('p', actions.gitPush, 'Push')
|
|
108
109
|
.map('v', actions.toggleGitDiffView, 'Toggle split/stacked')
|
|
110
|
+
.map('b', actions.toggleGitReviewBase, 'Review vs base')
|
|
109
111
|
.map('t', actions.toggleGitFileListMode, 'Toggle flat/tree')
|
|
110
112
|
.map('T', actions.toggleTreeCompaction, 'Toggle tree compaction')
|
|
111
113
|
.map(']', actions.shiftGitHeadOffset(-1), 'Newer commit')
|
|
@@ -182,6 +184,22 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
|
|
|
182
184
|
.map('<CR>', actions.confirmUpdateSelection, 'Confirm')
|
|
183
185
|
)
|
|
184
186
|
|
|
187
|
+
// -----------------------------------------------------------------------
|
|
188
|
+
// Modal: worktree-move
|
|
189
|
+
// -----------------------------------------------------------------------
|
|
190
|
+
.mode('modal.worktree-move', (m) =>
|
|
191
|
+
m
|
|
192
|
+
.map('<Esc>', actions.closeModal, 'Cancel')
|
|
193
|
+
.map('j', actions.moveModalSelection(1), 'Next')
|
|
194
|
+
.map('k', actions.moveModalSelection(-1), 'Prev')
|
|
195
|
+
.map('<Down>', actions.moveModalSelection(1))
|
|
196
|
+
.map('<Up>', actions.moveModalSelection(-1))
|
|
197
|
+
.map('<C-n>', actions.moveModalSelection(1))
|
|
198
|
+
.map('<C-p>', actions.moveModalSelection(-1))
|
|
199
|
+
.map('d', actions.toggleWorktreeMoveDelete, 'Toggle delete source')
|
|
200
|
+
.map('<CR>', actions.confirmWorktreeMove, 'Move')
|
|
201
|
+
)
|
|
202
|
+
|
|
185
203
|
// -----------------------------------------------------------------------
|
|
186
204
|
// Modal: rename-tab
|
|
187
205
|
// -----------------------------------------------------------------------
|
|
@@ -197,12 +215,16 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
|
|
|
197
215
|
// -----------------------------------------------------------------------
|
|
198
216
|
.mode('modal.new-tab.command-edit', (m) =>
|
|
199
217
|
m
|
|
200
|
-
.map('<Esc>', actions.
|
|
218
|
+
.map('<Esc>', actions.cancelNewTabModal, 'Cancel')
|
|
201
219
|
.map('<CR>', actions.launchSelectedAssistant, 'Launch')
|
|
220
|
+
.map('<C-w>', actions.toggleNewTabWorktree, 'WT')
|
|
221
|
+
.map('<C-d>', actions.deleteSelectedWorktree, 'Delete WT')
|
|
222
|
+
.map('<Tab>', actions.switchField, 'Next field')
|
|
202
223
|
.map('<C-n>', actions.moveModalSelection(1), 'Next')
|
|
203
224
|
.map('<C-p>', actions.moveModalSelection(-1), 'Prev')
|
|
204
225
|
.map('<Down>', actions.moveModalSelection(1))
|
|
205
226
|
.map('<Up>', actions.moveModalSelection(-1))
|
|
227
|
+
.map('<C-e>', actions.editSelectedAssistant, 'Edit')
|
|
206
228
|
.passthrough()
|
|
207
229
|
)
|
|
208
230
|
|
|
@@ -262,6 +284,7 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
|
|
|
262
284
|
.map('<C-a>', actions.snippetFilterPasteToGroup, 'Send to group')
|
|
263
285
|
.map('<C-n>', actions.moveModalSelection(1), 'Next')
|
|
264
286
|
.map('<C-p>', actions.moveModalSelection(-1), 'Prev')
|
|
287
|
+
.map('<C-o>', actions.openSelectedSnippetSourceInEditor, 'Open source file')
|
|
265
288
|
.map('<Down>', actions.moveModalSelection(1))
|
|
266
289
|
.map('<Up>', actions.moveModalSelection(-1))
|
|
267
290
|
.passthrough()
|
package/src/index.ts
CHANGED
package/src/resolver.ts
CHANGED
|
@@ -44,11 +44,16 @@ export function resolveConfig(userConfig: AimuxUserConfig): ResolvedConfig {
|
|
|
44
44
|
sessionBar: resolveSessionBar(userConfig.sessionBar),
|
|
45
45
|
sidebar: userConfig.sidebar ?? {},
|
|
46
46
|
snippets: userConfig.snippets ?? [],
|
|
47
|
+
snippetTriggerChar: resolveSnippetTriggerChar(userConfig.snippetTriggerChar),
|
|
47
48
|
statusBar: userConfig.statusBar ?? {},
|
|
48
49
|
theme: resolveTheme(userConfig.theme),
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
function resolveSnippetTriggerChar(value: string | undefined): string {
|
|
54
|
+
return typeof value === 'string' && value.length === 1 ? value : ':'
|
|
55
|
+
}
|
|
56
|
+
|
|
52
57
|
function resolveTheme(userConfig: AimuxUserConfig['theme']): ResolvedConfig['theme'] {
|
|
53
58
|
if (!userConfig) return undefined
|
|
54
59
|
return {
|
package/src/tui/registry.ts
CHANGED
|
@@ -76,7 +76,7 @@ export const TUI_THEMES: Record<string, TuiThemeJson> = {
|
|
|
76
76
|
'zenburn': zenburn as TuiThemeJson,
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
export type ThemeId = keyof typeof TUI_THEMES
|
|
79
|
+
export type ThemeId = keyof typeof TUI_THEMES
|
|
80
80
|
|
|
81
81
|
export const THEME_IDS: ThemeId[] = Object.keys(TUI_THEMES).sort() as ThemeId[]
|
|
82
82
|
|
package/src/tui/resolve.ts
CHANGED
|
@@ -12,13 +12,7 @@ const TRANSPARENT: RGBA = { a: 0, b: 0, g: 0, r: 0 }
|
|
|
12
12
|
function rgbaFromHex(hex: string): RGBA {
|
|
13
13
|
const h = hex.replace('#', '')
|
|
14
14
|
// expand short forms (#rgb, #rgba)
|
|
15
|
-
const expanded =
|
|
16
|
-
h.length === 3 || h.length === 4
|
|
17
|
-
? h
|
|
18
|
-
.split('')
|
|
19
|
-
.map((c) => c + c)
|
|
20
|
-
.join('')
|
|
21
|
-
: h
|
|
15
|
+
const expanded = h.length === 3 || h.length === 4 ? h.replaceAll(/(.)/g, '$1$1') : h
|
|
22
16
|
if (expanded.length === 6) {
|
|
23
17
|
const num = parseInt(expanded, 16)
|
|
24
18
|
return {
|
package/src/types.ts
CHANGED
|
@@ -27,11 +27,12 @@ export type ModeId =
|
|
|
27
27
|
| 'modal.git-commit.confirm'
|
|
28
28
|
| 'modal.git-commit.generating'
|
|
29
29
|
| 'modal.update-available'
|
|
30
|
+
| 'modal.worktree-move'
|
|
30
31
|
| 'modal.ai-usage'
|
|
31
32
|
|
|
32
33
|
// ─── Primitive app types ──────────────────────────────────────────────────────
|
|
33
34
|
|
|
34
|
-
export type BuiltinAssistantId = 'claude' | 'codex' | 'opencode' | 'terminal'
|
|
35
|
+
export type BuiltinAssistantId = 'claude' | 'codex' | 'opencode' | 'terminal' | 'antigravity'
|
|
35
36
|
export type AssistantId = BuiltinAssistantId | (string & {})
|
|
36
37
|
export type TabStatus = 'starting' | 'running' | 'disconnected' | 'error'
|
|
37
38
|
|
|
@@ -82,8 +83,11 @@ export interface TerminalModeState {
|
|
|
82
83
|
|
|
83
84
|
// ─── Layout ───────────────────────────────────────────────────────────────────
|
|
84
85
|
|
|
85
|
-
export
|
|
86
|
-
|
|
86
|
+
export interface LayoutLeaf {
|
|
87
|
+
type: 'leaf'
|
|
88
|
+
tabId: string
|
|
89
|
+
}
|
|
90
|
+
export interface LayoutSplit {
|
|
87
91
|
type: 'split'
|
|
88
92
|
direction: SplitDirection
|
|
89
93
|
ratio: number
|
|
@@ -105,6 +109,7 @@ export interface PersistedTabSnapshot {
|
|
|
105
109
|
terminalModes: TerminalModeState
|
|
106
110
|
errorMessage?: string
|
|
107
111
|
exitCode?: number
|
|
112
|
+
worktreeId?: string
|
|
108
113
|
}
|
|
109
114
|
|
|
110
115
|
export interface WorkspaceSnapshotV1 {
|
|
@@ -121,6 +126,23 @@ export interface WorkspaceSnapshotV1 {
|
|
|
121
126
|
tabGroupMap?: Record<string, string>
|
|
122
127
|
}
|
|
123
128
|
|
|
129
|
+
export type WorktreeSource = 'primary' | 'aimux-temp' | 'external'
|
|
130
|
+
|
|
131
|
+
export interface WorktreeRecord {
|
|
132
|
+
id: string
|
|
133
|
+
name: string
|
|
134
|
+
path: string
|
|
135
|
+
repoRoot: string
|
|
136
|
+
branch?: string
|
|
137
|
+
baseRef?: string
|
|
138
|
+
commitSha?: string
|
|
139
|
+
source: WorktreeSource
|
|
140
|
+
createdByAimux: boolean
|
|
141
|
+
color?: string
|
|
142
|
+
createdAt: string
|
|
143
|
+
updatedAt: string
|
|
144
|
+
}
|
|
145
|
+
|
|
124
146
|
export interface SessionRecord {
|
|
125
147
|
id: string
|
|
126
148
|
name: string
|
|
@@ -130,6 +152,8 @@ export interface SessionRecord {
|
|
|
130
152
|
lastOpenedAt: string
|
|
131
153
|
order?: number
|
|
132
154
|
workspaceSnapshot?: WorkspaceSnapshotV1
|
|
155
|
+
worktrees?: WorktreeRecord[]
|
|
156
|
+
activeWorktreeId?: string
|
|
133
157
|
}
|
|
134
158
|
|
|
135
159
|
export interface TabSession {
|
|
@@ -144,12 +168,15 @@ export interface TabSession {
|
|
|
144
168
|
command: string
|
|
145
169
|
errorMessage?: string
|
|
146
170
|
exitCode?: number
|
|
171
|
+
worktreeId?: string
|
|
147
172
|
}
|
|
148
173
|
|
|
149
174
|
export interface SnippetRecord {
|
|
150
175
|
id: string
|
|
151
176
|
name: string
|
|
152
177
|
content: string
|
|
178
|
+
trigger?: string
|
|
179
|
+
vars?: Record<string, SnippetVar>
|
|
153
180
|
}
|
|
154
181
|
|
|
155
182
|
export type DirectoryResultType = 'git-repo' | 'worktree' | 'workspace'
|
|
@@ -192,6 +219,7 @@ export interface GitFileEntry {
|
|
|
192
219
|
status: GitFileStatus
|
|
193
220
|
added: number | null
|
|
194
221
|
removed: number | null
|
|
222
|
+
repoPath?: string
|
|
195
223
|
}
|
|
196
224
|
|
|
197
225
|
export type GitPanelError = 'not-a-repo' | 'unknown'
|
|
@@ -242,6 +270,7 @@ export interface GitModeState {
|
|
|
242
270
|
diffView: GitDiffView
|
|
243
271
|
folds: Record<string, Record<string, FoldState>>
|
|
244
272
|
headOffset: number
|
|
273
|
+
reviewBase: boolean
|
|
245
274
|
}
|
|
246
275
|
|
|
247
276
|
interface ModalBase {
|
|
@@ -260,6 +289,16 @@ export interface ModalClosed extends ModalBase {
|
|
|
260
289
|
export interface ModalNewTab extends ModalBase {
|
|
261
290
|
type: 'new-tab'
|
|
262
291
|
editingCommand: AssistantId | null
|
|
292
|
+
activeField: 'assistant' | 'branch-name' | 'target-worktree' | 'worktree-name'
|
|
293
|
+
branchError: string | null
|
|
294
|
+
branchName: string
|
|
295
|
+
createWorktree: boolean
|
|
296
|
+
selectedAssistantId: AssistantId | null
|
|
297
|
+
step: 'assistant' | 'worktree' | 'worktree-create'
|
|
298
|
+
targetWorktreeIndex: number
|
|
299
|
+
worktreeDeleteConfirmId: string | null
|
|
300
|
+
worktreeDeleteMessage: string | null
|
|
301
|
+
worktreeName: string
|
|
263
302
|
}
|
|
264
303
|
export interface ModalSessionPicker extends ModalBase {
|
|
265
304
|
type: 'session-picker'
|
|
@@ -273,6 +312,7 @@ export interface ModalRenameTab extends ModalBase {
|
|
|
273
312
|
}
|
|
274
313
|
export interface ModalSnippetPicker extends ModalBase {
|
|
275
314
|
type: 'snippet-picker'
|
|
315
|
+
actionMessage?: string | null
|
|
276
316
|
}
|
|
277
317
|
export interface ModalThemePicker extends ModalBase {
|
|
278
318
|
type: 'theme-picker'
|
|
@@ -303,7 +343,9 @@ export interface ModalGitCommit extends ModalBase {
|
|
|
303
343
|
}
|
|
304
344
|
export interface ModalSnippetEditor extends ModalBase {
|
|
305
345
|
type: 'snippet-editor'
|
|
306
|
-
activeField: 'name' | 'content'
|
|
346
|
+
activeField: 'name' | 'trigger' | 'content'
|
|
347
|
+
nameBuffer: string
|
|
348
|
+
triggerBuffer: string
|
|
307
349
|
contentBuffer: string
|
|
308
350
|
}
|
|
309
351
|
|
|
@@ -317,6 +359,13 @@ export interface ModalAIUsage extends ModalBase {
|
|
|
317
359
|
type: 'ai-usage'
|
|
318
360
|
}
|
|
319
361
|
|
|
362
|
+
export interface ModalWorktreeMove extends ModalBase {
|
|
363
|
+
type: 'worktree-move'
|
|
364
|
+
/** The worktree being moved (may differ from the active one, e.g. a tab menu). */
|
|
365
|
+
sourceWorktreeId: string
|
|
366
|
+
deleteSource: boolean
|
|
367
|
+
}
|
|
368
|
+
|
|
320
369
|
export type ModalState =
|
|
321
370
|
| ModalClosed
|
|
322
371
|
| ModalNewTab
|
|
@@ -332,6 +381,7 @@ export type ModalState =
|
|
|
332
381
|
| ModalGitCommit
|
|
333
382
|
| ModalUpdateAvailable
|
|
334
383
|
| ModalAIUsage
|
|
384
|
+
| ModalWorktreeMove
|
|
335
385
|
|
|
336
386
|
export interface LayoutState {
|
|
337
387
|
terminalCols: number
|
|
@@ -398,6 +448,7 @@ export interface AppState {
|
|
|
398
448
|
gitMode: GitModeState
|
|
399
449
|
autoCommit: AutoCommitState
|
|
400
450
|
multiRepo: MultiRepoState
|
|
451
|
+
worktreeDivergence: Record<string, BranchDivergence>
|
|
401
452
|
pendingChords: string[] | null
|
|
402
453
|
}
|
|
403
454
|
|
|
@@ -405,6 +456,15 @@ export interface AppState {
|
|
|
405
456
|
|
|
406
457
|
export type ModalAction =
|
|
407
458
|
| { type: 'open-new-tab-modal' }
|
|
459
|
+
| { type: 'set-new-tab-branch-error'; message: string | null }
|
|
460
|
+
| {
|
|
461
|
+
type: 'set-new-tab-worktree-delete-state'
|
|
462
|
+
confirmWorktreeId?: string | null
|
|
463
|
+
message: string | null
|
|
464
|
+
}
|
|
465
|
+
| { type: 'enter-new-tab-worktree-create' }
|
|
466
|
+
| { type: 'select-new-tab-assistant'; assistantId?: AssistantId }
|
|
467
|
+
| { type: 'toggle-new-tab-worktree'; assistantId?: AssistantId }
|
|
408
468
|
| { type: 'open-help-modal'; scope?: ModeId }
|
|
409
469
|
| { type: 'open-split-picker'; direction: SplitDirection }
|
|
410
470
|
| { type: 'open-session-picker' }
|
|
@@ -429,7 +489,11 @@ export type ModalAction =
|
|
|
429
489
|
| { type: 'set-theme-entry-count'; count: number }
|
|
430
490
|
| { type: 'open-theme-picker' }
|
|
431
491
|
| { type: 'open-update-available-modal'; currentVersion: string; latestVersion: string }
|
|
492
|
+
| { type: 'set-modal-selection-index'; index: number }
|
|
432
493
|
| { type: 'open-ai-usage-modal' }
|
|
494
|
+
| { type: 'open-edit-custom-command'; assistantId: AssistantId }
|
|
495
|
+
| { type: 'open-worktree-move-modal'; sourceWorktreeId: string }
|
|
496
|
+
| { type: 'toggle-worktree-move-delete' }
|
|
433
497
|
|
|
434
498
|
export type SessionAction =
|
|
435
499
|
| { type: 'load-session'; sessionId: string; workspaceSnapshot?: WorkspaceSnapshotV1 }
|
|
@@ -439,6 +503,15 @@ export type SessionAction =
|
|
|
439
503
|
| { type: 'delete-session-record'; sessionId: string; openSessionPicker?: boolean }
|
|
440
504
|
| { type: 'reorder-sessions'; orderedIds: string[] }
|
|
441
505
|
| { type: 'set-session-status'; sessionId: string; status: SessionStatus }
|
|
506
|
+
| { type: 'add-worktree-record'; sessionId: string; worktree: WorktreeRecord; activate?: boolean }
|
|
507
|
+
| { type: 'remove-worktree-record'; sessionId: string; worktreeId: string }
|
|
508
|
+
| { type: 'set-active-worktree'; sessionId: string; worktreeId: string }
|
|
509
|
+
| {
|
|
510
|
+
type: 'update-worktree-record'
|
|
511
|
+
sessionId: string
|
|
512
|
+
worktreeId: string
|
|
513
|
+
patch: Partial<WorktreeRecord>
|
|
514
|
+
}
|
|
442
515
|
|
|
443
516
|
export type TabAction =
|
|
444
517
|
| { type: 'add-tab'; tab: TabSession }
|
|
@@ -514,10 +587,17 @@ export interface GitRefreshPayload {
|
|
|
514
587
|
files: GitFileEntry[]
|
|
515
588
|
}
|
|
516
589
|
|
|
590
|
+
/** Commits a worktree branch is ahead/behind the ref it forked from. */
|
|
591
|
+
export interface BranchDivergence {
|
|
592
|
+
ahead: number
|
|
593
|
+
behind: number
|
|
594
|
+
}
|
|
595
|
+
|
|
517
596
|
export type GitPanelAction =
|
|
518
597
|
| { type: 'git-refresh-success'; payload: GitRefreshPayload }
|
|
519
598
|
| { type: 'git-refresh-error'; kind: GitPanelError }
|
|
520
599
|
| { type: 'git-panel-reset' }
|
|
600
|
+
| { type: 'set-worktree-divergence'; divergence: Record<string, BranchDivergence> }
|
|
521
601
|
|
|
522
602
|
export type GitModeAction =
|
|
523
603
|
| { type: 'enter-git-mode' }
|
|
@@ -536,7 +616,9 @@ export type GitModeAction =
|
|
|
536
616
|
| { type: 'git-mode-set-pending-delete'; path: string | null }
|
|
537
617
|
| { type: 'git-mode-clear-diff-cache'; path: string }
|
|
538
618
|
| { type: 'git-mode-set-message'; message: string | null }
|
|
619
|
+
| { type: 'snippet-picker-set-message'; message: string | null }
|
|
539
620
|
| { type: 'git-mode-toggle-diff-view' }
|
|
621
|
+
| { type: 'git-mode-toggle-review-base' }
|
|
540
622
|
| { type: 'git-mode-shift-head-offset'; delta: number }
|
|
541
623
|
| { type: 'git-mode-set-head-offset'; offset: number }
|
|
542
624
|
| {
|
|
@@ -599,6 +681,7 @@ export type AppAction =
|
|
|
599
681
|
export type SideEffect =
|
|
600
682
|
| { type: 'quit'; state: AppState }
|
|
601
683
|
| { type: 'launch-selected-assistant' }
|
|
684
|
+
| { type: 'edit-selected-assistant' }
|
|
602
685
|
| { type: 'confirm-selected-session' }
|
|
603
686
|
| { type: 'delete-selected-session' }
|
|
604
687
|
| { type: 'open-rename-selected-session' }
|
|
@@ -635,9 +718,18 @@ export type SideEffect =
|
|
|
635
718
|
| { type: 'confirm-update-selection' }
|
|
636
719
|
| { type: 'switch-session-by-index'; index: number }
|
|
637
720
|
| { type: 'delete-session'; sessionId: string }
|
|
721
|
+
| { type: 'delete-worktree'; sessionId: string; worktreeId: string; force?: boolean }
|
|
722
|
+
| {
|
|
723
|
+
type: 'move-worktree'
|
|
724
|
+
sessionId: string
|
|
725
|
+
sourceWorktreeId: string
|
|
726
|
+
targetWorktreeId: string
|
|
727
|
+
deleteSource?: boolean
|
|
728
|
+
}
|
|
638
729
|
| { type: 'toggle-transparent' }
|
|
639
730
|
| { type: 'toggle-mode' }
|
|
640
731
|
| { type: 'open-file-in-editor'; path: string }
|
|
732
|
+
| { type: 'open-selected-snippet-source-in-editor' }
|
|
641
733
|
|
|
642
734
|
// ─── Key input / KeyResult / ModeContext ──────────────────────────────────────
|
|
643
735
|
|
|
@@ -689,10 +781,30 @@ export interface HooksConfig {
|
|
|
689
781
|
|
|
690
782
|
// ─── Snippet config (stub) ────────────────────────────────────────────────────
|
|
691
783
|
|
|
784
|
+
/**
|
|
785
|
+
* A snippet variable resolved at expansion time. The shape is a tagged union
|
|
786
|
+
* discriminated by which key is present (`sh` for now; future: `env`, `date`, …).
|
|
787
|
+
*/
|
|
788
|
+
export interface SnippetShellVar {
|
|
789
|
+
/** Shell command run via `sh -c`. The trimmed stdout is interpolated. */
|
|
790
|
+
sh: string
|
|
791
|
+
/** Kill the process after this many ms. Default 5000. */
|
|
792
|
+
timeout?: number
|
|
793
|
+
/** Trim trailing whitespace from stdout. Default true. */
|
|
794
|
+
trim?: boolean
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
export type SnippetVar = SnippetShellVar
|
|
798
|
+
|
|
692
799
|
export interface SnippetDef {
|
|
693
800
|
name: string
|
|
694
801
|
trigger?: string
|
|
695
802
|
text: string
|
|
803
|
+
/**
|
|
804
|
+
* Optional named variables. Reference them in `text` as `{{name}}`.
|
|
805
|
+
* The key is the variable name; the value declares how to resolve it.
|
|
806
|
+
*/
|
|
807
|
+
vars?: Record<string, SnippetVar>
|
|
696
808
|
}
|
|
697
809
|
|
|
698
810
|
// ─── Action value types ───────────────────────────────────────────────────────
|
|
@@ -759,7 +871,9 @@ export type GitPanePathConfig =
|
|
|
759
871
|
| { enabled: false }
|
|
760
872
|
| { enabled: true; pathFn?: (path: string) => string }
|
|
761
873
|
|
|
762
|
-
export
|
|
874
|
+
export interface GitPaneDiffCountConfig {
|
|
875
|
+
enabled: boolean
|
|
876
|
+
}
|
|
763
877
|
|
|
764
878
|
interface GitPaneBaseConfig {
|
|
765
879
|
/** Startup override for git pane visibility. Reapplied on each launch. */
|
|
@@ -891,6 +1005,12 @@ export interface AimuxUserConfig {
|
|
|
891
1005
|
gitPane?: GitPaneConfig
|
|
892
1006
|
hooks?: HooksConfig
|
|
893
1007
|
snippets?: SnippetDef[]
|
|
1008
|
+
/**
|
|
1009
|
+
* Single-character prefix that opens an inline snippet trigger.
|
|
1010
|
+
* Defaults to `:` (Espanso-style). Typing `<char><trigger><separator>` in
|
|
1011
|
+
* any non-alternate-screen terminal expands the matching snippet.
|
|
1012
|
+
*/
|
|
1013
|
+
snippetTriggerChar?: string
|
|
894
1014
|
autoCommit?: Partial<AutoCommitConfig>
|
|
895
1015
|
multiRepo?: Partial<MultiRepoConfig>
|
|
896
1016
|
statusBar?: StatusBarConfig
|
|
@@ -964,6 +1084,7 @@ export interface ResolvedConfig {
|
|
|
964
1084
|
}
|
|
965
1085
|
hooks: HooksConfig
|
|
966
1086
|
snippets: SnippetDef[]
|
|
1087
|
+
snippetTriggerChar: string
|
|
967
1088
|
autoCommit: AutoCommitConfig
|
|
968
1089
|
multiRepo: MultiRepoConfig
|
|
969
1090
|
statusBar: StatusBarConfig
|