- Go 99.2%
- Shell 0.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Skip pre-release tags when finding the previous tag for a release, so v0.3.0's notes cover everything since v0.2.1 rather than since its last release candidate. Pre-releases still compare with whatever came before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014yRhdKoAN89JPCt5DDfRYA |
||
| .crow | ||
| scripts | ||
| skills/claude-wrangler | ||
| .gitignore | ||
| AGENTS.md | ||
| all.go | ||
| all_test.go | ||
| clipboard.go | ||
| clipboard_test.go | ||
| completion.go | ||
| completion_test.go | ||
| config.go | ||
| connect.go | ||
| connect_test.go | ||
| doctor.go | ||
| doctor_test.go | ||
| env.go | ||
| go.mod | ||
| help.go | ||
| helpers_test.go | ||
| LICENSE.md | ||
| local.go | ||
| local_test.go | ||
| main.go | ||
| main_test.go | ||
| README.md | ||
| settings_test.go | ||
| skill.go | ||
| skill_test.go | ||
| target.go | ||
| trust.go | ||
| trust_test.go | ||
| update.go | ||
| update_test.go | ||
| version.go | ||
| version_test.go | ||
| workspace.go | ||
| workspace_test.go | ||
claude-wrangler
Keep a Claude Code session running 24/7 for each of your projects, reachable from your phone, and attach to it from your laptop with one command.
A workspace is a project directory with two systemd user services:
- Claude: a
screensession namedcw_<workspace>whose only window runs Claude Code with--remote-control, so you can pick it up from the Claude app or claude.ai. It continues the directory's last conversation, and if Claude ever exits, systemd starts it again a few seconds later. - Neovim (optional, on by default): a headless
nvimserver in the same directory, for attaching an editor from another machine.
Both start at boot (with lingering) and keep running while you're logged out.
Quick start
On the machine that will run your workspaces (Linux with systemd), after
installing claude-wrangler and logging in to Claude Code
(claude auth login):
loginctl enable-linger # keep workspaces running when you log out
claude-wrangler doctor # check everything they need is in place
claude-wrangler ws create :notes # ~/Projects/workspaces/notes; asks about trust
claude-wrangler ws start :notes # start it now and at every boot
claude-wrangler ws connect :notes # attach to Claude; Ctrl-a d detaches
From your laptop, with claude-wrangler installed there too:
claude-wrangler ws connect you@server:notes # the same Claude, over ssh
claude-wrangler ws connect you@server # or pick from a menu
On your phone, open the Claude app or claude.ai and pick the Remote Control
session cw_notes. ws is short for workspace; with the
recommended alias all this is cw ws ….
Requirements
The machine that runs the workspaces (the server):
- Linux with systemd, and a systemd user session for your account.
Lingering (
loginctl enable-linger) keeps workspaces running while you're logged out and starts them at boot. macOS can't host workspaces (no systemd); WSL can only if systemd is turned on there. - GNU screen (developed with 5.0; any recent version with
-U,-Tandbindkey … execshould do). - Claude Code, logged in with a claude.ai account (
claude auth login). Remote Control needs a claude.ai login, not an API key, and a plan that includes Remote Control. claude-wrangler relies on a few Claude Code behaviours; see Claude Code compatibility. - Neovim 0.9 or later, unless you turn the nvim servers off
(
"nvim": false, see Configuration). - sshd, to connect from other machines.
A machine you connect from (your laptop):
- Linux or macOS with OpenSSH (6.7 or later, for forwarding Unix sockets). Windows isn't supported, except possibly through WSL, which is untested.
- Neovim 0.9 or later for
claude-wrangler ws nvim. - For pasting images (Ctrl+V):
wl-paste(wl-clipboard) on Wayland,xclipon X11, orpngpasteon macOS. - claude-wrangler itself, ideally the same version as the server's. Even
that is optional for Claude:
ssh -t user@host screen -U -d -r cw_<name>attaches without it.
claude-wrangler doctor checks all of this on the machine you run it on.
Install
Install claude-wrangler on the machine that runs your workspaces and on each machine you connect from. Step 1 is needed everywhere and step 2 on the server; the rest is recommended.
1. Get the binary
From a release:
Download the binary for your system from the
releases page,
check it against SHA256SUMS, and put it on your PATH:
v=v0.1.0
os=$(uname -s | tr '[:upper:]' '[:lower:]') # linux or darwin
case $(uname -m) in x86_64|amd64) arch=amd64 ;; aarch64|arm64) arch=arm64 ;; armv6l|armv7l) arch=arm ;; esac
file=claude-wrangler-$os-$arch
base=https://git.jshaver.net/jshaver/claude-wrangler/releases/download/$v
curl -fsSLO "$base/$file" && curl -fsSLO "$base/SHA256SUMS"
grep " $file\$" SHA256SUMS | sha256sum -c # macOS: shasum -a 256 -c
mkdir -p ~/.local/bin && install -m 755 "$file" ~/.local/bin/claude-wrangler
| File | For |
|---|---|
claude-wrangler-linux-amd64 |
Linux on Intel/AMD |
claude-wrangler-linux-arm64 |
64-bit ARM Linux (Raspberry Pi 3/4/5 with a 64-bit OS, ARM servers) |
claude-wrangler-linux-arm |
32-bit ARM Linux (ARMv6 and later: every Raspberry Pi on a 32-bit OS) |
claude-wrangler-darwin-arm64 |
Macs with Apple silicon (M1 and later) |
claude-wrangler-darwin-amd64 |
Intel Macs |
~/.local/bin is also where connect, nvim and shell look for
claude-wrangler on the other machine, along with ~/go/bin. On a Mac, a binary
downloaded with a browser instead of curl is quarantined by Gatekeeper
because it isn't signed by Apple; clear that once with
xattr -d com.apple.quarantine ~/.local/bin/claude-wrangler.
Or with Go:
go install git.jshaver.net/jshaver/claude-wrangler@latest # or, from a clone: go install .
The binary lands in $(go env GOPATH)/bin (usually ~/go/bin). Building needs
Go 1.22 or later and only the standard library.
2. On the server: keep workspaces running
To keep workspaces running while you're logged out, and to start them at boot, enable lingering once on the machine that runs them:
loginctl enable-linger
claude-wrangler doctor then checks the rest of the
requirements.
3. Recommended: shell completion
eval "$(claude-wrangler completion zsh)" # in ~/.zshrc, after compinit
eval "$(claude-wrangler completion bash)" # in ~/.bashrc
(For zsh you can instead save the script as _claude-wrangler in a $fpath
directory.) It completes subcommands, flags, your workspace names, and hosts
from ~/.ssh/config for the remote commands.
4. Recommended: a short alias
claude-wrangler is a lot to type for every command. We recommend an alias,
cw, with completion hooked up to match:
# ~/.zshrc, after the completion line above
alias cw=claude-wrangler
compdef cw=claude-wrangler
# ~/.bashrc, after the completion line above
alias cw=claude-wrangler
complete -F _claude_wrangler cw
Then cw ws connect hub:notes, cw ws list, cw doctor and so on. This README
spells out claude-wrangler so its commands work with or without the alias.
Only your shell knows about it: anything that runs claude-wrangler for you
(screen's paste key, connect on the other machine) uses the full name.
5. Recommended, on the server: the Claude Code skill
claude-wrangler install-skill
installs a skill (built into the binary) in
~/.claude/skills/claude-wrangler/ that teaches Claude how workspaces work, so
you can ask it things like "how do I disconnect and keep you running?" (Ctrl-a
d) or use /claude-wrangler. Install it on the machine that runs the
workspaces; claude-wrangler update refreshes it, and after any other kind of
upgrade, rerun install-skill. The source
is skills/claude-wrangler/SKILL.md.
Updating
claude-wrangler update # this machine
claude-wrangler update you@server # another machine, over SSH
update asks Forgejo for the latest release (never a pre-release), and if it's
newer than what you have, downloads the binary for your machine, checks it
against the release's SHA256SUMS, and puts it in place of the one you're
running. It prints the version you had and the one you have now, and
refreshes the Claude Code skill if it's installed. Running workspaces carry
on; the next Ctrl+V already uses the new binary.
- Pre-releases (tags like
v0.3.0-rc.1) are only installed when you ask, withclaude-wrangler update --pre. That applies to that one run: a later plainupdateignores pre-releases again, so onv0.3.0-rc.1you stay put untilv0.3.0(or anything newer) is released. To keep testing pre-releases, use--preeach time. - A new major version (v1 to v2, say) may change how things work, so
updateasks first, defaulting to no.--yesskips the question. - A build that isn't a release (from a clone, or
go installof a commit, whichversionshows asdev-…orv0.0.0-…) isn't replaced unless you add--force, so a binary you built yourself doesn't get overwritten by surprise. - On another machine, claude-wrangler must already be installed for that
user, in a version that has
update; otherwise it says so and changes nothing. Its output, including any question, comes through your terminal. - The release files must be readable without logging in, which they are
once the repository is public. For a private copy, put a Forgejo token with
read access in
$CLAUDE_WRANGLER_TOKEN.
Usage
claude-wrangler workspace <command> [flags] [<target>] (ws for short)
claude-wrangler update | doctor | install-skill | completion | version | help
claude-wrangler help lists everything, and claude-wrangler help <command>
(or <command> --help, ws <command> --help) explains one in detail.
Targets
Every workspace command takes the same last argument, saying which workspace on which machine:
| Target | Means |
|---|---|
| (nothing) | pick from this machine's workspaces |
:notes |
workspace notes on this machine |
hub or hub: |
pick from hub's workspaces |
hub:notes |
workspace notes on hub |
hub can be user@hub or any host from ~/.ssh/config. A bare word is
always a host, as with ssh: claude-wrangler ws start notes means the
machine called notes. If that isn't reachable but there's a workspace of
that name here, the error says to write :notes.
On another machine the command runs there, over ssh, as
claude-wrangler workspace <command> … :<name>, with your terminal attached
for its questions and output. So claude-wrangler must be installed there for
the user you log in as: workspaces belong to the user that runs them. Every
step of one command shares a single ssh connection (a ControlMaster), so you
authenticate once and your ~/.ssh/config applies.
Connecting
claude-wrangler ws connect [<target>] # attach to the workspace's Claude
claude-wrangler ws nvim [<target>] # attach an nvim UI to its nvim server
claude-wrangler ws shell [<target>] # open a shell in its project directory
| Command | What it does |
|---|---|
connect |
Attaches to the workspace's screen session (screen -U -d -r cw_<name>, which detaches it from any other terminal). In it, Ctrl-a d detaches and leaves Claude running, Ctrl-a Esc scrolls back, and Ctrl+V pastes an image from the machine you're on. With no name, the menu also offers n to create a workspace (asking for its name and about trust) and start it. |
nvim |
Runs nvim --remote-ui against the workspace's nvim server; for another machine its socket is forwarded over ssh. :detach, or closing the terminal, leaves the server running. |
shell |
Opens a login shell in the project directory. The workspace doesn't need to be running. |
connect and nvim offer to rebuild a workspace set up by an older
claude-wrangler and to start a stopped one, and wait up to 15 seconds for one
that's restarting. On this machine connect refuses to attach a workspace's
screen session to itself (from its own Ctrl-a c shell).
Quitting Claude inside screen (Ctrl-C twice, /exit) ends the session, and
systemd starts it again about 5 seconds later with --continue, so the
conversation carries on. To leave without that restart, detach with
Ctrl-a d. The same goes for :q in an attached nvim UI.
Pasting images from your laptop
Claude Code's own image paste reads the clipboard of the machine Claude runs
on, and a terminal can only send text over ssh. So in a workspace attached
from another machine with claude-wrangler ws connect, Ctrl+V pastes the
image on your laptop's clipboard instead (change or turn off the key with
paste_key, see Configuration):
- The workspace's screen session catches Ctrl+V (its own screenrc; other screen sessions are untouched) and starts a background fetch, so you can keep typing.
- It asks your laptop, over the connection
connectalready has (a reverse socket forward), for the clipboard image. Nothing leaves your laptop until you press Ctrl+V. - The image is saved, unchanged, as
~/.claude-wrangler/clipboard/<name>/<hash>.<ext>on the server (only the latest is kept), and its path is typed into Claude's input. Claude can read that directory without asking (--add-dir).
Progress shows on screen's message line ("Fetching the image on your
clipboard...", "Inserted ...", "No image on your clipboard", or why it failed;
it gives up after 30 seconds). Your laptop needs wl-paste (wl-clipboard,
Wayland), xclip (X11) or pngpaste (macOS). In other screen windows
(Ctrl-a c), and when attached without a remote connect (plain ssh, or
connect :<name> on the server itself), Ctrl+V goes to Claude as usual, which
reads the server's own clipboard.
Managing workspaces
claude-wrangler ws list [--names|--running|--outdated] [<target>]
claude-wrangler ws create [--trust|--no-trust] [<target>]
claude-wrangler ws start|stop|restart [<target>]
claude-wrangler ws start|stop|restart --all [<host>]
claude-wrangler ws upgrade [<target>]
claude-wrangler ws upgrade --all [<host>]
claude-wrangler ws destroy [-y] [<target>]
claude-wrangler ws dir [<target>]
| Command | What it does |
|---|---|
list |
One line per workspace: its Claude service, whether its cw_ screen session exists, its nvim service, whether it starts at boot, and whether its services are current or outdated. With a workspace (:notes, hub:notes), just that one; without one it lists them all rather than asking. --names prints only the names; --running, those whose screen session exists; --outdated, those needing a rebuild. |
create |
Writes the workspace's services and, if it doesn't exist yet, its project directory ({workspaces_dir}/{name}). An existing directory is used as is; nothing in it is changed. Without a name it asks for one. Refuses a name whose cw_ screen session is already running. For --trust/--no-trust, see Claude Code trust. |
start |
Starts the workspace (Claude, and nvim through Wants=) and enables it, so it also comes back after a reboot. |
stop |
Stops both services and disables the workspace, so it stays down across reboots. |
restart |
Restarts both services. |
--all |
With start, stop or restart: every workspace on this machine, or on <host>, one after another, printing each one's state before and after. One that fails doesn't stop the rest. stop --all and then start --all pause everything, for example while updating. Run from inside a workspace, stop --all and restart --all do that workspace last, since doing it ends the session you're in. |
upgrade |
Rebuilds an outdated workspace's services; --all does every outdated one on this machine, or on <host>. See Upgrading. |
destroy |
Asks (unless -y), then removes both services. The project directory is kept. |
dir |
Prints the project directory, e.g. cd "$(claude-wrangler ws dir :notes)". |
Names may contain letters, digits, - and _. Screen sessions and Claude's
session and Remote Control names get a cw_ prefix (cw_notes), so they don't
collide with sessions you start by hand. create and start print the
commands to reach the workspace from another machine. Commands exit non-zero
on failure and print errors to stderr.
claude-wrangler itself
| Command | What it does |
|---|---|
update [--pre] [--force] [--yes] [[user@]host] |
Installs the latest release here, or on another machine; see Updating. |
doctor |
Checks everything workspaces depend on and says how to fix what's wrong; see Doctor. Exits 1 if something is broken. |
install-skill |
Installs the Claude Code skill. |
completion zsh|bash |
Prints the shell completion script; see shell completion. |
version |
Prints the version (--version works too). |
help [<command>] |
Explains a command; help workspace <command> for workspace commands. |
Doctor
claude-wrangler doctor changes nothing; it reports ok, warn, FAIL (and
exits 1) or - (just so you know) for:
- this machine as a host: the systemd user session, lingering,
screen,nvim(0.9 or later, unless turned off), Claude Code, that it supports Remote Control and is logged in with a claude.ai account (Remote Control needs one;claude auth status), and whether the skill is current; - each workspace: outdated or still on the old
workspaces.nvimlayout, enabled but not running, a service without its screen session, a missing nvim server, and Claude waiting at "Do you trust this folder?" (read from the screen window), which keeps Remote Control from connecting; - connecting out:
ssh,nvimfor thenvimcommand, and the clipboard tool image paste needs on a desktop.
Configuration
Settings are read from ~/.config/claude-wrangler/config.json
($XDG_CONFIG_HOME is honoured, and $CLAUDE_WRANGLER_CONFIG overrides the
path). The file is optional, every field has a default, and unknown fields are
rejected so typos don't go unnoticed:
{
"workspaces_dir": "~/Projects/workspaces",
"ssh_hostname": "example.com",
"ask_claude_trust": true,
"nvim": false,
"paste_key": "^[v"
}
| Field | Default | Meaning |
|---|---|---|
workspaces_dir |
~/Projects/workspaces |
Where project directories are created. |
ssh_hostname |
this machine's hostname | Host shown in the printed connect commands. |
command |
see below | Shell command run in the screen session. {name} becomes the workspace name, {session} the prefixed session name (cw_<name>), {claude} the claude binary and {clipboard} the pasted-images directory. |
ask_claude_trust |
true |
Whether create asks about Claude Code trust when neither switch is given. |
nvim |
true |
Whether each workspace gets a headless nvim server. With false there's no nvim unit, list shows off in the NVIM column, and claude-wrangler ws nvim says it's turned off. nvim only needs to be installed when this is on. |
paste_key |
"^V" |
The key that pastes an image from your laptop (see Pasting images), in screen's caret notation: "^V" is Ctrl+V, "^[v" is Alt-v (Escape, v). It must start with a control character. "off" leaves every key to Claude. On the machine you connect from, "off" stops connect forwarding the clipboard socket. |
screen_term |
"screen-256color" |
$TERM inside the screen session (screen -T). Try "screen" if a host lacks the 256-colour terminfo entry. |
The default command is
{claude} --continue --name {session} --remote-control {session} --add-dir {clipboard} || exec {claude} --name {session} --remote-control {session} --add-dir {clipboard}
It continues the directory's last conversation; the first time there is none,
--continue exits with an error straight away and a new session starts. Both
the session name (/resume) and the Remote Control name are cw_<workspace>.
Settings are baked into a workspace's units when it's created or rebuilt.
Changing nvim, paste_key or screen_term makes existing workspaces show as
outdated, and connect offers the rebuild, as does
claude-wrangler ws upgrade <target> (or --all). Changing command or
workspaces_dir doesn't flag them; run claude-wrangler ws upgrade yourself
to apply command to an existing workspace.
Claude Code trust
Claude Code asks "Do you trust this folder?" the first time it runs in a
directory, and won't connect to Remote Control until someone answers.
claude-wrangler ws create can answer it for you: it asks whether Claude Code should trust the new
workspace directory (with ask_claude_trust), or takes --trust / --no-trust
to answer in advance. Without a terminal to ask on, the answer is no; you can
still answer Claude's own prompt later with connect.
Answering yes sets projects[<dir>].hasTrustDialogAccepted in ~/.claude.json,
exactly what Claude Code's own "Yes" writes. claude-wrangler holds the lock
Claude Code uses while saving that file (the ~/.claude.json.lock directory),
re-reads the file under it, changes only that one entry (every other value is
kept byte for byte), and replaces the file atomically, keeping it private
(0600). If the lock is held for more than 5 seconds it gives up rather than
risk losing a write.
How a workspace is set up
For a workspace called notes:
| project directory | ~/Projects/workspaces/notes |
| Claude unit | ~/.config/systemd/user/workspace-notes.service, enabled into default.target.wants/ |
| screen session | cw_notes (screen -ls), started with screen -D -m -U -T screen-256color -c <screenrc> -S cw_notes /bin/sh -c <command> |
| screenrc | ~/.claude-wrangler/screen/notes.screenrc: sources your ~/.screenrc (if any) and binds Ctrl+V (paste_key) to image paste |
| pasted images | ~/.claude-wrangler/clipboard/notes/ |
| Neovim unit | ~/.config/systemd/user/nvim-workspace-notes.service, pulled in by the Claude unit's Wants= (not there with "nvim": false) |
| Neovim socket | $XDG_RUNTIME_DIR/nvim-workspace-notes.sock |
Inside screen, Claude runs with CLAUDE_CODE_DISABLE_MOUSE=1 and
TERM=screen-256color. Claude otherwise asks the terminal to report every mouse
movement in a format screen can't decode, and the reports leak into its input as
text like 119;12M118;9M; use screen's scrollback (Ctrl-a Esc) instead of the
mouse wheel. Attaching uses screen -U, so non-ASCII characters show correctly
even when the SSH session has no LANG.
Both units restart on any exit (Restart=always, 5 s) and treat exit status 1
as success, since screen and nvim exit 1 when systemd stops them. They are
deliberately not bound with PartOf=: that would restart Neovim every time
Claude restarts, so stop, restart and destroy act on both units instead.
The nvim unit removes stale sockets before each start and after each stop.
Upgrading
Both units carry an X-ClaudeWrangler-Layout=<n> line (systemd ignores X-
keys). When a new claude-wrangler changes how units are written, it bumps that
number, and workspaces whose units have an older number, no number (anything
from before this marker, including workspaces.nvim's single nvim unit), or
a missing nvim unit count as outdated. So do workspaces built with other
nvim, paste_key or screen_term settings than the current config: the
Claude unit lists any non-default ones in an X-ClaudeWrangler-Settings= line. list shows them, and:
startandrestartoffer to rebuild an outdated workspace first (default yes). Aworkspaces.nvimunit can't start until it's rebuilt.connectandnvimask the machine that runs the workspace (claude-wrangler ws list --outdatedthere) and offer to runclaude-wrangler ws upgradethere, so it's that machine's installed version that decides, not yours.claude-wrangler ws upgrade <target>rebuilds one without asking, andclaude-wrangler ws upgrade --all [<host>]every outdated one.
Rebuilding rewrites both units from the installed version and current config and reloads systemd. The project directory and Claude Code's trust are left alone, and whether the workspace starts at boot is kept. A running workspace is restarted (Claude continues its last conversation); a stopped one stays stopped.
After updating claude-wrangler, run claude-wrangler ws upgrade --all (or
… --all <host> for another machine) to bring existing workspaces up to
date; connect also offers it workspace by workspace.
An old startup_commands setting is rejected with a pointer to command.
Claude Code compatibility
claude-wrangler drives Claude Code from the outside, so it depends on some of its behaviour that isn't a stable interface. It was last checked with Claude Code 2.1.309. If a Claude Code update breaks something, this is where to look:
| Relies on | Used for | If it changes |
|---|---|---|
claude --continue exiting with an error straight away when the directory has no conversation |
The default command falls back to a new session (… || exec claude …) |
A new workspace's first start fails or loops; adjust command. |
--name <name> and --remote-control <name> |
Naming the session and the Remote Control entry cw_<name> |
The phone shows a generic name, or Remote Control doesn't start. |
--add-dir <dir> |
Letting Claude read pasted images without asking | Claude asks before opening each pasted image. |
CLAUDE_CODE_DISABLE_MOUSE=1 |
Stopping mouse reporting that screen can't pass through | Text like 119;12M appears in Claude's input. |
~/.claude.json: projects[<dir>].hasTrustDialogAccepted, written under the ~/.claude.json.lock directory lock |
create --trust |
--trust has no effect and Claude asks "Do you trust this folder?" anyway; doctor reports it. A file it can't parse is left alone. |
| The trust question's wording, "Yes, I trust this folder" | doctor spotting a workspace stuck at that question |
doctor stops noticing it. |
claude auth status --json (loggedIn, authMethod, subscriptionType) |
doctor checking for a claude.ai login |
doctor says it couldn't check the login. |
claude --help listing --remote-control |
doctor checking for Remote Control support |
doctor reports it missing. |
claude-wrangler doctor exercises most of these; run it after updating Claude
Code.
Security notes
A workspace is Claude Code running unattended, around the clock, as your user. Before you rely on it:
- Remote Control lets your claude.ai account drive it. Anyone who can use your claude.ai account (from the app or the web) can send prompts to every workspace and answer Claude's permission prompts, which means running commands and editing files on the server as you. Protect the account accordingly, with a strong password and two-step verification.
- claude-wrangler doesn't loosen Claude's permissions. Claude asks before
running commands or editing files exactly as it would in a terminal. The
commandsetting lets you add flags such as--dangerously-skip-permissions; on an always-on workspace reachable from your phone, think twice before doing that. - Trusting a directory trusts its contents.
create --trustanswers Claude Code's "Do you trust this folder?" for you, by editing~/.claude.json. A trusted project's own Claude settings (hooks, MCP servers) then take effect, so only trust directories whose contents you trust, as you would when Claude asks. - No ports are opened. Everything goes over SSH or through Unix sockets
that only your user can reach: screen's session socket, each nvim server's
socket in
$XDG_RUNTIME_DIR, and the clipboard socket below. Anyone who can run code as your user on the server can use them, which already means they could do anything you can. - Image paste reads your laptop's clipboard only when asked, and only for
images. Nothing leaves your laptop until you press the paste key in
Claude's window; claude-wrangler never reads text from the clipboard. While
connectis attached, though, the forwarded clipboard socket ($XDG_RUNTIME_DIR/claude-wrangler/clipboard-<name>.sock) answers any process running as your user on the server, including Claude itself, so a process there could fetch whatever image is on your laptop's clipboard at that moment. If that matters to you, set"paste_key": "off"in the config on your laptop:connectthen doesn't forward the socket at all. (Set it on the server too, so the key goes straight to Claude.) - Pasted images stay on the server. Each workspace keeps only its latest
pasted image, in
~/.claude-wrangler/clipboard/<name>/(readable only by you), whichdestroyremoves. Claude can read that directory without asking (--add-dir), and nothing outside it.
Releases
Crow CI (.crow/) vets and tests every push and pull request, with Go 1.22
(the oldest go.mod allows) and a current Go, and checks that the macOS build
works. To release, tag and push:
git tag -a v0.1.0 -m "v0.1.0" && git push origin v0.1.0
Once the tests pass, .crow/release.yaml builds linux binaries for amd64,
arm64 and 32-bit ARM (ARMv6 and later, so every Raspberry Pi), and darwin
binaries for amd64 and arm64 (scripts/build-release.sh). It then publishes
them, with SHA256SUMS and notes listing the commits since the previous tag
(scripts/release-notes.sh), as the Forgejo release for the tag, using
woodpeckerci/plugin-release. A tag with a -, like v0.2.0-rc1, becomes a
pre-release, and re-running the pipeline replaces the release's files.
It needs a Crow secret FORGEJO_TOKEN: a Forgejo access token with only the
write:repository scope. Allow it only for the tag event and the
woodpeckerci/plugin-release image, so no other build or step can read it.
claude-wrangler version prints the version. Release builds set it with
go build -ldflags "-X main.version=v0.1.0"
go install git.jshaver.net/jshaver/claude-wrangler@v0.1.0 reports v0.1.0
without that, and a build from a clone reports dev-<commit> (plus +dirty
with uncommitted changes) or a Go pseudo-version.
Development
go test ./...
go vet ./...
The tests use a fake systemctl, a fake screen -ls, temporary directories
and a scripted prompt, so they never touch your real units, sessions,
workspaces or ~/.claude.json. The SSH side of the remote commands needs a
real host; the commands they send are checked by running them through sh.
| File | Contents |
|---|---|
main.go |
Argument parsing and dispatch |
config.go |
The config file |
env.go |
The system interface (paths, systemctl, screen, terminal prompts) |
workspace.go |
Units and the local commands |
trust.go |
The ~/.claude.json trust update |
connect.go |
connect, nvim and shell (remote, over SSH) |
local.go |
connect, nvim and shell for a workspace on this machine |
completion.go |
zsh and bash completion |
doctor.go, version.go, update.go |
doctor, version and update |
.crow/, scripts/ |
CI and release builds |
clipboard.go |
Ctrl+V image paste: the laptop side in connect and the server side |
skill.go, skills/ |
The Claude Code skill and install-skill |
License
MIT; see LICENSE.md.