@brimveyn/aimux 1.14.2 → 1.14.4
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 +2 -2
- package/src/app-runtime/use-terminal-resize.ts +32 -8
- package/src/app.tsx +11 -0
- package/src/daemon/daemon.ts +55 -1
- package/src/daemon/runtime-paths.ts +9 -0
- package/src/daemon/session-registry.ts +2 -0
- package/src/index.tsx +14 -0
- package/src/integrations/claude-hook-server.ts +99 -0
- package/src/integrations/claude-hooks-install.ts +199 -0
- package/src/ipc/manager-protocol.ts +33 -2
- package/src/pty/assistant-status-arbiter.ts +94 -0
- package/src/pty/assistant-status-detection-loop.ts +20 -1
- package/src/pty/pty-manager.ts +4 -0
- package/src/pty/terminal-snapshot.ts +43 -66
- package/src/session-backend/local-session-backend.ts +58 -6
- package/src/session-backend/remote-session-backend.ts +10 -3
- package/src/session-backend/types.ts +16 -2
- package/src/state/types.ts +8 -0
- package/src/ui/components/git/git-panel.tsx +13 -6
- package/src/ui/components/git/git-view.tsx +9 -28
- package/src/ui/components/git/pane/git-pane-header.tsx +127 -0
- package/src/ui/components/git/pane/git-pane-widget.tsx +16 -10
- package/src/ui/components/layout/terminal-pane.tsx +10 -1
- package/src/ui/host-palette.ts +75 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { memo, useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { GitPanelState } from '../../../../state/types'
|
|
4
|
+
|
|
5
|
+
import { useAppStore } from '../../../../state/app-store'
|
|
6
|
+
import { dispatchGlobal, runSideEffectGlobal } from '../../../../state/dispatch-ref'
|
|
7
|
+
import { useTheme } from '../../../theme'
|
|
8
|
+
|
|
9
|
+
interface GitPaneHeaderProps {
|
|
10
|
+
gitPanel: GitPanelState
|
|
11
|
+
projectPath: string | undefined
|
|
12
|
+
headOffset?: number
|
|
13
|
+
baseLabel?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const GitPaneHeader = memo(function GitPaneHeader({
|
|
17
|
+
baseLabel,
|
|
18
|
+
gitPanel,
|
|
19
|
+
headOffset = 0,
|
|
20
|
+
projectPath,
|
|
21
|
+
}: GitPaneHeaderProps) {
|
|
22
|
+
const t = useTheme()
|
|
23
|
+
const fileListMode = useAppStore((s) => s.gitPane.fileListMode)
|
|
24
|
+
const nextFileListMode = fileListMode === 'tree' ? 'flat' : 'tree'
|
|
25
|
+
const toggleListMode = useCallback(() => {
|
|
26
|
+
dispatchGlobal({ type: 'git-mode-toggle-file-list-mode' })
|
|
27
|
+
runSideEffectGlobal({ mode: nextFileListMode, type: 'persist-git-file-list-mode' })
|
|
28
|
+
}, [nextFileListMode])
|
|
29
|
+
|
|
30
|
+
const hasProject = projectPath != null && projectPath !== ''
|
|
31
|
+
if (!hasProject) return null
|
|
32
|
+
if (gitPanel.error !== null) return null
|
|
33
|
+
|
|
34
|
+
const branch = gitPanel.branch
|
|
35
|
+
const branchLabel = branch != null && branch !== '' ? branch : 'detached'
|
|
36
|
+
const branchIsResolved = branch != null && branch !== ''
|
|
37
|
+
|
|
38
|
+
const ahead = gitPanel.ahead
|
|
39
|
+
const behind = gitPanel.behind
|
|
40
|
+
const showAhead = ahead > 0
|
|
41
|
+
const showBehind = behind > 0
|
|
42
|
+
const showTracking = showAhead || showBehind
|
|
43
|
+
|
|
44
|
+
const showToggle = gitPanel.files.length > 0
|
|
45
|
+
const showHistorical = headOffset > 0
|
|
46
|
+
const showReviewBase = baseLabel != null && baseLabel !== ''
|
|
47
|
+
const showScope = showHistorical || showReviewBase
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<box flexDirection="column" flexShrink={0} paddingBottom={1}>
|
|
51
|
+
<box flexDirection="row" justifyContent="space-between">
|
|
52
|
+
<box flexDirection="row" flexShrink={1} overflow="hidden">
|
|
53
|
+
<text selectable={false} fg={t.textMuted} wrapMode="none">
|
|
54
|
+
{'\u{e702}'}
|
|
55
|
+
</text>
|
|
56
|
+
<text selectable={false} fg={t.textMuted} wrapMode="none">
|
|
57
|
+
{' '}
|
|
58
|
+
</text>
|
|
59
|
+
<text selectable={false} fg={branchIsResolved ? t.text : t.textMuted} wrapMode="none">
|
|
60
|
+
{branchIsResolved ? <strong>{branchLabel}</strong> : branchLabel}
|
|
61
|
+
</text>
|
|
62
|
+
</box>
|
|
63
|
+
<box flexDirection="row" flexShrink={0} gap={2}>
|
|
64
|
+
{showTracking ? (
|
|
65
|
+
<box flexDirection="row" gap={1}>
|
|
66
|
+
{showAhead ? (
|
|
67
|
+
<text selectable={false} fg={t.textMuted} wrapMode="none">
|
|
68
|
+
{`↑${ahead}`}
|
|
69
|
+
</text>
|
|
70
|
+
) : null}
|
|
71
|
+
{showBehind ? (
|
|
72
|
+
<text selectable={false} fg={t.textMuted} wrapMode="none">
|
|
73
|
+
{`↓${behind}`}
|
|
74
|
+
</text>
|
|
75
|
+
) : null}
|
|
76
|
+
</box>
|
|
77
|
+
) : null}
|
|
78
|
+
{showToggle ? (
|
|
79
|
+
<box flexDirection="row" gap={1} onMouseDown={toggleListMode}>
|
|
80
|
+
<text
|
|
81
|
+
selectable={false}
|
|
82
|
+
fg={fileListMode === 'tree' ? t.primary : t.textMuted}
|
|
83
|
+
wrapMode="none"
|
|
84
|
+
>
|
|
85
|
+
tree
|
|
86
|
+
</text>
|
|
87
|
+
<text selectable={false} fg={t.textMuted} wrapMode="none">
|
|
88
|
+
|
|
|
89
|
+
</text>
|
|
90
|
+
<text
|
|
91
|
+
selectable={false}
|
|
92
|
+
fg={fileListMode === 'flat' ? t.primary : t.textMuted}
|
|
93
|
+
wrapMode="none"
|
|
94
|
+
>
|
|
95
|
+
flat
|
|
96
|
+
</text>
|
|
97
|
+
</box>
|
|
98
|
+
) : null}
|
|
99
|
+
</box>
|
|
100
|
+
</box>
|
|
101
|
+
{showScope ? (
|
|
102
|
+
<box flexDirection="row" gap={2}>
|
|
103
|
+
{showHistorical ? (
|
|
104
|
+
<text selectable={false} fg={t.warning} wrapMode="none">
|
|
105
|
+
<strong>HEAD~{headOffset}</strong>
|
|
106
|
+
</text>
|
|
107
|
+
) : null}
|
|
108
|
+
{showHistorical ? (
|
|
109
|
+
<text selectable={false} fg={t.textMuted} wrapMode="none">
|
|
110
|
+
[ newer · ] older
|
|
111
|
+
</text>
|
|
112
|
+
) : null}
|
|
113
|
+
{showReviewBase ? (
|
|
114
|
+
<text selectable={false} fg={t.primary} wrapMode="none">
|
|
115
|
+
<strong>{baseLabel}</strong>
|
|
116
|
+
</text>
|
|
117
|
+
) : null}
|
|
118
|
+
{showReviewBase ? (
|
|
119
|
+
<text selectable={false} fg={t.textMuted} wrapMode="none">
|
|
120
|
+
b: back
|
|
121
|
+
</text>
|
|
122
|
+
) : null}
|
|
123
|
+
</box>
|
|
124
|
+
) : null}
|
|
125
|
+
</box>
|
|
126
|
+
)
|
|
127
|
+
})
|
|
@@ -7,6 +7,7 @@ import { useRepoDiscovery } from '../../../../git/use-repo-discovery'
|
|
|
7
7
|
import { useAppStore } from '../../../../state/app-store'
|
|
8
8
|
import { getSessionProjectPath } from '../../../../state/session-worktrees'
|
|
9
9
|
import { GitPanel } from '../git-panel'
|
|
10
|
+
import { GitPaneHeader } from './git-pane-header'
|
|
10
11
|
|
|
11
12
|
interface GitPaneWidgetProps {
|
|
12
13
|
pollingEnabled: boolean
|
|
@@ -43,15 +44,20 @@ export const GitPaneWidget = memo(function GitPaneWidget({ pollingEnabled }: Git
|
|
|
43
44
|
const display = lastGoodRef.current ?? gitPanel
|
|
44
45
|
|
|
45
46
|
return (
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
<box flexDirection="column" flexGrow={1} flexShrink={1} flexBasis={0} overflow="hidden">
|
|
48
|
+
<GitPaneHeader gitPanel={display} projectPath={projectPath} />
|
|
49
|
+
<GitPanel
|
|
50
|
+
collapsedFolders={gitMode.collapsedFolders}
|
|
51
|
+
compact={treeCompaction}
|
|
52
|
+
diffCountConfig={diffCountConfig}
|
|
53
|
+
fileListMode={gitFileListMode}
|
|
54
|
+
gitPanel={display}
|
|
55
|
+
pathConfig={pathConfig}
|
|
56
|
+
projectPath={projectPath}
|
|
57
|
+
selectedEntryKey={gitMode.selectedEntryKey}
|
|
58
|
+
showFileListToggle={false}
|
|
59
|
+
showRemoteTracking={false}
|
|
60
|
+
/>
|
|
61
|
+
</box>
|
|
56
62
|
)
|
|
57
63
|
})
|
|
@@ -10,6 +10,7 @@ import { type MeasuredPaneRect, usePaneSizeReport } from '../../../app-runtime/u
|
|
|
10
10
|
import { logInputDebug } from '../../../debug/input-log'
|
|
11
11
|
import { dispatchGlobal, runSideEffectGlobal } from '../../../state/dispatch-ref'
|
|
12
12
|
import { type ContextMenuItem, openContextMenu } from '../../context-menu/controller'
|
|
13
|
+
import { resolvePaletteIndex } from '../../host-palette'
|
|
13
14
|
import { getCurrentTheme, useTheme } from '../../theme'
|
|
14
15
|
import { ContextMenuBox } from '../overlays/context-menu/context-menu-box'
|
|
15
16
|
|
|
@@ -76,8 +77,16 @@ function renderSpan(span: TerminalSpan, key: string): ReactNode {
|
|
|
76
77
|
node = <strong>{node}</strong>
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
// Palette indices are resolved here (not in the daemon) so they pick up
|
|
81
|
+
// the host terminal's actual ANSI palette queried at startup.
|
|
82
|
+
const fg =
|
|
83
|
+
span.fgPalette !== undefined
|
|
84
|
+
? resolvePaletteIndex(span.fgPalette)
|
|
85
|
+
: (span.fg ?? getCurrentTheme().text)
|
|
86
|
+
const bg = span.bgPalette !== undefined ? resolvePaletteIndex(span.bgPalette) : span.bg
|
|
87
|
+
|
|
79
88
|
return (
|
|
80
|
-
<span key={key} fg={
|
|
89
|
+
<span key={key} fg={fg} bg={bg}>
|
|
81
90
|
{node}
|
|
82
91
|
</span>
|
|
83
92
|
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Resolves ANSI palette indices (0-255) to hex strings using the host
|
|
2
|
+
// terminal's actual palette, queried via OSC 4 at startup (see index.tsx).
|
|
3
|
+
//
|
|
4
|
+
// Before detection completes — or when the host terminal doesn't respond —
|
|
5
|
+
// FALLBACK_PALETTE (xterm defaults) is used. Indices ≥16 fall back to the
|
|
6
|
+
// universal 6×6×6 cube + grayscale ramp, which every terminal agrees on.
|
|
7
|
+
|
|
8
|
+
const FALLBACK_PALETTE: readonly string[] = [
|
|
9
|
+
'#000000',
|
|
10
|
+
'#cd0000',
|
|
11
|
+
'#00cd00',
|
|
12
|
+
'#cdcd00',
|
|
13
|
+
'#0000ee',
|
|
14
|
+
'#cd00cd',
|
|
15
|
+
'#00cdcd',
|
|
16
|
+
'#e5e5e5',
|
|
17
|
+
'#7f7f7f',
|
|
18
|
+
'#ff0000',
|
|
19
|
+
'#00ff00',
|
|
20
|
+
'#ffff00',
|
|
21
|
+
'#5c5cff',
|
|
22
|
+
'#ff00ff',
|
|
23
|
+
'#00ffff',
|
|
24
|
+
'#ffffff',
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
const CUBE_CHANNELS = [0, 95, 135, 175, 215, 255] as const
|
|
28
|
+
|
|
29
|
+
const hostPalette: string[] = [...FALLBACK_PALETTE]
|
|
30
|
+
|
|
31
|
+
function toHex(value: number): string {
|
|
32
|
+
return `#${value.toString(16).padStart(6, '0')}`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function paletteFromFormula(index: number): string {
|
|
36
|
+
if (index >= 232) {
|
|
37
|
+
const shade = 8 + (index - 232) * 10
|
|
38
|
+
return toHex((shade << 16) | (shade << 8) | shade)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const normalized = index - 16
|
|
42
|
+
const r = Math.floor(normalized / 36)
|
|
43
|
+
const g = Math.floor((normalized % 36) / 6)
|
|
44
|
+
const b = normalized % 6
|
|
45
|
+
return toHex(
|
|
46
|
+
((CUBE_CHANNELS[r] ?? 0) << 16) | ((CUBE_CHANNELS[g] ?? 0) << 8) | (CUBE_CHANNELS[b] ?? 0)
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Replace entries 0..N-1 of the resolver with values queried from the host
|
|
51
|
+
* terminal. Null entries keep the existing fallback (terminal didn't respond
|
|
52
|
+
* for that index). Safe to call before any snapshots have been rendered. */
|
|
53
|
+
export function setHostPalette(palette: readonly (string | null)[]): void {
|
|
54
|
+
for (let index = 0; index < palette.length; index += 1) {
|
|
55
|
+
const value = palette[index]
|
|
56
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
57
|
+
hostPalette[index] = value
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Resolve an ANSI palette index (0-255) to a hex color. Indices 0-15 use
|
|
63
|
+
* the host-queried palette (or xterm fallback); 16-255 use the universal
|
|
64
|
+
* cube/grayscale formula unless the host explicitly customized them. */
|
|
65
|
+
const BLACK = '#000000'
|
|
66
|
+
const WHITE = '#ffffff'
|
|
67
|
+
|
|
68
|
+
export function resolvePaletteIndex(index: number): string {
|
|
69
|
+
if (index < 0) return BLACK
|
|
70
|
+
const override = hostPalette[index]
|
|
71
|
+
if (typeof override === 'string' && override.length > 0) return override
|
|
72
|
+
if (index < 16) return FALLBACK_PALETTE.at(index) ?? BLACK
|
|
73
|
+
if (index > 255) return WHITE
|
|
74
|
+
return paletteFromFormula(index)
|
|
75
|
+
}
|