@brimveyn/aimux 1.12.6 → 1.13.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/README.md +3 -0
- package/package.json +4 -3
- package/src/app-runtime/auto-commit-driver.ts +8 -4
- package/src/app-runtime/auto-commit-ref.ts +1 -1
- package/src/app-runtime/backend-attach-runtime.ts +18 -2
- package/src/app-runtime/backend-runtime-events.ts +1 -1
- package/src/app-runtime/selection-scroll.ts +2 -2
- package/src/app-runtime/session-actions.ts +16 -4
- package/src/app-runtime/side-effects.ts +538 -83
- package/src/app-runtime/snippet-actions.ts +3 -3
- package/src/app-runtime/use-auto-commit-driver.ts +7 -4
- package/src/app-runtime/use-backend-runtime.ts +3 -3
- package/src/app-runtime/use-directory-search.ts +7 -5
- package/src/app-runtime/use-mouse-handlers.ts +11 -6
- package/src/app-runtime/use-pane-size-report.ts +1 -1
- package/src/app-runtime/use-renderer-bindings.ts +6 -5
- package/src/app.tsx +12 -7
- package/src/auto-commit/headless-commands.ts +4 -6
- package/src/daemon/daemon.ts +3 -3
- package/src/daemon/runtime-paths.ts +1 -1
- package/src/daemon/session-manager.ts +1 -1
- package/src/daemon/session-registry.ts +10 -3
- package/src/diff-parser/parse-patch-files.ts +18 -18
- package/src/git/command-queue.ts +1 -1
- package/src/git/divergence.ts +46 -0
- package/src/git/git-diff.ts +12 -5
- package/src/git/git-poller.ts +33 -11
- package/src/git/git-status.ts +14 -3
- package/src/git/move-worktree.ts +96 -0
- package/src/git/use-repo-discovery.ts +2 -1
- package/src/git/worktree-divergence-poller.ts +59 -0
- package/src/git/worktree.ts +99 -0
- package/src/index.tsx +1 -1
- package/src/input/keymap/describe-bindings.ts +27 -9
- package/src/input/keymap/key-chord.ts +4 -4
- package/src/input/keymap/key-format.ts +2 -2
- package/src/input/keymap/sequence-resolver.ts +1 -1
- package/src/input/keymap/trie.ts +1 -1
- package/src/input/modes/bridge.ts +7 -0
- package/src/input/modes/transitions.ts +2 -1
- package/src/input/modes/types.ts +11 -1
- package/src/input/raw-input-handler.ts +1 -1
- package/src/input/terminal-text-extraction.ts +9 -5
- package/src/integrations/claude-syntax-overlay.ts +8 -8
- package/src/integrations/claude-theme-sync.ts +12 -12
- package/src/ipc/protocol.ts +3 -3
- package/src/platform/clipboard.ts +4 -2
- package/src/platform/worktree-deps.ts +22 -0
- package/src/platform/worktree-paths.ts +67 -0
- package/src/profile-paths.ts +3 -3
- package/src/pty/assistant-status-detection-loop.ts +2 -2
- package/src/pty/assistant-status-detector.ts +15 -6
- package/src/pty/command-registry.ts +2 -1
- package/src/pty/pty-manager.ts +1 -1
- package/src/services/ai-usage/adapters/claude.ts +5 -5
- package/src/services/ai-usage/adapters/codex.ts +8 -8
- package/src/services/ai-usage/cache.ts +2 -2
- package/src/services/ai-usage/pace.ts +3 -6
- package/src/services/ai-usage/provider.ts +1 -1
- package/src/session-backend/bootstrap.ts +2 -2
- package/src/session-backend/local-session-backend.ts +11 -11
- package/src/session-backend/remote-session-backend.ts +3 -3
- package/src/session-backend/types.ts +2 -2
- package/src/snippets/expand-variables.ts +7 -7
- package/src/snippets/run-shell-var.ts +1 -1
- package/src/snippets/trigger-detector.ts +1 -1
- package/src/state/ai-usage-store.ts +1 -1
- package/src/state/git-tree.ts +8 -4
- package/src/state/layout-tree.ts +8 -5
- package/src/state/reducers/git-mode-state.ts +44 -14
- package/src/state/reducers/git-panel-state.ts +38 -2
- package/src/state/reducers/modal-state.ts +355 -21
- package/src/state/reducers/session-state.ts +60 -1
- package/src/state/reducers/tab-state.ts +151 -85
- package/src/state/selectors.ts +6 -4
- package/src/state/session-catalog.ts +20 -5
- package/src/state/session-persistence.ts +8 -4
- package/src/state/session-worktrees.ts +227 -0
- package/src/state/store.ts +1 -0
- package/src/state/toast-store.ts +95 -0
- package/src/state/types.ts +92 -13
- package/src/state/validation.ts +25 -2
- package/src/terminal-manager/manager-client.ts +15 -13
- package/src/ui/components/git/diff-renderer/fold-strip.tsx +2 -1
- package/src/ui/components/git/diff-renderer/pierre-diff.tsx +1 -1
- package/src/ui/components/git/diff-renderer/prepare-diff.ts +2 -2
- package/src/ui/components/git/diff-renderer/split-view.tsx +6 -4
- package/src/ui/components/git/diff-renderer/stacked-view.tsx +6 -4
- package/src/ui/components/git/diff-renderer/use-diff-prefetch.ts +19 -6
- package/src/ui/components/git/diff-renderer/use-diff-preparation.ts +2 -2
- package/src/ui/components/git/git-panel.tsx +17 -10
- package/src/ui/components/git/git-view.tsx +79 -19
- package/src/ui/components/git/image-diff/image-diff-view.tsx +2 -2
- package/src/ui/components/git/image-diff/terminal-image-pane.tsx +5 -4
- package/src/ui/components/git/pane/git-pane-context-menu.ts +2 -2
- package/src/ui/components/git/pane/git-pane-widget.tsx +6 -4
- package/src/ui/components/layout/session-bar.tsx +3 -3
- package/src/ui/components/layout/sidebar/sidebar.tsx +162 -36
- package/src/ui/components/layout/sidebar/tab-item.tsx +17 -1
- package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +2 -2
- package/src/ui/components/layout/sidebar/use-sidebar-branch.ts +1 -1
- package/src/ui/components/layout/split-layout.tsx +1 -1
- package/src/ui/components/layout/terminal-pane.tsx +43 -40
- package/src/ui/components/modals/app/ai-usage-modal.tsx +8 -5
- package/src/ui/components/modals/app/help-modal.tsx +4 -2
- package/src/ui/components/modals/git/git-commit-modal.tsx +2 -2
- package/src/ui/components/modals/sessions/create-session-modal.tsx +5 -1
- package/src/ui/components/modals/sessions/session-picker-modal.tsx +5 -4
- package/src/ui/components/modals/shared/form.tsx +4 -5
- package/src/ui/components/modals/shared/modal-shell.tsx +3 -3
- package/src/ui/components/modals/shared/picker.tsx +28 -5
- package/src/ui/components/modals/snippets/snippet-picker-modal.tsx +11 -3
- package/src/ui/components/modals/tabs/new-tab-modal.tsx +173 -5
- package/src/ui/components/modals/worktree/worktree-move-modal.tsx +94 -0
- package/src/ui/components/overlays/ai-usage/ai-usage-indicator.tsx +3 -3
- package/src/ui/components/overlays/toast/toast-item.tsx +88 -0
- package/src/ui/components/overlays/toast/toast-position.ts +45 -0
- package/src/ui/components/overlays/toast/toast-stack.tsx +30 -0
- package/src/ui/components/overlays/toast/toast-viewport.tsx +24 -0
- package/src/ui/components/overlays/toast/use-toast-animation.ts +64 -0
- package/src/ui/components/primitives/input-field.tsx +1 -1
- package/src/ui/components/primitives/list-item.tsx +3 -3
- package/src/ui/filter-themes.ts +1 -1
- package/src/ui/root.tsx +57 -3
- package/src/ui/session-ordering.ts +2 -2
- package/src/ui/status-bar-model.ts +14 -7
- package/src/ui/terminal-graphics/capabilities.ts +1 -1
- package/src/ui/terminal-graphics/format-fallback.ts +7 -9
- package/src/ui/terminal-graphics/kitty.ts +2 -7
- package/src/update/version-check.ts +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,9 @@ snippets, themes, and fully configurable keymaps.
|
|
|
22
22
|
- configurable git pane (embedded in the sidebar or as a standalone pane) and a
|
|
23
23
|
dedicated [git mode](docs/guide/git-mode.md) for review / stage / commit /
|
|
24
24
|
push with a split or stacked diff view and shiki-powered highlighting
|
|
25
|
+
- [git worktrees](docs/guide/worktrees.md) for running agents on parallel
|
|
26
|
+
branches — create per-branch worktrees, review each against its base, and
|
|
27
|
+
squash-move a worktree's work into another
|
|
25
28
|
- built-in help generated from the resolved keymap
|
|
26
29
|
- theme picker with 67 built-in themes (shiki catalog + aimux house themes) and a `/` filter
|
|
27
30
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brimveyn/aimux",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "A terminal multiplexer for AI CLIs. Run Claude, Codex, OpenCode side-by-side with tabbed navigation, split panes, and persistent sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -53,14 +53,14 @@
|
|
|
53
53
|
"demo:themes": "vhs assets/themes.tape",
|
|
54
54
|
"restart-daemon": "AIMUX_PROFILE=dev bun run src/index.tsx restart-daemon",
|
|
55
55
|
"restart-terminal-manager": "AIMUX_PROFILE=dev bun run src/index.tsx restart-terminal-manager",
|
|
56
|
-
"lint": "oxlint .",
|
|
56
|
+
"lint": "oxlint --type-aware .",
|
|
57
57
|
"format": "oxfmt --write .",
|
|
58
58
|
"format:check": "oxfmt --check .",
|
|
59
59
|
"knip": "knip-bun",
|
|
60
60
|
"bump": "bun run scripts/bump.ts"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@brimveyn/aimux-config": "0.
|
|
63
|
+
"@brimveyn/aimux-config": "0.6.0",
|
|
64
64
|
"@opentui/core": "^0.1.90",
|
|
65
65
|
"@opentui/react": "^0.1.90",
|
|
66
66
|
"@resvg/resvg-wasm": "^2.6.2",
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"lefthook": "^2.1.4",
|
|
80
80
|
"oxfmt": "^0.42.0",
|
|
81
81
|
"oxlint": "^1.57.0",
|
|
82
|
+
"oxlint-tsgolint": "^0.23.0",
|
|
82
83
|
"typescript": "^6.0.2"
|
|
83
84
|
}
|
|
84
85
|
}
|
|
@@ -15,6 +15,7 @@ import { stripAnsi } from '../auto-commit/strip-ansi'
|
|
|
15
15
|
import { runSuggestion } from '../auto-commit/suggestion-runner'
|
|
16
16
|
import { workingTreeHash } from '../auto-commit/working-tree-hash'
|
|
17
17
|
import { isCommandAvailable } from '../pty/command-registry'
|
|
18
|
+
import { toast } from '../state/toast-store'
|
|
18
19
|
|
|
19
20
|
export interface TriggerInput {
|
|
20
21
|
enabled: boolean
|
|
@@ -63,7 +64,7 @@ const UNTRACKED_FILE_BYTES_CAP = 8_000
|
|
|
63
64
|
const SESSION_TAIL_BYTES_CAP = 8_000
|
|
64
65
|
|
|
65
66
|
export function extractSessionTail(buffer: string | undefined): string {
|
|
66
|
-
if (!buffer) return '[no session tail available]'
|
|
67
|
+
if (!(buffer != null && buffer !== '')) return '[no session tail available]'
|
|
67
68
|
const stripped = stripAnsi(buffer).trimEnd()
|
|
68
69
|
if (stripped.length === 0) return '[no session tail available]'
|
|
69
70
|
return stripped.length > SESSION_TAIL_BYTES_CAP
|
|
@@ -161,7 +162,7 @@ export async function onActivityTransition(
|
|
|
161
162
|
currentHash,
|
|
162
163
|
enabled: config.enabled,
|
|
163
164
|
git: args.git,
|
|
164
|
-
hasProjectPath: !!args.projectPath,
|
|
165
|
+
hasProjectPath: !!(args.projectPath != null && args.projectPath !== ''),
|
|
165
166
|
sessionId: args.sessionId,
|
|
166
167
|
state: state.autoCommit,
|
|
167
168
|
})
|
|
@@ -176,12 +177,15 @@ export async function onManualTrigger(
|
|
|
176
177
|
): Promise<void> {
|
|
177
178
|
const config = deps.getConfig()
|
|
178
179
|
const fail = (reason: string): void => {
|
|
179
|
-
|
|
180
|
+
// Manual auto-commit triggers can fire outside git mode, so surface the
|
|
181
|
+
// reason as a toast rather than an inline git-pane message that's unseen.
|
|
182
|
+
toast.warning(`Auto-commit: ${reason}`)
|
|
180
183
|
deps.dispatch({ sessionId: args.sessionId, type: 'auto-commit-clear' })
|
|
181
184
|
}
|
|
182
185
|
if (!config.enabled) return fail('disabled in config')
|
|
183
186
|
if (!args.git || args.git.files.length === 0) return fail('no changes to summarise')
|
|
184
|
-
if (!args.projectPath
|
|
187
|
+
if (!(args.projectPath != null && args.projectPath !== ''))
|
|
188
|
+
return fail('no project path for current session')
|
|
185
189
|
|
|
186
190
|
const currentHash = workingTreeHash(args.git)
|
|
187
191
|
const existing = deps.getState().autoCommit.bySession[args.sessionId]
|
|
@@ -14,7 +14,7 @@ export function clearActiveAutoCommitDriverIfMatches(trigger: TriggerFn): void {
|
|
|
14
14
|
if (activeTrigger === trigger) activeTrigger = null
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export function triggerAutoCommitNow(args: ActivityTransitionArgs): Promise<void> {
|
|
17
|
+
export async function triggerAutoCommitNow(args: ActivityTransitionArgs): Promise<void> {
|
|
18
18
|
if (!activeTrigger) return Promise.resolve()
|
|
19
19
|
return activeTrigger(args)
|
|
20
20
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MutableRefObject } from 'react'
|
|
2
2
|
|
|
3
3
|
import type { SessionBackend } from '../session-backend/types'
|
|
4
|
-
import type { AppAction, LayoutState, WorkspaceSnapshotV1 } from '../state/types'
|
|
4
|
+
import type { AppAction, LayoutState, TabSession, WorkspaceSnapshotV1 } from '../state/types'
|
|
5
5
|
|
|
6
6
|
import { logInputDebug } from '../debug/input-log'
|
|
7
7
|
import {
|
|
@@ -33,6 +33,22 @@ export function resizeSnapshotPanes(
|
|
|
33
33
|
})
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function mergeSnapshotTabMetadata(
|
|
37
|
+
tabs: TabSession[],
|
|
38
|
+
workspaceSnapshot: WorkspaceSnapshotV1 | undefined
|
|
39
|
+
): TabSession[] {
|
|
40
|
+
if (!workspaceSnapshot) return tabs
|
|
41
|
+
const persistedById = new Map(workspaceSnapshot.tabs.map((tab) => [tab.id, tab]))
|
|
42
|
+
return tabs.map((tab) => {
|
|
43
|
+
const persisted = persistedById.get(tab.id)
|
|
44
|
+
return persisted?.worktreeId != null &&
|
|
45
|
+
persisted?.worktreeId !== '' &&
|
|
46
|
+
!(tab.worktreeId != null && tab.worktreeId !== '')
|
|
47
|
+
? { ...tab, worktreeId: persisted.worktreeId }
|
|
48
|
+
: tab
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
36
52
|
function hydrateAttachedSession(
|
|
37
53
|
dispatch: (action: AppAction) => void,
|
|
38
54
|
sessionId: string,
|
|
@@ -47,7 +63,7 @@ function hydrateAttachedSession(
|
|
|
47
63
|
layoutTree: workspaceSnapshot?.layoutTree,
|
|
48
64
|
layoutTrees: workspaceSnapshot?.layoutTrees,
|
|
49
65
|
tabGroupMap: workspaceSnapshot?.tabGroupMap,
|
|
50
|
-
tabs: result.tabs,
|
|
66
|
+
tabs: mergeSnapshotTabMetadata(result.tabs, workspaceSnapshot),
|
|
51
67
|
type: 'hydrate-workspace',
|
|
52
68
|
})
|
|
53
69
|
// Dispatch the session-status snapshot *after* hydrate-workspace so
|
|
@@ -8,10 +8,10 @@ import type {
|
|
|
8
8
|
TabSession,
|
|
9
9
|
TerminalModeState,
|
|
10
10
|
} from '../state/types'
|
|
11
|
+
import type { TabRuntimeTimeouts } from './tab-runtime-timeouts'
|
|
11
12
|
|
|
12
13
|
import { logInputDebug } from '../debug/input-log'
|
|
13
14
|
import { clearTabSyntaxState, highlightSnapshot } from '../integrations/claude-syntax-overlay'
|
|
14
|
-
import { type TabRuntimeTimeouts } from './tab-runtime-timeouts'
|
|
15
15
|
|
|
16
16
|
interface BindBackendRuntimeEventsOptions {
|
|
17
17
|
backend: SessionBackend
|
|
@@ -4,7 +4,7 @@ interface SelectableLike {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
function isUsableTarget(target: SelectableLike | null | undefined): target is SelectableLike {
|
|
7
|
-
return Boolean(target?.selectable) && !target?.isDestroyed
|
|
7
|
+
return Boolean(target?.selectable) && !(target?.isDestroyed === true)
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
interface SelectionLike {
|
|
@@ -69,7 +69,7 @@ export function shiftSelectionByScroll(renderer: RendererSelectionApi, deltaLine
|
|
|
69
69
|
|
|
70
70
|
const live = renderer.getSelection()
|
|
71
71
|
|
|
72
|
-
if (!live?.isActive) {
|
|
72
|
+
if (!(live?.isActive === true)) {
|
|
73
73
|
resetSelectionShiftState(renderer)
|
|
74
74
|
return
|
|
75
75
|
}
|
|
@@ -5,6 +5,8 @@ import { logInputDebug } from '../debug/input-log'
|
|
|
5
5
|
import { createPrefixedId } from '../platform/id'
|
|
6
6
|
import { saveSessionCatalog } from '../state/session-catalog'
|
|
7
7
|
import { createEmptyWorkspaceSnapshot, serializeWorkspace } from '../state/session-persistence'
|
|
8
|
+
import { createPrimaryWorktree, ensureSessionWorktrees } from '../state/session-worktrees'
|
|
9
|
+
import { toast } from '../state/toast-store'
|
|
8
10
|
import { buildSessionsWithCurrentSnapshot } from '../state/workspace-save'
|
|
9
11
|
|
|
10
12
|
export function createSessionFromCurrentState(
|
|
@@ -14,10 +16,11 @@ export function createSessionFromCurrentState(
|
|
|
14
16
|
): { session: SessionRecord; sessions: SessionRecord[] } {
|
|
15
17
|
const now = new Date().toISOString()
|
|
16
18
|
const workspaceSnapshot =
|
|
17
|
-
state.currentSessionId || state.tabs.length === 0
|
|
19
|
+
(state.currentSessionId != null && state.currentSessionId !== '') || state.tabs.length === 0
|
|
18
20
|
? createEmptyWorkspaceSnapshot()
|
|
19
21
|
: serializeWorkspace(state)
|
|
20
22
|
const session: SessionRecord = {
|
|
23
|
+
activeWorktreeId: undefined,
|
|
21
24
|
createdAt: now,
|
|
22
25
|
id: createPrefixedId('session'),
|
|
23
26
|
lastOpenedAt: now,
|
|
@@ -25,10 +28,16 @@ export function createSessionFromCurrentState(
|
|
|
25
28
|
projectPath,
|
|
26
29
|
updatedAt: now,
|
|
27
30
|
workspaceSnapshot,
|
|
31
|
+
worktrees: undefined,
|
|
32
|
+
}
|
|
33
|
+
if (projectPath != null && projectPath !== '') {
|
|
34
|
+
const worktree = createPrimaryWorktree(projectPath, now)
|
|
35
|
+
session.activeWorktreeId = worktree.id
|
|
36
|
+
session.worktrees = [worktree]
|
|
28
37
|
}
|
|
29
38
|
|
|
30
39
|
let updatedSessions = state.sessions
|
|
31
|
-
if (state.currentSessionId) {
|
|
40
|
+
if (state.currentSessionId != null && state.currentSessionId !== '') {
|
|
32
41
|
const currentSnapshot = serializeWorkspace(state)
|
|
33
42
|
updatedSessions = state.sessions.map((entry) =>
|
|
34
43
|
entry.id === state.currentSessionId
|
|
@@ -39,7 +48,7 @@ export function createSessionFromCurrentState(
|
|
|
39
48
|
|
|
40
49
|
return {
|
|
41
50
|
session,
|
|
42
|
-
sessions: [...updatedSessions, session],
|
|
51
|
+
sessions: [...updatedSessions, ensureSessionWorktrees(session)],
|
|
43
52
|
}
|
|
44
53
|
}
|
|
45
54
|
|
|
@@ -79,7 +88,8 @@ export function handleCreateSessionEffect(
|
|
|
79
88
|
): void {
|
|
80
89
|
const { session, sessions } = createSessionFromCurrentState(state, name, projectPath)
|
|
81
90
|
logInputDebug('app.session.create', {
|
|
82
|
-
fromCurrentWorkspace:
|
|
91
|
+
fromCurrentWorkspace:
|
|
92
|
+
(state.currentSessionId == null || state.currentSessionId === '') && state.tabs.length > 0,
|
|
83
93
|
name,
|
|
84
94
|
sessionId: session.id,
|
|
85
95
|
tabCount: session.workspaceSnapshot?.tabs.length ?? 0,
|
|
@@ -137,6 +147,7 @@ export function handleDeleteSessionEffect(
|
|
|
137
147
|
sessionId: string,
|
|
138
148
|
options?: { openSessionPicker?: boolean }
|
|
139
149
|
): void {
|
|
150
|
+
const name = state.sessions.find((entry) => entry.id === sessionId)?.name
|
|
140
151
|
const remaining = deleteSessionRecords(state.sessions, sessionId)
|
|
141
152
|
logInputDebug('app.session.delete', {
|
|
142
153
|
remainingCount: remaining.length,
|
|
@@ -152,6 +163,7 @@ export function handleDeleteSessionEffect(
|
|
|
152
163
|
sessionId,
|
|
153
164
|
type: 'delete-session-record',
|
|
154
165
|
})
|
|
166
|
+
toast.success(name != null && name !== '' ? `Deleted workspace "${name}"` : 'Workspace deleted')
|
|
155
167
|
}
|
|
156
168
|
|
|
157
169
|
export function restartTabSession(
|