@brimveyn/aimux 1.2.5 → 1.3.1
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 +29 -8
- package/package.json +5 -5
- package/src/app-runtime/backend-runtime-events.ts +16 -1
- package/src/app-runtime/pty-write.ts +7 -4
- package/src/app-runtime/side-effects.ts +62 -4
- package/src/app-runtime/snippet-actions.ts +3 -2
- package/src/app-runtime/use-backend-runtime.ts +7 -2
- package/src/app-runtime/use-renderer-bindings.ts +2 -2
- package/src/app-runtime/use-terminal-resize.ts +15 -6
- package/src/app.tsx +89 -30
- package/src/config/loader.ts +2 -2
- package/src/config.ts +14 -3
- package/src/daemon/daemon.ts +279 -118
- package/src/daemon/runtime-paths.ts +34 -2
- package/src/daemon/session-manager.ts +20 -5
- package/src/daemon/session-registry.ts +18 -11
- package/src/index.tsx +19 -4
- package/src/input/keymap/describe-bindings.ts +68 -0
- package/src/input/keymap/key-format.ts +67 -0
- package/src/input/modes/bridge.ts +1 -0
- package/src/input/modes/transitions.ts +2 -0
- package/src/input/modes/types.ts +2 -0
- package/src/ipc/manager-protocol.ts +396 -0
- package/src/ipc/protocol.ts +98 -5
- package/src/platform/daemon-control.ts +42 -11
- package/src/profile-paths.ts +27 -0
- package/src/pty/pty-manager.ts +53 -3
- package/src/restart-daemon.ts +10 -10
- package/src/session-backend/bootstrap.ts +197 -46
- package/src/session-backend/local-session-backend.ts +12 -5
- package/src/session-backend/remote-session-backend.ts +143 -63
- package/src/session-backend/types.ts +4 -2
- package/src/state/reducers/modal-state.ts +18 -1
- package/src/state/reducers/tab-state.ts +20 -2
- package/src/state/session-catalog.ts +5 -4
- package/src/state/session-persistence.ts +9 -2
- package/src/state/snippet-catalog.ts +4 -4
- package/src/state/types.ts +23 -0
- package/src/state/validation.ts +8 -0
- package/src/terminal-manager/manager-client.ts +384 -0
- package/src/terminal-manager/terminal-manager.ts +288 -0
- package/src/ui/components/create-session-modal.tsx +3 -5
- package/src/ui/components/git-commit-modal.tsx +3 -5
- package/src/ui/components/help-modal.tsx +49 -68
- package/src/ui/components/list-item.tsx +24 -5
- package/src/ui/components/new-tab-modal.tsx +8 -9
- package/src/ui/components/pending-chord-overlay.tsx +28 -0
- package/src/ui/components/session-name-modal.tsx +3 -5
- package/src/ui/components/session-picker-modal.tsx +3 -1
- package/src/ui/components/snippet-editor-modal.tsx +3 -1
- package/src/ui/components/snippet-picker-modal.tsx +3 -1
- package/src/ui/components/status-bar.tsx +6 -2
- package/src/ui/components/theme-picker-modal.tsx +3 -6
- package/src/ui/components/update-available-modal.tsx +42 -0
- package/src/ui/keymap-context.ts +39 -0
- package/src/ui/root.tsx +12 -0
- package/src/ui/status-bar-model.ts +67 -39
- package/src/update/version-check.ts +67 -0
- package/src/update.ts +3 -3
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { ModeId, ResolvedKeymapConfig } from '@brimveyn/aimux-config'
|
|
2
|
+
|
|
1
3
|
import type { AppState, TabSession } from '../state/types'
|
|
2
4
|
|
|
5
|
+
import { buildHintText } from './keymap-context'
|
|
3
6
|
import { abbreviatePath } from './path-format'
|
|
4
7
|
|
|
5
8
|
export interface StatusBarModel {
|
|
@@ -8,6 +11,7 @@ export interface StatusBarModel {
|
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
const MAX_TAB_LABEL_LENGTH = 24
|
|
14
|
+
const HINT_LIMIT = 6
|
|
11
15
|
|
|
12
16
|
function truncateLabel(label: string): string {
|
|
13
17
|
if (label.length <= MAX_TAB_LABEL_LENGTH) {
|
|
@@ -25,39 +29,15 @@ function getActiveTabLabel(tab?: TabSession): string {
|
|
|
25
29
|
return `${truncateLabel(tab.title)} (${tab.status})`
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
function
|
|
29
|
-
|
|
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'
|
|
32
|
+
function hintForMode(config: ResolvedKeymapConfig, modeId: ModeId): string {
|
|
33
|
+
return buildHintText(config, modeId, HINT_LIMIT)
|
|
50
34
|
}
|
|
51
35
|
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return 'j/k file Ctrl+d/u page a stage d unstage/delete c commit p push Esc exit'
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function getStatusBarModel(state: AppState, activeTab?: TabSession): StatusBarModel {
|
|
36
|
+
export function getStatusBarModel(
|
|
37
|
+
state: AppState,
|
|
38
|
+
activeTab: TabSession | undefined,
|
|
39
|
+
config: ResolvedKeymapConfig
|
|
40
|
+
): StatusBarModel {
|
|
61
41
|
const currentSession = state.currentSessionId
|
|
62
42
|
? state.sessions.find((session) => session.id === state.currentSessionId)
|
|
63
43
|
: undefined
|
|
@@ -73,33 +53,81 @@ export function getStatusBarModel(state: AppState, activeTab?: TabSession): Stat
|
|
|
73
53
|
case 'terminal-input':
|
|
74
54
|
return {
|
|
75
55
|
left: `${getActiveTabLabel(activeTab)} ${sessionIcon} ${sessionLabel}`,
|
|
76
|
-
right:
|
|
56
|
+
right: hintForMode(config, 'terminal-input'),
|
|
77
57
|
}
|
|
78
|
-
case 'modal':
|
|
58
|
+
case 'modal': {
|
|
59
|
+
const modalMode = deriveModalModeId(state.modal.type)
|
|
79
60
|
return {
|
|
80
61
|
left: `${sessionIcon} ${sessionLabel}`,
|
|
81
|
-
right:
|
|
62
|
+
right: modalMode ? hintForMode(config, modalMode) : '',
|
|
82
63
|
}
|
|
64
|
+
}
|
|
83
65
|
case 'layout':
|
|
84
66
|
return {
|
|
85
67
|
left: `${getActiveTabLabel(activeTab)} ${sessionIcon} ${sessionLabel}`,
|
|
86
|
-
right:
|
|
68
|
+
right: hintForMode(config, 'layout'),
|
|
87
69
|
}
|
|
88
70
|
case 'git':
|
|
89
71
|
return {
|
|
90
72
|
left: `${sessionIcon} ${sessionLabel}`,
|
|
91
|
-
right:
|
|
73
|
+
right: hintForMode(config, 'git-mode'),
|
|
92
74
|
}
|
|
93
|
-
case 'command-edit':
|
|
75
|
+
case 'command-edit': {
|
|
76
|
+
const commandEditMode = deriveCommandEditModeId(state.modal.type)
|
|
94
77
|
return {
|
|
95
78
|
left: `${sessionIcon} ${sessionLabel}`,
|
|
96
|
-
right:
|
|
79
|
+
right: commandEditMode ? hintForMode(config, commandEditMode) : '',
|
|
97
80
|
}
|
|
81
|
+
}
|
|
98
82
|
case 'navigation':
|
|
99
83
|
default:
|
|
100
84
|
return {
|
|
101
85
|
left: `${sessionIcon} ${sessionLabel} ${getActiveTabLabel(activeTab)}`,
|
|
102
|
-
right:
|
|
86
|
+
right: hintForMode(config, 'navigation'),
|
|
103
87
|
}
|
|
104
88
|
}
|
|
105
89
|
}
|
|
90
|
+
|
|
91
|
+
function deriveModalModeId(modalType: AppState['modal']['type']): ModeId | null {
|
|
92
|
+
switch (modalType) {
|
|
93
|
+
case 'help':
|
|
94
|
+
return 'modal.help'
|
|
95
|
+
case 'new-tab':
|
|
96
|
+
return 'modal.new-tab'
|
|
97
|
+
case 'session-picker':
|
|
98
|
+
return 'modal.session-picker'
|
|
99
|
+
case 'snippet-picker':
|
|
100
|
+
return 'modal.snippet-picker'
|
|
101
|
+
case 'split-picker':
|
|
102
|
+
return 'modal.split-picker'
|
|
103
|
+
case 'theme-picker':
|
|
104
|
+
return 'modal.theme-picker'
|
|
105
|
+
case 'update-available':
|
|
106
|
+
return 'modal.update-available'
|
|
107
|
+
default:
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function deriveCommandEditModeId(modalType: AppState['modal']['type']): ModeId | null {
|
|
113
|
+
switch (modalType) {
|
|
114
|
+
case 'create-session':
|
|
115
|
+
return 'modal.create-session'
|
|
116
|
+
case 'git-commit':
|
|
117
|
+
return 'modal.git-commit'
|
|
118
|
+
case 'new-tab':
|
|
119
|
+
return 'modal.new-tab.command-edit'
|
|
120
|
+
case 'rename-tab':
|
|
121
|
+
return 'modal.rename-tab'
|
|
122
|
+
case 'session-name':
|
|
123
|
+
return 'modal.session-name'
|
|
124
|
+
case 'session-picker':
|
|
125
|
+
return 'modal.session-picker.filtering'
|
|
126
|
+
case 'snippet-editor':
|
|
127
|
+
return 'modal.snippet-editor'
|
|
128
|
+
case 'snippet-picker':
|
|
129
|
+
return 'modal.snippet-picker.filtering'
|
|
130
|
+
default:
|
|
131
|
+
return null
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { logDebug } from '../debug/input-log'
|
|
2
|
+
|
|
3
|
+
const NPM_REGISTRY_BASE = 'https://registry.npmjs.org'
|
|
4
|
+
|
|
5
|
+
export async function getCurrentPackageVersion(): Promise<string> {
|
|
6
|
+
const { version } = await import('../../package.json')
|
|
7
|
+
return version
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function fetchLatestNpmVersion(packageName: string): Promise<string | null> {
|
|
11
|
+
const debugOverride = process.env.AIMUX_DEBUG_UPDATE_LATEST
|
|
12
|
+
if (debugOverride) {
|
|
13
|
+
return debugOverride
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const url = `${NPM_REGISTRY_BASE}/${encodeURIComponent(packageName).replace('%40', '@')}/latest`
|
|
18
|
+
const controller = new AbortController()
|
|
19
|
+
const timer = setTimeout(() => controller.abort(), 5_000)
|
|
20
|
+
const res = await fetch(url, { signal: controller.signal })
|
|
21
|
+
clearTimeout(timer)
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
logDebug('update.fetchLatest.nonOk', { packageName, status: res.status })
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
const data = (await res.json()) as { version?: unknown }
|
|
27
|
+
if (typeof data.version !== 'string' || data.version.length === 0) {
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
return data.version
|
|
31
|
+
} catch (error) {
|
|
32
|
+
logDebug('update.fetchLatest.error', {
|
|
33
|
+
error: error instanceof Error ? error.message : String(error),
|
|
34
|
+
packageName,
|
|
35
|
+
})
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseSemver(version: string): number[] | null {
|
|
41
|
+
const trimmed = version.trim().replace(/^v/, '')
|
|
42
|
+
const core = trimmed.split(/[-+]/)[0] ?? ''
|
|
43
|
+
const parts = core.split('.')
|
|
44
|
+
if (parts.length === 0) return null
|
|
45
|
+
const numeric: number[] = []
|
|
46
|
+
for (const part of parts) {
|
|
47
|
+
const n = Number.parseInt(part, 10)
|
|
48
|
+
if (!Number.isFinite(n) || n < 0) return null
|
|
49
|
+
numeric.push(n)
|
|
50
|
+
}
|
|
51
|
+
return numeric
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function isNewerVersion(latest: string, current: string): boolean {
|
|
55
|
+
const a = parseSemver(latest)
|
|
56
|
+
const b = parseSemver(current)
|
|
57
|
+
if (!a || !b) return false
|
|
58
|
+
|
|
59
|
+
const len = Math.max(a.length, b.length)
|
|
60
|
+
for (let i = 0; i < len; i++) {
|
|
61
|
+
const av = a[i] ?? 0
|
|
62
|
+
const bv = b[i] ?? 0
|
|
63
|
+
if (av > bv) return true
|
|
64
|
+
if (av < bv) return false
|
|
65
|
+
}
|
|
66
|
+
return false
|
|
67
|
+
}
|
package/src/update.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getDaemonSocketPath } from './daemon/runtime-paths'
|
|
2
|
-
import {
|
|
2
|
+
import { findIpcDaemonPid } from './platform/daemon-control'
|
|
3
3
|
import { runRestartDaemon } from './restart-daemon'
|
|
4
4
|
|
|
5
5
|
const REPO = 'BrimVeyn/aimux'
|
|
@@ -52,9 +52,9 @@ export async function runUpdate(): Promise<number> {
|
|
|
52
52
|
|
|
53
53
|
process.stdout.write(`Updated to ${latest}.\n`)
|
|
54
54
|
|
|
55
|
-
const pid = await
|
|
55
|
+
const pid = await findIpcDaemonPid()
|
|
56
56
|
if (pid !== null) {
|
|
57
|
-
process.stdout.write(
|
|
57
|
+
process.stdout.write(`Restarting IPC daemon at ${getDaemonSocketPath()}...\n`)
|
|
58
58
|
await runRestartDaemon()
|
|
59
59
|
}
|
|
60
60
|
|