@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.
@@ -1,22 +1,28 @@
1
- import { createServer, type Socket } from 'node:net'
1
+ import { connect, createServer, type Socket } from 'node:net'
2
2
 
3
3
  import { logDebug } from '../debug/input-log'
4
4
  import {
5
5
  type ClientRequest,
6
6
  encodeMessage,
7
+ getProcessVersion,
8
+ IPC_PROTOCOL_MIN_VERSION,
7
9
  IPC_PROTOCOL_VERSION,
8
10
  MessageDecoder,
11
+ negotiateProtocolVersion,
9
12
  parseClientRequest,
10
13
  type ServerEvent,
11
14
  type ServerResponse,
12
15
  } from '../ipc/protocol'
13
- import { findDaemonPid } from '../platform/daemon-control'
16
+ import { findSocketProcessPid, spawnDetachedTerminalManager } from '../platform/daemon-control'
17
+ import { TerminalManagerClient } from '../terminal-manager/manager-client'
14
18
  import {
15
- getDaemonSocketPath,
19
+ getIpcDaemonSocketPath,
20
+ getSocketSecurityIssue,
21
+ getTerminalManagerSocketPath,
16
22
  removeDaemonSocketIfExists,
17
- tightenDaemonSocketPermissions,
23
+ removeTerminalManagerSocketIfExists,
24
+ tightenSocketPermissions,
18
25
  } from './runtime-paths'
19
- import { SessionManager } from './session-manager'
20
26
 
21
27
  function send(socket: Socket, message: ServerResponse | ServerEvent): void {
22
28
  socket.write(encodeMessage(message))
@@ -34,25 +40,88 @@ function requireSession(socket: Socket, attachedSessions: Map<Socket, string>):
34
40
  return sessionId
35
41
  }
36
42
 
43
+ function requireNegotiatedVersion(socket: Socket, versions: Map<Socket, number>): number {
44
+ const version = versions.get(socket)
45
+ if (version === undefined) {
46
+ throw new Error('Protocol handshake required before attach')
47
+ }
48
+ return version
49
+ }
50
+
51
+ async function canConnectToSocket(socketPath: string): Promise<boolean> {
52
+ const securityIssue = getSocketSecurityIssue(socketPath)
53
+ if (securityIssue) {
54
+ logDebug('daemon.socketUnhealthy', { issue: securityIssue, socketPath })
55
+ return false
56
+ }
57
+
58
+ return new Promise<boolean>((resolve) => {
59
+ const socket = connect(socketPath)
60
+ const finish = (result: boolean) => {
61
+ socket.removeAllListeners()
62
+ socket.destroy()
63
+ resolve(result)
64
+ }
65
+
66
+ socket.once('connect', () => finish(true))
67
+ socket.once('error', () => finish(false))
68
+ })
69
+ }
70
+
71
+ async function ensureTerminalManagerReady(manager: TerminalManagerClient): Promise<void> {
72
+ try {
73
+ logDebug('daemon.ensureTerminalManager.connectExisting.start', {
74
+ socketPath: getTerminalManagerSocketPath(),
75
+ })
76
+ await manager.connect()
77
+ logDebug('daemon.ensureTerminalManager.connectExisting.success')
78
+ return
79
+ } catch (error) {
80
+ logDebug('daemon.ensureTerminalManager.connectExisting.failed', {
81
+ error: error instanceof Error ? error.message : String(error),
82
+ })
83
+ const socketPath = getTerminalManagerSocketPath()
84
+ if (!(await canConnectToSocket(socketPath))) {
85
+ removeTerminalManagerSocketIfExists()
86
+ logDebug('daemon.ensureTerminalManager.spawn.start', { socketPath })
87
+ const ok = await spawnDetachedTerminalManager()
88
+ if (!ok) {
89
+ throw new Error('Failed to start terminal manager')
90
+ }
91
+ logDebug('daemon.ensureTerminalManager.spawn.success', { socketPath })
92
+ }
93
+ await manager.connect()
94
+ logDebug('daemon.ensureTerminalManager.connectAfterSpawn.success')
95
+ }
96
+ }
97
+
37
98
  export async function runDaemon(): Promise<void> {
38
- const socketPath = getDaemonSocketPath()
99
+ const socketPath = getIpcDaemonSocketPath()
39
100
  logDebug('daemon.start', { pid: process.pid, socketPath })
40
101
 
41
- const existingPid = await findDaemonPid(socketPath)
102
+ const existingPid = await findSocketProcessPid(socketPath)
42
103
  if (existingPid !== null && existingPid !== process.pid) {
43
104
  logDebug('daemon.alreadyRunning', { existingPid })
44
105
  process.stderr.write(`aimux daemon already running (pid ${existingPid})\n`)
45
106
  process.exit(1)
46
107
  }
47
108
 
48
- logDebug('daemon.removeStaleSocket', { socketPath })
49
109
  removeDaemonSocketIfExists()
50
110
 
51
- const sessionManager = new SessionManager()
111
+ const manager = new TerminalManagerClient()
112
+ await ensureTerminalManagerReady(manager)
113
+
52
114
  const sockets = new Set<Socket>()
53
115
  const attachedSessions = new Map<Socket, string>()
116
+ const negotiatedVersions = new Map<Socket, number>()
54
117
 
55
- sessionManager.on('render', (sessionId, tabId, viewport, terminalModes) => {
118
+ manager.on('render', (sessionId, tabId, viewport, terminalModes) => {
119
+ logDebug('daemon.manager.render', {
120
+ attachedSocketCount: sockets.size,
121
+ sessionId,
122
+ tabId,
123
+ viewportLines: viewport.lines.length,
124
+ })
56
125
  const event: ServerEvent = { payload: { tabId, terminalModes, viewport }, type: 'tabRender' }
57
126
  for (const socket of sockets) {
58
127
  if (attachedSessions.get(socket) === sessionId) {
@@ -60,7 +129,8 @@ export async function runDaemon(): Promise<void> {
60
129
  }
61
130
  }
62
131
  })
63
- sessionManager.on('exit', (sessionId, tabId, exitCode) => {
132
+ manager.on('exit', (sessionId, tabId, exitCode) => {
133
+ logDebug('daemon.manager.exit', { exitCode, sessionId, tabId })
64
134
  const event: ServerEvent = { payload: { exitCode, tabId }, type: 'tabExit' }
65
135
  for (const socket of sockets) {
66
136
  if (attachedSessions.get(socket) === sessionId) {
@@ -68,7 +138,8 @@ export async function runDaemon(): Promise<void> {
68
138
  }
69
139
  }
70
140
  })
71
- sessionManager.on('error', (sessionId, tabId, message) => {
141
+ manager.on('error', (sessionId, tabId, message) => {
142
+ logDebug('daemon.manager.error', { message, sessionId, tabId })
72
143
  const event: ServerEvent = { payload: { message, tabId }, type: 'tabError' }
73
144
  for (const socket of sockets) {
74
145
  if (attachedSessions.get(socket) === sessionId) {
@@ -83,117 +154,195 @@ export async function runDaemon(): Promise<void> {
83
154
  const decoder = new MessageDecoder<ClientRequest>(parseClientRequest)
84
155
 
85
156
  socket.on('data', (chunk) => {
86
- try {
87
- for (const message of decoder.push(chunk)) {
88
- try {
89
- logDebug('daemon.request', { id: message.id, type: message.type })
90
- switch (message.type) {
91
- case 'attach': {
92
- attachedSessions.set(socket, message.payload.sessionId)
93
- sessionManager.resize(
94
- message.payload.sessionId,
95
- message.payload.cols,
96
- message.payload.rows
97
- )
98
- const attachResult = sessionManager.attachSession(
99
- message.payload.sessionId,
100
- message.payload.workspaceSnapshot
101
- )
102
- send(socket, {
103
- id: message.id,
104
- payload: { protocolVersion: IPC_PROTOCOL_VERSION, ...attachResult },
105
- type: 'attachResult',
106
- })
107
- break
108
- }
109
- case 'createTab': {
110
- const sessionId = requireSession(socket, attachedSessions)
111
- sessionManager.createTab(sessionId, message.payload)
112
- sendOk(socket, message.id)
113
- break
114
- }
115
- case 'write': {
116
- const sessionId = requireSession(socket, attachedSessions)
117
- sessionManager.write(sessionId, message.payload.tabId, message.payload.data)
118
- sendOk(socket, message.id)
119
- break
120
- }
121
- case 'resizeClient': {
122
- const sessionId = requireSession(socket, attachedSessions)
123
- sessionManager.resize(sessionId, message.payload.cols, message.payload.rows)
124
- sendOk(socket, message.id)
125
- break
126
- }
127
- case 'resizeTab': {
128
- const sessionId = requireSession(socket, attachedSessions)
129
- sessionManager.resizeTab(
130
- sessionId,
131
- message.payload.tabId,
132
- message.payload.cols,
133
- message.payload.rows
134
- )
135
- sendOk(socket, message.id)
136
- break
137
- }
138
- case 'scroll': {
139
- const sessionId = requireSession(socket, attachedSessions)
140
- sessionManager.scroll(sessionId, message.payload.tabId, message.payload.deltaLines)
141
- sendOk(socket, message.id)
142
- break
143
- }
144
- case 'scrollToBottom': {
145
- const sessionId = requireSession(socket, attachedSessions)
146
- sessionManager.scrollToBottom(sessionId, message.payload.tabId)
147
- sendOk(socket, message.id)
148
- break
149
- }
150
- case 'setActiveTab': {
151
- const sessionId = requireSession(socket, attachedSessions)
152
- sessionManager.setActiveTab(sessionId, message.payload.tabId)
153
- sendOk(socket, message.id)
154
- break
155
- }
156
- case 'closeTab': {
157
- const sessionId = requireSession(socket, attachedSessions)
158
- sessionManager.closeTab(sessionId, message.payload.tabId)
159
- sendOk(socket, message.id)
160
- break
161
- }
162
- case 'disposeAll': {
163
- const sessionId = attachedSessions.get(socket)
164
- if (sessionId) {
165
- sessionManager.disposeSession(sessionId)
157
+ void (async () => {
158
+ try {
159
+ for (const message of decoder.push(chunk)) {
160
+ try {
161
+ switch (message.type) {
162
+ case 'hello': {
163
+ logDebug('daemon.request.hello', {
164
+ maxVersion: message.payload.maxVersion,
165
+ minVersion: message.payload.minVersion,
166
+ })
167
+ const selectedVersion = negotiateProtocolVersion(
168
+ message.payload.minVersion,
169
+ message.payload.maxVersion,
170
+ IPC_PROTOCOL_MIN_VERSION,
171
+ IPC_PROTOCOL_VERSION
172
+ )
173
+ if (selectedVersion === null) {
174
+ send(socket, {
175
+ id: message.id,
176
+ payload: { message: 'No compatible app protocol version' },
177
+ type: 'error',
178
+ })
179
+ break
180
+ }
181
+ negotiatedVersions.set(socket, selectedVersion)
182
+ logDebug('daemon.request.hello.success', { selectedVersion })
183
+ send(socket, {
184
+ id: message.id,
185
+ payload: {
186
+ maxVersion: IPC_PROTOCOL_VERSION,
187
+ minVersion: IPC_PROTOCOL_MIN_VERSION,
188
+ processVersion: getProcessVersion(),
189
+ selectedVersion,
190
+ },
191
+ type: 'helloResult',
192
+ })
193
+ break
194
+ }
195
+ case 'attach': {
196
+ logDebug('daemon.request.attach.start', {
197
+ cols: message.payload.cols,
198
+ rows: message.payload.rows,
199
+ sessionId: message.payload.sessionId,
200
+ snapshotTabs: message.payload.workspaceSnapshot?.tabs.length ?? 0,
201
+ })
202
+ const negotiatedVersion = requireNegotiatedVersion(socket, negotiatedVersions)
203
+ if (message.payload.protocolVersion !== negotiatedVersion) {
204
+ throw new Error(
205
+ `Protocol mismatch: client v${message.payload.protocolVersion}, daemon v${negotiatedVersion}`
206
+ )
207
+ }
208
+
209
+ attachedSessions.set(socket, message.payload.sessionId)
210
+ const attachResult = await manager.attachSession({
211
+ cols: message.payload.cols,
212
+ rows: message.payload.rows,
213
+ sessionId: message.payload.sessionId,
214
+ workspaceSnapshot: message.payload.workspaceSnapshot,
215
+ })
216
+ send(socket, {
217
+ id: message.id,
218
+ payload: {
219
+ activeTabId: attachResult.activeTabId,
220
+ protocolVersion: negotiatedVersion,
221
+ tabs: attachResult.tabs,
222
+ },
223
+ type: 'attachResult',
224
+ })
225
+ logDebug('daemon.request.attach.success', {
226
+ activeTabId: attachResult.activeTabId,
227
+ sessionId: message.payload.sessionId,
228
+ tabs: attachResult.tabs.length,
229
+ })
230
+ break
231
+ }
232
+ case 'createTab': {
233
+ const sessionId = requireSession(socket, attachedSessions)
234
+ requireNegotiatedVersion(socket, negotiatedVersions)
235
+ logDebug('daemon.request.createTab.start', {
236
+ command: message.payload.command,
237
+ sessionId,
238
+ tabId: message.payload.tabId,
239
+ title: message.payload.title,
240
+ })
241
+ await manager.createTab({ ...message.payload, sessionId })
242
+ sendOk(socket, message.id)
243
+ logDebug('daemon.request.createTab.success', {
244
+ sessionId,
245
+ tabId: message.payload.tabId,
246
+ })
247
+ break
248
+ }
249
+ case 'write': {
250
+ const sessionId = requireSession(socket, attachedSessions)
251
+ requireNegotiatedVersion(socket, negotiatedVersions)
252
+ await manager.write(sessionId, message.payload.tabId, message.payload.data)
253
+ sendOk(socket, message.id)
254
+ break
255
+ }
256
+ case 'resizeClient': {
257
+ const sessionId = requireSession(socket, attachedSessions)
258
+ requireNegotiatedVersion(socket, negotiatedVersions)
259
+ await manager.resize(sessionId, message.payload.cols, message.payload.rows)
260
+ sendOk(socket, message.id)
261
+ break
166
262
  }
167
- sendOk(socket, message.id)
168
- break
263
+ case 'resizeTab': {
264
+ const sessionId = requireSession(socket, attachedSessions)
265
+ requireNegotiatedVersion(socket, negotiatedVersions)
266
+ await manager.resizeTab(
267
+ sessionId,
268
+ message.payload.tabId,
269
+ message.payload.cols,
270
+ message.payload.rows
271
+ )
272
+ sendOk(socket, message.id)
273
+ break
274
+ }
275
+ case 'scroll': {
276
+ const sessionId = requireSession(socket, attachedSessions)
277
+ requireNegotiatedVersion(socket, negotiatedVersions)
278
+ await manager.scroll(sessionId, message.payload.tabId, message.payload.deltaLines)
279
+ sendOk(socket, message.id)
280
+ break
281
+ }
282
+ case 'scrollToBottom': {
283
+ const sessionId = requireSession(socket, attachedSessions)
284
+ requireNegotiatedVersion(socket, negotiatedVersions)
285
+ await manager.scrollToBottom(sessionId, message.payload.tabId)
286
+ sendOk(socket, message.id)
287
+ break
288
+ }
289
+ case 'setActiveTab': {
290
+ const sessionId = requireSession(socket, attachedSessions)
291
+ requireNegotiatedVersion(socket, negotiatedVersions)
292
+ await manager.setActiveTab(sessionId, message.payload.tabId)
293
+ sendOk(socket, message.id)
294
+ break
295
+ }
296
+ case 'closeTab': {
297
+ const sessionId = requireSession(socket, attachedSessions)
298
+ requireNegotiatedVersion(socket, negotiatedVersions)
299
+ await manager.closeTab(sessionId, message.payload.tabId)
300
+ sendOk(socket, message.id)
301
+ break
302
+ }
303
+ case 'disposeAll': {
304
+ const sessionId = attachedSessions.get(socket)
305
+ requireNegotiatedVersion(socket, negotiatedVersions)
306
+ if (sessionId) {
307
+ await manager.disposeSession(sessionId)
308
+ }
309
+ sendOk(socket, message.id)
310
+ break
311
+ }
312
+ case 'ping':
313
+ sendOk(socket, message.id)
314
+ break
169
315
  }
170
- case 'ping':
171
- sendOk(socket, message.id)
172
- break
316
+ } catch (error) {
317
+ const errorMessage = error instanceof Error ? error.message : String(error)
318
+ logDebug('daemon.request.error', {
319
+ error: errorMessage,
320
+ requestId: message.id,
321
+ type: message.type,
322
+ })
323
+ send(socket, { id: message.id, payload: { message: errorMessage }, type: 'error' })
173
324
  }
174
- } catch (error) {
175
- const errorMessage = error instanceof Error ? error.message : String(error)
176
- logDebug('daemon.request.error', { error: errorMessage, requestId: message.id })
177
- send(socket, { id: message.id, payload: { message: errorMessage }, type: 'error' })
178
325
  }
326
+ } catch (error) {
327
+ const message = error instanceof Error ? error.message : String(error)
328
+ logDebug('daemon.decoder.error', { error: message })
329
+ decoder.reset()
330
+ send(socket, { id: crypto.randomUUID(), payload: { message }, type: 'error' })
179
331
  }
180
- } catch (error) {
181
- const message = error instanceof Error ? error.message : String(error)
182
- logDebug('daemon.request.error', { error: message })
183
- decoder.reset()
184
- send(socket, { id: crypto.randomUUID(), payload: { message }, type: 'error' })
185
- }
332
+ })()
186
333
  })
187
334
 
188
335
  socket.on('close', () => {
189
- logDebug('daemon.client.close')
336
+ logDebug('daemon.client.close', { sessionId: attachedSessions.get(socket) ?? null })
190
337
  sockets.delete(socket)
191
338
  attachedSessions.delete(socket)
339
+ negotiatedVersions.delete(socket)
192
340
  })
193
341
  socket.on('error', () => {
194
- logDebug('daemon.client.error')
342
+ logDebug('daemon.client.error', { sessionId: attachedSessions.get(socket) ?? null })
195
343
  sockets.delete(socket)
196
344
  attachedSessions.delete(socket)
345
+ negotiatedVersions.delete(socket)
197
346
  })
198
347
  })
199
348
 
@@ -201,12 +350,11 @@ export async function runDaemon(): Promise<void> {
201
350
  server.once('error', reject)
202
351
  server.listen(socketPath, () => resolve())
203
352
  })
204
- tightenDaemonSocketPermissions(socketPath)
205
- logDebug('daemon.listening', { socketPath })
353
+ tightenSocketPermissions(socketPath)
206
354
 
207
355
  const gracefulShutdown = (signal: string) => {
208
356
  logDebug(`daemon.${signal}`)
209
- sessionManager.disposeAll()
357
+ manager.destroy()
210
358
  server.close()
211
359
  process.exit(0)
212
360
  }
@@ -216,18 +364,14 @@ export async function runDaemon(): Promise<void> {
216
364
 
217
365
  process.on('uncaughtException', (error) => {
218
366
  logDebug('daemon.uncaughtException', { error: error.message, stack: error.stack })
219
- sessionManager.disposeAll()
220
- server.close()
221
- process.exit(1)
367
+ gracefulShutdown('uncaughtException')
222
368
  })
223
369
 
224
370
  process.on('unhandledRejection', (reason) => {
225
371
  const message = reason instanceof Error ? reason.message : String(reason)
226
372
  const stack = reason instanceof Error ? reason.stack : undefined
227
373
  logDebug('daemon.unhandledRejection', { error: message, stack })
228
- sessionManager.disposeAll()
229
- server.close()
230
- process.exit(1)
374
+ gracefulShutdown('unhandledRejection')
231
375
  })
232
376
 
233
377
  await new Promise<void>(() => {
@@ -1,12 +1,18 @@
1
1
  import { chmodSync, constants, existsSync, lstatSync, mkdirSync, unlinkSync } from 'node:fs'
2
2
  import { dirname, join } from 'node:path'
3
3
 
4
+ import { getProfileName } from '../profile-paths'
5
+
6
+ export function getRuntimeProfile(): string {
7
+ return getProfileName()
8
+ }
9
+
4
10
  function getRuntimeBaseDir(): string {
5
11
  if (process.env.XDG_RUNTIME_DIR) {
6
- return join(process.env.XDG_RUNTIME_DIR, 'aimux')
12
+ return join(process.env.XDG_RUNTIME_DIR, `aimux-${getRuntimeProfile()}`)
7
13
  }
8
14
 
9
- return join(process.env.HOME ?? '.', '.local', 'state', 'aimux')
15
+ return join(process.env.HOME ?? '.', '.local', 'state', `aimux-${getRuntimeProfile()}`)
10
16
  }
11
17
 
12
18
  export function ensureRuntimeDir(): string {
@@ -24,6 +30,14 @@ export function getDaemonSocketPath(): string {
24
30
  return join(ensureRuntimeDir(), 'daemon.sock')
25
31
  }
26
32
 
33
+ export function getIpcDaemonSocketPath(): string {
34
+ return getDaemonSocketPath()
35
+ }
36
+
37
+ export function getTerminalManagerSocketPath(): string {
38
+ return join(ensureRuntimeDir(), 'terminal-manager.sock')
39
+ }
40
+
27
41
  export function ensureParentDir(filePath: string): void {
28
42
  mkdirSync(dirname(filePath), { recursive: true })
29
43
  }
@@ -36,6 +50,10 @@ export function tightenDaemonSocketPermissions(socketPath: string): void {
36
50
  }
37
51
  }
38
52
 
53
+ export function tightenSocketPermissions(socketPath: string): void {
54
+ tightenDaemonSocketPermissions(socketPath)
55
+ }
56
+
39
57
  export function getDaemonSocketSecurityIssue(socketPath: string): string | null {
40
58
  if (!existsSync(socketPath)) {
41
59
  return 'socket missing'
@@ -61,9 +79,23 @@ export function getDaemonSocketSecurityIssue(socketPath: string): string | null
61
79
  }
62
80
  }
63
81
 
82
+ export function getSocketSecurityIssue(socketPath: string): string | null {
83
+ return getDaemonSocketSecurityIssue(socketPath)
84
+ }
85
+
64
86
  export function removeDaemonSocketIfExists(): void {
65
87
  const socketPath = getDaemonSocketPath()
66
88
  if (existsSync(socketPath)) {
67
89
  unlinkSync(socketPath)
68
90
  }
69
91
  }
92
+
93
+ export function removeSocketIfExists(socketPath: string): void {
94
+ if (existsSync(socketPath)) {
95
+ unlinkSync(socketPath)
96
+ }
97
+ }
98
+
99
+ export function removeTerminalManagerSocketIfExists(): void {
100
+ removeSocketIfExists(getTerminalManagerSocketPath())
101
+ }
package/src/index.tsx CHANGED
@@ -4,13 +4,16 @@ import { createRoot } from '@opentui/react'
4
4
 
5
5
  import { App } from './app'
6
6
  import { runDaemon } from './daemon/daemon'
7
+ import { getRuntimeProfile } from './daemon/runtime-paths'
7
8
  import { logDebug } from './debug/input-log'
8
9
  import { runDoctor } from './doctor'
9
10
  import { runRestartDaemon } from './restart-daemon'
10
11
  import { createSessionBackend } from './session-backend/bootstrap'
12
+ import { runTerminalManager } from './terminal-manager/terminal-manager'
11
13
  import { runUpdate } from './update'
12
14
 
13
15
  const command = process.argv[2]
16
+ const runtimeProfile = getRuntimeProfile()
14
17
 
15
18
  if (command === '--version' || command === '-v' || command === 'version') {
16
19
  const { version } = await import('../package.json')
@@ -31,13 +34,18 @@ if (command === 'update') {
31
34
  }
32
35
 
33
36
  if (command === 'daemon') {
34
- logDebug('index.daemonMode')
37
+ logDebug('index.daemonMode', { runtimeProfile })
35
38
  await runDaemon()
36
39
  }
37
40
 
41
+ if (command === 'terminal-manager') {
42
+ logDebug('index.terminalManagerMode', { runtimeProfile })
43
+ await runTerminalManager()
44
+ }
45
+
38
46
  if (command === '--help' || command === '-h') {
39
47
  process.stdout.write(
40
- 'aimux -- terminal multiplexer for AI CLIs\n\nUsage:\n aimux Start aimux\n aimux update Update to latest version\n aimux doctor Diagnose setup issues\n aimux restart-daemon Restart background daemon\n\n'
48
+ 'aimux -- terminal multiplexer for AI CLIs\n\nUsage:\n aimux Start aimux\n aimux update Update to latest version\n aimux doctor Diagnose setup issues\n aimux restart-daemon Restart IPC daemon\n\n'
41
49
  )
42
50
  process.exit(0)
43
51
  }
@@ -51,6 +59,6 @@ const renderer = await createCliRenderer({
51
59
  })
52
60
 
53
61
  const backend = await createSessionBackend()
54
- logDebug('index.backendReady', { backend: backend.constructor.name })
62
+ logDebug('index.backendReady', { backend: backend.constructor.name, runtimeProfile })
55
63
 
56
64
  createRoot(renderer).render(<App backend={backend} />)