An open API service indexing awesome lists of open source software.

https://github.com/badbread/ctc

Start Claude Code sessions on your home server from your phone... one bash file, no server, drive from the Claude app.
https://github.com/badbread/ctc

bash claude-code cli remote-control ssh tmux tui

Last synced: about 9 hours ago
JSON representation

Start Claude Code sessions on your home server from your phone... one bash file, no server, drive from the Claude app.

Awesome Lists containing this project

README

          

# ctc: Claude Terminal Connect

**TL;DR** — _SSH to your Linux box, launch a session with `ctc`, then drive it from the native Claude app._

`ctc` runs `claude --remote-control` in a detached tmux session, one per
project, and hands you an arrow-key TUI to manage them. Detached, so it
outlives the shell that started it; the session keeps running in the Claude
app after you disconnect. One bash file, no daemon, just the SSH you already
trust.

### Demo

![ctc demo](docs/media/demo.gif)

The session manager: launch a new backend, manage the live ones — attach,
detach, flip launch/permission modes. Whatever shows up here also shows up
in the Claude app.

## Why

Driving my box's `claude` from the app meant four steps, every time:

```bash
ssh mybox
cd ~/projects/whatever
claude
/remote-control # inside claude, to push it to the app
```

And the session died with the SSH connection — drop the shell, lose `claude`.
Typing into a raw SSH terminal on a phone is its own punishment: no
autocomplete, no spellcheck, no dictation, no screenshots.

`ctc` collapses the launch to picking a project from a menu, and because the
backend's detached, the SSH session was only ever the ignition. Close it and
drive `claude` from the app — autocomplete, spellcheck, dictation, screenshots,
on whatever device you're holding.

## How (and the clock on it)

[Remote Control](https://code.claude.com/docs/en/remote-control) drives a running
`claude` from the app, but the process has to stay alive and there's no headless
mode. So: `claude --remote-control` in a detached tmux session, tmux as the
keepalive Claude Code doesn't ship.

That "doesn't ship" is the whole risk. `ctc` exists because of an open gap
([#30447](https://github.com/anthropics/claude-code/issues/30447)). The day
`--headless` lands, the tmux trick is dead weight and `ctc` is just a launcher
UI. Built on that clock on purpose.

## vs. the alternatives

- **`claude remote-control` server mode** (`--spawn worktree`, `--capacity`):
one process, many sessions, QR to connect. Great when you're *at* the box.
`ctc` is for when you're not: per-project backends spun up and managed over
SSH. They compose; point `ctc` at server mode if you want one process.
- **[Claude Code Channels](https://www.macstories.net/stories/first-look-hands-on-with-claude-codes-new-telegram-and-discord-integrations/)**
(official TG/Discord/iMessage, preview since 2026-03): messaging ergonomic, and
it has the *same* keepalive problem. Anthropic's docs literally say "combining
with tmux, screen, or a background process is the current workaround." `ctc`
hosts exactly that session. They stack.
- **web/Electron UIs** (claudecodeui, Codeman, …): browser file tree + shell,
plus a service to run and expose. Opposite bet. Nothing to host.

## Setup

Needs: an always-on Linux box (server, VPS, Pi, WSL, whatever) with `claude`
**logged in**, `tmux`, and `bash` 4+. A way to securely SSH into it from wherever
you are; `ctc` doesn't care how you get there.

`ctc` doesn't touch auth, it just `exec`s `claude` with whatever login's already
on the box.

Single file, zero deps:

```bash
curl -fsSL https://raw.githubusercontent.com/badbread/ctc/main/bin/ctc \
-o ~/.local/bin/ctc && chmod +x ~/.local/bin/ctc
```

Run `ctc`. First run autodetects your project dir (`~/projects`, `~/code`,
`~/src`, …) or asks once. Config at `~/.config/ctc/config`
([example](ctc.config.example)), all of it live-editable in `[o] options`.

git clone / installer instead

```bash
git clone https://github.com/badbread/ctc && cd ctc && ./install.sh
```

Copies to `~/.local/bin`, writes a starter config, checks for `claude`/`tmux`.
`git pull` to update.

Running ctc inside a container

`ctc` persists its config at `~/.config/ctc/config` and history at
`~/.local/state/ctc/`. Inside a container, those live on the writable layer
unless you mount them — every restart wipes the projects dir setting and you
re-pick it on first run. Mount them (and your projects dir, and `~/.claude*`
so Claude's login + per-project trust survive too):

```bash
docker run --rm -it \
-v "$HOME/.config/ctc:/root/.config/ctc" \
-v "$HOME/.local/state/ctc:/root/.local/state/ctc" \
-v "$HOME/.claude:/root/.claude" \
-v "$HOME/.claude.json:/root/.claude.json" \
-v "$HOME/projects:/root/projects" \
your-image ctc
```

Or set `CTC_CONFIG_DIR` / `CTC_STATE_DIR` (and `CLAUDE_CONFIG_DIR`) to paths
that already live on a mounted volume.

```bash
ctc # session manager
ctc my-api # fuzzy-jump to the project matching "my-api"
ctc ~/some/path # explicit dir
```

## Managing sessions

The menu is a process manager for your backends, one tmux session per project.
This is the part I actually live in.

- **Real liveness.** `● live` vs `◌ idle · claude exited`, off the pane's actual
`pane_current_command` (`claude`/`node` = live), not a bare `has-session`. A
crashed backend reads idle, not falsely healthy.
- **Attach / detach.** `[a]` drops into the running `claude`; **`Ctrl-b d`**
detaches back to the menu, session still live and still on the app. Detach ≠
quit. `/exit` ends it.
- **Kill.** `[k]` on one, or the multi-select screen (`Space` mark, `Enter`
confirm) to reap a batch.
- **Settings in `[o]`**, persisted to config: where ctc looks for projects
(projects dir + scan depth), plus the per-launch defaults applied to new
sessions — launch mode (`detached`/`attach`), permission mode
(`acceptEdits`/`auto`/`bypassPermissions`/`default`/`plan`, or Shift+Tab on
the main menu to cycle), model, `--remote-control`, `--continue`. Every
toggle maps to a real `claude` flag where one exists.

## One-tap launch

`ctc` is a TUI, so any SSH client works as-is. To make launching a single tap,
force a TTY and run it on login. `RemoteCommand` wants the full path if `ctc`
isn't on the non-interactive `PATH` (`~/.local/bin` usually isn't, `which ctc`):

```
Host ctc
HostName your.box.address
User you
RequestTTY force
RemoteCommand ctc # full path if needed: /home/you/.local/bin/ctc
```

Phone clients are where this shines, and they each have quirks:

- **Termux** (Android): `pkg install openssh`, host block in `~/.ssh/config`,
`ssh ctc`. One word? `echo 'ctc(){ ssh ctc; }' >> ~/.bashrc && source ~/.bashrc`.
`command not found` = stale shell, re-`source` or reopen; if Termux ignores
`~/.bashrc`, use `~/.profile`. Arrows/Tab/Ctrl on the extra-keys row (Ctrl for
`Ctrl-b d`).
- **Blink** (iOS): best iOS TUI client. Host + startup command `ctc`, or
`ssh ctc -t ctc`. Modifiers and Shift+Tab work.
- **Termius**: host + startup snippet `ctc`. Key row has Ctrl/arrows/Tab.

Keys: `↑↓`/`jk` move, `Enter` select, `q`/`Esc` back, bracketed letter
(`n o k a b /`) jumps straight to the action.

## AI

Most of the code was written with Claude Code.

## License

MIT, see [LICENSE](LICENSE).


*Independent, unofficial. "Claude"/"Claude Code" are Anthropic trademarks; not
affiliated or endorsed. It just launches the `claude` you already have.*