* Fix --help flag executing commands instead of showing help (#650)
The --help check fell through to command dispatch when subcommandUsage
returned nil for a command. Now --help always returns before dispatch,
printing a generic fallback when no specific help text exists.
Also adds missing subcommandUsage entries for: new-window, list-panes,
list-pane-surfaces, surface-health, trigger-flash, list-panels,
focus-panel, set-app-focus, ping, capabilities, identify, list-windows,
current-window, refresh-surfaces, current-workspace, list-notifications,
clear-notifications.
Closes#650
* Document all flags and options in CLI --help output
Every subcommand's help text now shows all accepted flags, options,
environment variable fallbacks, and positional arguments matching the
actual dispatch code. Also adds help entries for commands that were
returning the generic fallback (list-workspaces, list-panes,
list-pane-surfaces, surface-health, trigger-flash, list-panels,
focus-panel, set-app-focus, etc.)
* Show 'Unknown command' for invalid commands, add legacy alias help
Unknown commands now show "Unknown command 'X'. Run 'cmux help' to see
available commands." instead of the misleading "No detailed help
available." Also adds help entries for legacy browser aliases
(open-browser, navigate, etc.) pointing to 'cmux browser --help'.
* Audit all 89 CLI commands for complete help coverage
- Add missing `help` subcommandUsage entry
- Expand id|ref → id|ref|index for move-workspace-to-window,
close-workspace, select-workspace, rename-workspace
- Document CMUX_WORKSPACE_ID/CMUX_TAB_ID/CMUX_SURFACE_ID env var
defaults in tab-action, rename-workspace
- Expand browser help: get (--selector, --attr, --property),
find (--name, --exact, --index), network route (--abort, --body),
open/open-split/new env var defaults
- Remove duplicate rename-window help case (now handled by
rename-workspace combined case)
- Upgrade regression test to auto-extract dispatch+subcommandUsage
switches and flag any commands missing help entries
- Wrap eval scripts in async IIFE that detects and awaits thenables,
using callAsyncJavaScript when available (macOS 11+) (#603)
- Register console/error telemetry hooks as WKUserScript at document
start so they survive navigation and are active before page JS (#604)
- Return typed envelope {__cmux_t, __cmux_v} from eval to distinguish
undefined from no return value; CLI prints "undefined" (#605)
- Keep dialog hooks as lazy injection only (not document-start) to
avoid suppressing WKUIDelegate native dialogs
- Add regression tests for async wrapper and undefined CLI rendering
Hide browser subcommands that always return not_supported on WKWebView
(viewport, geolocation, offline, trace, network, screencast, input)
and the legacy browser alias notice from the help output. Command
handlers remain for backwards compatibility.
Closes https://github.com/manaflow-ai/cmux/issues/593
Adds `cmux tree` that prints the window > workspace > pane > surface
hierarchy with box-drawing characters. Includes server-side system.tree
RPC for single-round-trip performance.
Features:
- --all flag for all windows (default: current window only)
- --workspace flag to filter to a single workspace
- --json for structured JSON output
- Active path markers (◀ active) and caller identification (◀ here)
- Browser surfaces show their current URL
Closes https://github.com/manaflow-ai/cmux/issues/586
Moves socket control password from the macOS login keychain to a
plain file at ~/Library/Application Support/cmux/socket-control-password.
This eliminates the system keychain prompt that interrupts users on
first launch or after keychain changes.
- Directory created with 0700, file written with 0600 permissions
- One-time migration copies existing keychain password to the file,
deletes the keychain entry, and records a migration version in
UserDefaults so it runs only once
- CLI SocketPasswordResolver also reads from the file path
- Security framework import is now conditional (#if canImport)
- Adds SocketControlPasswordStoreTests covering round-trip, env
priority, path resolution, and migration behavior
Fixes https://github.com/manaflow-ai/cmux/issues/541
* Add sidebar metadata CLI subcommands and API docs
Expose set-status, clear-status, list-status, set-progress,
clear-progress, log, clear-log, list-log, and sidebar-state as
proper CLI subcommands with --help support and usage() listing.
Previously these only existed as raw socket commands.
Also adds a "Sidebar metadata commands" section to the docs site
API reference page.
* Quote multi-word values in socket command strings
Fix set-progress --label and log message forwarding to properly
quote values before sending to the socket tokenizer. Without
quoting, multi-word labels like "Build step one" would be split
into separate tokens. Also quote --source values for consistency.
* Fix socket quoting: escape backslashes and quote status values
Add socketQuote() helper that escapes both backslashes and double
quotes before wrapping in quotes. Apply it to:
- set-status value (prevents --flags in values being parsed as options)
- set-status --icon and --color values
- set-progress --label
- log --source and message text
Fixes values like "pytest --maxfail=1" or "C:\new\build" being
mangled by the socket tokenizer.
* Escape newlines in socketQuote to prevent socket framing breakage
The socket protocol uses newline as message terminator, so embedded
newlines/carriage returns in values would truncate the command.
* Parse flags before positionals in set-status, clear-status, set-progress
Fixes flags-first invocation like `cmux set-status --workspace workspace:2
build compiling` which previously grabbed `--workspace` as the key.
Now all flags are extracted first, then positional args are validated.
* Fix manual unread clear race on focused tab
* Add mark-as-read tab action and show ring for manual unread
* Flash then clear manual unread on tab focus
* Add tmux rename-window workspace compatibility
Implement workspace.rename in the v2 API and wire CLI commands rename-workspace/rename-window with help text.
Add a regression test that validates API and CLI rename parity plus error handling.
Refs: https://github.com/manaflow-ai/cmux/issues/153
* Add full tmux compatibility command matrix and regression coverage
When the server returns a plain-text error (e.g., "ERROR: Access denied
...") before the JSON protocol starts, sendV2() would pass it through
JSONSerialization which throws a confusing NSCocoaErrorDomain 3840 error.
Now sendV2() checks for "ERROR:" prefix and surfaces the real message.
Also includes the raw response in the fallback error for easier debugging.
Fixes https://github.com/manaflow-ai/cmux/issues/188
Previously, --help/-h was only checked at the top level before command
dispatch. Running e.g. `cmux new-workspace --help` would execute the
command instead of printing help.
Add per-subcommand help text for all commands that take arguments/flags.
The help check runs before socket connect so it works even when cmux is
not running. Commands without dedicated help (ping, help, list-windows,
etc.) fall through to normal behavior.
* CLI: add --command flag to new-workspace
Allows running an initial command in the new workspace's terminal:
cmux new-workspace --command "cd /path && claude"
After creating the workspace, waits 500ms for the shell to initialize,
then sends the command text via surface.send_text.
Closes#120
* CLI: error on unknown flags for new-workspace
Typos like `--comand` were silently ignored. Now reports the
unknown flag and lists known flags.
* CLI: skip --command send when workspace creation fails
If new_workspace returns an error instead of "OK <uuid>",
don't attempt to send the command (which would hit the wrong workspace).
* Remove index-based CLI APIs and make commands workspace-relative
Migrate 14 CLI commands from v1 text protocol to v2 JSON-RPC, making them
workspace-relative via CMUX_WORKSPACE_ID env var fallback. Update 7 more
commands to use normalize functions instead of legacy resolvers. Remove dead
code (5 structs, 5 parsers, 3 functions). Add regression tests.
Commands migrated v1→v2: send, send-key, send-panel, send-key-panel,
new-split, new-pane, new-surface, close-surface, list-panes,
list-pane-surfaces, list-panels, surface-health, focus-pane, focus-panel.
Commands updated: move-workspace-to-window, list-workspaces,
close-workspace, select-workspace, trigger-flash, resolveWorkspaceId,
resolveSurfaceId.
* Fix CMUX_SURFACE_ID env fallback when --workspace is overridden
When --workspace is explicitly passed, don't fall back to CMUX_SURFACE_ID
from the caller's environment. The caller's surface belongs to a different
workspace, causing "surface not found" errors. Only use the env var fallback
when the workspace is implicit (from CMUX_WORKSPACE_ID or server default).
Affects: send, send-key, new-split, close-surface, trigger-flash, identify,
notify, claude-hook.
* Validate surface before close and respect -- option terminator in send
P1: close-surface now validates the surface handle exists before sending
surface.close, preventing silent fallback to focused surface when a stale
or mistyped ref is provided.
P2: parseOption now respects -- as an option terminator. send, send-key,
send-panel, send-key-panel strip the -- marker from payload args. This
prevents payload tokens like --workspace from being consumed as routing
flags (e.g. `cmux send -- echo --workspace foo` sends all text correctly).
* Fix test_send_workspace_relative: add to main() and send valid text
The test was never called from main() and sent empty string which would
raise immediately via _run_cli. Send a space character instead and add
the test to the main() call list.
* Respect --id-format in text output and resolve workspace refs across windows
Text-mode list commands (list-workspaces, list-panes, list-panels,
list-pane-surfaces, surface-health) now honor --id-format for plain output,
not just --json. Added textHandle() helper that picks ref/id/both based on
the selected format.
resolveWorkspaceId now enumerates all windows when resolving a workspace
ref, so notify/claude-hook work correctly in multi-window sessions where
the target workspace may be in a non-active window.
* Preserve escape-sequence semantics for send text
The v1 server unescaped \n, \r, \t in send payloads before injecting into
the terminal. The v2 surface.send_text handler sends text verbatim. Add
CLI-side unescapeSendText() to restore the same behavior: \n and \r map
to carriage return (Enter key), \t maps to tab. Applied to send and
send-panel commands.
* Reject malformed handles instead of passing through to server
All four normalize functions (window, workspace, pane, surface) now throw
a clear error for unrecognized handle formats instead of passing them
through. Previously, a typo like `--panel foo` would forward `foo` to the
server which would silently fall back to the focused surface.
* Allow cross-workspace surface refs in close-surface and strip plural ID arrays
Remove workspace-scoped pre-validation from close-surface so explicit
surface refs/UUIDs from other workspaces work without requiring
--workspace. The server resolves workspace from surface_id directly.
Malformed handles are already caught by normalizeSurfaceHandle.
Extend formatIDs to strip plural _ids/_refs array pairs (e.g.
surface_ids/surface_refs in pane.list output) based on --id-format,
matching the existing singular _id/_ref stripping behavior.
* Honor explicit --window over CMUX_WORKSPACE_ID env fallback
When --window is passed globally, skip the CMUX_WORKSPACE_ID env var
fallback so commands operate on the targeted window's selected workspace
instead of the caller's workspace from a different window. Affects all
migrated commands that use workspaceFromArgsOrEnv or inline env fallback.
* Socket access control: process ancestry check + file permissions
Redesign socket control modes from (off, notifications, full) to
(off, cmuxOnly, allowAll):
- cmuxOnly (default): uses LOCAL_PEERPID + sysctl process tree walk to
verify the connecting process is a descendant of cmux. External
processes (SSH, other terminals) are rejected.
- allowAll: hidden mode accessible only via CMUX_SOCKET_MODE=allowAll
env var, skips ancestry check. Legacy "full"/"notifications" env
values map here for backward compat.
- off: disables socket entirely.
Security hardening:
- Server: chmod 0600 on socket after bind (owner-only access)
- CLI: stat() ownership check before connect (reject fake sockets)
Removes per-command allow-list (isCommandAllowed) — once a process
passes the ancestry check, all commands are available.
Includes migration for persisted UserDefaults values and env var
aliases (cmux_only, cmux-only, allow_all, allow-all).
* Add /sync-branch skill for submodule + main sync
- Sidebar now shows git branch, listening ports, log entries, progress bars, and status pills with expand/collapse
- Fix localhost/127.0.0.1 URL parsing by checking before generic URL(string:) which misinterprets the scheme
- Remove custom Keychain distinct ID in favor of PostHog SDK's built-in anonymous ID
- browser open now defaults to caller's workspace via CMUX_WORKSPACE_ID env var
- Improve CLI help text for environment variables
Instead of creating a merged config directory and injecting
CLAUDE_CONFIG_DIR on every terminal spawn, place a thin wrapper
script at Resources/bin/claude that intercepts claude invocations
to inject --session-id and --settings flags. This eliminates
blocking I/O on terminal creation and removes config management
complexity.
- Add Resources/bin/claude wrapper script with hook injection
- Add shell integration PATH fix (re-prepend after .zshrc/.bashrc)
- Add transcript reading for richer stop notifications
- Add set_status/clear_status to notifications socket allowlist
- Add Settings toggle to disable Claude Code integration
- Update docs to reflect automatic integration approach
- Unset CLAUDECODE env var to avoid nested session detection
* Fix blank terminal after split operations and add visual tests
## Blank Terminal Fix
- Add `needsRefreshAfterWindowChange` flag in GhosttyTerminalView
- Force terminal refresh when view is added to window, even if size unchanged
- Add `ghostty_surface_refresh()` call in attachToView for same-view reattachment
- Add debug logging for surface attachment lifecycle (DEBUG builds only)
## Bonsplit Migration
- Add bonsplit as local Swift package (vendor/bonsplit submodule)
- Replace custom SplitTree with BonsplitController
- Add Panel protocol with TerminalPanel and BrowserPanel implementations
- Add SidebarTab as main tab container with BonsplitController
- Remove old Splits/ directory (SplitTree, SplitView, TerminalSplitTreeView)
## Visual Screenshot Tests
- Add test_visual_screenshots.py for automated visual regression testing
- Uses in-app screenshot API (CGWindowListCreateImage) - no screen recording needed
- Generates HTML report with before/after comparisons
- Tests: splits, browser panels, focus switching, close operations, rapid cycles
- Includes annotation fields for easy feedback
## Browser Shortcut (⌘⇧B)
- Add keyboard shortcut to open browser panel in current pane
- Add openBrowser() method to TabManager
- Add shortcut configuration in KeyboardShortcutSettings
## Screenshot Command
- Add 'screenshot' command to TerminalController for in-app window capture
- Returns OK with screenshot ID and path
## Other
- Add tests/visual_output/ and tests/visual_report.html to .gitignore
* Add browser title subscription and set tab height to 30px
- Subscribe to BrowserPanel.$pageTitle changes to update bonsplit tabs
- Update tab titles in real-time as page navigation occurs
- Clean up subscriptions when panels are removed
- Set bonsplit tab bar and tab height to 30px (in submodule)
* Fix socket API regressions in list_surfaces, list_bonsplit_tabs, focus_pane
- list_surfaces: Remove [terminal]/[browser] suffix to keep UUID-only format
that clients and tests expect for parsing
- list_bonsplit_tabs --pane: Properly look up pane by UUID instead of
creating a new PaneID (requires bonsplit PaneID.id to be public)
- focus_pane: Accept both UUID strings and integer indices as documented
* Fix browser panel stability and keyboard shortcuts
- Prevent WKWebView focus lifecycle crashes during split/view reshuffles
- Match bracket shortcuts via keyCode (Cmd+Shift+[ / ], Cmd+Ctrl+[ / ])
- Support Ghostty config goto_split:* keybinds when WebView is focused
- Add focus_webview/is_webview_focused socket commands and regression tests
- Rename SidebarTab to Workspace and update docs
* Make ctrl+enter keybind test skippable
Skip when the Ghostty keybind isn't configured or when osascript can't send keystrokes (no Accessibility permission), so VM runs stay green.
* Auto-focus browser omnibar when blank
When a browser surface is focused but no URL is loaded yet, focus the address bar instead of the WKWebView.
* Stabilize socket surface indexing
* Focus browser omnibar escape; add webview keybind UI tests
- Escape in omnibar now returns focus to WKWebView\n- Add UI tests for Cmd+Ctrl+H pane navigation with WebKit focused (including Ghostty config)\n- Avoid flaky element screenshots in UpdatePillUITests on the UTM VM
* Fix browser drag-to-split blanks and socket parsing
* Fix webview-focused shortcuts and stabilize browser splits
- Match ctrl/shift shortcuts by keyCode where needed (Ctrl+H, bracket keys)
- Load Ghostty goto_split triggers reliably and refresh on config load
- Add debug socket helpers: set_shortcut + simulate_shortcut for tests
- Convert browser goto_split/keybind tests to socket-based injection (no osascript)
- Bump bonsplit for drag-to-split fixes
* Fix split layout collapse and harden socket pane APIs
* Stabilize OSC 99 notification test timing
* Fix terminal focus routing after split reparent
* Support simulate_shortcut enter for focus routing test
* Stabilize terminal focus routing test
* Fix frozen new terminal tabs after many splits
* Fix frozen new terminal tabs after splits
* Fix terminal freeze on launch/new tabs
* Update ghostty submodule
* Fix terminal focus/render stalls after split churn
* Fix nested split collapsing existing pane
* Fix nested split collapse + stabilize new-surface focus
* Update bonsplit submodule
* Fix SIGINT test flake
* Remove bonsplit tab-switch crossfade
* Remove PROJECTS.md
* Remove bonsplit tab selection animation
* Ignore generated test reports
* Middle click closes tab
* Revert unintended .gitignore change
* Fix build after main merge
* Revert "Fix build after main merge"
This reverts commit 16bf9816d0856b5385d52f886aa5eb50f3c9d9a4.
* Revert "Merge remote-tracking branch 'origin/main' into fix/blank-terminal-and-visual-tests"
This reverts commit 7c20fb53fd71fea7a19a3673f2dd73e5f0c783c4, reversing
changes made to 0aff107d787bc9d8bbc28220090b4ca7af72e040.
* Remove tab close fade animation
* Use terminal.fill icon
* Make terminal tab icon smaller
* Match browser globe tab icon size
* Bonsplit: tab min width 48 and tighter close button
* Bonsplit: smaller tab title font
* Show unread notification badge in bonsplit tabs and improve UI polish
Sync unread notification state to bonsplit tab badges (blue dot).
Improve EmptyPanelView with Terminal/Browser buttons and shortcut hints.
Add tooltips to close tab button and search overlay buttons.
* Fix reload.sh single-instance safety check on macOS
Replace GNU-only `ps -o etimes=` with portable `ps -o etime=` and
parse the dd-hh:mm:ss format manually for macOS compatibility.
* Centralize keyboard shortcut definitions into Action enum
Replace per-shortcut boilerplate with a single Action enum that holds
the label, defaults key, and default binding for each shortcut. All
call sites now use shortcut(for:). Settings UI is data-driven via
ForEach(Action.allCases). Titlebar tooltips update dynamically when
shortcuts are changed. Remove duplicate .keyboardShortcut() modifiers
from menu items that are already handled by the event monitor.
* Fix WKWebView consuming app menu shortcuts and close panel confirmation
Add CmuxWebView subclass that routes key equivalents through the main
menu before WebKit, so Cmd+N/Cmd+W/tab switching work when a browser
pane is focused. Fix Cmd+W close-panel path: bypass Bonsplit delegate
gating after the user confirms the running-process dialog by tracking
forceCloseTabIds. Add unit tests (CmuxWebViewKeyEquivalentTests) and
UI test scaffolding (MenuKeyEquivalentRoutingUITests) with a new
cmux-unit Xcode scheme.
* Update CLAUDE.md and PROJECTS.md with recent changes
CLAUDE.md: enforce --tag for reload commands, add cleanup safety rules.
PROJECTS.md: log notification badge, reload.sh fix, Cmd+W fix, WebView
key equiv fix, and centralized shortcuts work.
* Keep selection index stable on close
* Add concepts page documenting terminology hierarchy
New docs page explaining Window > Workspace > Pane > Surface > Panel
hierarchy with aligned ASCII diagram. Updated tabs.mdx and splits.mdx
to use consistent terminology (workspace instead of tab, surface
instead of panel) and corrected outdated CLI command references.
* Update bonsplit submodule
* WIP: improve split close stability and UI regressions
* Close terminal panel on child exit; hide terminal dirty dot
* Fix split close/focus regressions and stabilize UI tests
* Add unread Dock/Cmd+Tab badge with settings toggle
* Fix browser-surface shortcuts and Cmd+L browser opening
* Snapshot current workspace state before regression fixes
* Update bonsplit submodule snapshot
* Stabilize split-close regression capture and sidebar resize assertions
* Change default Show Notifications shortcut from Cmd+Shift+I to Cmd+I
* Fix update check readiness race, enable release update logging, and improve checking spinner
* Restore terminal file drop, fix browser omnibar click focus, and add panel workspace ID mutation for surface moves
* Add Cmd+digit workspace hints, titlebar shortcut pills, sidebar drag-reorder, and workspace placement settings
* Add v2 browser automation API, surface move/reorder commands, and short-handle ref system to TerminalController
* Add CLI browser command surface, --id-format flag, and move/reorder commands
* Extend test clients with move/reorder APIs, ref-handle support, and increased timeouts
* Harden test runner scripts with deterministic builds, retry logic, and robust socket readiness
* Stabilize existing test suites with focus-wait helpers, increased timeouts, and API shape updates
* Add terminal file drop e2e regression test
* Add v2 browser API, CLI ref resolution, and surface move/reorder test suites
* Add unit tests for shortcut hints, workspace reorder, drop planner, and update UI test stabilization
* Add cmux-debug-windows skill with snapshot script and agent config
* Update project docs: mark browser parity and move/reorder phases complete, add parallel agent workflow guidelines
* Update bonsplit submodule: re-entrant setPosition guard, tab shortcut hints, and moveTab/reorderTab API
* Add browser agent UX improvements: snapshot refs, placement reuse, diagnostics, and skill docs
- Upgrade browser.snapshot to emit accessibility tree text with element refs (eN)
- Add right-sibling pane reuse policy for browser.open_split placement
- Add rich not_found diagnostics with retry logic for selector actions
- Support --snapshot-after for post-action verification on mutating commands
- Allow browser fill with empty text for clearing inputs
- Default CLI --id-format to refs-first (UUIDs opt-in via --id-format uuids|both)
- Format legacy new-pane/new-surface output with short surface refs
- Add skills/cmuxterm-browser/ and skills/cmuxterm/ end-user skill docs
- Add regression tests for placement policy, snapshot refs, diagnostics, and ID defaults
* Update bonsplit submodule: keep raster favicons in color when inactive
* Fix zsh ZDOTDIR wrapper + log parsing with -- messages
* Fix CI race condition: serialize self-hosted builds with concurrency group
Two workflows racing on the same self-hosted runner caused DerivedData
corruption (release's rm -rf nuked DerivedData while CI was building).
Add shared concurrency group and scope DerivedData cleanup to project.
* Add --panel flag to new-split command
Allows splitting a specific panel without changing focus first.
Usage: cmuxterm new-split <direction> [--panel <id|index>]
Example: cmuxterm new-split down --panel 1
* Return new panel ID from new-split command
new-split now returns the UUID of the newly created panel, enabling
reliable chaining of split operations without index drift issues.
Before: OK
After: OK F2675177-3838-49AF-A1A0-1744C0048E99
Example workflow to create left + 2x2 grid on right:
RIGHT=$(cmuxterm new-split right | awk '{print $2}')
BOTTOM=$(cmuxterm new-split down --panel $RIGHT | awk '{print $2}')
cmuxterm new-split right --panel $RIGHT
cmuxterm new-split right --panel $BOTTOM