hand
Health Gecti
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 27 GitHub stars
Code Basarisiz
- rm -rf — Recursive force deletion command in install.sh
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
Things getting out of hand? You may need second `hand`.
Hand
Hand is a personal supervisor layer for agents. You talk to one supervisor agent. It captures every request as a task, starts Claude Code, Codex, opencode or Antigravity workers in isolated git worktrees through Luvus, waits for them with zero tokens, reads their reports, and asks you only through decisions. Each fleet is a folder with its own SQLite state, and one hand board shows every fleet.
Hand is built for one operator, not as a product for other users. It supports Linux, macOS and native Windows; Platform support says how each part is verified. The design and its non-goals are in docs/spec.md, and every term used here is defined in docs/vocabulary.md.
Wiki: deepwiki.com/atqamz/hand, a generated overview for browsing the code.
flowchart LR
operator["Operator"] -- "messages, answers" --> board["hand board"]
board -- "messages" --> supervisor["Supervisor sN"]
supervisor -- "tasks, plans, decisions" --> state[("hand.db")]
supervisor -- "hand attempt start" --> w1["Worker a1<br/>worktree t1-a1"]
supervisor -- "hand attempt start" --> w2["Worker a2<br/>worktree t2-a2"]
w1 -- "hand report add" --> state
w2 -- "hand report add" --> state
state -- "wakes, through hand watch" --> supervisor
state -- "tasks, reports, decisions" --> board
The idea of one agent running a crew of workers in worktrees is also the idea behind firstmate. Hand keeps the supervisor, the fleet memory and the determinism, in one Go binary with few dependencies.
Why
A single agent works well on one task. Running several at once leaves someone to:
- write each request down before work starts, so nothing lives only in chat;
- keep concurrent work in separate worktrees and branches;
- know which worker is running, blocked, quiet or finished, without watching terminals;
- keep plans, reports and decisions when a chat session ends;
- ask the operator only when a choice is theirs;
- stop and clean up workers without losing uncommitted work.
In Hand, the supervisor handles judgement and hand handles the mechanics. Workflow state lives in a state machine over SQLite, never in an agent's memory, so a fresh supervisor orients from hand orient alone. Waiting costs no tokens: a watcher wakes the supervisor only when something changed, when a timer it armed comes due, or when it idles while nothing watches its active tasks.
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.26.5 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 under~/.secondhand/luvus/, so a system upgrade never changes the Luvus a fleet runs: 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) or opencode 2.x (opencode). Run Codex once before using it, so its model cache exists. opencode uses the model from its own configuration. - Optional: the Antigravity CLI (
agy), logged in, so thatagy modelslists its models. - 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.
Platform support
Hand supports Linux, macOS (Apple silicon; Intel builds are released but not tested) and native Windows (amd64, Windows 10 or 11). Supported means the project fixes problems that people report on those systems, not that every step has run on a real machine; the table below says how each part is verified. Known gaps: Windows has run in CI, with the real Luvus, and not yet on a real Windows machine; the real Mac run of 2026-10-17 (#895) used a stand-in claude; Windows has no desktop notifications; the macOS LaunchAgent's RunAtLoad at a real login is unverified.
Report a problem with the bug form, and a vulnerability as SECURITY.md says.
CI runs the tests on GitHub's latest Ubuntu (amd64), macOS (Apple silicon) and Windows Server (amd64) runners. Every job installs the tested Luvus and runs the TestLive* tests against it; the other tests use a fake one. The Linux arm64 and Intel macOS builds are released but not run in CI.
In the table:
- daily: a real Linux machine runs it every day, with the real Luvus and harnesses;
- real: it has run on a real Linux machine, but not every day;
- CI: CI runs it on that OS, with the real system calls;
- CI, fake: CI runs it on that OS, but a fake stands in for the harness, Luvus, the release server, the browser, the notifier or the watcher and board processes;
- real Mac (#895): it ran on a real Mac (macOS 26.5.1, Apple silicon) in the first dogfood run, the issue linked. Stand-in claude: a script stood in for
claude, so the run covers Hand's own processes and panes but not the real worker path; - not run: the code exists but has never run on that OS;
- no: not supported.
| What | Linux | macOS | Windows |
|---|---|---|---|
| Install script | CI, fake | real Mac (#895) | CI, fake, in PowerShell 5.1 and 7 |
hand init and the Luvus pin |
daily | real Mac (#895) | CI, real Luvus |
| Download the tested Luvus | CI, real Luvus | CI, real Luvus | CI, real Luvus |
| Luvus server, panes and events | daily | real Mac (#895), stand-in claude | CI, real Luvus over its named pipe |
| Start claude, and accept its trust screen | daily | CI, fake | CI, fake |
| Start codex or agy | real | CI, fake | CI, fake |
| Start opencode | CI, fake | CI, fake | CI, fake |
| A worker reports through the footer | daily | CI, fake | CI, fake |
| Stop a worker | daily | real Mac (#895), stand-in claude | CI |
hand attach |
CI, fake | real Mac (#895), stand-in claude | not run |
| Start the watcher on demand, and the board keeps watchers alive | daily | real Mac (#895) | CI, fake |
hand update |
daily | real Mac (#895), from v0.11.0 | CI, fake |
Luvus switch on repin: stop the server through UHP and server.pid |
CI, fake | CI, fake | CI, fake |
| Luvus switch of a quiet fleet: stop the supervisor and the server, start the pin, resume the supervisor | CI, real Luvus, fake claude | CI, real Luvus, fake claude | CI, real Luvus, fake claude |
hand board --update-every, the timer's hand update |
CI, fake | CI, fake | CI, fake |
| Desktop notifications | daily | real Mac (#895) | no |
hand open starts the board and opens the browser |
CI, fake | real Mac (#895) | CI, fake |
| Paths over 260 characters | not needed | not needed | CI for Hand's git; not run for a worker's |
| Start the board at login | daily | real Mac (#895) by bootout and bootstrap; not at a real login | not run |
| The watcher starts a killed Luvus server again | CI, real Luvus, fake claude | real Mac (#895), stand-in claude | CI, real Luvus, fake claude |
On Linux, the setup without a systemd user session (plain SSH, for example) has run only in CI. Hand configures neither commit signing nor ssh on any OS.
Real-machine checks
To help, run the checks in docs/dogfood/ on a real machine, yourself or with a coding agent: one set of checks, with a throw-away home that never touches your own fleets. The checks live only there, riskiest first, and so do the checks for Linux without a systemd user session and for Linux arm64. Report what fails through the issue form; when a run passes, the row of the table above changes from "CI" to a real-machine result, with a link to the run issue.
Install
Install the latest release on Linux or macOS (amd64 or arm64):
curl -fsSL https://raw.githubusercontent.com/atqamz/hand/main/install.sh | sh
You can choose another version:
- Edge:
curl -fsSL https://raw.githubusercontent.com/atqamz/hand/main/install.sh | HAND_INSTALL_VERSION=edge sh. Edge is the rolling build ofmainthat passed CI. - A pinned release:
HAND_INSTALL_VERSION=v0.11.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.
To update an installed Hand, run hand update from anywhere. It does the following:
- downloads the newest build of the channel the running
handcame from, and checks it; - backs up every fleet's
hand.dband the old binary under~/.secondhand/backups/; - replaces the binary, then stops each fleet's watcher and the board by the process IDs they record (
watch.pidin the fleet folder,board.pidin~/.secondhand) and starts them again ashand supervisor startandhand opendo, on every OS; - runs
hand initin every fleet.
It also pins the Luvus version that build was tested with when that is newer than the pin. On every OS it compares the version a UHP ping reports with the pin's: a server that does not answer is not running and has nothing to switch, and one that answers without a version is unknown and is never switched.
A server that runs another version than the pin is switched, fleet by fleet, only when the fleet is quiet and settled. Quiet: no worker attempt is live, the supervisor is idle, no message waits for it, and no pane is live in that server besides the supervisor's and the shell Luvus opens when it starts. Settled: the fleet recorded no event for 15 minutes. The checks run in this order, and a fleet that fails one stays pending: <reason> in the fleets rows, which names a pane Hand did not start, since stopping the server ends it (a check before step 2 changes nothing, and the watcher keeps running):
- quiet and settled;
- stop that fleet's watcher, so nothing wakes the supervisor;
- the supervisor's screen revision (
content_revision) is unchanged over 30 seconds, and the fleet is still quiet; hand supervisor stop, then check quiet again: a message queued meanwhile resumes the supervisor and leaves the fleet pending;- stop the server (it sends
server.stopand waits up to 10 seconds for the process in the session folder'sserver.pidto exit; it ends the process itself, its whole process tree on Windows, only when the stop was acknowledged or the server no longer answers, so a server that answerspingbut refusesserver.stopis left running, and a recycled process ID is never signalled), start the pinned copy, and require thatpingreports the pin's version; - resume the supervisor, start the watcher, and record the event
luvus.switched(0.14.3 -> 0.15.0), whichhand orientlists and the board shows as one line in the chat.
Any failure at step 4 to 6 (the supervisor's stop, the server's stop or start, a wrong version) records luvus.failed with the reason (a server that comes back with another version is one), puts the supervisor and the watcher back, and leaves hand update with a non-zero exit. The board's timer does not try that pin again; your own hand update does, once the fleet is settled again. luvus.failed and luvus.switched are events like the others, so the 15 minutes start again after each. A hand update run inside a fleet's Luvus panes (LUVUS_SESSION names that fleet's session) leaves that fleet pending; the board's timer runs outside every pane and may switch it later.
What a switch costs:
- a
hand attachviewer is disconnected (Luvus cannot tell Hand who is attached): attach again; - text typed but not sent in the supervisor's pane is lost if the screen stayed still for the 30 seconds (a supervisor whose screen keeps changing while idle never passes that check, so its fleet stays pending);
- no worker loses work, because a switch needs no live attempt and no pane that Hand did not start; the files in the worktrees stay;
- the supervisor comes back as a new
sNon the same session.
When the binary is already current and no newer Luvus needs pinning, hand update visits only the fleets whose server is stale: it does not stop the board, does not run hand init, and leaves every other fleet's watcher alone. hand update --check lists a fleet's server as not running or unknown under that name, plans no switch for it, and shows would switch or would stay pending: <reason> for a stale one. hand update signals only a process whose recorded binary is the one being updated, and only while its recorded start time still matches, and it restarts only what it stopped. It stops a process with SIGTERM (TerminateProcess on Windows), which exits cleanly, so systemd does not restart an installed unit itself: an installed board unit is started again, and an installed watch unit stays inactive while the watcher runs as the transient unit secondhand-watch-<fleet id> until your next login. If both ever run, the second watcher finds watch.lock held and exits with status 3, and a second board fails to bind the board's address. While an update runs, the board starts no watcher. Once it starts changing things it ignores Ctrl-C, and its own steps run outside the terminal's process group, so it does not leave a fleet without its watcher. If it is killed anyway, the next hand update finishes the steps it left, from its journal at ~/.secondhand/update.json. A supervisor that could not be resumed after a switch stays in that journal, so the next hand update, including the board's timer, resumes it first. A failed update can leave the new Luvus pin in place; a running server ignores it, and the next hand update carries on. A failed switch never edits pin.json itself: the pin moves before any fleet is visited, and hand luvus pin BIN changes it by hand.
To restore a backup:
- stop the board, then the fleet's watcher, so the board cannot start it again. Their process IDs start
board.pidin~/.secondhandandwatch.pidin the fleet folder. A stale file can name a reused ID, so first check that each ID is still ahandprocess (ps -p PID; on Windows,Get-Process -Id PID), thenkill PID(Stop-Process -Id PID) and wait until it has exited; - run
hand supervisor stopin the fleet if its supervisor runs, at a time when no worker runs, so nothing writes tohand.db; - remove
hand.db-walandhand.db-shmnext to the fleet'shand.db; - copy the backup over
hand.db: ahand.db.<stamp>fromhand update, or a dailyhand-YYYYMMDD.dbfrom the watcher, both under$SECONDHAND_HOME/backups/<fleet id>/(~/.secondhand/backups/<fleet id>/unless you set it); - put the old binary back from
~/.secondhand/backups/hand.<stamp>if the new one is the problem; - run
hand opento start the board again, then start or resume the supervisor from the fleet's page.
The options:
hand update --checkshows what would change and changes nothing; itsunitsrows list the stop and start steps a real run would take for each fleet's watcher and the board, each markedwould run;hand update --channel edge(orstable) moves to the other channel;hand update --keep-luvusnever switches a fleet's Luvus server: a version difference stayspending: --keep-luvus, and a current binary returns at once;hand update --autois what the board's timer passes (Autostart): it does not try a switch again for a pin that already failed;- a
handbuilt from source needs--channel.
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:
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/atqamz/hand/main/install.ps1)))
The 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. Open a new terminal afterwards.
The options go after the command, as in ... -Edge:
-Edge: the rolling build ofmainthat passed CI;-Version v0.11.0: a pinned release;-Dir PATH: the folder, which may contain spaces.-TimeoutSec N: give up on a download that takes longer than N seconds (default 300).
HAND_INSTALL_VERSION, HAND_INSTALL_DIR and HAND_INSTALL_BASE set the same defaults as in install.sh.
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 true
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; claude
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 its whole process tree at once, with no graceful phase;
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.
Getting started
Install Hand as described above. You use two commands, then the browser. The other hand commands are for the supervisor, the agent that runs your fleet.
Make a fleet. In a folder of its own:
mkdir -p ~/fleets/demo && cd ~/fleets/demo hand initThis writes
hand.db, the folder'sAGENTS.mdandCLAUDE.md, thesecondhandskill, a starterrouting.jsonandmemory/operator.md, registers the folder under~/.secondhand, and pins Luvus, downloading the tested version when none is onPATH. It starts nothing. Running it again is safe.Open it.
hand openopens the fleet's page in your browser and logs you in.hand open --printprints the login link for another browser or a phone; keep it private, since it holds the fleet's token. When no board answers,hand openstarts one first (Keep the watcher and the board running).Work in the browser. A new fleet has no supervisor yet: press Start and pick a routing profile (
defaultis a good first one). That starts the supervisor in the fleet's Luvus session and its watcher. Then:- write to the supervisor in the Chat tab, and name the repositories it should work on by absolute path;
- answer its decisions on the Needs you tab;
- read its reports, and press Mark read.
The other commands (hand task, hand attempt, hand decision, hand report, hand orient, hand wait and the rest) are what the supervisor and its workers run. You rarely need them, and the command table is a reference.
The daily loop
What you do:
- Open the fleet's page with
hand open. The Needs you tab lists what waits on you: open decisions, the supervisor's blocked screens, unread reports, failed, exited or interrupted attempts, a supervisor that idles with unwatched work, and a supervisor that stopped unexpectedly. - Ask for work in the Chat tab. The supervisor captures each request as a task before doing anything else.
- Answer decisions on their card or their page, or with
hand decision answer dN "ANSWER". - Read reports, and press Mark read once you have read one.
- Follow a task on its page, or open its newest deliverable with
hand open tN --delivered. - Find a task with Browse finished tasks under the task list, or the search field above it. The Tasks page searches titles, goals and refs over every task: with a query it covers every status, and the Status filter narrows it to finished, active, inbox, done or abandoned. It also filters by project and by date (a date is a UTC day, shown when you hover the field), and groups the results by day, 50 to a page.
What the supervisor does, following the secondhand skill:
hand orient, at the start of every turn and after every wake.- Capture with
hand task add, start withhand task start tN, and plan withhand plan set --body-file PLAN.md tN. - Start a worker with
hand attempt start --profile NAME --prompt-file BRIEF.md tN. - Wait: wakes arrive as messages that start with
[hand v1 wake]. For something outside Hand, such as a PR review, it arms a timer withhand wake(managed supervisors only; one opened by hand waits withhand wait --timeout). Before it ends a turn it runshand orient, and it never ends one while tasks areunwatched. - Read reports with
hand report show rN, act on them, and acknowledge them withhand report ack rN. - Ask you with
hand decision ask tN "QUESTION". - Verify the acceptance check, then
hand attempt stop aNif the worker still runs,hand attempt clean aNandhand task done tN.
Routing profiles live in <home>/routing.json, and hand route list shows them. The starter has quick, default, high, deep and max: claude sonnet at rising effort.
Fleets
A fleet is any folder hand init has run in. Commands find it from --home DIR (given before the command), from the working directory or any folder inside it, or from $HAND_HOME. Run as many fleets as you like: each has its own Luvus session, worktrees and watcher, and one board serves them all. hand fleet list shows them.
Rename a fleet with hand init --name NEW. To move one, mv the folder and run hand init in its new place.
A fleet folder looks like this:
~/fleets/demo/
├── AGENTS.md Hand's; rewritten by every hand init
├── CLAUDE.md Hand's; points to AGENTS.md
├── .claude/ .agents/ .grok/ .pi/
│ the secondhand skill, for Claude Code, Codex, Grok and Pi
├── hand.db the fleet's state
├── routing.json routing profiles
├── memory/
│ ├── operator.md your standing preferences
│ └── projects/ one file of lessons per project
├── inbox/ files attached on the board, and .trash/ for pruned ones
└── board.token the board's token for this fleet
hand init rewrites AGENTS.md, CLAUDE.md and the skills every time, so put your own preferences in memory/operator.md instead. hand orient shows the supervisor up to 4000 bytes of that file on every turn.
Hand keeps shared state in ~/.secondhand, or $SECONDHAND_HOME. There, fleets/ holds a file per fleet ID that names its folder, and worktrees/ holds every worker worktree, outside every fleet:
~/.secondhand/
├── fleets/<fleet id> file naming the fleet folder
├── worktrees/<fleet id>/tN-aN/ one worktree per attempt
├── luvus/ pinned Luvus copies and pin.json
└── board.addr where the running board listens
Each attempt works on the branch hand/<fleet id>/tN-aN in the project's repository. hand attempt clean aN removes the worktree and keeps the branch.
Keep the watcher and the board running
hand supervisor start, resume and switch start the fleet's watcher when none runs. In a systemd user session it runs as the transient unit secondhand-watch-<fleet id>. Otherwise it runs detached, with its output in watch.log in the fleet folder. hand orient shows watch: running or watch: missing. That watcher stops with the user session, and systemd restarts it after a crash, except when it exits with status 3: another watcher already holds watch.lock, the fleet is not found, or the pinned Luvus changed. Retrying would not help in any of them. hand open starts the board the same way when none answers: systemctl --user start secondhand-board when that unit is installed, otherwise the transient unit secondhand-board, otherwise detached with its output in board.log in ~/.secondhand. It fails with the log or journal command to read when the board does not answer within 10 seconds. On every OS, the running board also keeps the watchers alive: at startup and every 30 seconds it starts the watcher of each fleet that has a live supervisor, or that has autoresume set in routing.json and an ended supervisor to resume, when its watch.lock is free. A fleet with neither gets no watcher, and a failure to start one is logged once to the board's log and does not stop the board. See Autostart to run the board at login.
In a systemd user session, Hand starts each fleet's Luvus server in a transient scope (systemd-run --user --scope), so the server leaves the cgroup of whatever started it, and restarting the watcher or the board never stops the agents. The scope has no fixed name and nothing restarts the server in it: when the server dies, the watcher's reconnect starts a new, empty one. Without a user session, for example over plain SSH, or when systemd-run fails, Hand starts Luvus directly, and its cgroup is its starter's.
Autostart
One autostart is enough: run hand board once at login. The board starts every fleet's watcher that needs one, so there is no per-fleet unit. Add --update-every to its command (Update on a timer) and it also keeps Hand up to date and switches the Luvus server of each fleet that is quiet; without the flag nothing switches by itself. An autostart that already exists needs the flag added to its command (the unit, the plist or the shortcut), and the board restarted, before it does.
Linux
mkdir -p ~/.config/systemd/user
hand unit board > ~/.config/systemd/user/secondhand-board.service
systemctl --user daemon-reload
systemctl --user enable --now secondhand-board
The unit starts with your graphical session: it is After= and WantedBy= graphical-session.target, which systemd reaches once the session manager has imported WAYLAND_DISPLAY, DISPLAY and the rest of the session environment. The Luvus server and every pane inherit that environment, so pinentry can prompt. It has no PartOf=, so ending the session does not stop the unit or the agents; they keep running while your user manager does, which after the last logout needs loginctl enable-linger. The target is started only by a session manager that integrates with systemd, such as uwsm, GNOME or KDE Plasma. A session that does not start it, and a host without a graphical session such as a headless server, never reaches that target, so the unit never starts there; edit WantedBy=graphical-session.target back to WantedBy=default.target and delete the After= line, then run systemctl --user daemon-reload and systemctl --user reenable secondhand-board, since the old link in graphical-session.target.wants stays until you do.
The unit keeps the PATH of the shell you ran hand unit board from. systemd starts user units at boot, before your desktop session exports its PATH, and the board and the watchers and Luvus server it starts need it to find claude, hand and your tools. Run hand unit board again, and systemctl --user daemon-reload, after you install tools into a new folder.
Migrating from watch units: earlier versions printed a unit per fleet with hand unit watch, which no longer exists. The board starts the watchers again within 30 seconds, and no Luvus restart or quiet time is needed. For each installed watch unit:
systemctl --user disable --now secondhand-watch-<name>
rm ~/.config/systemd/user/secondhand-watch-<name>.service
systemctl --user daemon-reload
To move from one board per fleet, remove each old board unit the same way, then install the one above:
systemctl --user disable --now secondhand-board-<name>
rm ~/.config/systemd/user/secondhand-board-<name>.service
systemctl --user daemon-reload
Update on a timer
hand board --update-every 1h makes the running board start hand update every hour, detached, on every OS. It is off by default (0), and the least it accepts is 10m. The automatic run uses the channel the installed binary was built for, so a build from source cannot use it: the board says so once and starts without it. The timer is what makes Hand switch a fleet's Luvus server by itself: each run switches the stale fleets that are quiet and settled (see Install), and with a current binary it visits only those. Turn the timer off (0), or run hand update --keep-luvus by hand, to never switch. A second hand update while one runs exits with a message, and the board skips its turn. The last automatic run's output is in update.log in ~/.secondhand. When the update restarts the board, the new board keeps the interval, which the board records in board.update-every (the last board start wins: one started without the flag clears it). A turn skipped because an update runs or the lock is held is tried again within 30 seconds. On Linux, add the flag to ExecStart= in ~/.config/systemd/user/secondhand-board.service and add KillMode=process under [Service], so that systemd does not end the update when the board stops, then run systemctl --user daemon-reload and systemctl --user restart secondhand-board:
ExecStart="/home/you/.local/bin/hand" board --addr 127.0.0.1:7777 --update-every 1h
KillMode=process
On macOS add <string>--update-every</string><string>1h</string> after <string>board</string> in the plist, and on Windows add --update-every 1h to the shortcut's Arguments.
macOS
Run on a real Mac with Hand 0.11.0: the recipe loads, runs the board and respawns the watcher. Still unverified (issue #761 tracks it): RunAtLoad at a real login, the Background Items notice that macOS may show at login, and the real claude on macOS. Save this as ~/Library/LaunchAgents/dev.secondhand.board.plist, with the absolute path of your hand binary, and a PATH that holds claude, git, luvus and your other tools, since launchd gives agents a minimal one:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>dev.secondhand.board</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/hand</string>
<string>board</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
Load and unload it with:
launchctl load ~/Library/LaunchAgents/dev.secondhand.board.plist
launchctl unload ~/Library/LaunchAgents/dev.secondhand.board.plist
The plist has no KeepAlive: a second board exits non-zero when it cannot bind the board's address, and KeepAlive would restart it in a loop. launchd therefore does not restart a crashed board; the next hand open starts it again.
Windows
To run the board at logon without admin rights, put a shortcut in the Startup folder. This creates it from PowerShell:
$link = (New-Object -ComObject WScript.Shell).CreateShortcut("$([Environment]::GetFolderPath('Startup'))\Hand board.lnk")
$link.TargetPath = "$env:LOCALAPPDATA\hand\bin\hand.exe"
$link.Arguments = "board"
$link.WindowStyle = 7
$link.Save()
shell:startup in the Run dialog opens the same folder, to move or delete the shortcut.
The board
The board listens on 127.0.0.1:7777. Each fleet has its own page at http://127.0.0.1:7777/<fleet id>/, and / lists the fleets. Each fleet keeps its own token in board.token in its folder, so logging in to one fleet never logs you out of another. A fleet you add or move shows up without a restart. The board also keeps each fleet's watcher alive (see above). To reach the board from a phone, use a tunnel such as ssh -L or tailscale serve.
A fleet's page has a slim rail of icons at the left edge (Board, Tasks, Conversation and the fleets this browser can open) and, from 900px wide, two panes. The work pane lists what waits on you (Needs), grouped by severity with the most severe first, then the tasks with a search field (Tasks). A decision shows its options as a choice list: pick one, add a note (required when the option starts with "Other"), and send. The supervisor pane on the right shows the supervisor controls, the conversation without tool calls or thinking, and the message box. Drag the line between the panes, or focus it and use the arrow keys, to resize the chat; the board remembers the width, and a double-click resets it. Below 900px the page has three tabs, Needs, Tasks and Chat, and the rail opens from a menu button as a sheet. Tasks, decisions and reports open in a new tab; the rail and the page's own links stay in place. The Tasks page searches every task. Each task and each decision also has its own page. The board is a projection of Hand's state, so restarting it loses nothing, and it updates itself as things change.
hand open opens a page in your browser, from inside a fleet:
| Command | Opens |
|---|---|
hand open, hand open supervisor |
the fleet page |
hand open tN |
the task |
hand open dN |
the decision |
hand open rN, hand open aN |
the report or attempt, on its task's page |
hand open tN --delivered |
the task's newest deliverable: a PR, a document or a file |
--delivered never opens a file itself, because the path is text from a worker's report or hand task done --deliverable and the OS opener runs programs: it opens the folder that holds a file, or the folder itself, and opens a URL only when its scheme is http or https; --print prints the path or URL as it is. It never opens a UNC path (\\host\share\...), because Windows would connect to the host it names and could leak credentials: it exits with an error that prints the path, and when the newest deliverable is a UNC path it does not fall back to an older one. It logs the browser in with the fleet's token through xdg-open (on macOS, open) and never prints the token; hand open --print prints the login link instead, for a phone or another browser, so keep that output private. It finds the board through ~/.secondhand/board.addr, which the running board writes, and starts a board first when none answers.
The board is where you start, chat with and resume the supervisor. Those controls work only while the board listens on a loopback address, which a tunnel keeps true. On a network address such as 0.0.0.0 the supervisor controls are off; answering decisions and marking reports read still work.
Alerts on your phone
The watcher alerts you when an attempt ends, blocks, hits a usage limit or goes quiet (a repeated turn end, attempt.idle, does not alert), when the supervisor asks a decision (t3 decision: Keep the old cookie name?), and once when the supervisor has idled 60 minutes with unwatched work (When the supervisor idles). By default that is a desktop notification. To send every alert to your own command instead, put its argv in routing.json. This sh example is for Linux and macOS:
"notify": ["sh", "-c", "exec curl -s -H \"Title: $HAND_NOTIFY_REF\" -H \"Click: $HAND_NOTIFY_URL\" --data-raw \"$HAND_NOTIFY_TEXT\" https://ntfy.sh/CHOOSE-A-LONG-RANDOM-TOPIC"]
Install the ntfy app on your phone and subscribe to that topic. Hand runs the command without a shell, with HAND_NOTIFY_KIND (decision.asked, attempt.blocked, attempt.quiet, attempt.limited, attempt.exited, attempt.failed, attempt.interrupted, attempt.long, supervisor.limited, supervisor.stalled), HAND_NOTIFY_REF (d3, a7, supervisor), HAND_NOTIFY_TEXT and HAND_NOTIFY_URL set. The URL is the item's board page at the address the running board listens on, and is empty when no board runs. The command gets 10 seconds, and a failure is ignored. When the timeout expires Hand kills the command; on Linux and macOS it kills the whole process group, but on Windows only the command itself, so a child of sh -c can outlive the kill. Prefer an argv that runs the program directly, or exec inside sh -c. A routing.json that cannot be read leaves the desktop notifier in use (on Windows there is none, so no alert is sent), and the watcher writes one line about it to its own output, watch.log when it runs detached, which no one is shown. --notify=false turns all alerts off.
On Windows, this toast recipe uses powershell.exe (unverified: it has not been run on a Windows machine). It borrows Windows PowerShell's app ID, because Windows may show no toast for an app ID that nothing registered. Hand starts it without a console window:
"notify": ["powershell.exe", "-NoProfile", "-Command", "[void][Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]; $x = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent('ToastText02'); $t = $x.GetElementsByTagName('text'); [void]$t.Item(0).AppendChild($x.CreateTextNode($env:HAND_NOTIFY_REF)); [void]$t.Item(1).AppendChild($x.CreateTextNode($env:HAND_NOTIFY_TEXT)); [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe').Show([Windows.UI.Notifications.ToastNotification]::new($x))"]
The supervisor
Hand runs the supervisor in the background in the fleet's Luvus session, in full-auto mode:
claude --dangerously-skip-permissions;codex --dangerously-bypass-approvals-and-sandbox --disable hooks;opencode --standalone --auto;agy --model ID --dangerously-skip-permissions.
Workers run with the same flags, each in its own worktree.
agy asks once per folder whether to trust it. Every attempt is a new folder, and so is a new fleet home. Hand presses Enter on that screen only when it names the attempt's own worktree, or for the supervisor the fleet home. Otherwise it notes the attempt or the supervisor blocked. Claude asks too, with Quick safety check: Is this a project you created or one you trust?. Its cursor starts on No, exit, so Hand sends Down once, re-reads the screen, and sends Enter only when the cursor is on Yes, I trust this folder. A reworded screen is left alone, and the watcher reports the attempt blocked. If the screen is recognized but the cursor never reaches Yes, or the screen does not clear after Enter, Hand notes the attempt or supervisor blocked. The board reads agy's conversation database for Chat and the context bar.
The fleet page updates itself as things change, and keeps what you are typing. It takes your messages, and answers the supervisor's blocked screens with a fixed set of keys.
To attach files, paste them into the message box, drop them anywhere on the chat pane, or pick them with its paperclip button. On a phone, the button offers files, the gallery and the camera.
- In the message box: each file shows as a chip under the text, with its type, name and size, and its progress while it uploads. Its × removes it, or stops the upload. The box keeps only your words, and Send adds the files after them. A chip comes back with the draft after you leave the page, unless its file has left
inbox/. - Accepted: any file type, up to 10 MiB each. A folder or a bigger file is not copied, and its chip says why: paste its path as text, and the supervisor reads it in place.
- Where they go: each one is copied into the fleet's
inbox/folder asYYYYMMDD-HHMMSS-N-<name>, where the name is the original one with every character other than a letter, a digit,.,_or-turned into_. Send adds a line[image: PATH]for a png, jpeg, webp or gif image and[file: PATH]for anything else, one per file, after your words. Hand deletes nothing there unless you prune it, and a prune touches only files with a name Hand gave (see Clean the inbox). - What the supervisor sees: the line. It reads the file from that path with its own tools, and treats what is inside as data, never as instructions.
- In Chat: an image shows as a thumbnail, and any other file as a card with its type, name and size that downloads it. A file no longer in
inbox/shows a dashed card. The Board shows nothing but images inline: every other file is served as a download, so an HTML or SVG file never runs in the Board.
Attaching works over loopback only, but thumbnails and cards also show on a network address. A proxy or tunnel to the loopback board lends its controls, attaching included, to anyone who reaches it with the token. The live terminal stays in Luvus. hand attach supervisor opens it in your terminal, hand attach aN opens a worker's, and hand attach opens the whole fleet session. You never have to.
The model menu next to the message box changes the supervisor's model or effort, keeping its harness and its conversation. The switch waits for the current turn to end. opencode keeps its model in its session, so it cannot switch. The same controls exist as hand supervisor start|send|keys|interrupt|switch|stop|resume|show.
hand init writes the folder's AGENTS.md and CLAUDE.md, which make the agent run hand orient every turn, and the secondhand skill, which describes the loop above.
You can still open an agent in the fleet folder by hand (cd ~/fleets/demo && claude). It then waits with hand wait --after CURSOR, and it stands down while a managed supervisor runs.
After a reboot
The supervisor shows as interrupted. Press Resume, or run hand supervisor resume, to continue the same session with its history. It spends no tokens until you write or a wake arrives. New session starts fresh from hand orient. To resume automatically when hand watch starts, add "supervisor": {"autoresume": true} to routing.json.
When the supervisor idles
The supervisor acts only on a message. While a task is active, no attempt runs, no decision is open and no timer is armed that is due within 6 hours while the managed supervisor runs, no event will ever come for it, and its work is unwatched. The watcher then wakes the supervisor with supervisor.stalled once it has been idle for 20 minutes, again 40 minutes later, then every 60 minutes while nothing changes. The wake names the tasks and the idle time. Any event other than the stall wake and its delivery starts the count again, and the next wake comes no sooner than after (20 minutes by default) after the supervisor last worked. Hand never sends it to a supervisor that is stopped, blocked, at a usage limit or switching models. After the first one, the Board's Needs shows "Supervisor idle for 1h12m with 3 active tasks and no running attempt" until the work is watched again, and the alert goes out once, on the first due stall wake at or after 60 minutes of idle time. hand orient lists the tasks as unwatched. Change the delays in routing.json (each from 1m to 24h), or turn the wake off with "stall": "off":
"stall": {"after": "20m", "every": "60m"}
Timers
The supervisor waits on something outside Hand, such as a PR review or CI, with a timer instead of a background script, which a switch or resume would lose. hand wake --after 30m --note "check PR 909" wakes it once 30 minutes from now, --at <UTC time, RFC 3339> at that time, and --every 30m --while t137 every 30 minutes until t137 is done or abandoned. The watcher delivers each one as the wake timer.fired w1: check PR 909. Timers live in hand.db, so they survive a watcher restart, a supervisor switch and a resume. Timers belong to the managed supervisor: hand wake is refused while none runs, and a supervisor opened by hand waits with hand wait --timeout instead. The watcher fires them only while the managed supervisor runs: one that came due while the watcher or the supervisor was down fires once when both are back, marked w1, 2h10m late, and a repeating one then carries on from there. While a timer due within 6 hours is armed and the managed supervisor runs, the work is not unwatched, so no stall wake comes; a timer due later does not count, so a far --at cannot silence the supervisor for days, and the stall wake names it (w2 is armed but due in 31d). A repeating timer counts while its next due time is within 6 hours. hand orient lists the armed timers and marks one beyond the horizon, and every one (no managed supervisor: not fired) while none runs. A timer is due at least 1 minute ahead, a fleet holds at most 20 armed timers, and a note is one line of at most 200 characters. hand wake list shows them and hand wake cancel wN drops one. Hand never asks GitHub or CI anything for a timer: it only wakes the supervisor, which does the checking.
Clean the inbox
Attachments pile up in inbox/. hand inbox size shows how many files Hand saved there, their total size, the age of the oldest and of the newest, and what the trash holds. hand inbox prune --older-than 30d moves every attachment last modified more than 30 days ago into inbox/.trash/, and deletes each file that has been in the trash for more than 7 days. With --dry-run it lists both and changes nothing. It prints how many files it moved and deleted, and the space it freed.
- What it touches: only regular files directly inside
inbox/with a name Hand gives (YYYYMMDD-HHMMSS-N.ext, orYYYYMMDD-HHMMSS-N-<name>, with or without an extension). It never touches a symlink, a folder, an upload still in progress (.upload-*) or a file with another name. It stops wheninbox/orinbox/.trashis a symlink rather than a real folder. - How it moves: a rename into
inbox/.trash/on the same disk, so nothing is copied. Each moved file gets the time of the move, so its 7 days in the trash start then. Until they end, you can move it back. - Failures: a file it cannot move or delete is listed under
failed, the others still go, and the command exits 1. - Repeats: a second run moves nothing new. Two prunes of one fleet never run at once: the second exits 3.
- Event: a prune that moves files records
inbox.prunedwith their count and size (3 files, 12 MiB). Deleting expired trash records no event: the move was recorded already.
To prune every day, add this to routing.json:
"inbox": {"keep": "30d"}
The watcher then runs the same prune once a UTC day. It writes the day to inbox/.trash/.pruned, so a restart does not run it again, and a file that failed waits for the next day's run. keep takes days, from 1d to 9999d. With "inbox": "off", or no inbox key, nothing is pruned. A bad value makes routing.json invalid, as a bad stall does: the error names the field, and the watcher logs it once and does not prune. hand orient shows inbox_folder: "612 files, 140 MiB" once the inbox holds more than 500 files or more than 100 MiB.
Safe use
Every worker runs with its permission prompts skipped (see The supervisor), as the operator's own OS user. It can read whatever that account can: ~/.ssh, ~/.config/gh, ~/.gnupg, other fleets and each fleet's board.token. A worktree is not a fence, and Hand builds no sandbox. A worker that obeys text planted in an issue, a PR or a review can use all of it. The protection is the setup around Hand:
- Own account. Run Hand as a dedicated OS user, or in a VM, with its own
~/.sshandghlogin and none of your other directories. - Narrow token. Log that account in to GitHub with a fine-grained personal access token limited to the repositories in use, with the Contents and Pull requests permissions and an expiry date. GitHub does not require an expiry, so set one. Do not use a broad
gh auth login. - Own signing key. Sign with a key used only by that account, ideally a FIDO key (
ssh-keygen -t ed25519-sk) that needs a touch for each signature. An unlocked signing agent signs whatever a process that can reach it asks. - Protected default branch. Require a pull request and refuse force-pushes, so a hijacked worker can propose a change but not land one. GitHub exempts repository admins from these rules unless "Do not allow bypassing the above settings" is on, so give the account write access rather than admin, or turn that setting on. Keep the account, and any role, team or app it belongs to, off every bypass list in branch protection and rulesets.
- Clean environment. Start fleets from a shell with no cloud or API secrets. Hand drops only
HAND_HOMEand the Claude, Codex and Luvus variables when it starts the Luvus server, and its panes inherit the rest of the environment. - Review and rotate. Keep production credentials off the machine, read worker PRs before you merge them, and rotate tokens after a suspicious run.
- Private board. Keep the board on loopback and reach it with
ssh -Linstead of exposing it. - Worker reach. Every pane's environment carries
LUVUS_SOCKET_PATHandLUVUS_API_ADDRESS, so a worker can reach the fleet's Luvus socket, list the panes and send keys or prompts to the others, the supervisor's included. Hand does not prevent this, so the worker boundary is the account separation above, not a Hand control.
Commands
Run hand with no command to list the commands, and a command with no subcommand, such as hand attempt, to list its subcommands. Put flags before the positional arguments; only hand open also takes --delivered after the ref, and hand task done its --deliverable. --home DIR, given before the command, picks the fleet.
| Command | Purpose |
|---|---|
hand init [--name NAME] [DIR] |
Make a fleet, or refresh, rename or adopt a moved one. |
hand fleet list |
List the registered fleets and whether each home is found. |
hand project add NAME PATH, hand project list |
Register a repository by its absolute path, and list projects. |
hand task add --goal TEXT PROJECT TITLE |
Capture a request as an inbox task. |
hand task start tN, hand task done [--deliverable PATH_OR_URL] tN, hand task abandon tN |
Move a task to active, done or abandoned. --deliverable, repeatable, records a file or link the supervisor produced itself: an http(s) URL or an absolute path; any other value is refused, and then nothing is recorded and the task stays as it was. |
hand task list [--status LIST] [--q TEXT] [--since DATE] [--until DATE] [--limit N], hand task show tN |
List tasks newest first (default inbox,active; --status takes the board's words: inbox, active, done, abandoned, finished for done and abandoned, and all alone for every status), or show one with its plan, attempt, report, deliverables (newest first) and decisions. --q searches every status unless --status is given, and keeps tasks where every word, in any case, is in the title or goal or is the whole ref (t12). --since and --until take a UTC YYYY-MM-DD and filter on the day a task finished (done or abandoned; its last update otherwise). Rows of finished tasks carry that day in a finished column. |
hand plan set --body-file PATH tN, hand plan show tN |
Add a plan revision (or --body TEXT), and show the current one. |
hand attempt start --profile NAME --prompt-file PATH tN |
Start a worker in a new worktree from the repository's fetched default branch (origin/HEAD, else origin/main, else origin/master, else its current branch; from a remote ref, unpushed local commits are left out, so pass --base HEAD to keep them); --harness, --model and --effort replace --profile, --base REF picks another start point, and --continue aN checks out the branch of aN, an ended and cleaned attempt of the same task, so pushes reach its PR. |
hand attempt list [--task tN] [--limit N], hand attempt show aN |
List attempts, or show one with its live agent state, or the last screen the watcher read from one that ended. |
hand attempt read [--lines N] aN |
Print a running worker's screen, as an indented multi-line block, and its revision. |
hand attempt keys --revision N aN KEY... |
Answer a worker's screen, only if it is still at that revision. |
hand attempt send --text TEXT aN |
Send a worker a message (or --file PATH, or --file - for stdin), only while it is at its prompt. |
hand attempt stop aN, hand attempt clean [--discard] aN |
Stop a worker, and remove an ended attempt's worktree while keeping its branch. |
hand report add --status STATUS --file - |
Report from inside a worker's worktree: progress, done or stuck, from stdin, a file, or --text. |
hand report list [--task tN] [--attempt aN] [--unacked], hand report show rN |
List and read reports. |
hand report ack [--by NAME] rN |
Acknowledge a report. |
hand decision ask [--file PATH|-] tN [QUESTION] |
Ask the operator about a task; the first line is the headline (at most 120 characters). |
hand decision answer [--by NAME] dN ANSWER, hand decision withdraw dN |
Answer or withdraw a decision. |
hand decision list [--all], hand decision show dN |
List open decisions (or all), and read one with its answer. |
hand route list |
Show the routing profiles and the harnesses. |
hand luvus pin [BIN] |
Pin a copy of a Luvus binary (default: luvus on PATH) for every fleet under this SECONDHAND_HOME. |
hand luvus show |
Show the pinned Luvus and the version of the fleet's running server (from a UHP ping), with match: yes, no, not running or unknown. |
hand orient |
Print the bounded fleet summary the supervisor starts every turn with. |
hand inbox size |
Show the attachments in the fleet's inbox/: how many, their size, the age of the oldest and of the newest, and the trash. |
hand inbox prune --older-than DURATION [--dry-run] |
Move the attachments older than DURATION (days, like 30d) to inbox/.trash/, and delete the files in the trash for more than 7 days; --dry-run only lists them. See Clean the inbox. |
hand wait [--after CURSOR] [--timeout DURATION] |
Block until a wake event arrives, for a supervisor opened by hand. |
hand wake --after DURATION|--at TIME|--every DURATION [--while tN] [--note TEXT] |
Arm a timer (refused without a running managed supervisor) that wakes it once after a delay, once at an RFC 3339 time, or repeatedly; --while tN cancels it when that task is done or abandoned. See Timers. |
hand wake list, hand wake cancel wN |
List the armed timers, or cancel one. |
hand watch [--every DURATION] [--notify=false] |
Run the fleet's watcher. |
hand supervisor start [--profile NAME] |
Start the managed supervisor; --harness, --model and --effort replace --profile, and with no flags it copies the last one. |
hand supervisor resume, hand supervisor stop, hand supervisor show |
Continue the last session, stop it, or show its state, screen revision and number of queued messages. |
hand supervisor send --text TEXT |
Queue a message for the supervisor (or --file PATH, or --file - for stdin). |
hand supervisor keys --revision N KEY... |
Answer the supervisor's blocked screen with enter, esc, up, down, 1, 2 or 3. |
hand supervisor interrupt |
Press Escape in the supervisor's terminal. |
hand supervisor force |
When Luvus misreads the supervisor as blocked but its screen shows an empty Claude Code prompt, type the queued messages (or else the wakes) into it. |
hand supervisor switch --model M --effort E |
Switch the model or effort after this turn; or --profile NAME, or --cancel. --harness H switches to another harness between turns. |
hand board [--addr ADDR] [--update-every DURATION] |
Serve the board for every fleet. |
hand open [--print] [REF], hand open tN --delivered |
Open the fleet page, a task, decision, report or attempt, or a task's newest deliverable, starting the board first when none runs. --print prints the login link instead, for another device or browser. |
hand attach [supervisor], hand attach aN |
Open the fleet's Luvus session, the supervisor's terminal, or a worker's. |
hand unit board |
Print the board's systemd user unit (Linux only; on macOS and Windows it exits 2, see Autostart). It starts with the graphical session. Your PATH, and a SECONDHAND_HOME if set, go into the unit. |
hand update [--channel edge|stable] [--check] [--keep-luvus] [--auto] |
Update the binary from its channel, back up and refresh every fleet, restart each fleet's watcher and the board, and pin the tested Luvus. |
hand version |
Print the version, the channel (source, edge or stable), the commit, the state schema and the tested Luvus. |
Upgrading from 0.7
The new Hand does not read 0.7 data. Its supervisor rebuilds what still matters, with your approval. docs/vocabulary.md lists the 0.7 terms that changed.
- With 0.7 still installed, finish or
hand teardownevery live task. - Keep the old binary if you like, for example
cp "$(command -v hand)" ~/.local/bin/hand-0.7. - Build the new one, either as
hand-nextto try it beside 0.7 or ashandto replace it. - In the fleet folder, remove the 0.7 wiring that would keep waking a supervisor: the Stop hook with
supervision claude-stopin.claude/settings.json, and.pi/extensions/hand-*and.opencode/plugins/hand-*.hand initwarns if they are still there. - Run
hand initthere with the new binary. The oldAGENTS.mdand skill are replaced. Everything else (data/,state/,config/,projects/) is left alone, and the folder gets asecondhand-migrateskill. - Open the supervisor there and ask it to migrate the fleet. It lists what it would create (projects, open tasks, queue items, decisions, routing and memory), then waits for your approval before it changes anything.
- Afterwards it can remove the 0.7 pool worktrees and shared files, and then archive the old data into
legacy-0.7/. Each of those steps waits for your approval. Its last step runshand init, which removes the migrate skill. - Trust the worktrees folder once, so Claude does not ask in every worktree:
w="${SECONDHAND_HOME:-$HOME/.secondhand}/worktrees"; mkdir -p "$w" && cd "$w" && claude. On Windows, use the PowerShell line under Windows.
Development
Read AGENTS.md and docs/spec.md before changing anything. The dependencies are modernc.org/sqlite, and golang.org/x/sys for macOS process start times. Before committing, run:
gofmt -l .
go vet ./...
go test -race ./...
The tests set SECONDHAND_HOME to a temporary folder and never touch the real one.
License
MIT
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi