@brimveyn/aimux 1.2.0 → 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 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
- - **Daemon mode** — Background daemon keeps sessions alive across terminal restarts
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 background daemon
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/aimux.config.ts` at startup. Set it up with:
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 daemon backends
175
- ├── daemon/ # background session daemon
176
- ├── ipc/ # daemon protocol
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.2.0",
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,14 +51,13 @@
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
- "format:check": "oxfmt --check .",
58
- "prepare": "lefthook install"
57
+ "format:check": "oxfmt --check ."
59
58
  },
60
59
  "dependencies": {
61
- "@brimveyn/aimux-config": "^0.2.0",
60
+ "@brimveyn/aimux-config": "0.1.0",
62
61
  "@opentui/core": "^0.1.90",
63
62
  "@opentui/react": "^0.1.90",
64
63
  "@xterm/headless": "^6.0.0",
@@ -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)
@@ -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 = join(homedir(), '.config', 'aimux')
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 = join(process.env.HOME ?? '~', '.config', 'aimux.json')
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(dirname(CONFIG_PATH), { recursive: true })
136
+ mkdirSync(getProfileConfigDir(), { recursive: true })
137
137
  writeFileSync(CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`)
138
138
  return true
139
139
  } catch (error) {