# cmux

A macOS terminal app with vertical tabs, using libghostty (GhosttyKit.xcframework) for terminal emulation.

## User Preferences

- **Always use Release builds** when building and launching for testing
- Build libghostty with `-Doptimize=ReleaseFast` for performance

## Development

### Build and launch (Release)
```bash
cd /Users/lawrencechen/fun/cmux-terminal/GhosttyTabs
pkill -9 cmux 2>/dev/null
xcodebuild -scheme cmux -configuration Release build
open ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-cbjivvtpirygxbbgqlpdpiiyjnwh/Build/Products/Release/cmux.app
```

### Rebuild libghostty (optimized)
```bash
cd /tmp/ghostty
zig build -Demit-xcframework=true -Dxcframework-target=native -Doptimize=ReleaseFast
cp -R /tmp/ghostty/macos/GhosttyKit.xcframework /Users/lawrencechen/fun/cmux-terminal/GhosttyTabs/
```

### Project structure
- `Sources/` - Swift source files
  - `cmuxApp.swift` - App entry point with keyboard shortcuts
  - `ContentView.swift` - Main UI with vertical tabs sidebar
  - `TabManager.swift` - Tab state management
  - `GhosttyTerminalView.swift` - libghostty terminal integration
  - `GhosttyConfig.swift` - Ghostty config parser
  - `TerminalController.swift` - Unix socket server for programmatic control
- `tests/` - Test files and utilities
  - `cmux.py` - Python client library for socket API
  - `test_ctrl_socket.py` - Main automated test suite
- `GhosttyKit.xcframework/` - libghostty static library (gitignored, rebuild from /tmp/ghostty)
- `ghostty.h` - Ghostty C API header
- `cmux-Bridging-Header.h` - Swift bridging header

### Keyboard Shortcuts
- `Cmd+T` / `Cmd+N` / `Ctrl+Shift+`` - New tab
- `Cmd+W` - Close tab
- `Cmd+Shift+]` / `Ctrl+Tab` - Next tab
- `Cmd+Shift+[` / `Ctrl+Shift+Tab` - Previous tab
- `Cmd+1-9` - Jump to tab by number

### Config
Reads user's Ghostty config from:
`~/Library/Application Support/com.mitchellh.ghostty/config`

## Testing

### Unix Socket Control API

cmux exposes a Unix socket at `/tmp/cmux.sock` for programmatic control and automated testing. The socket is created when the app launches.

#### Socket Commands

Text-based protocol with newline-delimited commands:

| Command | Description | Response |
|---------|-------------|----------|
| `ping` | Check if server is running | `PONG` |
| `list_tabs` | List all tabs | `* 0: <UUID> <title>` (per line) |
| `new_tab` | Create a new tab | `OK <UUID>` |
| `close_tab <id>` | Close tab by UUID | `OK` or `ERROR: ...` |
| `select_tab <id\|index>` | Select tab by UUID or index | `OK` or `ERROR: ...` |
| `current_tab` | Get current tab UUID | `<UUID>` |
| `send <text>` | Send text to terminal | `OK` |
| `send_key <key>` | Send special key | `OK` |
| `help` | Show available commands | Help text |

#### Special Keys for `send_key`

- `ctrl-c`, `ctrl-d`, `ctrl-z`, `ctrl-\` - Control signals
- `enter`, `tab`, `escape`, `backspace` - Common keys
- `ctrl-<letter>` - Any control+letter combination

#### Text Escaping for `send`

Use `\n` for Enter (carriage return), `\t` for tab, `\r` for raw CR.

### Python Client Library

Located at `tests/cmux.py`:

```python
from cmux import cmux

with cmux() as client:
    # Send text with Enter
    client.send("echo hello\n")

    # Send special keys
    client.send_ctrl_c()  # Interrupt
    client.send_ctrl_d()  # EOF
    client.send_key("enter")

    # Tab management
    tabs = client.list_tabs()
    client.new_tab()
    client.select_tab(0)
```

### Running Tests

```bash
# Build and launch the app first
pkill -9 cmux 2>/dev/null
xcodebuild -scheme cmux -configuration Release build
open ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-cbjivvtpirygxbbgqlpdpiiyjnwh/Build/Products/Release/cmux.app
sleep 3

# Run the main test suite (tests Ctrl+C, Ctrl+D)
python3 tests/test_ctrl_socket.py

# Interactive CLI for manual testing
python3 tests/cmux.py
```

### Writing New Tests

1. **Use marker files for verification** - Create temp files to verify commands executed:
   ```python
   marker = Path(tempfile.gettempdir()) / f"test_marker_{os.getpid()}"
   client.send(f"touch {marker}\n")
   time.sleep(0.5)
   assert marker.exists()
   ```

2. **Allow settling time** - Terminal commands need time to execute:
   ```python
   client.send("sleep 5\n")
   time.sleep(0.3)  # Wait for command to start
   client.send_ctrl_c()
   time.sleep(0.3)  # Wait for interrupt
   ```

3. **Clean up marker files** - Always remove test artifacts:
   ```python
   try:
       # test code
   finally:
       marker.unlink(missing_ok=True)
   ```

### Test Files

- `tests/cmux.py` - Python client library for socket API
- `tests/test_ctrl_socket.py` - Automated Ctrl+C/D test suite (main tests)
- `tests/test_signals_auto.py` - PTY-based signal tests (standalone)
- `tests/test_ctrl_interactive.py` - Interactive manual tests
- `tests/test_ctrl_signals.sh` - Simple bash signal test
- `tests/test_app_keystrokes.sh` - AppleScript keystroke tests (deprecated)
