@brimveyn/aimux 1.2.5 → 1.3.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 +29 -8
- package/package.json +4 -4
- package/src/app-runtime/backend-runtime-events.ts +9 -0
- package/src/app-runtime/side-effects.ts +8 -0
- package/src/config/loader.ts +2 -2
- package/src/config.ts +3 -3
- package/src/daemon/daemon.ts +262 -118
- package/src/daemon/runtime-paths.ts +34 -2
- package/src/index.tsx +11 -3
- package/src/ipc/manager-protocol.ts +347 -0
- package/src/ipc/protocol.ts +55 -3
- package/src/platform/daemon-control.ts +42 -11
- package/src/profile-paths.ts +27 -0
- package/src/pty/pty-manager.ts +24 -0
- package/src/restart-daemon.ts +10 -10
- package/src/session-backend/bootstrap.ts +197 -46
- package/src/session-backend/remote-session-backend.ts +126 -59
- package/src/state/session-catalog.ts +5 -4
- package/src/state/snippet-catalog.ts +4 -4
- package/src/terminal-manager/manager-client.ts +360 -0
- package/src/terminal-manager/terminal-manager.ts +272 -0
- package/src/update.ts +3 -3
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ A terminal multiplexer for AI CLIs. Manage multiple AI assistant sessions (Claud
|
|
|
21
21
|
- **Directory picker** — Fuzzy-search git repos and worktrees from `$HOME` using `fzf`
|
|
22
22
|
- **Session persistence** — Workspace state (tabs, titles, layout) saved and restored on restart
|
|
23
23
|
- **Git status panel** — Branch + diff summary in the sidebar
|
|
24
|
-
- **
|
|
24
|
+
- **Seamless daemon updates** — A restartable IPC daemon now reconnects to a long-lived terminal manager, so updates and IPC changes do not kill live tabs
|
|
25
25
|
- **Snippets** — Save and reuse prompt snippets across sessions
|
|
26
26
|
- **Theme picker** — Switch between 11 built-in themes on the fly
|
|
27
27
|
- **Pending-chord indicator** — Bottom-right overlay shows mid-sequence key state (like nvim's `which-key`)
|
|
@@ -58,20 +58,22 @@ aimux # start the TUI
|
|
|
58
58
|
aimux version # print version
|
|
59
59
|
aimux doctor # check setup
|
|
60
60
|
aimux update # self-update
|
|
61
|
-
aimux restart-daemon # restart
|
|
61
|
+
aimux restart-daemon # restart IPC daemon only
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
`aimux update` and `aimux restart-daemon` restart only the IPC daemon. Live PTYs and headless terminal state stay in the long-lived terminal-manager process, so active tabs can be reattached instead of being restarted.
|
|
65
|
+
|
|
64
66
|
## Configuration
|
|
65
67
|
|
|
66
|
-
aimux reads `~/.config/aimux
|
|
68
|
+
aimux reads `~/.config/aimux/<profile>/aimux.config.ts` at startup. The default installed profile is `default`, while the repository dev scripts use `dev`. Set up the default profile with:
|
|
67
69
|
|
|
68
70
|
```bash
|
|
69
|
-
mkdir -p ~/.config/aimux && cd ~/.config/aimux
|
|
71
|
+
mkdir -p ~/.config/aimux/default && cd ~/.config/aimux/default
|
|
70
72
|
bun init -y
|
|
71
73
|
bun add -d @brimveyn/aimux-config
|
|
72
74
|
```
|
|
73
75
|
|
|
74
|
-
Then create `~/.config/aimux/aimux.config.ts`:
|
|
76
|
+
Then create `~/.config/aimux/default/aimux.config.ts`:
|
|
75
77
|
|
|
76
78
|
```ts
|
|
77
79
|
import { defineConfig, actions, themes } from '@brimveyn/aimux-config'
|
|
@@ -158,6 +160,14 @@ Keystrokes pass through to the active tab's PTY. Configured shortcuts:
|
|
|
158
160
|
|
|
159
161
|
## Architecture
|
|
160
162
|
|
|
163
|
+
Runtime split:
|
|
164
|
+
|
|
165
|
+
- `aimux` UI connects to the IPC daemon over the app protocol.
|
|
166
|
+
- The IPC daemon owns the app-facing socket, protocol negotiation, and reconnect behavior.
|
|
167
|
+
- The terminal manager owns PTYs, xterm headless emulators, and live session state.
|
|
168
|
+
|
|
169
|
+
This split lets the app or IPC daemon change protocols without dropping live shells.
|
|
170
|
+
|
|
161
171
|
```
|
|
162
172
|
aimux/
|
|
163
173
|
├── packages/
|
|
@@ -171,9 +181,10 @@ aimux/
|
|
|
171
181
|
├── ui/ # OpenTUI React components
|
|
172
182
|
├── state/ # reducers + app store
|
|
173
183
|
├── pty/ # PTY and terminal emulation
|
|
174
|
-
├── session-backend/ # local and
|
|
175
|
-
├── daemon/ #
|
|
176
|
-
├──
|
|
184
|
+
├── session-backend/ # local and remote backends
|
|
185
|
+
├── daemon/ # IPC daemon / broker
|
|
186
|
+
├── terminal-manager/ # long-lived PTY/session owner
|
|
187
|
+
├── ipc/ # app and manager protocols
|
|
177
188
|
└── input/
|
|
178
189
|
├── modes/ # mode registry + transitions
|
|
179
190
|
├── keymap/ # prefix trie + sequence resolver
|
|
@@ -201,6 +212,16 @@ bun run check # typecheck
|
|
|
201
212
|
bun run lint # oxlint
|
|
202
213
|
```
|
|
203
214
|
|
|
215
|
+
By default the app talks to the background IPC daemon. For explicit single-process debugging only, set `AIMUX_LOCAL_BACKEND=1` before starting aimux.
|
|
216
|
+
|
|
217
|
+
Profiles live under `~/.config/aimux/<profile>/`. Each profile gets its own config, session catalog, snippet catalog, and matching runtime namespace.
|
|
218
|
+
|
|
219
|
+
The repository `bun run dev`, `bun run start`, and `bun run restart-daemon` scripts set `AIMUX_PROFILE=dev`, so source builds use `~/.config/aimux/dev/` and their own IPC daemon / terminal-manager sockets instead of interfering with a globally installed `aimux` instance.
|
|
220
|
+
|
|
221
|
+
You can override the active profile manually with `AIMUX_PROFILE=<name>` when you need multiple isolated environments on the same machine. `AIMUX_RUNTIME_PROFILE` is still accepted as a fallback alias for runtime compatibility.
|
|
222
|
+
|
|
223
|
+
This profile move is intentionally breaking: aimux no longer reads legacy flat config or catalog files once profile directories are enabled.
|
|
224
|
+
|
|
204
225
|
## License
|
|
205
226
|
|
|
206
227
|
MIT © BrimVeyn
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brimveyn/aimux",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
|
-
"dev": "bun --watch src/index.tsx",
|
|
46
|
-
"start": "bun run src/index.tsx",
|
|
45
|
+
"dev": "AIMUX_PROFILE=dev bun --watch src/index.tsx",
|
|
46
|
+
"start": "AIMUX_PROFILE=dev bun run src/index.tsx",
|
|
47
47
|
"test": "bun test",
|
|
48
48
|
"check": "tsc --noEmit",
|
|
49
49
|
"demo": "vhs assets/demo.tape",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"demo:tabs": "vhs assets/tabs.tape",
|
|
52
52
|
"demo:splits": "vhs assets/splits.tape",
|
|
53
53
|
"demo:themes": "vhs assets/themes.tape",
|
|
54
|
-
"restart-daemon": "bun run src/index.tsx restart-daemon",
|
|
54
|
+
"restart-daemon": "AIMUX_PROFILE=dev bun run src/index.tsx restart-daemon",
|
|
55
55
|
"lint": "oxlint .",
|
|
56
56
|
"format": "oxfmt --write .",
|
|
57
57
|
"format:check": "oxfmt --check ."
|
|
@@ -3,6 +3,7 @@ import type { MutableRefObject } from 'react'
|
|
|
3
3
|
import type { SessionBackend } from '../session-backend/types'
|
|
4
4
|
import type { AppAction, TabSession, TerminalModeState } from '../state/types'
|
|
5
5
|
|
|
6
|
+
import { logInputDebug } from '../debug/input-log'
|
|
6
7
|
import { type TabRuntimeTimeouts } from './tab-runtime-timeouts'
|
|
7
8
|
|
|
8
9
|
const IDLE_ACTIVITY_TIMEOUT_MS = 2_000
|
|
@@ -44,6 +45,12 @@ export function bindBackendRuntimeEvents({
|
|
|
44
45
|
return
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
logInputDebug('app.backend.event.render', {
|
|
49
|
+
lines: viewport.lines.length,
|
|
50
|
+
tabId,
|
|
51
|
+
viewportY: viewport.viewportY,
|
|
52
|
+
})
|
|
53
|
+
|
|
47
54
|
dispatch({ tabId, terminalModes, type: 'replace-tab-viewport', viewport })
|
|
48
55
|
if (timeouts.isStartupGraceActive(tabId) || resizingRef.current) {
|
|
49
56
|
return
|
|
@@ -54,12 +61,14 @@ export function bindBackendRuntimeEvents({
|
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
const handleExit = (tabId: string, exitCode: number) => {
|
|
64
|
+
logInputDebug('app.backend.event.exit', { exitCode, tabId })
|
|
57
65
|
clearTabRuntimeState(timeouts, tabId)
|
|
58
66
|
dispatch({ exitCode, status: 'exited', tabId, type: 'set-tab-status' })
|
|
59
67
|
dispatch({ activity: undefined, tabId, type: 'set-tab-activity' })
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
const handleError = (tabId: string, message: string) => {
|
|
71
|
+
logInputDebug('app.backend.event.error', { message, tabId })
|
|
63
72
|
clearTabRuntimeState(timeouts, tabId)
|
|
64
73
|
dispatch({ message, tabId, type: 'set-tab-error' })
|
|
65
74
|
}
|
|
@@ -245,6 +245,14 @@ export function startTabSession(
|
|
|
245
245
|
rows: number,
|
|
246
246
|
cwd?: string
|
|
247
247
|
): void {
|
|
248
|
+
logInputDebug('app.tab.start.request', {
|
|
249
|
+
cols,
|
|
250
|
+
command: tab.command,
|
|
251
|
+
cwd: cwd ?? null,
|
|
252
|
+
rows,
|
|
253
|
+
tabId: tab.id,
|
|
254
|
+
title: tab.title,
|
|
255
|
+
})
|
|
248
256
|
startStartupGrace(tab.id)
|
|
249
257
|
|
|
250
258
|
const { args, executable } = parseCommand(tab.command)
|
package/src/config/loader.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type AimuxUserConfig, resolveConfig, type ResolvedConfig } from '@brimveyn/aimux-config'
|
|
2
|
-
import { homedir } from 'node:os'
|
|
3
2
|
import { join } from 'node:path'
|
|
4
3
|
|
|
5
4
|
import { logDebug } from '../debug/input-log'
|
|
5
|
+
import { getProfileConfigDir } from '../profile-paths'
|
|
6
6
|
|
|
7
7
|
const CONFIG_FILENAMES = ['aimux.config.ts', 'aimux.config.js']
|
|
8
8
|
|
|
@@ -11,7 +11,7 @@ const CONFIG_FILENAMES = ['aimux.config.ts', 'aimux.config.js']
|
|
|
11
11
|
* a resolved config. Falls back to pure defaults if no config file exists.
|
|
12
12
|
*/
|
|
13
13
|
export async function loadUserConfig(): Promise<ResolvedConfig> {
|
|
14
|
-
const configDir =
|
|
14
|
+
const configDir = getProfileConfigDir()
|
|
15
15
|
|
|
16
16
|
for (const filename of CONFIG_FILENAMES) {
|
|
17
17
|
const configPath = join(configDir, filename)
|
package/src/config.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
-
import { dirname, join } from 'node:path'
|
|
3
2
|
|
|
4
3
|
import type { WorkspaceSnapshotV1 } from './state/types'
|
|
5
4
|
|
|
6
5
|
import { logDebug } from './debug/input-log'
|
|
6
|
+
import { getProfileConfigDir } from './profile-paths'
|
|
7
7
|
import { isWorkspaceSnapshotV1 } from './state/validation'
|
|
8
8
|
import { THEME_IDS, type ThemeId } from './ui/themes'
|
|
9
9
|
|
|
10
|
-
export const CONFIG_PATH =
|
|
10
|
+
export const CONFIG_PATH = `${getProfileConfigDir()}/aimux.json`
|
|
11
11
|
|
|
12
12
|
export interface AimuxConfig {
|
|
13
13
|
version: 2
|
|
@@ -133,7 +133,7 @@ export function loadConfig(): AimuxConfig {
|
|
|
133
133
|
|
|
134
134
|
export function saveConfig(config: AimuxConfig): boolean {
|
|
135
135
|
try {
|
|
136
|
-
mkdirSync(
|
|
136
|
+
mkdirSync(getProfileConfigDir(), { recursive: true })
|
|
137
137
|
writeFileSync(CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`)
|
|
138
138
|
return true
|
|
139
139
|
} catch (error) {
|