@brimveyn/aimux 1.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.
Files changed (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +206 -0
  3. package/package.json +79 -0
  4. package/src/app-runtime/backend-attach-runtime.ts +135 -0
  5. package/src/app-runtime/backend-runtime-events.ts +78 -0
  6. package/src/app-runtime/click-selection-resolver.ts +130 -0
  7. package/src/app-runtime/pty-write.ts +32 -0
  8. package/src/app-runtime/render-invalidation.ts +20 -0
  9. package/src/app-runtime/selection-clipboard.ts +69 -0
  10. package/src/app-runtime/selection-scroll.ts +143 -0
  11. package/src/app-runtime/session-actions.ts +170 -0
  12. package/src/app-runtime/side-effects.ts +434 -0
  13. package/src/app-runtime/snippet-actions.ts +90 -0
  14. package/src/app-runtime/split-drag-controller.ts +15 -0
  15. package/src/app-runtime/tab-runtime-timeouts.ts +86 -0
  16. package/src/app-runtime/terminal-mouse-adapter.ts +31 -0
  17. package/src/app-runtime/use-backend-runtime.ts +99 -0
  18. package/src/app-runtime/use-directory-search.ts +52 -0
  19. package/src/app-runtime/use-mouse-handlers.ts +183 -0
  20. package/src/app-runtime/use-renderer-bindings.ts +163 -0
  21. package/src/app-runtime/use-terminal-resize.ts +141 -0
  22. package/src/app-runtime/use-workspace-autosave.ts +39 -0
  23. package/src/app.tsx +247 -0
  24. package/src/config/loader.ts +36 -0
  25. package/src/config.ts +146 -0
  26. package/src/daemon/daemon.ts +236 -0
  27. package/src/daemon/runtime-paths.ts +69 -0
  28. package/src/daemon/session-manager.ts +109 -0
  29. package/src/daemon/session-registry.ts +207 -0
  30. package/src/debug/input-log.ts +29 -0
  31. package/src/doctor.ts +106 -0
  32. package/src/git/git-poller.ts +49 -0
  33. package/src/git/git-status.ts +183 -0
  34. package/src/index.tsx +56 -0
  35. package/src/input/keymap/build-handlers.ts +34 -0
  36. package/src/input/keymap/key-chord.ts +201 -0
  37. package/src/input/keymap/keymap-mode-handler.ts +88 -0
  38. package/src/input/keymap/sequence-resolver.ts +117 -0
  39. package/src/input/keymap/trie.ts +103 -0
  40. package/src/input/modes/bridge.ts +58 -0
  41. package/src/input/modes/handlers/index.ts +18 -0
  42. package/src/input/modes/handlers/shared.ts +70 -0
  43. package/src/input/modes/registry.ts +34 -0
  44. package/src/input/modes/transitions.ts +37 -0
  45. package/src/input/modes/types.ts +63 -0
  46. package/src/input/mouse-forwarding.ts +98 -0
  47. package/src/input/multi-click-detector.ts +55 -0
  48. package/src/input/paste.ts +12 -0
  49. package/src/input/raw-input-handler.ts +191 -0
  50. package/src/input/terminal-text-extraction.ts +78 -0
  51. package/src/ipc/protocol.ts +341 -0
  52. package/src/platform/clipboard.ts +13 -0
  53. package/src/platform/daemon-control.ts +62 -0
  54. package/src/platform/id.ts +7 -0
  55. package/src/platform/project-search.ts +75 -0
  56. package/src/pty/command-registry.ts +69 -0
  57. package/src/pty/pty-manager.ts +268 -0
  58. package/src/pty/terminal-snapshot.ts +210 -0
  59. package/src/restart-daemon.ts +28 -0
  60. package/src/session-backend/bootstrap.ts +107 -0
  61. package/src/session-backend/local-session-backend.ts +164 -0
  62. package/src/session-backend/remote-session-backend.ts +373 -0
  63. package/src/session-backend/types.ts +47 -0
  64. package/src/state/app-store.ts +19 -0
  65. package/src/state/layout-resize.ts +56 -0
  66. package/src/state/layout-tree.ts +323 -0
  67. package/src/state/reducers/git-panel-state.ts +102 -0
  68. package/src/state/reducers/modal-state.ts +358 -0
  69. package/src/state/reducers/session-state.ts +86 -0
  70. package/src/state/reducers/tab-state.ts +531 -0
  71. package/src/state/reducers/ui-state.ts +29 -0
  72. package/src/state/selectors.ts +30 -0
  73. package/src/state/session-catalog.ts +96 -0
  74. package/src/state/session-persistence.ts +210 -0
  75. package/src/state/snippet-catalog.ts +90 -0
  76. package/src/state/store.ts +93 -0
  77. package/src/state/terminal-modes.ts +11 -0
  78. package/src/state/types.ts +367 -0
  79. package/src/state/validation.ts +154 -0
  80. package/src/state/workspace-save.ts +33 -0
  81. package/src/ui/components/create-session-modal.tsx +100 -0
  82. package/src/ui/components/git-panel.tsx +200 -0
  83. package/src/ui/components/help-modal.tsx +79 -0
  84. package/src/ui/components/input-field.tsx +18 -0
  85. package/src/ui/components/list-item.tsx +30 -0
  86. package/src/ui/components/modal-filter-bar.tsx +18 -0
  87. package/src/ui/components/modal-shell.tsx +47 -0
  88. package/src/ui/components/new-tab-modal.tsx +52 -0
  89. package/src/ui/components/pending-chord-indicator.tsx +41 -0
  90. package/src/ui/components/session-name-modal.tsx +15 -0
  91. package/src/ui/components/session-picker-modal.tsx +93 -0
  92. package/src/ui/components/sidebar-group-metadata.ts +44 -0
  93. package/src/ui/components/sidebar-scroll.ts +33 -0
  94. package/src/ui/components/sidebar.tsx +225 -0
  95. package/src/ui/components/snippet-editor-modal.tsx +39 -0
  96. package/src/ui/components/snippet-picker-modal.tsx +56 -0
  97. package/src/ui/components/split-layout.tsx +201 -0
  98. package/src/ui/components/status-bar.tsx +60 -0
  99. package/src/ui/components/surface.tsx +63 -0
  100. package/src/ui/components/tab-item.tsx +118 -0
  101. package/src/ui/components/terminal-pane.tsx +215 -0
  102. package/src/ui/components/theme-picker-modal.tsx +35 -0
  103. package/src/ui/components/use-sidebar-auto-scroll.ts +63 -0
  104. package/src/ui/components/use-sidebar-branch.ts +36 -0
  105. package/src/ui/directory-search.ts +1 -0
  106. package/src/ui/git-branch.ts +11 -0
  107. package/src/ui/path-format.ts +6 -0
  108. package/src/ui/root.tsx +261 -0
  109. package/src/ui/status-bar-model.ts +87 -0
  110. package/src/ui/theme.ts +9 -0
  111. package/src/ui/themes.ts +255 -0
  112. package/src/ui/ui-tokens.ts +11 -0
  113. package/src/update.ts +62 -0
@@ -0,0 +1,261 @@
1
+ import type { MouseEvent } from '@opentui/core'
2
+
3
+ import type { TerminalContentOrigin } from '../input/raw-input-handler'
4
+ import type { ModalState, SessionRecord, SnippetRecord } from '../state/types'
5
+ import type { ThemeId } from './themes'
6
+
7
+ import { useAppStore } from '../state/app-store'
8
+ import { getTreeForTab, PANE_BORDER, type SplitDirection } from '../state/layout-tree'
9
+ import { CreateSessionModal } from './components/create-session-modal'
10
+ import { HelpModal } from './components/help-modal'
11
+ import { NewTabModal } from './components/new-tab-modal'
12
+ import { PendingChordIndicator } from './components/pending-chord-indicator'
13
+ import { SessionNameModal } from './components/session-name-modal'
14
+ import { SessionPickerModal } from './components/session-picker-modal'
15
+ import { Sidebar } from './components/sidebar'
16
+ import { SnippetEditorModal } from './components/snippet-editor-modal'
17
+ import { SnippetPickerModal } from './components/snippet-picker-modal'
18
+ import { SplitLayout } from './components/split-layout'
19
+ import { StatusBar } from './components/status-bar'
20
+ import { TerminalPane } from './components/terminal-pane'
21
+ import { ThemePickerModal } from './components/theme-picker-modal'
22
+ import { theme } from './theme'
23
+
24
+ function getCreateSessionFields(modal: ModalState) {
25
+ if (modal.type !== 'create-session') {
26
+ return { directoryQuery: '', sessionName: '' }
27
+ }
28
+
29
+ if (modal.activeField === 'directory') {
30
+ return {
31
+ directoryQuery: modal.editBuffer ?? '',
32
+ sessionName: modal.nameBuffer,
33
+ }
34
+ }
35
+
36
+ return {
37
+ directoryQuery: modal.nameBuffer,
38
+ sessionName: modal.editBuffer ?? '',
39
+ }
40
+ }
41
+
42
+ function getSnippetEditorFields(modal: ModalState) {
43
+ if (modal.type !== 'snippet-editor') {
44
+ return { snippetContent: '', snippetName: '' }
45
+ }
46
+
47
+ if (modal.activeField === 'name') {
48
+ return {
49
+ snippetContent: modal.contentBuffer,
50
+ snippetName: modal.editBuffer ?? '',
51
+ }
52
+ }
53
+
54
+ return {
55
+ snippetContent: modal.editBuffer ?? '',
56
+ snippetName: modal.contentBuffer,
57
+ }
58
+ }
59
+
60
+ function renderModal(
61
+ modal: ModalState,
62
+ options: {
63
+ customCommands: Record<string, string>
64
+ sessions: SessionRecord[]
65
+ currentSessionId: string | null
66
+ currentTabCount: number
67
+ snippets: SnippetRecord[]
68
+ themeId: ThemeId
69
+ createSessionFields: { directoryQuery: string; sessionName: string }
70
+ snippetEditorFields: { snippetName: string; snippetContent: string }
71
+ }
72
+ ) {
73
+ switch (modal.type) {
74
+ case 'new-tab':
75
+ case 'split-picker':
76
+ return (
77
+ <NewTabModal
78
+ selectedIndex={modal.selectedIndex}
79
+ customCommands={options.customCommands}
80
+ editBuffer={modal.editBuffer}
81
+ />
82
+ )
83
+ case 'session-picker':
84
+ return (
85
+ <SessionPickerModal
86
+ sessions={options.sessions}
87
+ selectedIndex={modal.selectedIndex}
88
+ currentSessionId={options.currentSessionId}
89
+ currentTabCount={options.currentTabCount}
90
+ filter={modal.editBuffer}
91
+ />
92
+ )
93
+ case 'session-name':
94
+ return (
95
+ <SessionNameModal
96
+ title={modal.sessionTargetId ? 'Rename session' : 'Create session'}
97
+ value={modal.editBuffer ?? ''}
98
+ />
99
+ )
100
+ case 'rename-tab':
101
+ return <SessionNameModal title="Rename tab" value={modal.editBuffer ?? ''} />
102
+ case 'create-session':
103
+ return (
104
+ <CreateSessionModal
105
+ activeField={modal.activeField}
106
+ directoryQuery={options.createSessionFields.directoryQuery}
107
+ sessionName={options.createSessionFields.sessionName}
108
+ results={modal.directoryResults}
109
+ selectedIndex={modal.selectedIndex}
110
+ pendingProjectPath={modal.pendingProjectPath}
111
+ />
112
+ )
113
+ case 'snippet-picker':
114
+ return (
115
+ <SnippetPickerModal
116
+ snippets={options.snippets}
117
+ selectedIndex={modal.selectedIndex}
118
+ filter={modal.editBuffer}
119
+ />
120
+ )
121
+ case 'snippet-editor':
122
+ return (
123
+ <SnippetEditorModal
124
+ activeField={modal.activeField}
125
+ snippetName={options.snippetEditorFields.snippetName}
126
+ snippetContent={options.snippetEditorFields.snippetContent}
127
+ isEditing={modal.sessionTargetId !== null}
128
+ />
129
+ )
130
+ case 'theme-picker':
131
+ return (
132
+ <ThemePickerModal selectedIndex={modal.selectedIndex} currentThemeId={options.themeId} />
133
+ )
134
+ case 'help':
135
+ return <HelpModal />
136
+ case null:
137
+ return null
138
+ default:
139
+ modal satisfies never
140
+ }
141
+ }
142
+
143
+ interface RootViewProps {
144
+ themeId: ThemeId
145
+ contentOrigin: TerminalContentOrigin
146
+ mouseForwardingEnabled: boolean
147
+ localScrollbackEnabled: boolean
148
+ onTerminalMouseEvent: (event: MouseEvent, origin: TerminalContentOrigin) => void
149
+ onTerminalScrollEvent: (event: MouseEvent) => void
150
+ onTerminalClick?: (event: MouseEvent, origin: TerminalContentOrigin, tabId?: string) => void
151
+ onPaneActivate?: (tabId: string) => void
152
+ onSplitResize?: (tabId: string, ratio: number, axis: SplitDirection) => void
153
+ onSeparatorDragStart?: (info: {
154
+ tabId: string
155
+ direction: SplitDirection
156
+ screenStart: number
157
+ totalSize: number
158
+ }) => void
159
+ onSeparatorDrag?: (event: MouseEvent) => boolean
160
+ onSeparatorDragEnd?: () => void
161
+ terminalCols: number
162
+ terminalRows: number
163
+ }
164
+
165
+ export function RootView({
166
+ contentOrigin,
167
+ localScrollbackEnabled,
168
+ mouseForwardingEnabled,
169
+ onPaneActivate,
170
+ onSeparatorDrag,
171
+ onSeparatorDragEnd,
172
+ onSeparatorDragStart,
173
+ onSplitResize,
174
+ onTerminalClick,
175
+ onTerminalMouseEvent,
176
+ onTerminalScrollEvent,
177
+ terminalCols,
178
+ terminalRows,
179
+ themeId,
180
+ }: RootViewProps) {
181
+ const tabs = useAppStore((s) => s.tabs)
182
+ const activeTabId = useAppStore((s) => s.activeTabId)
183
+ const layoutTrees = useAppStore((s) => s.layoutTrees)
184
+ const tabGroupMap = useAppStore((s) => s.tabGroupMap)
185
+ const focusMode = useAppStore((s) => s.focusMode)
186
+ const modal = useAppStore((s) => s.modal)
187
+ const snippets = useAppStore((s) => s.snippets)
188
+ const customCommands = useAppStore((s) => s.customCommands)
189
+ const sessions = useAppStore((s) => s.sessions)
190
+ const currentSessionId = useAppStore((s) => s.currentSessionId)
191
+
192
+ const activeTab = tabs.find((tab) => tab.id === activeTabId)
193
+ const activeTree = activeTabId ? getTreeForTab(layoutTrees, tabGroupMap, activeTabId) : null
194
+ const createSessionFields = getCreateSessionFields(modal)
195
+ const snippetEditorFields = getSnippetEditorFields(modal)
196
+ const splitChrome = PANE_BORDER * 2
197
+
198
+ return (
199
+ <box flexDirection="column" width="100%" height="100%" backgroundColor={theme.background}>
200
+ <box flexDirection="row" gap={0} padding={0} flexGrow={1}>
201
+ <Sidebar onTabActivate={onPaneActivate} />
202
+ {activeTree && activeTree.type === 'split' ? (
203
+ <SplitLayout
204
+ node={activeTree}
205
+ tabs={tabs}
206
+ activeTabId={activeTabId}
207
+ focusMode={focusMode}
208
+ contentOrigin={{
209
+ cols: terminalCols + splitChrome,
210
+ rows: terminalRows + splitChrome,
211
+ x: contentOrigin.x - PANE_BORDER,
212
+ y: contentOrigin.y - PANE_BORDER,
213
+ }}
214
+ mouseForwardingEnabled={mouseForwardingEnabled}
215
+ localScrollbackEnabled={localScrollbackEnabled}
216
+ onTerminalMouseEvent={onTerminalMouseEvent}
217
+ onTerminalScrollEvent={onTerminalScrollEvent}
218
+ onTerminalClick={onTerminalClick}
219
+ onPaneActivate={onPaneActivate}
220
+ onSplitResize={onSplitResize}
221
+ onSeparatorDragStart={onSeparatorDragStart}
222
+ onSeparatorDrag={onSeparatorDrag}
223
+ onSeparatorDragEnd={onSeparatorDragEnd}
224
+ bounds={{
225
+ cols: terminalCols + splitChrome,
226
+ rows: terminalRows + splitChrome,
227
+ x: 0,
228
+ y: 0,
229
+ }}
230
+ />
231
+ ) : (
232
+ <TerminalPane
233
+ tab={activeTab}
234
+ tabId={activeTabId ?? undefined}
235
+ isActive
236
+ focusMode={focusMode}
237
+ contentOrigin={contentOrigin}
238
+ mouseForwardingEnabled={mouseForwardingEnabled}
239
+ localScrollbackEnabled={localScrollbackEnabled}
240
+ onTerminalMouseEvent={onTerminalMouseEvent}
241
+ onTerminalScrollEvent={onTerminalScrollEvent}
242
+ onTerminalClick={onTerminalClick}
243
+ onPaneActivate={onPaneActivate}
244
+ />
245
+ )}
246
+ </box>
247
+ <StatusBar />
248
+ {renderModal(modal, {
249
+ createSessionFields,
250
+ currentSessionId,
251
+ currentTabCount: tabs.length,
252
+ customCommands,
253
+ sessions,
254
+ snippetEditorFields,
255
+ snippets,
256
+ themeId,
257
+ })}
258
+ <PendingChordIndicator />
259
+ </box>
260
+ )
261
+ }
@@ -0,0 +1,87 @@
1
+ import type { AppState, TabSession } from '../state/types'
2
+
3
+ import { abbreviatePath } from './path-format'
4
+
5
+ export interface StatusBarModel {
6
+ left: string
7
+ right: string
8
+ }
9
+
10
+ const MAX_TAB_LABEL_LENGTH = 24
11
+
12
+ function truncateLabel(label: string): string {
13
+ if (label.length <= MAX_TAB_LABEL_LENGTH) {
14
+ return label
15
+ }
16
+
17
+ return `${label.slice(0, MAX_TAB_LABEL_LENGTH - 3)}...`
18
+ }
19
+
20
+ function getActiveTabLabel(tab?: TabSession): string {
21
+ if (!tab) {
22
+ return 'no tab'
23
+ }
24
+
25
+ return `${truncateLabel(tab.title)} (${tab.status})`
26
+ }
27
+
28
+ function getNavigationHint(activeTab?: TabSession): string {
29
+ if (!activeTab) {
30
+ return 'Ctrl+g sessions Ctrl+n new Ctrl+b toggle Ctrl+h/l resize ? help'
31
+ }
32
+
33
+ if (activeTab.status === 'disconnected') {
34
+ return 'Ctrl+r restart restored tab Ctrl+w close i focus'
35
+ }
36
+
37
+ return 'Ctrl+g sessions j/k move Shift+J/K reorder Ctrl+r restart Ctrl+w close i focus ? help'
38
+ }
39
+
40
+ function getInputHint(activeTab?: TabSession): string {
41
+ if (!activeTab) {
42
+ return 'Ctrl+n new no active tab to focus'
43
+ }
44
+
45
+ if (activeTab.status === 'disconnected') {
46
+ return 'Ctrl+z unfocus Ctrl+w layout Ctrl+r restart'
47
+ }
48
+
49
+ return 'Ctrl+z unfocus Ctrl+w layout typing goes to active tab'
50
+ }
51
+
52
+ export function getStatusBarModel(state: AppState, activeTab?: TabSession): StatusBarModel {
53
+ const currentSession = state.currentSessionId
54
+ ? state.sessions.find((session) => session.id === state.currentSessionId)
55
+ : undefined
56
+ const sessionName = currentSession?.name ?? 'no session'
57
+ const sessionLabel = currentSession?.projectPath
58
+ ? `${sessionName} (${abbreviatePath(currentSession.projectPath)})`
59
+ : sessionName
60
+
61
+ // \u{f0b1} = nf-fa-briefcase (session icon)
62
+ const sessionIcon = '\u{f0b1}'
63
+
64
+ switch (state.focusMode) {
65
+ case 'terminal-input':
66
+ return {
67
+ left: `${getActiveTabLabel(activeTab)} ${sessionIcon} ${sessionLabel}`,
68
+ right: getInputHint(activeTab),
69
+ }
70
+ case 'modal':
71
+ return {
72
+ left: `${sessionIcon} ${sessionLabel}`,
73
+ right: 'j/k move Enter confirm n/r/d actions Esc cancel',
74
+ }
75
+ case 'layout':
76
+ return {
77
+ left: `${getActiveTabLabel(activeTab)} ${sessionIcon} ${sessionLabel}`,
78
+ right: 'h/j/k/l focus |/- split H/L resize q close Esc cancel',
79
+ }
80
+ case 'navigation':
81
+ default:
82
+ return {
83
+ left: `${sessionIcon} ${sessionLabel} ${getActiveTabLabel(activeTab)}`,
84
+ right: getNavigationHint(activeTab),
85
+ }
86
+ }
87
+ }
@@ -0,0 +1,9 @@
1
+ import { type ThemeColors, type ThemeId, THEMES } from './themes'
2
+
3
+ export const theme: ThemeColors = { ...THEMES.aimux.colors }
4
+
5
+ export function applyTheme(id: ThemeId): void {
6
+ const entry = THEMES[id]
7
+ if (!entry) return
8
+ Object.assign(theme, entry.colors)
9
+ }
@@ -0,0 +1,255 @@
1
+ export interface ThemeColors {
2
+ background: string
3
+ panel: string
4
+ panelMuted: string
5
+ panelHighlight: string
6
+ overlay: string
7
+ border: string
8
+ borderActive: string
9
+ text: string
10
+ textMuted: string
11
+ accent: string
12
+ accentAlt: string
13
+ warning: string
14
+ danger: string
15
+ success: string
16
+ dim: string
17
+ }
18
+
19
+ export type ThemeId =
20
+ | 'aimux'
21
+ | 'dracula'
22
+ | 'dracula-at-night'
23
+ | 'everforest'
24
+ | 'tokyo-night'
25
+ | 'gruvbox-dark'
26
+ | 'catppuccin-mocha'
27
+ | 'nord'
28
+ | 'solarized-dark'
29
+ | 'one-dark'
30
+ | 'kanagawa'
31
+
32
+ export const THEMES: Record<ThemeId, { name: string; colors: ThemeColors }> = {
33
+ 'aimux': {
34
+ colors: {
35
+ accent: '#7cd1b8',
36
+ accentAlt: '#c4a7e7',
37
+ background: '#11151b',
38
+ border: '#2d3f52',
39
+ borderActive: '#7cd1b8',
40
+ danger: '#f38ba8',
41
+ dim: '#243242',
42
+ overlay: '#0b1016',
43
+ panel: '#16202b',
44
+ panelHighlight: '#1f3344',
45
+ panelMuted: '#1c2734',
46
+ success: '#8bd5ca',
47
+ text: '#edf4ff',
48
+ textMuted: '#6b829a',
49
+ warning: '#f6c177',
50
+ },
51
+ name: 'Aimux',
52
+ },
53
+ 'catppuccin-mocha': {
54
+ colors: {
55
+ accent: '#cba6f7',
56
+ accentAlt: '#f5c2e7',
57
+ background: '#1e1e2e',
58
+ border: '#45475a',
59
+ borderActive: '#cba6f7',
60
+ danger: '#f38ba8',
61
+ dim: '#313244',
62
+ overlay: '#181825',
63
+ panel: '#232334',
64
+ panelHighlight: '#313244',
65
+ panelMuted: '#2a2a3c',
66
+ success: '#a6e3a1',
67
+ text: '#cdd6f4',
68
+ textMuted: '#6c7086',
69
+ warning: '#f9e2af',
70
+ },
71
+ name: 'Catppuccin Mocha',
72
+ },
73
+ 'dracula': {
74
+ colors: {
75
+ accent: '#bd93f9',
76
+ accentAlt: '#ff79c6',
77
+ background: '#282a36',
78
+ border: '#44475a',
79
+ borderActive: '#bd93f9',
80
+ danger: '#ff5555',
81
+ dim: '#383a4a',
82
+ overlay: '#1f2029',
83
+ panel: '#2d2f3d',
84
+ panelHighlight: '#3d4056',
85
+ panelMuted: '#343746',
86
+ success: '#50fa7b',
87
+ text: '#f8f8f2',
88
+ textMuted: '#6272a4',
89
+ warning: '#f1fa8c',
90
+ },
91
+ name: 'Dracula',
92
+ },
93
+ 'dracula-at-night': {
94
+ colors: {
95
+ accent: '#bd93f9',
96
+ accentAlt: '#ff79c6',
97
+ background: '#0e1419',
98
+ border: '#2a3440',
99
+ borderActive: '#bd93f9',
100
+ danger: '#ff5555',
101
+ dim: '#1e2630',
102
+ overlay: '#090d11',
103
+ panel: '#131920',
104
+ panelHighlight: '#222a33',
105
+ panelMuted: '#1a2129',
106
+ success: '#50fa7b',
107
+ text: '#f8f8f2',
108
+ textMuted: '#6272a4',
109
+ warning: '#f1fa8c',
110
+ },
111
+ name: 'Dracula At Night',
112
+ },
113
+ 'everforest': {
114
+ colors: {
115
+ accent: '#a7c080',
116
+ accentAlt: '#d699b6',
117
+ background: '#2d353b',
118
+ border: '#4f585e',
119
+ borderActive: '#a7c080',
120
+ danger: '#e67e80',
121
+ dim: '#3d484d',
122
+ overlay: '#232a2e',
123
+ panel: '#343f44',
124
+ panelHighlight: '#475258',
125
+ panelMuted: '#3d484d',
126
+ success: '#a7c080',
127
+ text: '#d3c6aa',
128
+ textMuted: '#859289',
129
+ warning: '#dbbc7f',
130
+ },
131
+ name: 'Everforest',
132
+ },
133
+ 'gruvbox-dark': {
134
+ colors: {
135
+ accent: '#b8bb26',
136
+ accentAlt: '#d3869b',
137
+ background: '#282828',
138
+ border: '#504945',
139
+ borderActive: '#b8bb26',
140
+ danger: '#fb4934',
141
+ dim: '#3c3836',
142
+ overlay: '#1f1d1b',
143
+ panel: '#2e2e2e',
144
+ panelHighlight: '#504945',
145
+ panelMuted: '#3c3836',
146
+ success: '#b8bb26',
147
+ text: '#ebdbb2',
148
+ textMuted: '#928374',
149
+ warning: '#fabd2f',
150
+ },
151
+ name: 'Gruvbox Dark',
152
+ },
153
+ 'kanagawa': {
154
+ colors: {
155
+ accent: '#7e9cd8',
156
+ accentAlt: '#957fb8',
157
+ background: '#1f1f28',
158
+ border: '#363646',
159
+ borderActive: '#7e9cd8',
160
+ danger: '#e82424',
161
+ dim: '#2a2a37',
162
+ overlay: '#171720',
163
+ panel: '#24242e',
164
+ panelHighlight: '#363646',
165
+ panelMuted: '#2a2a37',
166
+ success: '#76946a',
167
+ text: '#dcd7ba',
168
+ textMuted: '#727169',
169
+ warning: '#e6c384',
170
+ },
171
+ name: 'Kanagawa',
172
+ },
173
+ 'nord': {
174
+ colors: {
175
+ accent: '#88c0d0',
176
+ accentAlt: '#b48ead',
177
+ background: '#2e3440',
178
+ border: '#434c5e',
179
+ borderActive: '#88c0d0',
180
+ danger: '#bf616a',
181
+ dim: '#3b4252',
182
+ overlay: '#252a33',
183
+ panel: '#333a47',
184
+ panelHighlight: '#434c5e',
185
+ panelMuted: '#3b4252',
186
+ success: '#a3be8c',
187
+ text: '#eceff4',
188
+ textMuted: '#7b88a1',
189
+ warning: '#ebcb8b',
190
+ },
191
+ name: 'Nord',
192
+ },
193
+ 'one-dark': {
194
+ colors: {
195
+ accent: '#61afef',
196
+ accentAlt: '#c678dd',
197
+ background: '#282c34',
198
+ border: '#3e4452',
199
+ borderActive: '#61afef',
200
+ danger: '#e06c75',
201
+ dim: '#3b4048',
202
+ overlay: '#1f2329',
203
+ panel: '#2c313a',
204
+ panelHighlight: '#3b4048',
205
+ panelMuted: '#333842',
206
+ success: '#98c379',
207
+ text: '#abb2bf',
208
+ textMuted: '#636d83',
209
+ warning: '#e5c07b',
210
+ },
211
+ name: 'One Dark',
212
+ },
213
+ 'solarized-dark': {
214
+ colors: {
215
+ accent: '#268bd2',
216
+ accentAlt: '#d33682',
217
+ background: '#002b36',
218
+ border: '#2e5560',
219
+ borderActive: '#268bd2',
220
+ danger: '#dc322f',
221
+ dim: '#073642',
222
+ overlay: '#00242c',
223
+ panel: '#003340',
224
+ panelHighlight: '#0a4050',
225
+ panelMuted: '#073642',
226
+ success: '#859900',
227
+ text: '#fdf6e3',
228
+ textMuted: '#657b83',
229
+ warning: '#b58900',
230
+ },
231
+ name: 'Solarized Dark',
232
+ },
233
+ 'tokyo-night': {
234
+ colors: {
235
+ accent: '#7aa2f7',
236
+ accentAlt: '#bb9af7',
237
+ background: '#1a1b26',
238
+ border: '#3b4261',
239
+ borderActive: '#7aa2f7',
240
+ danger: '#f7768e',
241
+ dim: '#292e42',
242
+ overlay: '#141722',
243
+ panel: '#1f2335',
244
+ panelHighlight: '#292e42',
245
+ panelMuted: '#24283b',
246
+ success: '#9ece6a',
247
+ text: '#c0caf5',
248
+ textMuted: '#565f89',
249
+ warning: '#e0af68',
250
+ },
251
+ name: 'Tokyo Night',
252
+ },
253
+ }
254
+
255
+ export const THEME_IDS = Object.keys(THEMES) as ThemeId[]
@@ -0,0 +1,11 @@
1
+ export const uiTokens = {
2
+ modalWidth: {
3
+ lg: 56,
4
+ md: 48,
5
+ xl: '60%',
6
+ },
7
+ padding: {
8
+ compact: 1,
9
+ section: 1,
10
+ },
11
+ } as const
package/src/update.ts ADDED
@@ -0,0 +1,62 @@
1
+ import { getDaemonSocketPath } from './daemon/runtime-paths'
2
+ import { findDaemonPid } from './platform/daemon-control'
3
+ import { runRestartDaemon } from './restart-daemon'
4
+
5
+ const REPO = 'BrimVeyn/aimux'
6
+
7
+ async function getLatestRelease(): Promise<string | null> {
8
+ const res = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`)
9
+ if (!res.ok) return null
10
+ const data = (await res.json()) as { tag_name: string }
11
+ return data.tag_name
12
+ }
13
+
14
+ async function getCurrentVersion(): Promise<string> {
15
+ const { version } = await import('../package.json')
16
+ return `v${version}`
17
+ }
18
+
19
+ export async function runUpdate(): Promise<number> {
20
+ process.stdout.write('Checking for updates...\n')
21
+
22
+ const latest = await getLatestRelease()
23
+ if (latest === null) {
24
+ process.stderr.write('Failed to fetch latest release from GitHub.\n')
25
+ return 1
26
+ }
27
+
28
+ const current = await getCurrentVersion()
29
+ if (latest === current) {
30
+ process.stdout.write(`Already up to date (${current}).\n`)
31
+ return 0
32
+ }
33
+
34
+ process.stdout.write(`Updating aimux ${current} → ${latest}...\n`)
35
+
36
+ const remove = Bun.spawn(['bun', 'remove', '-g', 'aimux'], {
37
+ stderr: 'inherit',
38
+ stdout: 'inherit',
39
+ })
40
+ await remove.exited
41
+
42
+ const install = Bun.spawn(['bun', 'install', '-g', `github:${REPO}#${latest}`], {
43
+ stderr: 'inherit',
44
+ stdout: 'inherit',
45
+ })
46
+
47
+ const exitCode = await install.exited
48
+ if (exitCode !== 0) {
49
+ process.stderr.write('Update failed.\n')
50
+ return 1
51
+ }
52
+
53
+ process.stdout.write(`Updated to ${latest}.\n`)
54
+
55
+ const pid = await findDaemonPid(getDaemonSocketPath())
56
+ if (pid !== null) {
57
+ process.stdout.write('Restarting daemon...\n')
58
+ await runRestartDaemon()
59
+ }
60
+
61
+ return 0
62
+ }