-
Notifications
You must be signed in to change notification settings - Fork 61
🤖 feat: add simplified terminal UI via Ink (mux tui)
#2360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 74044019d3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| dispatch({ | ||
| type: "CHAT_ADD_MESSAGE", | ||
| message: { role: "user", content: messageText }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove duplicate optimistic user message append
This eagerly inserts the user message into local state before workspace.sendMessage, but the same user turn is also delivered through the active workspace.onChat stream (the backend emits user type: "message" events), so each successful send is rendered twice in the TUI transcript. This happens in the normal connected-chat flow and makes the conversation history inaccurate unless you dedupe by message ID or rely on the stream event only.
Useful? React with 👍 / 👎.
| if (isStreamError(event)) { | ||
| dispatch({ type: "SET_ERROR", error: event.error }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reset stream state when stream-error arrives
On stream-error the code only sets a top-level error, leaving chat.isStreaming and streamingBuffer untouched. In terminal-error paths the backend can emit stream-error without a follow-up stream-end/stream-abort, so the TUI can remain stuck showing an active typing state and stale partial output even though the stream has already ended. Clear streaming state (same as abort/end) when handling this event.
Useful? React with 👍 / 👎.
| branchName: trimmedBranchName, | ||
| trunkBranch: recommendedTrunk ?? undefined, | ||
| title: trimmedTitle.length > 0 ? trimmedTitle : undefined, | ||
| runtimeConfig: { type: "local" }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stop forcing workspace creation to local runtime
Hardcoding runtimeConfig: { type: "local" } changes workspace creation semantics from the backend default and bypasses project/runtime expectations: local runtime does not create isolated worktrees and can also be rejected by policy in environments that only allow worktree/remote runtimes. This means mux tui workspace creation can silently produce incorrect workspace behavior or fail in policy-enforced setups.
Useful? React with 👍 / 👎.
Ink internally imports react-devtools-core. Marking it as --external left a bare import in the ESM bundle that Node couldn't resolve at runtime. Installing it as a devDependency and removing the --external flag lets esbuild bundle it into dist/cli/tui.mjs.
Summary
Adds a full-screen terminal UI (
mux tui) built on Ink (React renderer for terminals) that connects to a running Mux server via oRPC. The TUI supports project/workspace navigation, creation, and interactive chat with streaming, tool calls, andask_user_questionsupport.Background
Users working in terminal-only environments (SSH, remote servers, headless setups) need a way to interact with Mux without the desktop Electron app. The TUI reuses the existing oRPC API layer so all business logic stays centralized in the backend.
Implementation
Architecture
mux server), same API surface as the Electron frontenddist/cli/tui.mjsbuilt with esbuild, loaded via dynamicimport()from the CJS CLI entry (mirrorsmux apipattern)Core Features
nto create,Tabto switch panesask_user_questioninteractive flowMUX_SERVER_URL→~/.mux/server.lock→ fallbackhttp://localhost:3000Visual Design (Dark Theme)
─ Projects ───────)#6fbf73for user,#ce93d8for assistantAnti-Flashing
\x1b[?1049h) so terminal double-buffers all Ink re-renders — no visible flashing on keypressesFile Layout
src/cli/tui.tsxsrc/cli/tui/TuiApp.tsxsrc/cli/tui/tuiStore.ts,src/cli/tui/tuiTypes.tsFullScreenLayout,Sidebar,SelectableList,MarkdownText,CodeBlock,ToolCallBlockChatScreen,CreateProjectScreen,CreateWorkspaceScreenValidation
make static-checkpasses (typecheck, lint, fmt, build, broken-links)mux tuiconnects to running server, navigates projects/workspaces, sends/receives chat messagesGenerated with
mux• Model:anthropic:claude-opus-4-6• Thinking:xhigh• Cost:$12.33