Add documentation site and Homebrew installation

- Add Fumadocs-based docs site (docs-site/)
- Document all features: tabs, notifications, splits, socket API, CLI
- Add Claude Code hooks guide with cmuxterm detection
- Update README with native macOS emphasis and brew install
- Add homebrew-cmuxterm to .gitignore (has its own repo)
This commit is contained in:
Lawrence Chen 2026-01-29 04:41:39 -08:00
parent 70e00b5b00
commit f36525f5f1
31 changed files with 8262 additions and 2 deletions

2
.gitignore vendored
View file

@ -16,6 +16,7 @@ xcuserdata/
# GhosttyKit binary (rebuild from /tmp/ghostty with zig build)
GhosttyKit.xcframework/
GhosttyKit.xcframework.bak-*/
# Release artifacts
GhosttyTabs-*.zip
@ -25,3 +26,4 @@ __pycache__/
*.pyc
*.pyo
.pytest_cache/
homebrew-cmuxterm/

View file

@ -1,5 +1,5 @@
<h1 align="center">cmuxterm</h1>
<p align="center">A Ghostty-based terminal with vertical tabs and a notification panel for macOS</p>
<p align="center">A lightweight native macOS terminal with vertical tabs and notifications for AI coding agents</p>
<p align="center">
<a href="https://github.com/manaflow-ai/cmuxterm/releases/latest/download/cmuxterm-macos.dmg">
@ -9,10 +9,25 @@
## Features
- **Native macOS app** — Built with Swift and AppKit, not Electron. Fast startup, low memory.
- **Vertical tabs** — See all your terminals at a glance in a sidebar
- **Notification panel** — Tabs flash when AI agents (Claude Code, Codex) need your attention
- **Built on libghostty** — Native macOS performance with Ghostty's GPU-accelerated rendering
- **Lightweight** — Small binary, minimal resource footprint. No bundled browser engine.
- **GPU-accelerated** — Powered by libghostty for smooth rendering
## Install
**Homebrew:**
```bash
brew tap manaflow-ai/cmuxterm
brew install --cask cmuxterm
```
Or [download the DMG](https://github.com/manaflow-ai/cmuxterm/releases/latest/download/cmuxterm-macos.dmg) directly.
## Why cmuxterm?
Running multiple AI coding agents? cmuxterm helps you manage them. Instead of losing track of which terminal needs input, the notification panel shows you exactly where to look.
A native macOS app means it launches instantly, uses minimal RAM, and feels right at home on your Mac.

35
docs-site/.gitignore vendored Normal file
View file

@ -0,0 +1,35 @@
# dependencies
node_modules
.pnpm-store
# next.js
.next/
out/
# production
build
dist
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
.env
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# fumadocs
.source/

58
docs-site/README.md Normal file
View file

@ -0,0 +1,58 @@
# cmuxterm Documentation
Documentation website for [cmuxterm](https://github.com/manaflow-ai/cmuxterm), built with [Fumadocs](https://fumadocs.vercel.app) and Next.js.
## Development
```bash
# Install dependencies
npm install
# Start dev server
npm run dev
```
Open [http://localhost:3000](http://localhost:3000) to view the docs.
## Deployment
This site is deployed to Vercel. Push to main to trigger a deployment.
### Manual Deploy
```bash
npm run build
npx vercel --prod
```
## Structure
```
docs-site/
├── app/ # Next.js app router
│ ├── docs/ # Documentation pages
│ └── page.tsx # Landing page
├── content/
│ └── docs/ # MDX documentation files
└── lib/
└── source.ts # Fumadocs source configuration
```
## Adding Documentation
1. Create a new `.mdx` file in `content/docs/`
2. Add frontmatter with title and description
3. Add the page to `content/docs/meta.json`
Example:
```mdx
---
title: My Page
description: Description of my page
---
# My Page
Content here...
```

View file

@ -0,0 +1,46 @@
import { source } from '@/lib/source';
import {
DocsPage,
DocsBody,
DocsDescription,
DocsTitle,
} from 'fumadocs-ui/page';
import { notFound } from 'next/navigation';
import defaultMdxComponents from 'fumadocs-ui/mdx';
export default async function Page(props: {
params: Promise<{ slug?: string[] }>;
}) {
const params = await props.params;
const page = source.getPage(params.slug);
if (!page) notFound();
const MDX = page.data.body;
return (
<DocsPage toc={page.data.toc} full={page.data.full}>
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription>{page.data.description}</DocsDescription>
<DocsBody>
<MDX components={{ ...defaultMdxComponents }} />
</DocsBody>
</DocsPage>
);
}
export async function generateStaticParams() {
return source.generateParams();
}
export async function generateMetadata(props: {
params: Promise<{ slug?: string[] }>;
}) {
const params = await props.params;
const page = source.getPage(params.slug);
if (!page) notFound();
return {
title: page.data.title,
description: page.data.description,
};
}

View file

@ -0,0 +1,20 @@
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import type { ReactNode } from 'react';
import { source } from '@/lib/source';
export default function Layout({ children }: { children: ReactNode }) {
return (
<DocsLayout
tree={source.pageTree}
nav={{
title: 'cmuxterm',
url: '/',
}}
sidebar={{
defaultOpenLevel: 1,
}}
>
{children}
</DocsLayout>
);
}

11
docs-site/app/global.css Normal file
View file

@ -0,0 +1,11 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--fd-background-card: theme(colors.zinc.50);
}
.dark {
--fd-background-card: theme(colors.zinc.900);
}

23
docs-site/app/layout.tsx Normal file
View file

@ -0,0 +1,23 @@
import './global.css';
import { RootProvider } from 'fumadocs-ui/provider';
import { Inter } from 'next/font/google';
import type { ReactNode } from 'react';
const inter = Inter({
subsets: ['latin'],
});
export const metadata = {
title: 'cmuxterm Documentation',
description: 'Documentation for cmuxterm - A Ghostty-based terminal for AI agents',
};
export default function Layout({ children }: { children: ReactNode }) {
return (
<html lang="en" className={inter.className} suppressHydrationWarning>
<body className="flex flex-col min-h-screen">
<RootProvider>{children}</RootProvider>
</body>
</html>
);
}

View file

@ -0,0 +1,266 @@
---
title: Claude Code Hooks
description: Set up notifications for Claude Code using cmuxterm hooks
---
# Claude Code Hooks
cmuxterm integrates with [Claude Code](https://docs.anthropic.com/en/docs/claude-code) via hooks to notify you when tasks complete or when Claude needs attention.
## Detecting cmuxterm
Before sending notifications, you should detect if you're running inside cmuxterm to avoid conflicts with other terminals.
### Detection Methods
**1. Check for the socket:**
```bash
[ -S /tmp/cmuxterm.sock ] && echo "In cmuxterm"
```
**2. Check for the CLI:**
```bash
command -v cmuxterm &>/dev/null && echo "cmuxterm available"
```
**3. Check the TERM_PROGRAM environment variable:**
```bash
[ "$TERM_PROGRAM" = "ghostty" ] && [ -S /tmp/cmuxterm.sock ] && echo "In cmuxterm"
```
<Callout type="info">
cmuxterm sets `TERM_PROGRAM=ghostty` since it's built on Ghostty. Use the socket check to distinguish from regular Ghostty.
</Callout>
## Setting Up Hooks
Claude Code supports hooks that run on specific events. Add these to your `~/.claude/settings.json`:
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Task",
"hooks": [
"/path/to/cmuxterm-notify.sh"
]
}
],
"Stop": [
"/path/to/cmuxterm-notify.sh"
]
}
}
```
## Notification Hook Script
Create a script that checks for cmuxterm and sends notifications:
```bash
#!/bin/bash
# ~/.claude/hooks/cmuxterm-notify.sh
# Only proceed if we're in cmuxterm
if [ ! -S /tmp/cmuxterm.sock ]; then
exit 0
fi
# Parse the hook event from stdin (Claude Code passes JSON)
EVENT=$(cat)
# Extract event type and details
EVENT_TYPE=$(echo "$EVENT" | jq -r '.event // "unknown"')
TOOL_NAME=$(echo "$EVENT" | jq -r '.tool_name // ""')
SESSION=$(echo "$EVENT" | jq -r '.session_id // ""' | cut -c1-8)
case "$EVENT_TYPE" in
"Stop")
cmuxterm notify \
--title "Claude Code" \
--subtitle "Task Complete" \
--body "Session $SESSION finished"
;;
"PostToolUse")
if [ "$TOOL_NAME" = "Task" ]; then
cmuxterm notify \
--title "Claude Code" \
--subtitle "Agent Finished" \
--body "Task agent completed in session $SESSION"
fi
;;
esac
```
Make it executable:
```bash
chmod +x ~/.claude/hooks/cmuxterm-notify.sh
```
## Example Configurations
### Notify on All Completions
Get notified whenever Claude Code finishes a task:
```json
{
"hooks": {
"Stop": [
"~/.claude/hooks/cmuxterm-notify.sh"
]
}
}
```
### Notify on Long-Running Tasks
Only notify for Task tool completions (agent subprocesses):
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Task",
"hooks": ["~/.claude/hooks/cmuxterm-notify.sh"]
}
]
}
}
```
### Notify on Errors
Add error notifications:
```bash
#!/bin/bash
# cmuxterm-notify.sh with error handling
if [ ! -S /tmp/cmuxterm.sock ]; then
exit 0
fi
EVENT=$(cat)
EVENT_TYPE=$(echo "$EVENT" | jq -r '.event // "unknown"')
ERROR=$(echo "$EVENT" | jq -r '.error // ""')
if [ -n "$ERROR" ] && [ "$ERROR" != "null" ]; then
cmuxterm notify \
--title "Claude Code Error" \
--body "$ERROR"
elif [ "$EVENT_TYPE" = "Stop" ]; then
cmuxterm notify \
--title "Claude Code" \
--body "Task complete"
fi
```
## Advanced: Conditional Notifications
Only notify if the terminal is not focused:
```bash
#!/bin/bash
# cmuxterm-notify.sh with focus detection
if [ ! -S /tmp/cmuxterm.sock ]; then
exit 0
fi
# cmuxterm automatically suppresses notifications for focused tabs,
# so we can always send - it will handle suppression for us
EVENT=$(cat)
EVENT_TYPE=$(echo "$EVENT" | jq -r '.event // "unknown"')
if [ "$EVENT_TYPE" = "Stop" ]; then
cmuxterm notify \
--title "Claude Code" \
--subtitle "Ready" \
--body "Waiting for your input"
fi
```
## Using OSC Sequences Directly
If you prefer not to use the CLI, you can emit OSC sequences directly:
```bash
#!/bin/bash
# Direct OSC notification (no CLI needed)
if [ ! -S /tmp/cmuxterm.sock ]; then
exit 0
fi
EVENT=$(cat)
EVENT_TYPE=$(echo "$EVENT" | jq -r '.event // "unknown"')
if [ "$EVENT_TYPE" = "Stop" ]; then
# OSC 777 notification
printf '\e]777;notify;Claude Code;Task complete\a'
fi
```
## Full Example Setup
1. Create the hooks directory:
```bash
mkdir -p ~/.claude/hooks
```
2. Create the notification script:
```bash
cat > ~/.claude/hooks/cmuxterm-notify.sh << 'EOF'
#!/bin/bash
# cmuxterm notification hook for Claude Code
# Skip if not in cmuxterm
[ -S /tmp/cmuxterm.sock ] || exit 0
EVENT=$(cat)
EVENT_TYPE=$(echo "$EVENT" | jq -r '.event // "unknown"')
TOOL=$(echo "$EVENT" | jq -r '.tool_name // ""')
case "$EVENT_TYPE" in
"Stop")
cmuxterm notify --title "Claude Code" --body "Session complete"
;;
"PostToolUse")
[ "$TOOL" = "Task" ] && cmuxterm notify --title "Claude Code" --body "Agent finished"
;;
esac
EOF
chmod +x ~/.claude/hooks/cmuxterm-notify.sh
```
3. Configure Claude Code:
```bash
cat > ~/.claude/settings.json << 'EOF'
{
"hooks": {
"Stop": ["~/.claude/hooks/cmuxterm-notify.sh"],
"PostToolUse": [
{
"matcher": "Task",
"hooks": ["~/.claude/hooks/cmuxterm-notify.sh"]
}
]
}
}
EOF
```
4. Restart Claude Code to apply the hooks.
Now you'll receive desktop notifications in cmuxterm whenever Claude Code finishes a task or needs attention.

View file

@ -0,0 +1,222 @@
---
title: CLI Reference
description: Command-line interface for cmuxterm
---
# CLI Reference
cmuxterm includes a command-line tool for controlling the terminal from scripts and other tools.
## Installation
The CLI is bundled with cmuxterm. Inside cmuxterm terminals, it's available automatically. For external use:
```bash
# Create symlink to CLI
sudo ln -sf "/Applications/cmuxterm.app/Contents/MacOS/cmuxterm" /usr/local/bin/cmuxterm
```
## Global Options
| Option | Description |
|--------|-------------|
| `--socket PATH` | Use a custom socket path |
| `--json` | Output in JSON format |
| `--tab ID` | Target a specific tab |
| `--panel ID` | Target a specific panel |
## Environment Variables
| Variable | Description |
|----------|-------------|
| `CMUX_SOCKET_PATH` | Default socket path |
| `CMUX_TAB_ID` | Default tab ID |
| `CMUX_PANEL_ID` | Default panel ID |
## Commands
### Tab Management
#### list-tabs
List all open tabs.
```bash
cmuxterm list-tabs
cmuxterm list-tabs --json
```
#### new-tab
Create a new tab.
```bash
cmuxterm new-tab
```
#### select-tab
Switch to a specific tab.
```bash
cmuxterm select-tab --tab <tab-id>
```
#### current-tab
Get the current tab info.
```bash
cmuxterm current-tab
cmuxterm current-tab --json
```
#### close-tab
Close a tab.
```bash
cmuxterm close-tab --tab <tab-id>
```
### Split Management
#### new-split
Create a new split pane.
```bash
cmuxterm new-split right
cmuxterm new-split down
cmuxterm new-split left
cmuxterm new-split up
```
#### list-panels
List panels in the current tab.
```bash
cmuxterm list-panels
cmuxterm list-panels --json
```
#### focus-panel
Focus a specific panel.
```bash
cmuxterm focus-panel --panel <panel-id>
```
### Input Commands
#### send
Send text to the terminal.
```bash
cmuxterm send "echo hello"
cmuxterm send "ls -la\n" # Include newline to execute
```
#### send-key
Send a key press.
```bash
cmuxterm send-key enter
cmuxterm send-key tab
cmuxterm send-key escape
```
#### send-panel
Send text to a specific panel.
```bash
cmuxterm send-panel --panel <panel-id> "command"
```
#### send-key-panel
Send a key press to a specific panel.
```bash
cmuxterm send-key-panel --panel <panel-id> enter
```
### Notifications
#### notify
Send a notification.
```bash
cmuxterm notify --title "Title" --body "Message body"
cmuxterm notify --title "Title" --subtitle "Subtitle" --body "Body"
```
#### list-notifications
List all notifications.
```bash
cmuxterm list-notifications
cmuxterm list-notifications --json
```
#### clear-notifications
Clear all notifications.
```bash
cmuxterm clear-notifications
```
### Utility
#### ping
Check if cmuxterm is running and responsive.
```bash
cmuxterm ping
```
## Examples
### Build Script with Notification
```bash
#!/bin/bash
npm run build
if [ $? -eq 0 ]; then
cmuxterm notify --title "✓ Build Success" --body "Ready to deploy"
else
cmuxterm notify --title "✗ Build Failed" --body "Check the logs"
fi
```
### Create Tab and Run Command
```bash
#!/bin/bash
# Create a new tab
result=$(cmuxterm new-tab --json)
tab_id=$(echo "$result" | jq -r '.id')
# Select the new tab
cmuxterm select-tab --tab "$tab_id"
# Send a command
cmuxterm send "npm run dev\n"
```
### Monitor Multiple Tabs
```bash
#!/bin/bash
# List all tabs and their directories
cmuxterm list-tabs --json | jq -r '.tabs[] | "\(.title): \(.directory)"'
```

View file

@ -0,0 +1,121 @@
---
title: Configuration
description: Configure cmuxterm appearance and behavior
---
# Configuration
cmuxterm reads configuration from Ghostty config files, giving you familiar options if you're coming from Ghostty.
## Config File Locations
cmuxterm looks for configuration in these locations (in order):
1. `~/.config/ghostty/config`
2. `~/Library/Application Support/com.mitchellh.ghostty/config`
Create the config file if it doesn't exist:
```bash
mkdir -p ~/.config/ghostty
touch ~/.config/ghostty/config
```
## Appearance Options
### Font
```ini
font-family = JetBrains Mono
font-size = 14
```
### Colors
```ini
# Theme (or use individual colors below)
theme = Dracula
# Custom colors
background = #1e1e2e
foreground = #cdd6f4
cursor-color = #f5e0dc
cursor-text = #1e1e2e
selection-background = #585b70
selection-foreground = #cdd6f4
```
### Split Panes
```ini
# Opacity for unfocused splits (0.0 to 1.0)
unfocused-split-opacity = 0.7
# Fill color for unfocused splits
unfocused-split-fill = #1e1e2e
# Divider color between splits
split-divider-color = #45475a
```
## Behavior Options
### Scrollback
```ini
# Number of lines to keep in scrollback buffer
scrollback-limit = 10000
```
### Working Directory
```ini
# Default directory for new terminals
working-directory = ~/Projects
```
## App Settings
In-app settings are available via **cmuxterm → Settings** (⌘,):
### Theme Mode
Choose between:
- **System** - Follow macOS appearance
- **Light** - Always light mode
- **Dark** - Always dark mode
### Automation Mode
Control socket access level:
- **Off** - No socket control (most secure)
- **Notifications only** - Only allow notification commands
- **Full control** - Allow all socket commands
<Callout type="warn">
On shared machines, consider using "Notifications only" mode to prevent other processes from controlling your terminals.
</Callout>
## Example Config
Here's a complete example configuration:
```ini
# Font
font-family = SF Mono
font-size = 13
# Colors
theme = One Dark
# Scrollback
scrollback-limit = 50000
# Splits
unfocused-split-opacity = 0.85
split-divider-color = #3e4451
# Working directory
working-directory = ~/code
```

View file

@ -0,0 +1,179 @@
---
title: Environment Variables
description: Environment variables for configuring cmuxterm
---
# Environment Variables
cmuxterm uses environment variables for configuration and integration.
## Socket Control
### CMUX_SOCKET_PATH
Override the default socket path.
```bash
export CMUX_SOCKET_PATH=/custom/path/cmuxterm.sock
```
Default: `/tmp/cmuxterm.sock` (release) or `/tmp/cmuxterm-debug.sock` (debug)
### CMUX_SOCKET_ENABLE
Enable or disable the control socket.
```bash
export CMUX_SOCKET_ENABLE=1 # Enable
export CMUX_SOCKET_ENABLE=0 # Disable
```
Values: `1`, `0`, `true`, `false`, `yes`, `no`
### CMUX_SOCKET_MODE
Override the socket access mode.
```bash
export CMUX_SOCKET_MODE=notifications # Notifications only
export CMUX_SOCKET_MODE=full # Full control
export CMUX_SOCKET_MODE=off # Disabled
```
## CLI Context
### CMUX_TAB_ID
Default tab ID for CLI commands.
```bash
export CMUX_TAB_ID=abc123
cmuxterm send "hello" # Sends to tab abc123
```
cmuxterm automatically sets this in each terminal session.
### CMUX_PANEL_ID
Default panel ID for CLI commands.
```bash
export CMUX_PANEL_ID=xyz789
cmuxterm send-panel "hello" # Sends to panel xyz789
```
cmuxterm automatically sets this for each split pane.
## Terminal Environment
cmuxterm sets these variables in terminal sessions:
### TERM
Terminal type for compatibility.
```bash
TERM=xterm-ghostty
```
### TERM_PROGRAM
Identifies the terminal application.
```bash
TERM_PROGRAM=ghostty
```
<Callout type="info">
cmuxterm sets this to `ghostty` since it's built on Ghostty. Use the socket check to distinguish from regular Ghostty.
</Callout>
### GHOSTTY_RESOURCES_DIR
Path to Ghostty resources.
```bash
GHOSTTY_RESOURCES_DIR=/path/to/resources
```
## Testing & Debug
### CMUX_UI_TEST_SHOW_SETTINGS
Show the settings window on app launch (for UI testing).
```bash
export CMUX_UI_TEST_SHOW_SETTINGS=1
```
### CMUX_UI_TEST_TRIGGER_UPDATE_CHECK
Trigger an update check on app launch (for testing).
```bash
export CMUX_UI_TEST_TRIGGER_UPDATE_CHECK=1
```
### CMUX_COMMIT
Override the commit hash displayed in the About window.
```bash
export CMUX_COMMIT=abc1234
```
## Detection Script
Check if running inside cmuxterm:
```bash
#!/bin/bash
is_cmuxterm() {
# Check for cmuxterm socket
if [ -S "${CMUX_SOCKET_PATH:-/tmp/cmuxterm.sock}" ]; then
return 0
fi
return 1
}
if is_cmuxterm; then
echo "Running in cmuxterm"
echo "Tab ID: ${CMUX_TAB_ID:-unknown}"
echo "Panel ID: ${CMUX_PANEL_ID:-unknown}"
else
echo "Not in cmuxterm"
fi
```
## Shell Configuration
Add to your `~/.bashrc` or `~/.zshrc`:
```bash
# cmuxterm integration
if [ -S "${CMUX_SOCKET_PATH:-/tmp/cmuxterm.sock}" ]; then
# We're in cmuxterm
# Function to notify on long commands
notify_done() {
"$@"
local exit_code=$?
cmuxterm notify --title "Command Complete" --body "$1"
return $exit_code
}
# Alias for builds
alias build='notify_done npm run build'
fi
```
## Precedence
Environment variables override app settings:
1. Environment variable (if set)
2. App settings (Settings window)
3. Default value
For example, if `CMUX_SOCKET_MODE=full` is set, it overrides the app's Automation Mode setting.

View file

@ -0,0 +1,57 @@
---
title: Introduction
description: cmuxterm - A lightweight native macOS terminal with vertical tabs and notifications for AI coding agents
---
# cmuxterm
cmuxterm is a lightweight, native macOS terminal built on Ghostty for managing multiple AI coding agents. It features vertical tabs, a notification panel, and a socket-based control API.
## Why cmuxterm?
- **Native macOS app** - Built with Swift and AppKit, not Electron. Fast startup, low memory usage, and native look and feel.
- **Lightweight** - Small binary, minimal resource footprint. No bundled browser engine.
- **GPU-accelerated** - Powered by libghostty for buttery smooth rendering.
When running AI coding agents like Claude Code, Codex, or similar tools, you need to:
- **Manage multiple terminal sessions** - Each agent runs in its own terminal
- **Know when agents need attention** - Agents waiting for input should notify you
- **Automate terminal control** - Programmatically create tabs, send input, and more
cmuxterm solves these with:
- **Vertical sidebar tabs** - All terminals visible at a glance
- **OSC 99/777 notifications** - Desktop alerts when agents need you
- **Unix socket API** - Full programmatic control for automation
## Quick Start
```bash
brew tap manaflow-ai/cmuxterm
brew install --cask cmuxterm
```
Or [download the DMG](https://github.com/manaflow-ai/cmuxterm/releases/latest/download/cmuxterm-macos.dmg) directly.
## Key Features
<Cards>
<Card title="Vertical Tabs" href="/tabs">
All terminals in a resizable sidebar with keyboard navigation
</Card>
<Card title="Notifications" href="/notifications">
Desktop alerts via OSC 99/777 sequences when agents need attention
</Card>
<Card title="Socket API" href="/socket-api">
Unix socket for programmatic control - create tabs, send input, and more
</Card>
<Card title="Claude Code Hooks" href="/claude-code-hooks">
Send notifications from Claude Code when tasks complete
</Card>
</Cards>
## Requirements
- macOS 13.0 or later
- Apple Silicon or Intel Mac

View file

@ -0,0 +1,70 @@
---
title: Installation
description: How to install cmuxterm on macOS
---
# Installation
## Homebrew (Recommended)
Install via Homebrew:
```bash
brew tap manaflow-ai/cmuxterm
brew install --cask cmuxterm
```
To update later:
```bash
brew upgrade --cask cmuxterm
```
## Manual Download
Download the latest release from GitHub:
<a href="https://github.com/manaflow-ai/cmuxterm/releases/latest/download/cmuxterm-macos.dmg">
<img src="/macos-badge.png" alt="Download cmuxterm for macOS" width="180" />
</a>
Then:
1. Open the downloaded `.dmg` file
2. Drag **cmuxterm** to your **Applications** folder
3. Open cmuxterm from Applications
<Callout type="info">
On first launch, macOS may ask you to confirm opening an app from an identified developer. Click **Open** to proceed.
</Callout>
## Verify Installation
Open cmuxterm and you should see:
- A terminal window with a vertical tab sidebar on the left
- One initial tab already open
- The Ghostty-powered terminal ready for input
## CLI Tool
cmuxterm includes a command-line tool for automation. It's bundled inside the app and works automatically when you run commands inside cmuxterm.
To use the CLI from outside cmuxterm, you can create a symlink:
```bash
sudo ln -sf "/Applications/cmuxterm.app/Contents/MacOS/cmuxterm" /usr/local/bin/cmuxterm
```
Then you can run commands like:
```bash
cmuxterm list-tabs
cmuxterm notify --title "Build Complete" --body "Your build finished successfully"
```
## Updates
cmuxterm checks for updates automatically using Sparkle. When an update is available, you'll see an update pill in the titlebar. Click it to install the latest version.
You can also check for updates manually via **cmuxterm → Check for Updates** in the menu bar.

View file

@ -0,0 +1,74 @@
---
title: Keyboard Shortcuts
description: Complete list of cmuxterm keyboard shortcuts
---
# Keyboard Shortcuts
## Tab Management
| Shortcut | Action |
|----------|--------|
| **⌘T** | New tab |
| **⌘N** | New tab |
| **⌘W** | Close pane (or tab if single pane) |
| **⌘⇧W** | Close tab |
| **⌘B** | Toggle sidebar |
## Tab Navigation
| Shortcut | Action |
|----------|--------|
| **⌘1** - **⌘9** | Jump to tab 1-9 |
| **⌘]** | Next tab |
| **⌘[** | Previous tab |
| **⌃Tab** | Next tab |
| **⌃⇧Tab** | Previous tab |
## Notifications
| Shortcut | Action |
|----------|--------|
| **⌘⇧U** | Jump to latest unread notification |
| **⌘⇧I** | Show notifications panel |
## Split Panes
| Shortcut | Action |
|----------|--------|
| **⌘D** | Split right |
| **⌘⇧D** | Split down |
| **⌘⌥←** | Focus left pane |
| **⌘⌥→** | Focus right pane |
| **⌘⌥↑** | Focus pane above |
| **⌘⌥↓** | Focus pane below |
## Window
| Shortcut | Action |
|----------|--------|
| **⌘,** | Settings |
| **⌘Q** | Quit |
| **⌘M** | Minimize |
| **⌘H** | Hide |
## Terminal
| Shortcut | Action |
|----------|--------|
| **⌘K** | Clear scrollback |
| **⌘C** | Copy (when text selected) |
| **⌘V** | Paste |
| **⌘+** | Increase font size |
| **⌘-** | Decrease font size |
| **⌘0** | Reset font size |
## Symbol Key
| Symbol | Key |
|--------|-----|
| ⌘ | Command |
| ⌥ | Option/Alt |
| ⌃ | Control |
| ⇧ | Shift |
| ← → ↑ ↓ | Arrow keys |

View file

@ -0,0 +1,21 @@
{
"title": "Documentation",
"pages": [
"index",
"---Getting Started---",
"installation",
"configuration",
"---Features---",
"tabs",
"notifications",
"splits",
"keyboard-shortcuts",
"---Automation---",
"socket-api",
"cli",
"claude-code-hooks",
"---Reference---",
"osc-sequences",
"environment-variables"
]
}

View file

@ -0,0 +1,127 @@
---
title: Notifications
description: Desktop notifications in cmuxterm for AI agents
---
# Notifications
cmuxterm supports desktop notifications, allowing AI agents and scripts to alert you when they need attention.
## How It Works
1. A process in the terminal sends an OSC escape sequence
2. cmuxterm parses the notification title and body
3. If the terminal isn't focused, a desktop notification appears
4. The tab shows an unread badge
## Sending Notifications
### Using the CLI
The simplest way to send a notification:
```bash
cmuxterm notify --title "Task Complete" --body "Your build finished"
```
With a subtitle:
```bash
cmuxterm notify --title "Claude Code" --subtitle "Waiting" --body "Agent needs input"
```
### Using OSC Sequences
You can send notifications directly using escape sequences:
#### OSC 99 (Kitty Protocol)
```bash
# Basic notification
printf '\e]99;i=1;e=1;d=0:Hello World\e\\'
# With title and body
printf '\e]99;i=1;e=1;d=0;p=title:My Title\e\\'
printf '\e]99;i=1;e=1;d=0;p=body:Message body here\e\\'
```
#### OSC 777 (RXVT Protocol)
```bash
printf '\e]777;notify;My Title;Message body here\a'
```
See [OSC Sequences](/docs/osc-sequences) for full format documentation.
## Notification Behavior
### Suppression
Notifications are suppressed (no desktop alert) when:
- The cmuxterm window is focused
- The specific tab sending the notification is active
- The notification panel is open
This prevents duplicate alerts when you're already looking at the terminal.
### Notification Panel
Press **⌘⇧I** to open the notification panel, showing all notifications:
- Click a notification to jump to that tab
- Notifications are marked as read when viewed
- Clear all with the clear button
### Quick Jump
Press **⌘⇧U** to jump directly to the tab with the most recent unread notification.
## Notification Lifecycle
1. **Received** - Notification appears in panel, desktop alert fires (if not suppressed)
2. **Unread** - Badge shown on tab
3. **Read** - Cleared when you view that tab
4. **Cleared** - Removed from panel
## Integration Examples
### Notify After Long Command
```bash
# Add to your shell config
notify-after() {
"$@"
local exit_code=$?
if [ $exit_code -eq 0 ]; then
cmuxterm notify --title "✓ Command Complete" --body "$1"
else
cmuxterm notify --title "✗ Command Failed" --body "$1 (exit $exit_code)"
fi
return $exit_code
}
# Usage
notify-after npm run build
```
### Notify When Build Finishes
```bash
npm run build && cmuxterm notify --title "Build Success" --body "Ready to deploy"
```
### Watch Script Completion
```bash
#!/bin/bash
# long-running-task.sh
do_work() {
# ... your task
sleep 10
}
do_work
cmuxterm notify --title "Task Complete" --body "long-running-task.sh finished"
```

View file

@ -0,0 +1,204 @@
---
title: OSC Sequences
description: OSC escape sequence reference for cmuxterm notifications
---
# OSC Sequences
cmuxterm supports two OSC (Operating System Command) escape sequence standards for sending notifications from terminal programs.
## OSC 99 (Kitty Protocol)
The Kitty notification protocol provides rich notification support with multiple parameters.
### Format
```
ESC ] 99 ; <params> ; <payload> ESC \
```
Or using BEL terminator:
```
ESC ] 99 ; <params> ; <payload> BEL
```
### Parameters
Parameters are semicolon-separated key=value pairs:
| Parameter | Description | Values |
|-----------|-------------|--------|
| `i` | Notification ID | Any string |
| `e` | Event type | `1` = new notification |
| `d` | Done flag | `0` = more data coming, `1` = complete |
| `p` | Payload type | `title`, `body`, `subtitle` |
### Payload
After the final semicolon, the payload contains the notification content. If `p=` is specified, the payload is interpreted as that type.
### Examples
**Simple notification:**
```bash
printf '\e]99;i=1;e=1;d=0:Hello World\e\\'
```
**Notification with title and body:**
```bash
# Set title
printf '\e]99;i=1;e=1;d=0;p=title:Build Complete\e\\'
# Set body
printf '\e]99;i=1;e=1;d=1;p=body:All tests passed\e\\'
```
**With subtitle:**
```bash
printf '\e]99;i=1;e=1;d=0;p=title:Claude Code\e\\'
printf '\e]99;i=1;e=1;d=0;p=subtitle:Session abc123\e\\'
printf '\e]99;i=1;e=1;d=1;p=body:Waiting for input\e\\'
```
### Shell Function
```bash
notify_osc99() {
local title="$1"
local body="$2"
local id="${3:-1}"
printf '\e]99;i=%s;e=1;d=0;p=title:%s\e\\' "$id" "$title"
printf '\e]99;i=%s;e=1;d=1;p=body:%s\e\\' "$id" "$body"
}
# Usage
notify_osc99 "Build Done" "Your project compiled successfully"
```
## OSC 777 (RXVT Protocol)
The RXVT/urxvt protocol is simpler, with a fixed format for title and body.
### Format
```
ESC ] 777 ; notify ; <title> ; <body> BEL
```
Or with ST terminator:
```
ESC ] 777 ; notify ; <title> ; <body> ESC \
```
### Examples
**Basic notification:**
```bash
printf '\e]777;notify;Hello;World\a'
```
**Build completion:**
```bash
printf '\e]777;notify;Build Complete;All 42 tests passed\a'
```
**With ST terminator:**
```bash
printf '\e]777;notify;Task Done;Ready for review\e\\'
```
### Shell Function
```bash
notify_osc777() {
local title="$1"
local body="$2"
printf '\e]777;notify;%s;%s\a' "$title" "$body"
}
# Usage
notify_osc777 "Download Complete" "File saved to ~/Downloads"
```
## Comparison
| Feature | OSC 99 | OSC 777 |
|---------|--------|---------|
| Title | ✓ | ✓ |
| Body | ✓ | ✓ |
| Subtitle | ✓ | ✗ |
| Notification ID | ✓ | ✗ |
| Complexity | Higher | Lower |
| Compatibility | Kitty, cmuxterm | RXVT, cmuxterm |
## Recommendations
- **Use OSC 777** for simple title + body notifications
- **Use OSC 99** when you need subtitles or notification IDs
- **Use the CLI** (`cmuxterm notify`) for the easiest integration
## Testing
Test notifications in your terminal:
```bash
# OSC 777 (simpler)
printf '\e]777;notify;Test;This is a test notification\a'
# OSC 99 (with subtitle)
printf '\e]99;i=test;e=1;d=0;p=title:Test Notification\e\\'
printf '\e]99;i=test;e=1;d=0;p=subtitle:Subtitle here\e\\'
printf '\e]99;i=test;e=1;d=1;p=body:This is the body text\e\\'
```
## Integration with Other Tools
### tmux
If using tmux inside cmuxterm, enable passthrough:
```bash
# In .tmux.conf
set -g allow-passthrough on
```
Then wrap sequences:
```bash
printf '\ePtmux;\e\e]777;notify;Title;Body\a\e\\'
```
### SSH
Notifications work over SSH if your SSH client supports OSC passthrough. Most modern terminals do.
### Python
```python
import sys
def notify(title: str, body: str):
"""Send OSC 777 notification."""
sys.stdout.write(f'\x1b]777;notify;{title};{body}\x07')
sys.stdout.flush()
notify("Script Complete", "Processing finished")
```
### Node.js
```javascript
function notify(title, body) {
process.stdout.write(`\x1b]777;notify;${title};${body}\x07`);
}
notify('Build Done', 'webpack finished');
```

View file

@ -0,0 +1,263 @@
---
title: Socket API
description: Unix socket API for programmatic control of cmuxterm
---
# Socket API
cmuxterm exposes a Unix socket for programmatic control, enabling automation and integration with tools like Claude Code.
## Socket Location
| Build | Socket Path |
|-------|-------------|
| Release | `/tmp/cmuxterm.sock` |
| Debug | `/tmp/cmuxterm-debug.sock` |
You can override the path with the `CMUX_SOCKET_PATH` environment variable.
## Access Modes
cmuxterm has three access modes, configurable in Settings:
| Mode | Description |
|------|-------------|
| **Off** | Socket disabled |
| **Notifications only** | Only notification commands allowed |
| **Full control** | All commands enabled |
<Callout type="warn">
On shared machines, use "Notifications only" mode to prevent other users from controlling your terminals.
</Callout>
## Protocol
Commands are sent as newline-terminated JSON:
```json
{"command": "command-name", "arg1": "value1"}
```
Responses are newline-terminated JSON:
```json
{"success": true, "data": {...}}
{"success": false, "error": "Error message"}
```
## Commands Reference
### Tab Management
#### list-tabs
List all open tabs.
```json
{"command": "list-tabs"}
```
Response:
```json
{
"success": true,
"tabs": [
{"id": "uuid", "title": "Tab 1", "directory": "/Users/..."}
]
}
```
#### new-tab
Create a new tab.
```json
{"command": "new-tab"}
```
#### select-tab
Switch to a specific tab.
```json
{"command": "select-tab", "id": "tab-uuid"}
```
#### current-tab
Get the currently active tab.
```json
{"command": "current-tab"}
```
#### close-tab
Close a specific tab.
```json
{"command": "close-tab", "id": "tab-uuid"}
```
### Split Management
#### new-split
Create a new split pane.
```json
{"command": "new-split", "direction": "right"}
```
Direction options: `left`, `right`, `up`, `down`
#### list-panels
List all panes in the current tab.
```json
{"command": "list-panels"}
```
#### focus-panel
Focus a specific pane.
```json
{"command": "focus-panel", "id": "panel-uuid"}
```
### Input Control
#### send
Send text input to a terminal.
```json
{"command": "send", "text": "echo hello\n"}
```
#### send-key
Send a key press.
```json
{"command": "send-key", "key": "enter"}
```
Key options: `enter`, `tab`, `escape`, `backspace`, `delete`, `up`, `down`, `left`, `right`
#### send-panel
Send text to a specific panel.
```json
{"command": "send-panel", "id": "panel-uuid", "text": "ls -la\n"}
```
#### send-key-panel
Send a key press to a specific panel.
```json
{"command": "send-key-panel", "id": "panel-uuid", "key": "enter"}
```
### Notifications
#### notify
Send a notification.
```json
{
"command": "notify",
"title": "Build Complete",
"subtitle": "Project X",
"body": "Build finished successfully"
}
```
#### list-notifications
List all notifications.
```json
{"command": "list-notifications"}
```
#### clear-notifications
Clear all notifications.
```json
{"command": "clear-notifications"}
```
### App Control
#### ping
Check if the socket is responsive.
```json
{"command": "ping"}
```
Response:
```json
{"success": true, "pong": true}
```
#### set-app-focus
Simulate app focus/unfocus (for testing).
```json
{"command": "set-app-focus", "focused": true}
```
## Example: Python Client
```python
import socket
import json
def send_command(cmd):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect('/tmp/cmuxterm.sock')
sock.send(json.dumps(cmd).encode() + b'\n')
response = sock.recv(4096).decode()
sock.close()
return json.loads(response)
# List tabs
tabs = send_command({"command": "list-tabs"})
print(tabs)
# Send notification
send_command({
"command": "notify",
"title": "Hello",
"body": "From Python!"
})
```
## Example: Shell Script
```bash
#!/bin/bash
# Function to send commands
cmux_cmd() {
echo "$1" | nc -U /tmp/cmuxterm.sock
}
# List tabs
cmux_cmd '{"command": "list-tabs"}'
# Send notification
cmux_cmd '{"command": "notify", "title": "Done", "body": "Task complete"}'
```

View file

@ -0,0 +1,111 @@
---
title: Split Panes
description: Working with split panes in cmuxterm
---
# Split Panes
cmuxterm supports splitting terminal panes within a tab, allowing you to view multiple terminals side by side.
## Creating Splits
### Keyboard Shortcuts
| Shortcut | Action |
|----------|--------|
| **⌘D** | Split right |
| **⌘⇧D** | Split down |
### Using the CLI
```bash
# Split in a specific direction
cmuxterm new-split right
cmuxterm new-split down
cmuxterm new-split left
cmuxterm new-split up
```
## Navigating Splits
### Keyboard
Use **⌘⌥** + arrow keys to move between splits:
| Shortcut | Action |
|----------|--------|
| **⌘⌥←** | Focus left pane |
| **⌘⌥→** | Focus right pane |
| **⌘⌥↑** | Focus pane above |
| **⌘⌥↓** | Focus pane below |
### CLI
```bash
# List all panes in current tab
cmuxterm list-panels
# Focus a specific pane
cmuxterm focus-panel --panel <panel-id>
```
## Closing Splits
- **⌘W** - Close the focused pane
- If only one pane remains, **⌘W** closes the tab
## Visual Styling
Unfocused panes are visually distinguished to help you identify the active pane.
### Configuration
In your Ghostty config:
```ini
# Dim unfocused panes (0.0 = fully dimmed, 1.0 = no dimming)
unfocused-split-opacity = 0.7
# Background fill for unfocused panes
unfocused-split-fill = #1e1e2e
# Color of the divider between panes
split-divider-color = #45475a
```
## Split Layout
cmuxterm uses a binary split tree structure:
- Each split creates two child panes
- Nested splits create a tree of panes
- Each pane has a unique `panel-id` for CLI access
### Example Layout
```
┌────────────────────────────────┐
│ Tab 1 │
├──────────────┬─────────────────┤
│ │ Pane 2 │
│ Pane 1 ├─────────────────┤
│ │ Pane 3 │
└──────────────┴─────────────────┘
```
This layout is created by:
1. Split right → Pane 1 | Pane 2
2. Focus Pane 2
3. Split down → Pane 2 | Pane 3
## Sending Input to Specific Panes
With the CLI, you can send input to specific panes:
```bash
# Send text to a specific pane
cmuxterm send-panel --panel <panel-id> "echo hello"
# Send a keypress to a specific pane
cmuxterm send-key-panel --panel <panel-id> enter
```

View file

@ -0,0 +1,74 @@
---
title: Vertical Tabs
description: Managing terminal tabs in cmuxterm
---
# Vertical Tabs
cmuxterm displays all your terminal tabs in a vertical sidebar on the left side of the window, making it easy to see and switch between multiple terminals at a glance.
## Sidebar Features
- **Always visible** - All tabs shown at once, no hidden tabs
- **Resizable** - Drag the sidebar edge to resize (140-360pt)
- **Current directory** - Each tab shows its working directory
- **Notification badges** - Unread notification indicators
- **Keyboard navigation** - Quick switching without mouse
## Tab Operations
### Create New Tab
- **⌘T** or **⌘N** - Create a new tab
### Close Tab
- **⌘⇧W** - Close the current tab
- **⌘W** - Close the current pane (closes tab if only one pane)
### Switch Tabs
| Shortcut | Action |
|----------|--------|
| **⌘1** - **⌘9** | Jump to tab 1-9 |
| **⌘]** | Next tab |
| **⌘[** | Previous tab |
| **⌃Tab** | Next tab |
| **⌃⇧Tab** | Previous tab |
### Jump to Notifications
- **⌘⇧U** - Jump to the tab with the latest unread notification
- **⌘⇧I** - Show the notifications panel
## Tab Titles
Tab titles are determined by:
1. **Terminal title** - Set via OSC escape sequence
2. **Current directory** - Shown if no title is set
3. **Tab number** - Fallback display
You can set a custom tab title from within the terminal:
```bash
# Set tab title
echo -e "\033]0;My Custom Title\007"
# Or using printf
printf "\033]0;Build Server\007"
```
## Toggle Sidebar
- **⌘B** - Toggle the sidebar visibility
When the sidebar is hidden, you can still use keyboard shortcuts to navigate between tabs.
## Tab Indicators
Tabs display visual indicators:
- **Blue dot** - Unread notification in this tab
- **Bold title** - Currently selected tab
- **Directory path** - Current working directory for the tab

8
docs-site/lib/source.ts Normal file
View file

@ -0,0 +1,8 @@
import { docs, meta } from '@/.source';
import { createMDXSource } from 'fumadocs-mdx';
import { loader } from 'fumadocs-core/source';
export const source = loader({
baseUrl: '/',
source: createMDXSource(docs, meta),
});

10
docs-site/next.config.mjs Normal file
View file

@ -0,0 +1,10 @@
import { createMDX } from 'fumadocs-mdx/next';
const withMDX = createMDX();
/** @type {import('next').NextConfig} */
const config = {
reactStrictMode: true,
};
export default withMDX(config);

6122
docs-site/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

28
docs-site/package.json Normal file
View file

@ -0,0 +1,28 @@
{
"name": "cmuxterm-docs",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"fumadocs-core": "^14.0.0",
"fumadocs-mdx": "^11.0.0",
"fumadocs-ui": "^14.0.0",
"next": "^15.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.16",
"typescript": "^5.7.0"
}
}

View file

@ -0,0 +1,9 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
export default config;

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,7 @@
import { defineConfig, defineDocs } from 'fumadocs-mdx/config';
export const { docs, meta } = defineDocs({
dir: 'content/docs',
});
export default defineConfig();

View file

@ -0,0 +1,33 @@
import { createPreset } from 'fumadocs-ui/tailwind-plugin';
import type { Config } from 'tailwindcss';
const config: Config = {
content: [
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./content/**/*.{md,mdx}',
'./node_modules/fumadocs-ui/dist/**/*.js',
],
presets: [createPreset()],
theme: {
extend: {
colors: {
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
950: '#082f49',
},
},
},
},
};
export default config;

40
docs-site/tsconfig.json Normal file
View file

@ -0,0 +1,40 @@
{
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./*"
]
},
"target": "ES2017"
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}

4
docs-site/vercel.json Normal file
View file

@ -0,0 +1,4 @@
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"framework": "nextjs"
}