* Adapt welcome screen colors for light mode
Detect macOS appearance and use a darker cyan-to-purple gradient
with sufficient contrast (WCAG AA 4.5:1+) on light backgrounds.
Replace dim ANSI attribute with explicit true-color for light mode,
since dim can reduce contrast further against light backgrounds.
* Keep original gradient colors, only adapt tagline and dim text
The cyan-to-purple gradient looks good in both modes. Only the
tagline gray and dim description text need darker values for
light backgrounds.
* Add claude-teams CLI command
* Add claude-teams launcher regression test
* Exec claude-teams launcher in place
* Add existing-shim claude-teams regression test
* Reuse claude-teams shim and refresh dev CLI
* Add wrapper-selection claude-teams regression test
* Launch real claude binary for claude-teams
* Add claude-teams auto-mode launcher regression test
* Default claude-teams to fake tmux auto mode
* Build tagged reloads under DerivedData
* Add claude-teams tmux sequence regression test
* Fix claude-teams tmux teammate compatibility
* Add claude-teams split focus regression test
* Keep claude-teams leader pane focused
* Tighten claude-teams review fixes
* Pass claude-teams help through to Claude
* Use sentinel TERM_PROGRAM in claude-teams test
* Add cmux welcome command with ASCII logo and shortcuts
Adds `cmux welcome` CLI command that prints a blue-to-purple gradient
chevron logo, version info, and key shortcuts. Auto-runs once on first
workspace creation via UserDefaults. Adds "Welcome" to the ? help menu
in the sidebar footer, which opens a new workspace running the command.
* Polish welcome, feedback, and shortcuts flows
* Add workspace pages in the titlebar
* Add workspace pages UI test target entry
* Relax workspace pages UI test titlebar checks
* Use page close button in workspace pages UI test
* Stabilize workspace pages UI test interruptions
* Skip page close confirms in UI tests
* Clean up superseded workspace handoffs
* Tighten page hint UI assertions
---------
Co-authored-by: cmux <cmux@cmuxs-Mac-mini.local>
* Return browser screenshot image URL
* Make screenshot path/url best effort
* cli: omit screenshot png_base64 from json output
* browser wait: fail fast on js errors and include screenshot in help
* browser wait: avoid main-actor default world warning
* tests: scope contentWorld regression check to function signature
* browser screenshot: clean up output handling and tests
* browser wait: resolve snapshot refs in selector waits
* Fix Claude wrapper hook injection when cmux socket is stale
* Harden socket listener lifecycle and rearm policy
* Unset CLAUDECODE in stale-socket passthrough
* Harden listener cleanup and bound claude ping probe
* Guard socket unlink during listener startup window
* Add markdown viewer panel with live file watching
Introduce a new PanelType.markdown that renders .md files in a dedicated
panel using MarkdownUI (SwiftUI), with live file watching via DispatchSource
so content auto-updates when the file changes on disk.
- New MarkdownPanel class with file system watcher (write/delete/rename/extend)
- New MarkdownPanelView with custom cmux theme (headings, code blocks, tables,
blockquotes, inline code, lists, horizontal rules, light/dark mode)
- Full workspace integration: SurfaceKind, creation methods, tab subscription
- Session persistence: snapshot/restore across app restarts
- V2 socket command: markdown.open (validates path, resolves workspace, splits)
- CLI command: cmux markdown open <path> with routing flags and help text
- Agent skill: skills/cmux-markdown/ with SKILL.md, openai.yaml, and references
- Cross-link from skills/cmux/SKILL.md to the new markdown skill
- SPM dependency: gonzalezreal/swift-markdown-ui 2.4.1
* Fix unreachable guard in markdown subcommand dispatch
Use looksLikePath() to distinguish subcommands from path arguments
so the guard can catch unknown subcommands and future subcommands
are parsed correctly.
* Use .isoLatin1 fallback instead of .ascii for encoding recovery
ASCII is a strict subset of UTF-8, so falling back to .ascii after
UTF-8 fails is dead code. Use .isoLatin1 which accepts all 256 byte
values and covers legacy encodings like Windows-1252.
* Mark fileWatchSource as nonisolated(unsafe) for deinit safety
deinit is not guaranteed to run on the main actor, so accessing
@MainActor-isolated storage is a data race under strict concurrency.
DispatchSource.cancel() is thread-safe, so nonisolated(unsafe) is
sufficient with a documented invariant that writes only occur on main.
* Fix file watcher reattach: retry loop with cancellation guard
- Replace one-shot 500ms retry with up to 6 attempts (3s total window)
so files that reappear after a slow atomic replace are picked up
- Add isClosed flag checked before each retry to prevent restarting
the watcher after close()/deinit
* Harden path validation in markdown.open command
Reject directories and non-absolute paths before panel creation
to prevent ambiguous behavior and generic downstream failures.
* Always reattach file watcher on delete/rename events
After an atomic save (delete old + create new), the DispatchSource still
points to the old inode. Previously we only reattached when the file was
unreadable, so successful atomic saves left the watcher on a stale inode
and live updates silently stopped. Now we always stop and reattach:
immediately if the new file is readable, via retry loop if not.
* Restore markdown panels even when file is missing at launch
MarkdownPanel already handles unavailable files gracefully (shows
'file unavailable' UI and retries via the reattach loop). Dropping
the panel on restore lost the user's layout for files that may
reappear shortly after (network drives, build artifacts, etc.).
* Harden markdown CLI parsing and startup reconnect behavior
---------
Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
* Clear sidebar notification when user submits prompt in Claude Code
Add UserPromptSubmit hook to the Claude Code wrapper that calls
`cmux claude-hook prompt-submit`. This clears the workspace notification
and sets status back to "Running" when the user addresses Claude's question,
so the "waiting for input" preview in the sidebar goes away.
Also adds --tab support to clear_notifications socket command and
--workspace support to the clear-notifications CLI command for
per-workspace notification clearing.
Closes https://github.com/manaflow-ai/cmux/issues/799
* Address review feedback: stricter error handling
- clear-notifications CLI: error on explicit --workspace failure instead of
falling back to global clear. Env var still gracefully degrades.
- prompt-submit hook: propagate sendV1Command errors instead of swallowing
with try?.
- clear_notifications socket: validate --tab flag is present before resolving,
reject malformed args instead of falling back to selected tab.
* Gate env workspace fallback on windowId == nil in clear-notifications
Matches the pattern used by other CLI commands to avoid using
CMUX_WORKSPACE_ID from the caller shell when --window targets
a different window.
* Add `cmux <path>` to open directories and Homebrew binary stanza
CLI: `cmux .` or `cmux /path/to/dir` opens a new workspace at the
given directory. If the app isn't running, it launches first and waits
for the socket. Also adds `--cwd` flag to `new-workspace`.
Server: `workspace.create` now accepts an optional `cwd` parameter,
passed through to `TabManager.addWorkspace(workingDirectory:)`.
Homebrew: adds `binary` stanza to the cask so `cmux` CLI is globally
available after `brew install --cask cmux`. Updated both the cask file,
the CI workflow template, and the manual release script so automated
version bumps preserve the stanza.
* Address review: validate cwd type, fix socket detection, propagate errors
- looksLikePath now also matches paths containing `/` (e.g. `foo/bar`)
- openPath uses socket connection attempt instead of fileExists to detect
whether the app is running (Unix sockets may not appear on filesystem)
- launchApp/activateApp now throw instead of swallowing errors with try?
- Server validates that cwd param is a string, returns invalid_params error
if wrong type is passed
* 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.