@brimveyn/aimux 1.7.3 → 1.8.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.
Files changed (62) hide show
  1. package/README.md +27 -15
  2. package/package.json +2 -2
  3. package/src/app-runtime/auto-commit-driver.ts +286 -0
  4. package/src/app-runtime/auto-commit-ref.ts +20 -0
  5. package/src/app-runtime/backend-attach-runtime.ts +3 -1
  6. package/src/app-runtime/session-actions.ts +7 -2
  7. package/src/app-runtime/side-effects.ts +111 -4
  8. package/src/app-runtime/split-drag-controller.ts +31 -1
  9. package/src/app-runtime/use-auto-commit-driver.ts +133 -0
  10. package/src/app-runtime/use-mouse-handlers.ts +93 -5
  11. package/src/app-runtime/use-terminal-resize.ts +24 -16
  12. package/src/app.tsx +71 -19
  13. package/src/auto-commit/default-auto-commit-prompt.md +46 -0
  14. package/src/auto-commit/headless-commands.ts +40 -0
  15. package/src/auto-commit/output-parser.ts +21 -0
  16. package/src/auto-commit/prompt-loader.ts +33 -0
  17. package/src/auto-commit/staging-mode.ts +5 -0
  18. package/src/auto-commit/strip-ansi.ts +13 -0
  19. package/src/auto-commit/suggestion-runner.ts +55 -0
  20. package/src/auto-commit/working-tree-hash.ts +24 -0
  21. package/src/config.ts +45 -2
  22. package/src/daemon/session-registry.ts +1 -0
  23. package/src/index.tsx +1 -1
  24. package/src/input/keymap/help-entries.ts +4 -4
  25. package/src/input/modes/bridge.ts +6 -0
  26. package/src/input/modes/transitions.ts +3 -1
  27. package/src/input/modes/types.ts +4 -0
  28. package/src/ipc/manager-protocol.ts +2 -2
  29. package/src/ipc/protocol.ts +2 -8
  30. package/src/pty/assistant-status-detector.ts +1 -1
  31. package/src/pty/terminal-snapshot.ts +38 -4
  32. package/src/services/ai-usage/adapters/claude.ts +139 -0
  33. package/src/services/ai-usage/adapters/codex.ts +191 -0
  34. package/src/services/ai-usage/provider.ts +84 -0
  35. package/src/services/ai-usage/spawn.ts +49 -0
  36. package/src/services/ai-usage/types.ts +20 -0
  37. package/src/session-backend/local-session-backend.ts +10 -2
  38. package/src/state/ai-usage-store.ts +29 -0
  39. package/src/state/git-pane-sizing.ts +15 -0
  40. package/src/state/reducers/auto-commit-state.ts +59 -0
  41. package/src/state/reducers/git-panel-state.ts +12 -7
  42. package/src/state/reducers/modal-state.ts +106 -2
  43. package/src/state/reducers/session-state.ts +26 -14
  44. package/src/state/reducers/ui-state.ts +6 -0
  45. package/src/state/session-persistence.ts +14 -5
  46. package/src/state/store.ts +26 -15
  47. package/src/state/types.ts +59 -3
  48. package/src/state/workspace-save.ts +6 -1
  49. package/src/ui/ai-usage/controller.ts +35 -0
  50. package/src/ui/components/ai-usage-indicator.tsx +131 -0
  51. package/src/ui/components/ai-usage-popover.tsx +152 -0
  52. package/src/ui/components/context-menu-overlay.tsx +4 -2
  53. package/src/ui/components/create-session-modal.tsx +2 -2
  54. package/src/ui/components/git-commit-modal.tsx +167 -18
  55. package/src/ui/components/git-pane-context-menu.ts +26 -0
  56. package/src/ui/components/session-bar.tsx +2 -2
  57. package/src/ui/components/session-picker-modal.tsx +4 -4
  58. package/src/ui/components/sidebar.tsx +117 -31
  59. package/src/ui/components/status-bar.tsx +2 -0
  60. package/src/ui/components/terminal-pane.tsx +18 -3
  61. package/src/ui/root.tsx +114 -13
  62. package/src/ui/status-bar-model.ts +1 -1
package/src/ui/root.tsx CHANGED
@@ -1,14 +1,19 @@
1
1
  import type { MouseEvent } from '@opentui/core'
2
2
 
3
3
  import type { TerminalContentOrigin } from '../input/raw-input-handler'
4
- import type { ModalState, SessionRecord, SnippetRecord } from '../state/types'
4
+ import type { FocusMode, ModalState, SessionRecord, SnippetRecord } from '../state/types'
5
5
  import type { ThemeId } from './themes'
6
6
 
7
7
  import { useAppStore } from '../state/app-store'
8
+ import { dispatchGlobal } from '../state/dispatch-ref'
9
+ import { getGitPaneWidthFromRatio } from '../state/git-pane-sizing'
8
10
  import { getTreeForTab, PANE_BORDER, type SplitDirection } from '../state/layout-tree'
11
+ import { AIUsagePopover } from './components/ai-usage-popover'
12
+ import { ContextMenuBox } from './components/context-menu-box'
9
13
  import { ContextMenuOverlay } from './components/context-menu-overlay'
10
14
  import { CreateSessionModal } from './components/create-session-modal'
11
15
  import { GitCommitModal } from './components/git-commit-modal'
16
+ import { buildGitPaneContextMenu } from './components/git-pane-context-menu'
12
17
  import { GitPaneWidget } from './components/git-pane-widget'
13
18
  import { GitView } from './components/git-view'
14
19
  import { HelpModal } from './components/help-modal'
@@ -25,7 +30,7 @@ import { StatusBar } from './components/status-bar'
25
30
  import { TerminalPane } from './components/terminal-pane'
26
31
  import { ThemePickerModal } from './components/theme-picker-modal'
27
32
  import { UpdateAvailableModal } from './components/update-available-modal'
28
- import { useBg } from './theme'
33
+ import { useBg, useTokens } from './theme'
29
34
 
30
35
  function getCreateSessionFields(modal: ModalState) {
31
36
  if (modal.type !== 'create-session') {
@@ -74,6 +79,9 @@ function renderModal(
74
79
  themeId: ThemeId
75
80
  createSessionFields: { directoryQuery: string; sessionName: string }
76
81
  snippetEditorFields: { snippetName: string; snippetContent: string }
82
+ focusMode: FocusMode
83
+ activeAssistant?: string
84
+ autoCommitModel?: string
77
85
  }
78
86
  ) {
79
87
  switch (modal.type) {
@@ -103,7 +111,7 @@ function renderModal(
103
111
  case 'session-name':
104
112
  return (
105
113
  <SessionNameModal
106
- title={modal.sessionTargetId ? 'Rename session' : 'Create session'}
114
+ title={modal.sessionTargetId ? 'Rename workspace' : 'Create workspace'}
107
115
  value={modal.editBuffer ?? ''}
108
116
  />
109
117
  )
@@ -172,8 +180,11 @@ function renderModal(
172
180
  return (
173
181
  <GitCommitModal
174
182
  activeField={modal.activeField}
183
+ assistant={options.activeAssistant}
175
184
  body={bodyText}
176
185
  cursorPos={modal.cursorPos ?? (modal.editBuffer ?? '').length}
186
+ model={options.autoCommitModel}
187
+ stage={modal.stage}
177
188
  title={titleText}
178
189
  />
179
190
  )
@@ -195,6 +206,17 @@ interface RootViewProps {
195
206
  onTerminalClick?: (event: MouseEvent, origin: TerminalContentOrigin, tabId?: string) => void
196
207
  onPaneActivate?: (tabId: string) => void
197
208
  onSplitResize?: (tabId: string, ratio: number, axis: SplitDirection) => void
209
+ onSidebarResizeStart?: (info: { initialWidth: number; screenStart: number }) => void
210
+ onGitPaneResizeStart?: (info: {
211
+ initialWidth: number
212
+ screenStart: number
213
+ side: 'left' | 'right'
214
+ }) => void
215
+ onEmbeddedGitResizeStart?: (info: {
216
+ containerStart: number
217
+ position: 'top' | 'bottom'
218
+ totalSize: number
219
+ }) => void
198
220
  onSeparatorDragStart?: (info: {
199
221
  tabId: string
200
222
  direction: SplitDirection
@@ -211,10 +233,13 @@ export function RootView({
211
233
  contentOrigin,
212
234
  localScrollbackEnabled,
213
235
  mouseForwardingEnabled,
236
+ onEmbeddedGitResizeStart,
237
+ onGitPaneResizeStart,
214
238
  onPaneActivate,
215
239
  onSeparatorDrag,
216
240
  onSeparatorDragEnd,
217
241
  onSeparatorDragStart,
242
+ onSidebarResizeStart,
218
243
  onSplitResize,
219
244
  onTerminalClick,
220
245
  onTerminalMouseEvent,
@@ -238,7 +263,7 @@ export function RootView({
238
263
  const gitPaneMode = useAppStore((s) => s.gitPane.mode)
239
264
  const gitPaneVisible = useAppStore((s) => s.gitPane.visible)
240
265
  const gitPanePosition = useAppStore((s) => s.gitPane.position)
241
- const gitPaneRatio = useAppStore((s) => s.gitPane.ratio)
266
+ const gitPaneRatio = useAppStore((s) => s.gitPane.paneRatio)
242
267
 
243
268
  const activeTab = tabs.find((tab) => tab.id === activeTabId)
244
269
  const activeTree = activeTabId ? getTreeForTab(layoutTrees, tabGroupMap, activeTabId) : null
@@ -254,11 +279,14 @@ export function RootView({
254
279
  <StatusBar />
255
280
  <PendingChordOverlay />
256
281
  <ContextMenuOverlay />
282
+ <AIUsagePopover />
257
283
  {renderModal(modal, {
284
+ activeAssistant: activeTab?.assistant,
258
285
  createSessionFields,
259
286
  currentSessionId,
260
287
  currentTabCount: tabs.length,
261
288
  customCommands,
289
+ focusMode,
262
290
  sessions,
263
291
  snippetEditorFields,
264
292
  snippets,
@@ -269,12 +297,36 @@ export function RootView({
269
297
  }
270
298
 
271
299
  return (
272
- <box flexDirection="column" width="100%" height="100%" backgroundColor={editorBg}>
300
+ <box
301
+ flexDirection="column"
302
+ width="100%"
303
+ height="100%"
304
+ backgroundColor={editorBg}
305
+ onMouseDrag={(event) => {
306
+ if (onSeparatorDrag?.(event)) {
307
+ event.preventDefault()
308
+ event.stopPropagation()
309
+ }
310
+ }}
311
+ onMouseUp={() => {
312
+ onSeparatorDragEnd?.()
313
+ }}
314
+ >
273
315
  {sessionBarPosition === 'top' && <SessionBar />}
274
316
  <box flexDirection="row" gap={0} padding={0} flexGrow={1}>
275
- <Sidebar onTabActivate={onPaneActivate} />
317
+ <Sidebar
318
+ onTabActivate={onPaneActivate}
319
+ onEmbeddedGitResizeStart={onEmbeddedGitResizeStart}
320
+ onResizeDrag={onSeparatorDrag}
321
+ onResizeDragEnd={onSeparatorDragEnd}
322
+ onSidebarResizeStart={onSidebarResizeStart}
323
+ />
276
324
  {gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'left' ? (
277
- <GitPaneInPaneMode ratio={gitPaneRatio} />
325
+ <GitPaneInPaneMode
326
+ position="left"
327
+ ratio={gitPaneRatio}
328
+ onGitPaneResizeStart={onGitPaneResizeStart}
329
+ />
278
330
  ) : null}
279
331
  {activeTree && activeTree.type === 'split' ? (
280
332
  <SplitLayout
@@ -321,7 +373,11 @@ export function RootView({
321
373
  />
322
374
  )}
323
375
  {gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'right' ? (
324
- <GitPaneInPaneMode ratio={gitPaneRatio} />
376
+ <GitPaneInPaneMode
377
+ position="right"
378
+ ratio={gitPaneRatio}
379
+ onGitPaneResizeStart={onGitPaneResizeStart}
380
+ />
325
381
  ) : null}
326
382
  </box>
327
383
  {sessionBarPosition === 'bottom' && <SessionBar />}
@@ -333,6 +389,7 @@ export function RootView({
333
389
  currentSessionId,
334
390
  currentTabCount: tabs.length,
335
391
  customCommands,
392
+ focusMode,
336
393
  sessions,
337
394
  snippetEditorFields,
338
395
  snippets,
@@ -342,14 +399,58 @@ export function RootView({
342
399
  )
343
400
  }
344
401
 
345
- function GitPaneInPaneMode({ ratio }: { ratio: number }) {
402
+ function GitPaneInPaneMode({
403
+ onGitPaneResizeStart,
404
+ position,
405
+ ratio,
406
+ }: {
407
+ ratio: number
408
+ position: 'left' | 'right'
409
+ onGitPaneResizeStart?: (info: {
410
+ initialWidth: number
411
+ screenStart: number
412
+ side: 'left' | 'right'
413
+ }) => void
414
+ }) {
346
415
  const bg = useBg('elevated')
347
- // Ratio maps to a fixed column count (20..80), mirroring the reservation in
348
- // use-terminal-resize so the terminal-content area stays in sync.
349
- const width = Math.max(20, Math.min(80, Math.round(ratio * 80)))
416
+ const tokens = useTokens()
417
+ const gitPane = useAppStore((s) => s.gitPane)
418
+ const width = getGitPaneWidthFromRatio(ratio)
419
+ const contentWidth = Math.max(1, width - 1)
420
+ const gitPaneMenu = buildGitPaneContextMenu(
421
+ gitPane,
422
+ () => dispatchGlobal({ type: 'toggle-git-pane' }),
423
+ (mode, nextPosition) => {
424
+ dispatchGlobal({ mode, type: 'set-git-pane-mode' })
425
+ dispatchGlobal({ position: nextPosition, type: 'set-git-pane-position' })
426
+ }
427
+ )
428
+ const handle = (
429
+ <box
430
+ width={1}
431
+ flexShrink={0}
432
+ backgroundColor={tokens.border}
433
+ onMouseDown={(event) => {
434
+ event.preventDefault()
435
+ event.stopPropagation()
436
+ onGitPaneResizeStart?.({ initialWidth: width, screenStart: event.x, side: position })
437
+ }}
438
+ />
439
+ )
350
440
  return (
351
441
  <box flexDirection="column" width={width} flexShrink={0} backgroundColor={bg} overflow="hidden">
352
- <GitPaneWidget pollingEnabled />
442
+ <box flexDirection="row" flexGrow={1} overflow="hidden">
443
+ {position === 'right' ? handle : null}
444
+ <ContextMenuBox
445
+ width={contentWidth}
446
+ flexGrow={1}
447
+ overflow="hidden"
448
+ rightClickMenu={gitPaneMenu}
449
+ >
450
+ <GitPaneWidget pollingEnabled />
451
+ </ContextMenuBox>
452
+ {position === 'left' ? handle : null}
453
+ </box>
353
454
  </box>
354
455
  )
355
456
  }
@@ -61,7 +61,7 @@ export function getStatusBarModel(
61
61
  const currentSession = state.currentSessionId
62
62
  ? state.sessions.find((session) => session.id === state.currentSessionId)
63
63
  : undefined
64
- const sessionName = currentSession?.name ?? 'no session'
64
+ const sessionName = currentSession?.name ?? 'no workspace'
65
65
  const sessionLabel = currentSession?.projectPath
66
66
  ? `${sessionName} (${abbreviatePath(currentSession.projectPath)})`
67
67
  : sessionName