New script that launches Claude Code (via claude-profile) inside a detached tmux session with --remote-control enabled, in a project's directory, named after the project (Title-cased, auto-incrementing #N on collision). Lets a session be started for later attach over VPN or control from the Claude app. Spec-first per repo conventions: specs/claude-tmux.spec.md, script, and tests/test-claude-tmux.sh (18 dryrun assertions, all passing). Claude-Session: https://claude.ai/code/session_014oa7T7z1h5n34vu8vX9zGT
106 lines
4.6 KiB
Markdown
106 lines
4.6 KiB
Markdown
# claude-tmux
|
|
|
|
## Purpose
|
|
|
|
Spawn a detached `tmux` session running Claude Code (via `claude-profile`) with
|
|
Remote Control enabled, in a project's directory, with the session named after
|
|
the project so it is easy to `tmux attach` to and easy to identify in the Claude
|
|
app.
|
|
|
|
## Usage
|
|
|
|
```
|
|
claude-tmux [PROJECT] [OPTIONS]
|
|
```
|
|
|
|
### Arguments
|
|
|
|
| Argument | Default | Description |
|
|
|----------|---------|-------------|
|
|
| `PROJECT` | *(prompt if TTY)* | Project name (folder under `~/dev/claude` or `~/dev/claude/projects`), or an explicit path to a directory |
|
|
|
|
### Flags
|
|
|
|
| Flag | Default | Description |
|
|
|------|---------|-------------|
|
|
| `--mode <name>` | profile's `last-mode`, else `quick` | Engagement mode passed to `claude-profile` |
|
|
| `--profile <name>` | `oreillyit-anthropic` | Claude Code profile (resolves to `~/.claude-<name>`, or `~/.claude` for `default`) |
|
|
| `--name <display>` | Title-cased project name | Override the session / Remote-Control name |
|
|
| `--attach` | off | Attach (or `switch-client` if already inside tmux) after spawning |
|
|
| `--dryrun`, `-n` | off | Preview all actions without creating anything |
|
|
| `--help`, `-h` | | Show usage information |
|
|
|
|
## Behaviour
|
|
|
|
1. Resolve the profile directory (`~/.claude-<profile>`, or `~/.claude` for
|
|
`default`). Error if it does not exist.
|
|
2. Resolve the project. If `PROJECT` is empty and stdin is a TTY, prompt for it;
|
|
if empty and not a TTY, error. Resolve the folder by trying, in order: an
|
|
existing path, `~/dev/claude/<PROJECT>`, `~/dev/claude/projects/<PROJECT>`.
|
|
Error if none exist.
|
|
3. Resolve the engagement mode. If `--mode` is not given, read the profile's
|
|
`last-mode` file, falling back to `quick`. The mode is **always** resolved
|
|
explicitly and passed to `claude-profile` — the launcher must never leave
|
|
`claude-profile` to show its interactive mode picker, because Remote Control
|
|
only registers in the app once `claude` is actually running.
|
|
4. Derive the base session name: Title-Case the project folder basename
|
|
(`agent-runtimes` → `Agent Runtimes`), unless `--name` overrides it.
|
|
5. Auto-increment: if a tmux session with the base name already exists, append
|
|
` #2`, ` #3`, … until a free name is found. The first session gets no suffix.
|
|
6. Create a detached tmux session named after the resolved name, with its working
|
|
directory set to the project folder.
|
|
7. Send the launch command into the session's pane (sent literally, followed by
|
|
Enter):
|
|
`claude-profile <profile> --mode <mode> --project <basename> -- --remote-control "<name>"`
|
|
8. Print the attach hint and the Remote-Control name. If `--attach` is set,
|
|
attach (or `switch-client` when already inside tmux).
|
|
|
|
`claude-profile` is located via `PATH`, falling back to a sibling of this script.
|
|
|
|
## Dryrun Behaviour
|
|
|
|
When `--dryrun` / `-n` is passed, no tmux session is created and nothing is sent.
|
|
Output (values substituted):
|
|
|
|
```
|
|
[dryrun] Project: agent-runtimes (/home/paul/dev/claude/projects/agent-runtimes)
|
|
[dryrun] Profile: oreillyit-anthropic (/home/paul/.claude-oreillyit-anthropic)
|
|
[dryrun] Mode: deep
|
|
[dryrun] Session name: Agent Runtimes
|
|
[dryrun] Would run: tmux new-session -d -s "Agent Runtimes" -c "/home/paul/dev/claude/projects/agent-runtimes"
|
|
[dryrun] Would send: claude-profile oreillyit-anthropic --mode deep --project agent-runtimes -- --remote-control Agent\ Runtimes
|
|
[dryrun] Attach with: tmux attach -t "Agent Runtimes"
|
|
```
|
|
|
|
## Edge Cases
|
|
|
|
| Case | Handling |
|
|
|------|----------|
|
|
| No PROJECT and stdin is a TTY | Prompt `Project name:` |
|
|
| No PROJECT and not a TTY (e.g. run by an agent) | Error: "No project given…", exit 1 |
|
|
| Project folder not found | Error naming the searched locations, exit 1 |
|
|
| Profile directory not found | Error, exit 1 |
|
|
| Session name already taken | Auto-increment with ` #N` suffix |
|
|
| Project name has underscores | Treated like hyphens for Title-Casing |
|
|
| `claude-profile` not found | Error, exit 1 |
|
|
| Two concurrent sessions on the same profile | Both work: `claude-profile` rewrites the shared `active-mode.env`, but each session's **cwd takes precedence** for project auto-selection, so the correct project is still picked |
|
|
|
|
## Examples
|
|
|
|
```bash
|
|
# Spawn a Remote-Control session for agent-runtimes (Title-cased, mode from last-mode)
|
|
$ claude-tmux agent-runtimes
|
|
Spawned tmux session: Agent Runtimes
|
|
attach: tmux attach -t "Agent Runtimes"
|
|
|
|
# A second one auto-increments
|
|
$ claude-tmux agent-runtimes
|
|
Spawned tmux session: Agent Runtimes #2
|
|
|
|
# Override the mode, and attach immediately
|
|
$ claude-tmux cluster-bootstrap --mode deep --attach
|
|
|
|
# Preview only
|
|
$ claude-tmux dns-manager --dryrun
|
|
```
|