Comma Agents
@comma-agents/tui

Architecture

High-level architecture of the TUI application — provider tree, routing, and data flow.

Architecture

The TUI is a React application rendered in the terminal via Ink. It connects to the daemon over WebSocket and provides a multi-tab interface for running strategies, viewing logs, and navigating commands.

Entry Point

The app bootstraps in two stages. main.tsx loads first, hijacking console to capture all output into the log store before anything else runs. It dynamically imports bootstrap.tsx, which parses CLI arguments (strategy, daemon URL, initial input) and renders the Ink tree:

render(
  <MemoryRouter>
    <UserConfigContextProvider>
      <ThemeContextProvider>
        <DaemonContextProvider>
          <ChatSessionsContextProvider>
            <ModalContextProvider>
              <App />
            </ModalContextProvider>
          </ChatSessionsContextProvider>
        </DaemonContextProvider>
      </ThemeContextProvider>
    </UserConfigContextProvider>
  </MemoryRouter>
)

Providers are nested from outermost (most foundational) to innermost (most derived). Each layer depends on the providers above it.

Provider Tree

ProviderPurposeDepends On
MemoryRouterIn-memory routing (/intro, /chat, /logs)--
UserConfigContextProviderLoads persisted user config (theme name) from disk--
ThemeContextProviderResolves the active theme and injects it into the treeUserConfig
DaemonContextProviderOpens a WebSocket to the daemon, parses and dispatches messages--
ChatSessionsContextProviderManages chat sessions, subscribes to daemon events by runDaemon
ModalContextProviderControls modal open/close state with a stack--

Routing

The App component renders a Frame shell with tab-based routing:

RoutePageWhen Shown
/chatChatPageAfter a strategy has been started (or a session loaded)
/chat (fallback)IntroPageBefore any strategy is started -- shows strategy picker and prompt input
/logsLogsPageAlways accessible via Alt+2 -- shows captured console output

The /intro route is not a tab; it renders inside /chat as the fallback when no chat session is active, and renders without the Frame tab bar.

Data Flow

Daemon (WebSocket)


useWebSocket ←── raw text frames


DaemonContextProvider
    │  parses JSON, validates with Zod
    │  dispatches to type-registered listeners

ChatSessionsContextProvider
    │  listens for: agent_streaming, request_input,
    │  request_permission, strategy_started, etc.
    │  routes messages to sessions by daemonRunId
    │  updates per-session ChatMessage arrays

useChat / useChatSessions
    │  consumers read messages, status, pending input

ChatPage / IntroPage
    │  renders MessageList, ChatTextArea,
    │  StatusBar, PermissionPrompt

User input (sendInput / sendPermissionDecision / startStrategy)


useDaemonCommand → DaemonContextProvider.send → WebSocket → Daemon

Commands flow down through typed useDaemonCommand hooks that auto-inject type and a fresh requestId. Events flow up through useDaemonSubscription hooks registered in ChatSessionsContextProvider.

Keyboard Shortcuts

KeyAction
Ctrl+CExit the application
Ctrl+RReset the current chat session
Ctrl+POpen the command palette
Alt+1 / Alt+2Switch tabs (Chat / Logs)
Meta+EnterSubmit input in the chat text area

CLI Arguments

The TUI accepts optional flags:

FlagDescription
--strategy <name>Pre-select a strategy (build, plan, qa, talk)
--input <text>Auto-start a chat with this prompt on mount
--daemon-url <url>Daemon WebSocket URL (default: ws://localhost:7422/ws)
--devEnable the Dev tab (Alt+3) with component playground

On this page