sillyjoint

one agent session · every device

A security-first CLI that joins two computers around one tmux-backed coding-agent session. Start Claude, Codex, aider, or any terminal agent on one machine; attach to it from another over SSH on your Tailscale network. No public relay, no cloud, no daemons, no stored passwords.

Free & open source MIT-licensed Zero dependencies Pure Python stdlib
01 · Promo

Watch SillyJoint in 45 seconds

Two devices, one coding-agent session, security-first wiring. The full story.

02 · Install

Install in one command

Pick whichever fits your setup. Both paths are dependency-free.

Homebrew macOS · Linux

Tap the repo and install:

$ brew tap KiranChilledOut/sillyjointtunnel \
    https://github.com/KiranChilledOut/sillyjointtunnel.git
$ brew install KiranChilledOut/sillyjointtunnel/sillyjoint

Homebrew's short tap form expects homebrew-sillyjointtunnel; this repo lives at sillyjointtunnel, so the explicit URL is required.

From source macOS · Linux · WSL · Windows

Clone and run the installer:

$ git clone https://github.com/KiranChilledOut/sillyjointtunnel.git
$ cd sillyjointtunnel
$ python3 install.py

Adds sillyjoint to zsh, bash, and PowerShell profiles. Pass --no-shell-path to skip.

03 · Setup

One-time setup & doctor

After install, run setup to create local state and doctor to verify dependencies. Both are idempotent.

~ · sillyjoint setup
sillyjoint v0.1.35 one agent session · every device home ~/.sillyjoint status ready next ──── sillyjoint walkthrough beginner setup sillyjoint doctor check tmux, ssh, tailscale sillyjoint prepare make this host attachable sillyjoint ssh-config enable speed up remote commands

Run once after install. Writes to ~/.sillyjoint/ and verifies the install state.

~ · sillyjoint doctor
sillyjoint doctor v0.1.35 · ~/.sillyjoint dependencies ──────────── tmux /opt/homebrew/bin/tmux ssh /usr/bin/ssh tailscale /usr/local/bin/tailscale security ──────── no public relay no password storage transport · SSH over Tailscale ready

sillyjoint doctor checks tmux, ssh, tailscale, and the security posture. JSON via --json.

04 · Walkthrough

Guided 5-step setup

If you have zero networking experience, run the walkthrough first. It opens in a separate terminal so passwords and login flows stay outside any AI chat.

~ · sillyjoint walkthrough · step 2/5
sillyjoint walkthrough step 2/5 step 2 · sillyjoint version ─────────────────────────── Confirm the CLI and Tailscale are visible in this terminal. $ sillyjoint --version $ tailscale status $ tailscale ip -4 checks ────── sillyjoint --version: sillyjoint 0.1.35 tailscale status: 100.72.50.48 tailscale ip -4: 100.72.50.48 Enter next r recheck b back q quit

Five steps, each with auto-detected environment checks:

1

Install

SillyJoint, Tailscale, tmux, SSH on both machines.

2

Verify versions

Confirm everything is reachable on this terminal's PATH.

3

Local setup

Create ~/.sillyjoint/ and run doctor.

4

Host machine

Enable Remote Login/SSH and prepare a session.

5

Remote machine

Connect from the second device.

Why a separate terminal

The walkthrough opens in its own window so that SSH passwords, Tailscale browser logins, and other secrets never appear in the AI agent's chat history.

05 · Prepare

Launch your agent through SillyJoint

Instead of typing claude or codex, type sillyjoint prepare. It starts your agent in a managed tmux session, drops you into it so you can start working, and registers it so any other device on your Tailscale network can attach later. Detach with Ctrl-b d when you're done; reconnect from anywhere with sillyjoint connect.

~/project · sillyjoint prepare
sillyjoint prepare Create or import a tmux-backed session for remote attach. choose an agent ─────────────── 1 Claude — Claude Code (uses local proxy) 2 Codex — Codex CLI 3 Custom command — type your own local agent command 4 Shell — no agent, just a login shell in tmux Choose [1]:

Three flows, same machinery underneath:

Interactivesillyjoint prepare
Guided dry-runsillyjoint prepare --guided --dry-run
Scriptedsillyjoint session start work --agent claude --cwd "$PWD"
Default

The "Claude" menu choice runs claude --proxy under the hood. Pass --agent from the CLI for the advanced variants (claude_resume, claude_proxy_resume, etc.).

Already in tmux?

Register the current tmux session instead of starting a new one. Same tmux keeps running; nothing restarts.

$ sillyjoint session import --name work \
    --agent claude --cwd "$PWD"
$ sillyjoint prepare --session work

Want to keep an existing conversation?

Start a fresh SillyJoint session, but pass the agent's resume flag as the launch command — it picks up where the previous run left off.

# Claude — continue last conversation
$ sillyjoint session start work --agent claude \
    --cwd "$PWD" --command "claude --proxy -c"

# Codex — resume last session
$ sillyjoint session start work --agent codex \
    --cwd "$PWD" --command "codex resume"
06 · Connect

Attach from any device

On a second machine on the same Tailscale tailnet — laptop, phone-via-iSH, another desktop — run probe then connect. SillyJoint discovers Tailscale devices, lets you search by name, and opens an SSH-backed tmux attach.

probe

Preflight check before attaching. Verifies SSH, tmux, the remote SillyJoint binary, and that the target session is alive.

$ sillyjoint probe --profile macbook

connect

Attach to the remote tmux session. Optionally probe first to refuse attaching if anything is off.

$ sillyjoint connect --profile macbook --probe
~ · sillyjoint probe --profile linux-work
sillyjoint probe linux-work remote chilledout@100.115.18.100 session work version 0.1.35 checks ────── ssh reachable tmux installed /usr/bin/tmux sillyjoint found /home/chilledout/.local/bin/sillyjoint session 'work' alive ready · run sillyjoint connect --profile linux-work
Remote profiles

Save host, user, and remote SillyJoint path once with sillyjoint remote add <name>. Then every subsequent command just takes --profile <name>. Profiles live in ~/.sillyjoint/remotes.json — no passwords stored.

07 · Speed

SSH ControlMaster bake-in

Every remote command opens a fresh SSH connection by default. Enable ControlMaster once and they share one connection per host for 10 minutes. One password prompt, ~10× faster send/capture/ask.

Opt in once:

$ sillyjoint ssh-config enable

SillyJoint writes a clearly-marked block into ~/.ssh/config — never touches anything outside its markers. Adding or removing a profile auto-refreshes the block. Disable any time with sillyjoint ssh-config disable.

Per-call escape hatch

Need a one-off raw user@host connection? Pass --no-controlmaster on any remote command.

~/.ssh/config · sillyjoint managed block
# >>> sillyjoint:start (managed block — do not edit) >>> # SillyJoint manages this block. Host sillyjoint-* ControlMaster auto ControlPath ~/.ssh/cm-%r@%h:%p ControlPersist 10m ServerAliveInterval 60 # profile: linux-work Host sillyjoint-linux-work HostName 100.115.18.100 User chilledout # <<< sillyjoint:end <<<
One password per 10mnot one per command
~50ms per calldown from ~500ms
Auto-syncsprofile changes update the block
08 · Commands

Daily-use command reference

The commands you'll actually run. Full reference in COMMANDS.md.

Command
Use case
Example
setup
Create local state
sillyjoint setup
doctor
Check tmux, SSH, Tailscale
sillyjoint doctor
status
Sessions, profiles, env at a glance
sillyjoint status
walkthrough
Guided beginner setup
sillyjoint walkthrough
prepare
Make this host attachable
sillyjoint prepare --session work
session start
Start a tmux-backed agent
sillyjoint session start work --agent claude --cwd "$PWD"
session import
Register current tmux session
sillyjoint session import --name work --agent claude
remote add
Save a remote profile
sillyjoint remote add macbook --probe
probe
Preflight before attach
sillyjoint probe --profile macbook
connect
Attach to remote tmux session
sillyjoint connect --profile macbook --probe
send
Type into a remote session
sillyjoint send "run tests" --profile macbook
capture
Read remote pane output
sillyjoint capture --profile macbook --lines 200
ask
Send a prompt and read the answer
sillyjoint ask "run tests" --profile macbook --wait-until-quiet
ssh-config enable
Bake ControlMaster into ~/.ssh/config
sillyjoint ssh-config enable
skill install
Install agent skill files
sillyjoint skill install
09 · Security

What we don't do

SillyJoint is a coordinator, not a relay. Everything below is by design.

No public terminal relaySSH stays the authority boundary
No password storageZero secrets handled
No raw secret collectionTokens, prompts, API keys — never seen
No command execution APISSH is the only execution path
No background daemonNothing runs unless you invoke it
No cloud dependencyWorks fully offline (over Tailscale)
Tailscale = the network boundary(for now — additional transports planned)
Free & open sourceMIT-licensed, audit the whole thing
Zero dependenciesPure Python stdlib · easy to audit

Full security model: SECURITY.md