* Add `cmux omo` command for OpenCode + oh-my-openagent integration
Same pattern as `cmux claude-teams`: creates a tmux shim so
oh-my-openagent's TmuxSessionManager spawns agents as native cmux
splits instead of tmux panes. Sets TMUX/TMUX_PANE env vars, prepends
shim to PATH, and execs into opencode.
Closes https://github.com/manaflow-ai/cmux/issues/2085
* Auto-install oh-my-opencode plugin when running cmux omo
Before launching opencode, cmux omo now:
- Checks if oh-my-opencode is registered in ~/.config/opencode/opencode.json
- If not, creates/updates the config with the plugin entry
- Checks if the npm package is installed in node_modules
- If not, runs bun add (or npm install) to install it
- Then proceeds with tmux shim setup and exec
* Use shadow config dir to avoid modifying user's opencode setup
Instead of writing directly to ~/.config/opencode/opencode.json,
cmux omo now creates a shadow config at ~/.cmuxterm/omo-config/ that
layers oh-my-opencode on top of the user's existing config. Symlinks
node_modules, package.json, bun.lock, and plugin config from the
original dir. Sets OPENCODE_CONFIG_DIR to the shadow directory.
Running plain `opencode` remains unaffected.
* Add Agent Integrations docs section with Claude Code Teams and oh-my-opencode pages
Adds sectioned sidebar navigation to the docs site. The new Agent
Integrations section contains separate pages for cmux claude-teams and
cmux omo, documenting usage, tmux shim mechanics, directory layout,
environment variables, and the shadow config approach. Both pages
include a nightly-only warning. Full English and Japanese translations,
nav item keys added to all 19 locales.
* Remove uppercase from sidebar section headers
* Add more spacing above and below sidebar section headers
* Enable tmux mode in oh-my-opencode config, improve docs
- cmux omo now writes tmux.enabled=true to the shadow oh-my-opencode.json
config. Without this, oh-my-openagent's TmuxSessionManager won't spawn
visual panes even though $TMUX is set (the config defaults to false).
- Nightly warnings now link to /nightly instead of generic text.
- Added "What you get" section to oh-my-opencode docs explaining the
visual pane behavior (auto-layout, idle cleanup, queueing).
- Added tmux.enabled step to first-run and how-it-works sections.
* Add terminal-notifier shim to route oh-my-openagent notifications to cmux
oh-my-openagent sends macOS notifications via terminal-notifier
(args: -title <t> -message <m> [-activate <id>]). The shim in
~/.cmuxterm/omo-bin/terminal-notifier intercepts these calls and
routes them through cmux notify, so notifications appear in cmux's
sidebar panel instead of as raw macOS notifications.
* Add pane geometry to tmux-compat for oh-my-openagent grid planning
oh-my-openagent's TmuxSessionManager needs pane geometry (columns,
rows, position, window dimensions) to decide where to spawn agent
panes. Without this data, agents run headlessly.
Server side:
- pane.list v2 response now includes pixel_frame, cell_size, columns,
rows per pane, plus container_frame at the top level
- Uses BonsplitController.layoutSnapshot() for pixel geometry and
ghostty_surface_size() for terminal grid dimensions
CLI side:
- tmuxEnrichContextWithGeometry() computes character-cell positions
from pixel frames and cell dimensions for tmux format variables
(pane_width, pane_height, pane_left, pane_top, pane_active,
window_width, window_height)
- list-panes now resolves pane targets (%uuid) via tmuxResolvePaneTarget
instead of failing with "Workspace not found"
- display-message enriched with geometry for format strings like
#{pane_width},#{window_width}
- tmux -V now returns "tmux 3.4" (needed by oh-my-openagent's
tmux-path-resolver verification)
* Add socket tests for tmux-compat pane geometry
6 tests verifying the geometry enrichment works end-to-end:
- pane.list returns pixel_frame, columns, rows, cell_size, container_frame
- tmux -V returns version string
- list-panes -F renders geometry format variables as integers
- list-panes -t %<uuid> resolves pane targets
- display -p renders pane_width and window_width
- After split, two panes have different positions and halved widths
All 6 pass on macmini (cmux-macmini).
* Handle tmux -V in shim script directly (no socket needed)
oh-my-openagent's tmux-path-resolver runs tmux -V to verify the binary
works. The __tmux-compat handler requires a socket connection, which
may not be established at verification time. Handle -V in the bash
shim directly to avoid the socket dependency.
* Lower default tmux pane min widths for cmux omo
oh-my-openagent defaults: main_pane_min_width=120, agent_pane_min_width=40,
requiring 161+ columns. Most terminal windows are narrower, causing
decideSpawnActions to return canSpawn=false and defer agents forever.
cmux omo now sets: main_pane_min_width=60, agent_pane_min_width=30,
main_pane_size=50, requiring only 91 columns. Also moved tmux -V
handling into the bash shim to avoid needing a socket connection for
the version check.
* Resolve merge conflicts with main (main-vertical layout, focus param)
- Keep upstream main-vertical layout anchoring from #2119
- Keep upstream focus param (v2Bool) instead of no_focus
- Combine with our -d flag handling: -d sets focus=false
- Include customCommands nav item from main
* Implement select-layout equalize and resize-pane absolute width
When oh-my-openagent spawns agent panes, it calls select-layout
main-vertical after each split to redistribute panes evenly, then
resize-pane -x <columns> to set the main pane width. Both were
previously no-ops, causing cascading uneven splits.
Server side:
- Add workspace.equalize_splits v2 API that calls the existing
TabManager.equalizeSplits (sets all dividers to 0.5)
CLI side:
- select-layout now calls workspace.equalize_splits before tracking
main-vertical state
- resize-pane -x <columns> without directional flags now computes
the pixel delta from current to desired width and resizes accordingly
* Fix equalize to use proportional divider positions
The previous equalize set all dividers to 0.5, which in a right-
recursive binary tree (from successive splits) gives 50/25/12.5/6.25%
instead of equal sizes.
New algorithm counts leaf panes on each side of each split and sets
the divider to N_left / (N_left + N_right). For 5 panes in a chain:
1/5, 1/4, 1/3, 1/2, giving each pane exactly 20%.
* Fix select-layout main-vertical to only equalize vertical splits
The proportional equalize was treating the top-level horizontal split
(main vs agent column) the same as vertical splits, setting the main
pane to 1/6 of the window with 5 agents.
For main-vertical layout, only equalize vertical splits (the agent
column), leaving the horizontal main/agent divider untouched. The
subsequent resize-pane -x handles the main pane width.
workspace.equalize_splits now accepts an optional orientation filter
("vertical" or "horizontal") to scope which splits get equalized.
* Re-equalize agent column after kill-pane
* Address PR review comments
- Fix cmux omo --help: remove omo from the help-bypass guard so
--help shows usage text instead of trying to launch opencode
- Don't overwrite unreadable opencode.json: fail with an error
instead of silently resetting to empty config
- Drain installer pipes concurrently before waitUntilExit to
prevent deadlock from full pipe buffers during bun/npm install
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Follow-up to #1875 which added Copilot CLI hook instructions to
docs/notifications.md but not the live docs site. Adds the section
to page.tsx with translation keys for all 19 locales.
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Add workspace creation config regression test
* Sanitize workspace creation config snapshot
* Remove duplicate WorkspaceCreationConfigSanitizationTests class
Main already has this test class; the branch's version was a duplicate
that would cause a compilation error after merge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: austinpower1258 <austinwang115@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(sidebar): make listening ports clickable to open in browser
Wrap each sidebar port in a Button that opens http://localhost:{port}
in the cmux built-in browser (or system browser as fallback), matching
the existing PR link click behavior.
Fixes#1602
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address bot review feedback on port clickability
- Localize port label text with String(localized:) instead of bare literal
- Add sidebar.port.label and sidebar.port.openTooltip keys to
Localizable.xcstrings with English and Japanese translations
- Respect openSidebarPullRequestLinksInCmuxBrowser user preference in
openPortLink, matching the openPullRequestLink pattern exactly
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: keyboard shortcuts not working with Russian and other non-Latin layouts
When a non-Latin input source (Russian, etc.) is active, event characters
are non-ASCII. The ANSI keyCode fallback was blocked when the layout-based
translation resolved a character, leaving no safety net. Now the keyCode
fallback is always available for non-Latin layouts, matching the physical
key position — similar to Ghostty's `physical:` keybinding behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add unit tests for Russian keyboard layout shortcut matching
Two tests for Cmd+T with non-Latin (Russian) keyboard layout:
1. Layout provider returns "t" (normal ASCII fallback) — verifies
the layout-based matching path works with Cyrillic event chars.
2. Layout provider returns nil (translation failure) — verifies the
ANSI keyCode fallback catches the shortcut by physical key position.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: scope ANSI keyCode fallback to non-ASCII events, add Russian to comments
Address review feedback:
- Split !hasUsableEventChars into two precise conditions:
(hasEventChars && !eventCharsAreASCII) for non-Latin layouts, and
(!hasEventChars && layoutCharacter empty) for synthetic/empty-char events.
This prevents unintended keyCode fallback on Dvorak/Colemak with empty
synthetic events.
- Add "Russian" to the non-Latin layout list in the guard comment at line 10626.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Allow naming a workspace at creation time instead of requiring a
separate rename-workspace call afterward.
Threads a title parameter through:
- CLI: --name <title> flag parsed and sent as "title" in v2 params
- V2 handler: extracts title, passes to TabManager.addWorkspace()
- TabManager: uses provided title instead of auto-generated
"Terminal N" and calls setCustomTitle() to persist it
- V1 handler: accepts optional name argument
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Previously, GHOSTTY_ACTION_PWD used AppDelegate.shared?.tabManager to
update the current working directory, which only returns the key window's
TabManager. In multi-window scenarios, cwd updates from non-key windows
were written to the wrong TabManager, causing panelDirectories to be
empty at save time and falling back to $HOME on restore.
Fix by using tabManagerFor(tabId:) to precisely route the update to
whichever window owns the tab, ensuring all windows persist their
working directories correctly.
Fixes#2125
The Korean CJK font mapping added in PR #1017 was removed in PR #1700,
but the koreanRanges static property was left behind as dead code.
Related: #1700, #1693
applicationShouldTerminate was returning .terminateNow unconditionally,
bypassing QuitWarningSettings. Now it shows the same confirmation alert
used by handleQuitShortcutWarning, with an isQuitWarningConfirmed flag
to prevent a double dialog when the Cmd+Q shortcut path already confirmed.
Fixes#2139
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix SEO indexing: add hreflang, canonicals, sitemap per-locale entries
Google Search Console showed 380 not-indexed vs 86 indexed pages.
Root causes: missing hreflang tags on rendered pages (only in sitemap),
no canonical on homepage, inconsistent canonicals wiping parent hreflang,
sitemap only listing English URLs, trailing slash duplicates, and
_next/static chunks being crawled as pages.
Changes:
- Add buildAlternates() utility for consistent canonical + hreflang
- Add hreflang tags to all pages via alternates.languages in metadata
- Add self-referencing canonical URLs to every page (homepage had none)
- Expand sitemap to emit separate entries for each locale
- Add missing /docs/custom-commands to sitemap
- Remove skipTrailingSlashRedirect to normalize trailing slashes
- Block /_next/ in robots.txt to stop chunk crawling
* Add per-page alternates to docs sub-pages and blog index
Docs sub-pages and blog index only returned title/description in
generateMetadata, so they inherited the parent layout's alternates
(pointing to /docs or /blog). Now each page sets its own
buildAlternates() with the correct path so canonical and hreflang
point to the actual page URL.
* Derive openGraph.url from buildAlternates to avoid drift
* Redirect non-English legal pages to English, remove from sitemap
Legal pages (privacy policy, TOS, EULA) are untranslated English content.
Serving them under every locale creates 54 duplicate URLs. Now:
- Middleware 301-redirects /ja/privacy-policy etc. to /privacy-policy
- Sitemap only includes English URLs for legal pages (no locale variants)
- Legal page metadata uses static English-only canonical
* Fix legal page redirect to only match /<locale>/<page> paths
endsWith matched too broadly (e.g. /docs/eula). Now only redirects
when the path after the first segment is an exact legal page match.
* Skip next-intl for legal pages to prevent locale redirect loop
Without this, a Japanese user hitting /privacy-policy could be
redirected by next-intl to /ja/privacy-policy, which our middleware
redirects back to /privacy-policy, creating a loop.
* Rewrite legal pages to /en/ instead of NextResponse.next()
Pages live under app/[locale]/, so skipping next-intl entirely
would break route resolution. Rewrite to /en/privacy-policy etc.
so Next.js can resolve the [locale] segment correctly.
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Fix claude-teams pane anchoring: main-vertical layout + focus
Claude's agent teams sends `split-window -h` for each teammate then
`select-layout main-vertical` to stack them vertically. Three fixes:
1. Implement select-layout main-vertical: track layout state in the
tmux compat store so subsequent horizontal splits of the leader
pane get redirected to vertical splits of the right-side column.
2. Pass focus:false to surface.split from the split-window handler
so internal bonsplit focus stays on the leader pane.
3. Fix tmuxCompatStoreURL to respect $HOME env var (was using
NSString.expandingTildeInPath which ignores $HOME).
Closes https://github.com/manaflow-ai/cmux/issues/2118
* Fix claude-teams split routing: auto-seed main-vertical on first right split
The split-window routing was broken in two ways:
1. After the first teammate split (right), the main-vertical state wasn't
being created, so all subsequent splits also went right instead of
stacking down in the right column.
2. The old code only redirected splits when main-vertical was already active
AND the target was the leader surface. Claude's teams protocol targets
arbitrary panes from list-panes, not necessarily the leader.
Now the first teammate split goes right (creating the column), auto-seeds
the main-vertical state, and all subsequent splits stack downward. The
caller's surface (CMUX_SURFACE_ID) is used as the anchor regardless of
which pane Claude targets.
Also adds caller surface preference in pane target resolution so the
caller's exact surface is used when the target pane matches, preventing
stale selected-surface references after tab switches.
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* test: reproduce Cmd+N snapshot workspace lifetime race
* fix: retain snapshot workspaces through Cmd+N creation
* fix: repair workspace lifetime regression test
* fix: extract workspace config through self to avoid Xcode 16.x ARC crash
The snapshot approach (c1998e34) navigated workspace → panel → surface
through local variables. Xcode 16.4's -O ARC optimizer aggressively
elides retains on these locals through inlined call chains, causing
use-after-free on every Cmd+N in CI-built nightlies.
Fix: extract preferredWorkingDirectory and inheritedTerminalFontPoints
through self (always retained) BEFORE capturing locals. The snapshot
is now purely value-typed with no Workspace references held in locals.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Dev builds should use the most permissionless socket mode so coding
agents and external tools can connect without any restrictions.
Changes automation → allowAll in the LSEnvironment plist entries and
the launch-time env vars.
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Fix update attempt refreshing pill without actually updating
The attemptUpdate() subscriber watched for .updateAvailable state to
auto-confirm, but showUpdateFound used setStateAfterMinimumCheckDelay
which delays the transition by up to 2 seconds. During that window,
dismissUpdateInstallation (from a background probe race) could cancel
the pending transition, reverting state to idle without ever confirming.
The subscriber then tore down on the transient idle, silently abandoning
the update.
Fix: move auto-confirm to the Sparkle driver level via an
autoInstallOnNextUpdate flag. When set, showUpdateFound immediately
calls reply(.install) bypassing the delay entirely. The subscriber
is kept as a fallback but no longer tears down on transient idle
while the flag is active.
Closes https://github.com/manaflow-ai/cmux/issues/2166
* Revert "Fix update attempt refreshing pill without actually updating"
This reverts commit 1cd842dd924bf114b096f222851c47d2e36ad4d9.
* Fix update attempt refreshing pill without actually updating
The attemptUpdate() subscriber tore down monitoring whenever it saw
.idle after observing progress. During check startup (retry loop,
background probe race), state can transiently return to .idle before
Sparkle's interactive check begins. The subscriber interpreted this
as "check completed" and stopped monitoring, so the auto-confirm
for .updateAvailable never fired.
Fix: add !state.isIdle to the teardown guard so monitoring only
stops on terminal failures (.notFound, .error), not transient idle.
Closes https://github.com/manaflow-ai/cmux/issues/2166
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* New window inherits size from current window
When creating a new window via Cmd+Shift+N, use the key window's
frame dimensions instead of the hardcoded 460x360 default. The new
window cascades from the existing window's position so it doesn't
stack directly on top.
* Use Ghostty's cascade algorithm for new window positioning
Match upstream Ghostty's window cascade logic: maintain a
lastCascadePoint that tracks where the next window should appear.
First window seeds the point from its own top-left corner, subsequent
windows advance the cascade point via NSWindow.cascadeTopLeft(from:).
On window close, reset the cascade point to the closing window's
position so the next window appears nearby.
New windows still inherit the key window's size so Cmd+Shift+N
creates a window matching the previous one's dimensions.
* Fix frame-to-contentRect conversion and use preferred window resolver
Convert existingFrame to a content rect via
NSWindow.contentRect(forFrameRect:styleMask:) so the new window
matches the source window's actual size instead of growing by
titlebar insets on each Cmd+Shift+N.
Use preferredMainWindowContextForWorkspaceCreation to resolve the
source window, consistent with showOpenFolderPanel and other
callers.
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
The confirm dialog showed a sanitized command (BiDi/zero-width stripped)
but executed the raw string, creating a display/consent mismatch.
Now the command is sanitized once and the same string is used for both
display and execution.
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Handle Cmd+O in handleCustomShortcut to prevent Documents folder open
Cmd+O for "Open Folder" was only handled in SwiftUI menu, which can
fail due to focus bugs when terminal is focused. This caused AppKit's
default NSDocumentController to open the Documents folder instead.
Now Cmd+O is intercepted in handleCustomShortcut like other shortcuts.
Fixes#2010
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix fallback directory loss and deduplicate Open Folder logic
Address review feedback:
1. Pass selected directory URL to fallback window creation so the
user's folder choice is not silently discarded
2. Replace inline NSOpenPanel code in cmuxApp.swift menu action
with a call to AppDelegate.showOpenFolderPanel() to avoid
future divergence between the two code paths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Set NSOpenPanel directoryURL to current terminal working directory
Address review feedback: set panel.directoryURL to the focused
terminal's working directory so Open Folder starts in a contextually
relevant location instead of AppKit's default.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Use shared main-window resolver and openWorkspaceForExternalDirectory in showOpenFolderPanel
Address review feedback: use preferredMainWindowContextForWorkspaceCreation
for directory seeding (works when auxiliary windows are key) and
openWorkspaceForExternalDirectory for workspace creation (ensures
shouldBringToFront and consistent fallback behavior).
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Add set-color/clear-color workspace actions for tab color via CLI
Expose the existing tab color functionality through the workspace-action
CLI command, enabling programmatic tab color setting without the GUI
context menu.
Supports both named colors (Red, Blue, Amber, etc.) and hex values
(#RRGGBB). Named colors resolve against the built-in palette via
case-insensitive matching.
Usage:
cmux workspace-action --action set-color --color blue
cmux workspace-action --action set-color --color "#C0392B"
cmux workspace-action set-color Amber
cmux workspace-action clear-color
* Return explicit null color in clear_color JSON response
Restore "color": null in the clear_color response payload so JSON
consumers can distinguish "color was cleared" from "no color field".
---------
Co-authored-by: Ariel Tobiana <arieltobiana@gmail.com>
The socket API's send-key command only supported enter, escape, tab,
backspace, and ctrl+letter. Arrow keys and shift+tab had to be sent
as raw escape sequences via send_text, but 0x1B is filtered out by
socketTextChunks and converted to a standalone Escape key event,
breaking multi-byte sequences like \x1b[A (ArrowUp).
This commit extends sendNamedKey to handle:
- Arrow keys: up/down/left/right (with arrow_up/arrowup aliases)
- shift+tab / shift-tab / backtab
- home, end
- delete / del / forward_delete
- pageup / page_up
- pagedown / page_down
These map directly to the existing sendKeyEvent infrastructure using
the appropriate kVK_* keycodes and modifier flags.
Co-authored-by: I Luk Kim <yirugi@gmail.com>
* Reduce shell integration prompt latency
Three changes to cut ~10-15ms from every precmd/preexec cycle:
1. Use zsh/net/unix (zsocket) for socket sends when available. Eliminates
fork+exec of ncat/socat/nc for every telemetry send (~3ms per send,
3-4 sends per prompt cycle). Falls back to external tools if the
module is unavailable.
2. Replace _cmux_kill_process_tree (synchronous /bin/ps -ax | awk) with
direct kill in _cmux_stop_pr_poll_loop. The tree-kill enumerated all
system processes on every command (~5-13ms). Orphaned children (gh,
sleep) finish on their own within seconds.
3. Minor savings: guard _cmux_patch_ghostty_semantic_redraw after first
success, make _cmux_clear_pr_for_panel async, cache bash send tool.
* Address review: process-group kill, fix clear_pr race, reorder bash init
1. Use kill -KILL -- -$PID (process-group kill) instead of plain kill.
Background jobs are process-group leaders, so this kills all
descendants (gh, sleep) without /bin/ps overhead.
2. Keep bash _cmux_clear_pr_for_panel synchronous to prevent race
with the next report_pr from the poll loop. Zsh version uses
_cmux_send_bg which is synchronous when zsocket is available.
3. Move _cmux_detect_send_tool after _cmux_fix_path in bash so the
cached tool lookup runs with the final PATH.
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Reject invalid color values at parse time with a clear error message
instead of silently ignoring them. Colors are normalized to #RRGGBB
via WorkspaceTabColorSettings.normalizedHex during decode.
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Pre-launch app for browser UI test on headless CI runners
XCUIApplication.launch() blocks ~60s then fails on headless WarpBuild
runners because foreground activation requires a GUI login session.
Apply the same pre-launch strategy used for the display resolution test:
- CI shell launches the app with env vars before running xcodebuild
- Test detects pre-launched app via manifest, uses activate() instead of
launch() to avoid killing and relaunching the app
- Falls back to clicking the window for focus via accessibility framework
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert "Pre-launch app for browser UI test on headless CI runners"
This reverts commit a540e2fd99aaa1395b91a8d50caa797cdd7551b8.
* feat: cmux.json for custom commands
* tests: add cmux json tests
* fix: pr review feedback: validation, translations, input handling, and palette improvements
- Fix Danish ("Overfladedef inition") and Norwegian ("rotmapp") translation typos
- Add empty-string check for baseCwd fallback in command palette handlers
- Coalesce \r\n into single Return keypress in sendInput
- Redact command text from timeout log to prevent secret leakage
- Add decode-time validation: reject hybrid/empty commands, ambiguous layout
nodes, wrong split children count, and empty pane surfaces
- Namespace custom command IDs with "cmux.config.command." prefix
- Forward command description to palette subtitle when available
- Update tests for new validation rules and ID prefix
* fix: address PR review feedback — per-window config isolation, blank validation, ancestor walk,
palette sanitization
* fix: fallback to current dir cmux.json watching if no any cmux.json found in full acesor walk
* ci: trigger CI for fork PR
* Add directory trust for cmux.json command confirmation
The confirm dialog now shows the actual command text and has an "Always
trust commands from this folder" checkbox. When checked, future confirm
commands from that directory skip the dialog.
Trust is scoped to the git repo root if the cmux.json is inside a repo,
so trusting once covers all subdirectories. Non-git directories are
trusted by exact path. Global config is always trusted.
Trusted directories are persisted in ~/Library/Application Support/cmux/
trusted-directories.json.
* Add trusted directories section to Settings
Shows all trusted directories with per-directory revoke buttons and a
Clear All option. Placed in a "Custom Commands" section between
Automation and Browser in Settings.
* Replace trusted directories list with editable textarea
One path per line, with a Save button that activates on changes.
Users can add, remove, or edit paths directly.
* Auto-save trusted directories on edit, remove Save button
Matches the behavior of other textarea settings (browser host
whitelist, external URL patterns) which auto-save via @AppStorage.
* Sanitize command text in confirm dialog against BiDi attacks
Strip zero-width and BiDi override characters from the command preview
so the dialog shows exactly what will be executed.
---------
Co-authored-by: austinpower1258 <austinwang115@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>