Requirements
- Linux, macOS (Apple silicon; Intel builds are released but not tested), or Windows 10 or 11 (amd64). See Platform support.
- git, to make worktrees. Go 1.27.3 or newer only to build Hand from source.
- Luvus, which Hand needs for its UHP 1.x protocol and was tested with at 0.14.
hand initpins a copy in the fleet folder, underluvus/<os>-<arch>/, so a system upgrade never changes the Luvus a fleet runs and the copy travels with the fleet: theluvusonPATHwhen there is one, otherwise the tested version, which it downloads and checks against the release's SHA-256 (nothing is pinned if the check fails).hand luvus pinpins another one. - At least one harness, logged in: Claude Code (
claude), Codex (codex), opencode 2.x (opencode) or oh-my-pi (omp). Hand lists each harness's models itself, with a listing command and no prompt (see Safe use), and keeps the lists in~/.secondhand/models/: without a list Hand cannot verify a model and warns, but still starts it;hand models --refreshmakes the lists now. On Windows, Hand starts only a real.exefor a harness, never a.cmdshim: see Windows. Hand finds a harness onPATH, then in mise'sshimsfolder (shimsunderMISE_DATA_DIRwhen it is absolute, else$XDG_DATA_HOME/mise, else~/.local/share/mise; on Windows%LOCALAPPDATA%\mise). It reads only that folder: ashims_dirset in mise's config, the system foldermise reshim --systemwrites (/usr/local/share/mise/shims) and mise's command wrappers are not searched, so add such a folder toPATH. opencode uses the model from its own configuration unless a start names one asprovider/id. - Optional: the Antigravity CLI (
agy), logged in, so thatagy modelslists its models; signed out, Hand has no agy list and warns. - Optional: oh-my-pi (
omp, froms=$(curl -fsSL https://omp.sh/install) && sh -c "$s"), logged in, for the supervisor and workers. Hand lists its models withomp models --json, reads its agent folder fromPI_CODING_AGENT_DIR(give an absolute path, and the same one in the Luvus server's environment, the watcher's and the Board's), else$PI_CONFIG_DIR/agent(a relative value is under the home directory), else~/.omp/agent, and finds a supervisor's session under that folder'ssessions/. Measured on Linux only: Windows and macOS have not run a realomp, and Hand starts no.cmdshim for it. - Optional: the GitHub Copilot CLI (
copilot, fromnpm install -g @github/copilot, or downloaded by runninggh copilotonce), logged in withcopilot login, for workers only. Hand looks forcopilotin four places and takes the first hit:PATH; mise'sshimsfolder, as above; npm's globalbinfolder (NPM_CONFIG_PREFIX, else whatnpm prefix -gprints, which Hand asks once per process, 3 seconds at most, without a shell;prefixitself on Windows); and the foldergh copilotdownloads to,gh/copilotunder$XDG_DATA_HOME, else~/.local/share/gh/copilot(on Windows%LOCALAPPDATA%\GitHub CLI\copilot). The harness then starts by that absolute path, so a board started before you changedPATHfinds it too. Hand does not check the login: signed out, Copilot showsPlease use /login to sign inand drops the brief, and Hand notes the attempt blocked when it sees that line at launch. - Optional: the native window,
hand-desktop, on Linux amd64 and arm64 (one window per user; an alert's Open andhand-desktop --open LINKuse it). It needs a display (X11 or Wayland; no cgo) and a running Board, which it reads over loopback;hand updateinstalls it next tohandfrom a release that carries it, together with a launcher entry ($XDG_DATA_HOME/applications/hand-desktop.desktop, default~/.local/share/applications, and the icon$XDG_DATA_HOME/icons/hicolor/512x512/apps/hand-desktop.png) so any XDG launcher opens it asHand, and its asset names arehand-desktop-linux-amd64.tar.gzandhand-desktop-linux-arm64.tar.gz. macOS and Windows have no desktop asset yet and run the Board in a browser (hand open). - Optional:
notify-sendfor desktop notifications andxdg-openforhand open(on macOS, the built-inosascriptandopen; on Windows,hand openrunsrundll32 url.dll,FileProtocolHandlerand there are no built-in notifications; see Alerts on your phone for a PowerShell recipe), and a systemd user session to keep the watcher, the board and Luvus running.
Install a release
Install the latest release on Linux or macOS (amd64 or arm64):
s=$(curl -fsSL https://raw.githubusercontent.com/atqamz/hand/main/install.sh) && sh -c "$s"You can choose another version:
- Edge:
s=$(curl -fsSL https://raw.githubusercontent.com/atqamz/hand/main/install.sh) && HAND_INSTALL_VERSION=edge sh -c "$s". Edge is the rolling build ofmainthat passed CI. - A pinned release:
HAND_INSTALL_VERSION=v0.14.0.
HAND_INSTALL_DIR picks the folder; the default is ~/.local/bin. The script checks the download against the release's checksums.txt, and installs nothing if it does not match. It also fetches hand-iroh, the sidecar for phone access over iroh, when the release carries it and the new hand names it: the asset is checked against its own .sha256 and must report the version that hand needs, then it goes next to hand. If that check fails, the script installs neither and leaves what you have. A release without it (edge, 0.13.0 and earlier) is not an error: the script says so and goes on. It does not fetch hand-desktop: on Linux, run hand update once after installing to put the window next to hand (see Requirements). Do not pipe the download into sh: when curl fails, sh gets empty input and exits 0, as does sh -c "$(curl ...)". The assignment in the command above makes a failed fetch of install.sh stop it with curl's exit code, and a failure inside the script exits 1 with one install.sh: line. The command needs a POSIX shell (sh, bash, zsh), not fish.
On Windows, run this in PowerShell; Windows has the options:
irm https://raw.githubusercontent.com/atqamz/hand/main/install.ps1 | iexReleases after v0.11.0 and the edge build also get a signed build attestation, best effort: a failed signing does not hold back the release. To check who built a download: gh attestation verify hand-linux-amd64.tar.gz --repo atqamz/hand (the GitHub CLI, signed in with gh auth login). If it fails, treat the download as unverified. The installers and hand update do not check it.
Build from source
To build from source instead:
git clone https://github.com/atqamz/hand
cd hand
go build -o ~/.local/bin/hand .To try it next to an older Hand, build it under another name such as ~/.local/bin/hand-next. Hand calls itself by its binary's name in the fleet's AGENTS.md, its skill, its help lines and its errors, so a supervisor in that fleet runs hand-next. When you later install it as plain hand, run that hand init in each fleet to rewrite them.
Windows
Native Windows is supported, without WSL. It has run in CI and not yet on a real Windows machine, so report any problem through the issue form. Platform support lists what CI covers, and docs/dogfood/windows.md has the checks for a real Windows machine.
Install the latest release into %LOCALAPPDATA%\hand\bin from PowerShell 5.1 or newer. No admin rights are needed:
irm https://raw.githubusercontent.com/atqamz/hand/main/install.ps1 | iexThe script downloads hand-windows-amd64.zip, checks it against the release's hand-windows-amd64.sha256, installs nothing if it does not match, and adds the folder to your user PATH once. It fetches hand-iroh-windows-amd64.zip too, as install.sh fetches its sidecar, and puts hand-iroh.exe next to hand.exe; a running one is renamed to hand-iroh.exe.old as hand.exe is. There is no hand-desktop asset for Windows yet: use the Board in a browser. Open a new terminal afterwards.
Set the options as environment variables before the command, as in $env:HAND_INSTALL_VERSION = 'edge'; irm https://raw.githubusercontent.com/atqamz/hand/main/install.ps1 | iex:
HAND_INSTALL_VERSION:edgefor the rolling build ofmainthat passed CI, or a pinned release such asv0.14.0;HAND_INSTALL_DIR: the folder, which may contain spaces;HAND_INSTALL_BASE: the releases URL, as ininstall.sh.
They stay set in that PowerShell window until you close it.
Or pass them as switches, which win over the variables:
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/atqamz/hand/main/install.ps1))) -Edge-Edge, or-Version v0.14.0;-Dir PATH;-Base URL;-TimeoutSec N: give up on a download that takes longer than N seconds (default 300). It has no variable.
Hand runs its own git with core.longpaths=true, and starts Luvus with GIT_CONFIG_COUNT, GIT_CONFIG_KEY_0 and GIT_CONFIG_VALUE_0 set to the same, so a worker's git inherits it unless you already set GIT_CONFIG_COUNT. A Luvus server you started yourself does not have it. For the git you run and as a fallback, allow long paths once:
git config --global core.longpaths trueHand never starts a .cmd or .bat for a harness: cmd.exe would run it in the wrong folder and mangle the prompt, and text from an issue that reaches the prompt could run as a command. npm install -g makes such a shim (%APPDATA%\npm\codex.cmd) and leaves the native .exe inside the package under node_modules. For claude (@anthropic-ai/claude-code), codex (@openai/codex), opencode (opencode-ai) and copilot (@github/copilot, whose .exe is in its @github/copilot-win32-x64 or -arm64 package under it), Hand looks there beside the shim and starts that .exe, so the npm install works. For agy and omp, and when the exe is missing (--omit=optional, or --ignore-scripts for claude and opencode, whose install script puts it there), Hand refuses the shim, naming the file. The omp installer has a PowerShell path that should produce a real omp.exe, but a Windows install has not been run. Install the standalone Codex instead, from its README: powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex". As of Codex 0.163 it puts codex.exe in %LOCALAPPDATA%\Programs\OpenAI\Codex\bin and on your user PATH. Hand takes a .exe anywhere on PATH before any .cmd, so putting the folder of a codex.exe on PATH is enough too. Get-Command codex -All lists what PATH holds, and hand doctor and hand models show a harness found only as a batch file as unavailable. The board searches the PATH it started with, so restart it after you change PATH or install a harness: hand board stop, then hand board.
Claude asks once per folder whether to trust it, and every worktree is a new folder. Trust the worktrees folder once from PowerShell, so it does not ask in each one:
$w = if ($env:SECONDHAND_HOME) { "$env:SECONDHAND_HOME\worktrees" } else { "$HOME\.secondhand\worktrees" }; New-Item -ItemType Directory -Force $w | Out-Null; cd $w; claudeCodex asks once per repository whether to trust it, and a worktree is trusted through its main repository. Run codex once in the repository root and choose Trust, and it asks in none of that repository's worktrees: Hand then presses nothing and prints trust: not asked. Without that, Hand presses the screen when it names the worker's own worktree and the project's repository, which saves the trust for that repository in $env:CODEX_HOME\config.toml (~\.codex\config.toml by default) for all of your Codex use; Safe use says what that means. Hand never edits config.toml.
Hand tells a Windows worker to write its report to a file outside the worktree and run hand report add --status done --file PATH, which works in PowerShell and in Git Bash.
Signing and ssh, from secondary sources that nobody has verified on a Windows machine running Hand: HTTPS with gh auth login is the low-ceremony way to push, and needs no agent. Windows' ssh-agent service is disabled by default, and Git for Windows' bundled ssh does not talk to it unless you set git config --global core.sshCommand C:/Windows/System32/OpenSSH/ssh.exe. A worker that signs commits with an ssh key needs both.
Windows user profile folders whose names contain spaces or non-ASCII characters are untested.
What differs from Linux and macOS:
- there are no units: no service keeps the watcher, the board or Luvus alive.
hand supervisor startstarts the watcher when it is free,hand openstarts the board when none answers, the running board starts the watchers again within 30 seconds of a crash, and Luvus starts directly; - desktop notifications are off: the watcher's notify step does nothing;
- stopping a worker ends the root and its descendants at once, found by parent process ID, with no graceful phase. The root is
hand, with the harness as its child, whereas Linux and macOS replacehandwith the harness and signal the worker's process group instead, and Linux also ends the processes left in the attempt's worktree; macOS does not. What each reaches is in Attempt; hand updateswaps the binary by renaming the runninghand.exetohand.exe.old, stops the watcher and the board by their process IDs, and starts them again. The nexthandrun deletes the old file.
See Autostart to run the board at logon.