wherdr 1.3.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/BDNMzG2s.js +1 -0
- package/.output/public/_nuxt/BETID3_E.js +1 -0
- package/.output/public/_nuxt/BHenDMeH.js +1 -0
- package/.output/public/_nuxt/BYZVrreW.js +1 -0
- package/.output/public/_nuxt/BhgehRdJ.js +1 -0
- package/.output/public/_nuxt/{yzABg5xl.js → Bpoxvzh6.js} +1 -1
- package/.output/public/_nuxt/BtH82ZuZ.js +9 -0
- package/.output/public/_nuxt/C25ROUa9.js +3 -0
- package/.output/public/_nuxt/C3Bkn4ac.js +62 -0
- package/.output/public/_nuxt/CDWjMk9C.js +2 -0
- package/.output/public/_nuxt/{Ci8N4mWR.js → CXJnLmWv.js} +1 -1
- package/.output/public/_nuxt/CuUQ4mEf.js +1 -0
- package/.output/public/_nuxt/DFGEl0v2.js +23 -0
- package/.output/public/_nuxt/{CKhgznq4.js → DFmgQVme.js} +42 -39
- package/.output/public/_nuxt/DZGKxROq.js +6 -0
- package/.output/public/_nuxt/DmkWvmnE.js +2 -0
- package/.output/public/_nuxt/DrKKeFFE.js +31 -0
- package/.output/public/_nuxt/DyfFwwGq.js +1 -0
- package/.output/public/_nuxt/FaNto0ga.js +105 -0
- package/.output/public/_nuxt/NIF0VwHk.js +25 -0
- package/.output/public/_nuxt/R0Y4VoeX.js +1 -0
- package/.output/public/_nuxt/board.CEEcFc6M.css +1 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/0b28b3e9-664e-4347-a35b-afb0a711d5c5.json +1 -0
- package/.output/public/_nuxt/entry.CKoXcwRa.css +2 -0
- package/.output/public/_nuxt/pdf.min.t82Z80JU.mjs +28 -0
- package/.output/public/_nuxt/pdf.worker.min.wgc6bjNh.mjs +28 -0
- package/.output/public/_nuxt/settings.Cdb21nNw.css +1 -0
- package/.output/public/sw.js +1 -1
- package/.output/server/chunks/_/branding.mjs +1 -1
- package/.output/server/chunks/_/changes.mjs +4 -4
- package/.output/server/chunks/_/fileDownload.mjs +278 -0
- package/.output/server/chunks/_/files.mjs +60 -20
- package/.output/server/chunks/_/machineOrder.mjs +1 -1
- package/.output/server/chunks/_/onboarding.mjs +1 -1
- package/.output/server/chunks/_/package.mjs +11 -3
- package/.output/server/chunks/_/plugins.mjs +92 -122
- package/.output/server/chunks/_/restart.mjs +1 -1
- package/.output/server/chunks/_/selfupdate.mjs +1 -1
- package/.output/server/chunks/_/settingstabs.mjs +36 -0
- package/.output/server/chunks/_/updates.mjs +1 -1
- package/.output/server/chunks/_/worktrees.mjs +1 -1
- package/.output/server/chunks/_/ws.mjs +43 -6
- package/.output/server/chunks/nitro/nitro.mjs +14126 -10238
- package/.output/server/chunks/raw/check-doc-links.mjs +5 -0
- package/.output/server/chunks/raw/check-leaks.mjs +1 -1
- package/.output/server/chunks/raw/check-release-notes.mjs +5 -0
- package/.output/server/chunks/raw/herdr-plugin.mjs +1 -1
- package/.output/server/chunks/raw/install-claude-statusline.mjs +1 -1
- package/.output/server/chunks/raw/prerelease-summary.mjs +5 -0
- package/.output/server/chunks/raw/prerelease.mjs +7 -0
- package/.output/server/chunks/raw/reveal.mjs +1 -1
- package/.output/server/chunks/raw/test-affected.mjs +5 -0
- package/.output/server/chunks/routes/api/_..._.mjs +6 -5
- package/.output/server/chunks/routes/api/agents.post.mjs +6 -5
- package/.output/server/chunks/routes/api/ask-tab.post.mjs +6 -5
- package/.output/server/chunks/routes/api/attach.post.mjs +6 -5
- package/.output/server/chunks/routes/api/auth/disable.post.mjs +6 -5
- package/.output/server/chunks/routes/api/auth/lock-all.post.mjs +6 -5
- package/.output/server/chunks/routes/api/auth/lock.post.mjs +6 -5
- package/.output/server/chunks/routes/api/auth/login/options.post.mjs +6 -5
- package/.output/server/chunks/routes/api/auth/login/verify.post.mjs +6 -5
- package/.output/server/chunks/routes/api/auth/max-session.post.mjs +6 -5
- package/.output/server/chunks/routes/api/auth/register/options.post.mjs +6 -5
- package/.output/server/chunks/routes/api/auth/register/verify.post.mjs +6 -5
- package/.output/server/chunks/routes/api/auth/status.get.mjs +6 -5
- package/.output/server/chunks/routes/api/changes.get.mjs +7 -6
- package/.output/server/chunks/routes/api/chat/image.get.mjs +7 -6
- package/.output/server/chunks/routes/api/chat.get.mjs +44 -11
- package/.output/server/chunks/routes/api/choose.post.mjs +44 -30
- package/.output/server/chunks/routes/api/claude-setup.post.mjs +42 -0
- package/.output/server/chunks/routes/api/claude-statusline.post.mjs +7 -6
- package/.output/server/chunks/routes/api/clear-queued-input.post.mjs +42 -0
- package/.output/server/chunks/routes/api/close/status.get.mjs +7 -6
- package/.output/server/chunks/routes/api/close.post.mjs +6 -5
- package/.output/server/chunks/routes/api/codex-update.post.mjs +7 -6
- package/.output/server/chunks/routes/api/commands.get.mjs +9 -7
- package/.output/server/chunks/routes/api/config.get.mjs +10 -13
- package/.output/server/chunks/routes/api/dirs.get.mjs +7 -6
- package/.output/server/chunks/routes/api/dismiss-stopped.post.mjs +7 -6
- package/.output/server/chunks/routes/api/dismiss.post.mjs +7 -6
- package/.output/server/chunks/routes/api/effort.post.mjs +7 -6
- package/.output/server/chunks/routes/api/efforts.get.mjs +7 -6
- package/.output/server/chunks/routes/api/file.post.mjs +7 -6
- package/.output/server/chunks/routes/api/files/download.get.mjs +12 -24
- package/.output/server/chunks/routes/api/files/download.head.mjs +41 -0
- package/.output/server/chunks/routes/api/files/read.get.mjs +8 -7
- package/.output/server/chunks/routes/api/files.get.mjs +7 -6
- package/.output/server/chunks/routes/api/health.get.mjs +6 -5
- package/.output/server/chunks/routes/api/input.post.mjs +33 -15
- package/.output/server/chunks/routes/api/interrupt.post.mjs +24 -21
- package/.output/server/chunks/routes/api/isgit.get.mjs +8 -8
- package/.output/server/chunks/routes/api/machine/awake.get.mjs +7 -6
- package/.output/server/chunks/routes/api/machine/awake.post.mjs +7 -6
- package/.output/server/chunks/routes/api/machine/order.get.mjs +6 -5
- package/.output/server/chunks/routes/api/machine/order.post.mjs +6 -5
- package/.output/server/chunks/routes/api/machine/reconnect.post.mjs +40 -0
- package/.output/server/chunks/routes/api/machine/rename.post.mjs +7 -6
- package/.output/server/chunks/routes/api/machine/status.post.mjs +39 -0
- package/.output/server/chunks/routes/api/menu-left.post.mjs +40 -0
- package/.output/server/chunks/routes/api/menu.post.mjs +10 -6
- package/.output/server/chunks/routes/api/model.post.mjs +7 -6
- package/.output/server/chunks/routes/api/models.get.mjs +7 -6
- package/.output/server/chunks/routes/api/nav.post.mjs +40 -26
- package/.output/server/chunks/routes/api/onboarding.get.mjs +6 -5
- package/.output/server/chunks/routes/api/onboarding.post.mjs +6 -5
- package/.output/server/chunks/routes/api/phone.get.mjs +8 -10
- package/.output/server/chunks/routes/api/phone.post.mjs +8 -10
- package/.output/server/chunks/routes/api/plugins/actions.get.mjs +6 -8
- package/.output/server/chunks/routes/api/plugins/invoke.post.mjs +6 -8
- package/.output/server/chunks/routes/api/plugins/projects.get.mjs +10 -8
- package/.output/server/chunks/routes/api/plugins/projects.post.mjs +11 -7
- package/.output/server/chunks/routes/api/prompt.post.mjs +22 -13
- package/.output/server/chunks/routes/api/push/quiet.post.mjs +7 -6
- package/.output/server/chunks/routes/api/push/subscribe.post.mjs +7 -7
- package/.output/server/chunks/routes/api/push/test.post.mjs +7 -6
- package/.output/server/chunks/routes/api/push/unsubscribe.post.mjs +7 -6
- package/.output/server/chunks/routes/api/quotas.get.mjs +7 -6
- package/.output/server/chunks/routes/api/rename.post.mjs +6 -5
- package/.output/server/chunks/routes/api/requeue.post.mjs +7 -6
- package/.output/server/chunks/routes/api/restart.get.mjs +6 -5
- package/.output/server/chunks/routes/api/restart.post.mjs +6 -5
- package/.output/server/chunks/routes/api/reveal.post.mjs +91 -8
- package/.output/server/chunks/routes/api/screen-key.post.mjs +19 -15
- package/.output/server/chunks/routes/api/screen.get.mjs +8 -40
- package/.output/server/chunks/routes/api/search.get.mjs +7 -6
- package/.output/server/chunks/routes/api/seen.post.mjs +7 -6
- package/.output/server/chunks/routes/api/sessions.get.mjs +7 -6
- package/.output/server/chunks/routes/api/sessions.post.mjs +7 -6
- package/.output/server/chunks/routes/api/settings-tab.post.mjs +90 -0
- package/.output/server/chunks/routes/api/{plugins/thread-limits.get.mjs → settings.get.mjs} +10 -9
- package/.output/server/chunks/routes/api/settings.post.mjs +40 -0
- package/.output/server/chunks/routes/api/space.post.mjs +34 -18
- package/.output/server/chunks/routes/api/state.get.mjs +7 -6
- package/.output/server/chunks/routes/api/tasks/action.post.mjs +39 -0
- package/.output/server/chunks/routes/api/{plugins/profiles.get.mjs → tasks/add.post.mjs} +12 -14
- package/.output/server/chunks/routes/api/tasks/after.post.mjs +39 -0
- package/.output/server/chunks/routes/api/tasks/approve.post.mjs +39 -0
- package/.output/server/chunks/routes/api/{project/report.get.mjs → tasks/archived.get.mjs} +12 -13
- package/.output/server/chunks/routes/api/{gitroot.get.mjs → tasks/autopilot.get.mjs} +13 -14
- package/.output/server/chunks/routes/api/tasks/autopilot.post.mjs +39 -0
- package/.output/server/chunks/routes/api/{index.get.mjs → tasks/board-settings.get.mjs} +12 -13
- package/.output/server/chunks/routes/api/tasks/board-settings.post.mjs +39 -0
- package/.output/server/chunks/routes/api/tasks/coordinator.get.mjs +40 -0
- package/.output/server/chunks/routes/api/tasks/coordinator.post.mjs +39 -0
- package/.output/server/chunks/routes/api/tasks/details.get.mjs +40 -0
- package/.output/server/chunks/routes/api/tasks/limit.post.mjs +39 -0
- package/.output/server/chunks/routes/api/tasks/links.get.mjs +39 -0
- package/.output/server/chunks/routes/api/tasks/models.get.mjs +40 -0
- package/.output/server/chunks/routes/api/tasks/pick.post.mjs +39 -0
- package/.output/server/chunks/routes/api/tasks/question.post.mjs +39 -0
- package/.output/server/chunks/routes/api/tasks/scheduled.get.mjs +40 -0
- package/.output/server/chunks/routes/api/{project → tasks}/settings.get.mjs +10 -11
- package/.output/server/chunks/routes/api/tasks/start.get.mjs +40 -0
- package/.output/server/chunks/routes/api/tasks/tell.post.mjs +39 -0
- package/.output/server/chunks/routes/api/tasks.get.mjs +39 -0
- package/.output/server/chunks/routes/api/unqueue.post.mjs +7 -6
- package/.output/server/chunks/routes/api/update/status.get.mjs +6 -5
- package/.output/server/chunks/routes/api/update.get.mjs +8 -10
- package/.output/server/chunks/routes/api/update.post.mjs +8 -10
- package/.output/server/chunks/routes/api/upload.post.mjs +7 -6
- package/.output/server/chunks/routes/api/worktrees/remove.post.mjs +6 -5
- package/.output/server/chunks/routes/api/worktrees.get.mjs +6 -5
- package/.output/server/chunks/routes/favicon.ico.get.mjs +6 -5
- package/.output/server/chunks/routes/icons/_name_.get.mjs +7 -6
- package/.output/server/chunks/routes/renderer.mjs +8 -7
- package/.output/server/chunks/routes/uploads/_...name_.get.mjs +7 -6
- package/.output/server/chunks/routes/ws/_..._.mjs +7 -6
- package/.output/server/chunks/routes/ws/events.mjs +9 -7
- package/.output/server/chunks/routes/ws/term.mjs +7 -6
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/index.mjs +7 -6
- package/.output/server/package.json +1 -1
- package/README.md +44 -1424
- package/bin/lib/commands.mjs +44 -9
- package/bin/lib/panel.mjs +3 -11
- package/bin/lib/service.mjs +41 -2
- package/bin/lib/skill.mjs +1 -1
- package/bin/lib/tailnet.d.mts +6 -3
- package/bin/lib/tailnet.mjs +70 -32
- package/bin/lib/userpath.d.mts +14 -0
- package/bin/lib/userpath.mjs +142 -0
- package/docs/README.md +58 -0
- package/docs/RELEASING.md +174 -0
- package/docs/board.md +164 -0
- package/docs/command.md +58 -0
- package/docs/configuration.md +39 -0
- package/docs/conversation.md +132 -0
- package/docs/development.md +38 -0
- package/docs/devices.md +58 -0
- package/docs/files.md +29 -0
- package/docs/herdr-projects-1x.md +278 -0
- package/docs/home.md +52 -0
- package/docs/installation.md +174 -0
- package/docs/machines.md +69 -0
- package/docs/migrate-from-1.x-docker.md +160 -0
- package/docs/networks.md +154 -0
- package/docs/notifications.md +83 -0
- package/docs/overview.md +71 -0
- package/docs/phone.md +26 -0
- package/docs/plugins.md +109 -0
- package/docs/quick-start.md +89 -0
- package/docs/quotas.md +65 -0
- package/docs/release-notes-2.0.0.md +84 -0
- package/docs/security.md +117 -0
- package/docs/terminal.md +15 -0
- package/docs/troubleshooting.md +45 -0
- package/package.json +11 -3
- package/skills/wherdr/SKILL.md +24 -167
- package/.output/public/_nuxt/B4Yk2tB1.js +0 -23
- package/.output/public/_nuxt/BBw_0a4M.js +0 -6
- package/.output/public/_nuxt/BSy6OCIY.js +0 -1
- package/.output/public/_nuxt/BX5PM2JQ.js +0 -1
- package/.output/public/_nuxt/BgPDnf2S.js +0 -1
- package/.output/public/_nuxt/BkOxQzbS.js +0 -2
- package/.output/public/_nuxt/CP8o925U.js +0 -1
- package/.output/public/_nuxt/CWwyK2XK.js +0 -2
- package/.output/public/_nuxt/CkbmecwS.js +0 -1
- package/.output/public/_nuxt/Cr3SrGky.js +0 -1
- package/.output/public/_nuxt/DAg9fiVh.js +0 -89
- package/.output/public/_nuxt/DT8qZ0Wv.js +0 -1
- package/.output/public/_nuxt/DaaJIRB3.js +0 -1
- package/.output/public/_nuxt/DcJi16XA.js +0 -1
- package/.output/public/_nuxt/SiL1FJ5s.js +0 -3
- package/.output/public/_nuxt/VpjCgSXu.js +0 -36
- package/.output/public/_nuxt/builds/meta/c735b6c0-1bd0-47d7-a208-d310f0a06287.json +0 -1
- package/.output/public/_nuxt/dPxB2L94.js +0 -89
- package/.output/public/_nuxt/entry.CGWKmYU3.css +0 -2
- package/.output/public/_nuxt/xYX85SPI.js +0 -1
- package/.output/server/chunks/_/pluginPolicy.mjs +0 -93
- package/.output/server/chunks/_/projectBoard.mjs +0 -366
- package/.output/server/chunks/_/projectDocs.mjs +0 -106
- package/.output/server/chunks/_/projectInbox.mjs +0 -135
- package/.output/server/chunks/_/projectThreads.mjs +0 -46
- package/.output/server/chunks/_/projectThreads2.mjs +0 -61
- package/.output/server/chunks/_/projectsActions.mjs +0 -143
- package/.output/server/chunks/_/projectsPlugin.mjs +0 -56
- package/.output/server/chunks/_/reveal.mjs +0 -115
- package/.output/server/chunks/raw/dev.wherdr.open.mjs +0 -7
- package/.output/server/chunks/raw/docker-entrypoint.mjs +0 -7
- package/.output/server/chunks/raw/install-host-open.mjs +0 -7
- package/.output/server/chunks/raw/wherdr-open.mjs +0 -7
- package/.output/server/chunks/routes/api/board.get.mjs +0 -147
- package/.output/server/chunks/routes/api/plugins/thread-limits.post.mjs +0 -45
- package/.output/server/chunks/routes/api/project/inbox.get.mjs +0 -43
- package/.output/server/chunks/routes/api/project/inbox.post.mjs +0 -43
- package/.output/server/chunks/routes/api/project/memory.get.mjs +0 -42
- package/.output/server/chunks/routes/api/project/routines.get.mjs +0 -43
- package/.output/server/chunks/routes/api/project/thread.get.mjs +0 -44
- package/.output/server/chunks/routes/api/project/thread.post.mjs +0 -45
- package/bin/install-docker-skill.sh +0 -20
package/README.md
CHANGED
|
@@ -1,1442 +1,62 @@
|
|
|
1
|
-
|
|
2
|
-
<img src="server/assets/branding/icons/icon-512.png" alt="wherdr logo" width="120">
|
|
3
|
-
</p>
|
|
1
|
+
# wherdr
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
**Upgrading to 2.0.0?** Read the [release notes](docs/release-notes-2.0.0.md) for breaking changes and the 1.x migration checklist.
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
computer and
|
|
5
|
+
wherdr is an unofficial, self-hosted web client for [Herdr](https://herdr.dev).
|
|
6
|
+
Start coding agents on your computer, follow their work and reply from your phone or browser.
|
|
7
|
+
It uses the same Herdr session, with live conversations, real terminals and a herdr-projects Board.
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+

|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<p align="center"><a href="https://github.com/afloury/wherdr/actions/workflows/ci.yml"><img src="https://github.com/afloury/wherdr/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a></p>
|
|
11
|
+
[Try the demo](https://wherdr.dev/demo/) · [Website](https://wherdr.dev) · [Launch film](assets/wherdr-launch.mp4)
|
|
15
12
|
|
|
16
13
|
## Quick start
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
**Recommended.** Paste this into Claude Code, Codex or any coding agent: it installs and sets up wherdr for you.
|
|
21
|
-
|
|
22
|
-
```text
|
|
23
|
-
Read https://wherdr.dev/agent.md and help me set up wherdr. Check what is already installed first, and ask me before installing anything.
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
It follows [our setup guide for agents](https://wherdr.dev/agent.md): checks what you already have,
|
|
27
|
-
asks before installing anything, sets up your phone over Tailscale, never exposes wherdr to the Internet.
|
|
28
|
-
|
|
29
|
-
### Or run the installer yourself
|
|
30
|
-
|
|
31
|
-
On the computer that runs [Herdr](https://herdr.dev) (≥ 0.9.1), macOS or Linux:
|
|
15
|
+
On the Linux or macOS computer running **Herdr ≥ 0.9.1**, run:
|
|
32
16
|
|
|
33
17
|
```sh
|
|
34
18
|
curl -fsSL https://wherdr.dev/install | sh
|
|
35
19
|
```
|
|
36
20
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
Other ways, after it: `herdr plugin install afloury/wherdr` (the same plugin, without the
|
|
44
|
-
checks), `npx wherdr` (or `bunx wherdr`, `pnpm dlx wherdr`; see
|
|
45
|
-
[the `wherdr` command](#the-wherdr-command)), `brew install afloury/tap/wherdr`, or
|
|
46
|
-
[Docker by hand](#recommended-always-on-server--tailscale).
|
|
47
|
-
|
|
48
|
-
At first launch, wherdr opens a **setup guide**: the Herdr agents it found, your phone
|
|
49
|
-
(Tailscale), then the passkey lock and notifications. Skip it any time; finishing or skipping is
|
|
50
|
-
saved on the server (`data/onboarding.json`), so your other devices do not show it again. Reopen
|
|
51
|
-
it from Settings › About › **Setup guide**. On a phone that already opens wherdr through its
|
|
52
|
-
tailnet address, the phone step becomes Add to Home Screen.
|
|
53
|
-
|
|
54
|
-
**Passkeys are tied to the address they are created on.** Once wherdr's tailnet address is
|
|
55
|
-
published and answers, the guide opened on `localhost` shows **Continue on
|
|
56
|
-
<machine>.<tailnet>.ts.net**, which reopens it at the same step on that address, and its Security
|
|
57
|
-
step (like Settings › Security) sends the passkey lock there. Nothing redirects on its own:
|
|
58
|
-
`localhost` stays valid on the computer. The installer, the Herdr plugin and `wherdr open` open
|
|
59
|
-
that address too when it is already published.
|
|
60
|
-
|
|
61
|
-
wherdr is a small self-hosted web app that talks to the Herdr server on your machine. **On a
|
|
62
|
-
computer**, it can replace the Herdr terminal client day to day: your spaces, tabs and split panes
|
|
63
|
-
side by side, live conversations and real terminals, drag-and-drop layout, keyboard shortcuts
|
|
64
|
-
and a project board for herdr-projects. **On a phone**, installed as a PWA, it is the companion
|
|
65
|
-
that follows your agents when you are away: push notifications when an agent finishes or needs
|
|
66
|
-
you, one-tap answers, quick replies and offline reading.
|
|
67
|
-
|
|
68
|
-
It lists your agents live — Claude Code, Codex and the other agents Herdr recognizes — and works
|
|
69
|
-
on the same Herdr session `herdr` attaches to: nothing is copied, nothing runs in the cloud. Use
|
|
70
|
-
it on `localhost`, or from your other devices over Tailscale.
|
|
71
|
-
|
|
72
|
-
> [!WARNING]
|
|
73
|
-
> **wherdr is a remote shell on your machine.** Anyone who can reach it can start agents and
|
|
74
|
-
> run commands as your user. **Never expose it on the public Internet** (no port forwarding, no
|
|
75
|
-
> public reverse proxy, no tunnel). Use it on `localhost`, or from your other devices through a
|
|
76
|
-
> private network such as [Tailscale](https://tailscale.com). Enabling the passkey lock is
|
|
77
|
-
> strongly recommended. See [Security](#security).
|
|
78
|
-
|
|
79
|
-
<p align="center">
|
|
80
|
-
<img src="docs/screenshots/desktop.png" alt="wherdr on a computer: agent sidebar and conversation" width="900">
|
|
81
|
-
</p>
|
|
82
|
-
<p align="center"><sub>On a computer</sub></p>
|
|
83
|
-
<br>
|
|
84
|
-
<p align="center">
|
|
85
|
-
<img src="docs/screenshots/home-phone.png" alt="Agent list on a phone" width="340">
|
|
86
|
-
|
|
87
|
-
<img src="docs/screenshots/chat-phone.png" alt="Conversation view on a phone" width="340">
|
|
88
|
-
</p>
|
|
89
|
-
<p align="center"><sub>On a phone: the same agents, the same session.</sub></p>
|
|
21
|
+
The installer checks prerequisites, offers Node.js 22 if needed, installs the Herdr plugin
|
|
22
|
+
and starts wherdr as your user at **http://localhost:7683**. It also configures automatic
|
|
23
|
+
startup: at login on macOS; at boot on Linux with lingering enabled, otherwise at login. The installer reports which case applies.
|
|
24
|
+
Open that address, or press **prefix+i** in Herdr, to follow the setup guide: agents, phone access
|
|
25
|
+
through Tailscale, optional passkey lock and notifications. See [first launch](docs/quick-start.md).
|
|
90
26
|
|
|
91
|
-
|
|
92
|
-
Claude Code, Codex and other product names are trademarks of their respective owners.*
|
|
93
|
-
|
|
94
|
-
## Why wherdr
|
|
95
|
-
|
|
96
|
-
- **Herdr's splits, live.** A tab with several panes is drawn in Herdr's real layout, every pane
|
|
97
|
-
live; drag a pane to move it or a divider to resize, and the change goes to Herdr itself.
|
|
98
|
-
- **A passkey lock.** Face ID, Touch ID, Windows Hello or Android unlock the app; once a passkey
|
|
99
|
-
exists, nothing about your agents is served to a locked session.
|
|
100
|
-
- **The agent's diff.** Git status and diff of each agent's working folder, one click away from
|
|
101
|
-
its conversation.
|
|
102
|
-
- **The herdr-projects board.** A coordinator and its threads grouped under their project, with
|
|
103
|
-
the project's task lists as a live board and one-click replies.
|
|
104
|
-
- **Keyboard and global search.** One shortcut searches every agent and conversation; next
|
|
105
|
-
agent, new tab, pane swaps and `1`–`9` answers never need the mouse.
|
|
106
|
-
- **Real time, and private.** The real terminal over a WebSocket, a push when an agent needs
|
|
107
|
-
you, and your phone through Tailscale: nothing is on the Internet.
|
|
108
|
-
|
|
109
|
-
Details in [Features](#features) and [Security](#security).
|
|
110
|
-
|
|
111
|
-
## Contents
|
|
112
|
-
|
|
113
|
-
- [Quick start](#quick-start)
|
|
114
|
-
- [Why wherdr](#why-wherdr)
|
|
115
|
-
- [Features](#features)
|
|
116
|
-
- [How it works](#how-it-works)
|
|
117
|
-
- [Requirements](#requirements)
|
|
118
|
-
- [Installation](#installation): [ask your AI agent](#ask-your-ai-agent-1) ·
|
|
119
|
-
[or run the installer yourself](#the-one-command) ·
|
|
120
|
-
[which setup?](#which-setup) ·
|
|
121
|
-
[always-on server + Tailscale](#recommended-always-on-server--tailscale) ·
|
|
122
|
-
[other private networks](#other-private-networks) ·
|
|
123
|
-
[the `wherdr` command](#the-wherdr-command) ·
|
|
124
|
-
[from source](#from-source-no-docker) ·
|
|
125
|
-
[as a Herdr plugin](#install-as-a-herdr-plugin) ·
|
|
126
|
-
[Docker on a Mac: Reveal/Open](#wherdr-in-docker-on-a-mac-reveal-in-finder-open-and-modalto)
|
|
127
|
-
- [Updating](#updating)
|
|
128
|
-
- [Install it as an app](#install-it-as-an-app)
|
|
129
|
-
- [Configuration](#configuration)
|
|
130
|
-
- [Several machines (SSH)](#several-machines-ssh)
|
|
131
|
-
- [Claude and Codex quotas](#claude-and-codex-quotas)
|
|
132
|
-
- [Notifications](#notifications)
|
|
133
|
-
- [Works great with herdr-projects](#works-great-with-herdr-projects)
|
|
134
|
-
- [Herdr plugins](#herdr-plugins)
|
|
135
|
-
- [Security](#security)
|
|
136
|
-
- [Troubleshooting](#troubleshooting)
|
|
137
|
-
- [Contributing](#contributing) · [License](#license)
|
|
138
|
-
|
|
139
|
-
## Features
|
|
140
|
-
|
|
141
|
-
### Everywhere
|
|
142
|
-
|
|
143
|
-
- **Live agent list**, one line per Herdr space, grouped by state (your turn, working, ready).
|
|
144
|
-
The list is compact by default: agent, title, project badge and state. When an agent asks for
|
|
145
|
-
permission or asks a question, its card opens on the question and its options: **answer in one
|
|
146
|
-
tap** without opening it; the card is one line again once answered. Turn off Settings ›
|
|
147
|
-
Appearance › Home screen › **Compact list** for detailed cards, with the folder, the model and
|
|
148
|
-
a preview of each agent's last answer.
|
|
149
|
-
- **Conversation view**: the agent's real transcript (Claude Code, Codex and omp), rendered as
|
|
150
|
-
Markdown, with grouped tool calls, images (full-screen viewer with pinch / wheel zoom and pan), timestamps, search and infinite scroll back to the
|
|
151
|
-
first message. Reading a conversation never touches the terminal, so it never resizes a pane.
|
|
152
|
-
A command the agent proposes (shell code block, or inline `! command`) can be copied without
|
|
153
|
-
its prompt, or run in the agent's shell mode (`!`, Claude Code, Codex and omp) after you
|
|
154
|
-
confirm the full command.
|
|
155
|
-
- **Terminal view**: the real terminal (xterm.js, WebGL or DOM renderer) with take-over. Opening
|
|
156
|
-
it resizes the real pane to your screen (or its cell in a split); closing it gives the pane the size it had before
|
|
157
|
-
(after 30 seconds when the connection just dropped, e.g. a locked phone), unless a Herdr
|
|
158
|
-
client attached meanwhile laid it out its own way. On a
|
|
159
|
-
computer, drag over the text on screen to select it: releasing the button copies it, as do
|
|
160
|
-
`⌘C` / `Ctrl+Shift+C`. The history is browsed with the wheel, not by dragging past the edge.
|
|
161
|
-
Links (OSC 8 hyperlinks and plain `http(s)://` URLs, even wrapped over several rows) open in a
|
|
162
|
-
new tab with `⌘`+click / `Ctrl`+click, or a tap on a phone; only http and https open. A long
|
|
163
|
-
line wrapped by the width is copied in one piece, without the breaks between its rows. An image
|
|
164
|
-
pasted with `⌘V` (Mac), here or in a side-by-side terminal, goes to the agent's machine and its
|
|
165
|
-
path is pasted, which omp and Claude Code attach as an image, as in the conversation.
|
|
166
|
-
- **Interactive agent screens**: in a conversation, an uncertain menu or panel opens the
|
|
167
|
-
pane's real terminal in a modal window, with arrows, Enter, Escape, Tab, digits and y/n.
|
|
168
|
-
The rest of the app waits behind a scrim (the window fills the screen on a phone); in a
|
|
169
|
-
split, only the cell in use opens it. It closes after the agent's message field returns.
|
|
170
|
-
Close it yourself at any time with its button or a click on the scrim: that releases the
|
|
171
|
-
terminal without pressing Escape, which goes to the agent, and the same interaction stays
|
|
172
|
-
closed until you reopen it. Existing native questions, approvals, menus, waiting screens and
|
|
173
|
-
Claude settings cards keep their controls. The window is a fallback after those
|
|
174
|
-
recognizers, with two explicit exceptions: Codex's two-column `/usage` menu and
|
|
175
|
-
its Agent Command Center. The slash menu still lists and sends commands. On a phone, the window
|
|
176
|
-
temporarily fits the real pane to its width and restores its size on release, using
|
|
177
|
-
the same lease as Terminal view; it does not shrink 120 columns into unreadable text.
|
|
178
|
-
A pane already shown in Terminal keeps its existing live view. Try `/usage` on the
|
|
179
|
-
Codex agent in the browser demo (scripted, no account data).
|
|
180
|
-
In the compact list, native questions keep their preview and one-tap answers.
|
|
181
|
-
Unrecognized blocking screens offer an **Open interactive terminal** button.
|
|
182
|
-
- **Composer**: send messages while the agent works (queued, cancellable), attach or paste
|
|
183
|
-
photos and the files the agent can read (Claude Code: PDFs, notebooks, text and code of any
|
|
184
|
-
extension; Codex: notebooks, text and code), run slash commands (`/compact`, `/clear`, `/context`, `/usage`…) and see the output
|
|
185
|
-
of local commands. Stop button while the agent works. A long pasted text (log, output) becomes
|
|
186
|
-
a "Pasted text · N lines" card, sent to the agent as it is and shown the same way in the
|
|
187
|
-
conversation, where it opens in full with Copy; long typed messages fold with Show more.
|
|
188
|
-
- **Codex updates and limits**: in a Codex conversation, a new Codex version shows above the
|
|
189
|
-
field with an **Update** button (runs Codex's official update command on that machine, after
|
|
190
|
-
confirmation), then **Restart to update** on the same conversation. When wherdr cannot run it
|
|
191
|
-
(Docker, read-only home), Update exits Codex, runs the command in the agent's own terminal and
|
|
192
|
-
resumes the same conversation (only while Codex is idle). Codex's own weekly-limit warning
|
|
193
|
-
(its footer gauge, or its startup heads-up) is repeated above the field, with its values, while
|
|
194
|
-
Codex shows it.
|
|
195
|
-
- **After Codex updates itself and exits**: a **Your turn** card offers **Restart Codex**
|
|
196
|
-
for a new conversation, with the original launch arguments. For an existing conversation,
|
|
197
|
-
choose **Resume conversation** (its exact session, with the original options) or **Start
|
|
198
|
-
fresh**. If wherdr cannot identify the session, Resume opens Codex's picker rather than
|
|
199
|
-
choosing another pane's latest conversation. The usual attention notification applies.
|
|
200
|
-
Nothing restarts automatically: the pane must still show the same empty shell prompt,
|
|
201
|
-
with the shell in the foreground. Unrecognized shell prompts require a manual restart
|
|
202
|
-
in the terminal. wherdr must have observed Codex before it exited.
|
|
203
|
-
**Dismiss** puts the card away for good: the pane is a plain shell again, to use as you like.
|
|
204
|
-
Project panel messages wait in the queue until the agent returns; if it does not (Dismiss, or
|
|
205
|
-
a Codex that simply left), their text goes back into the pane's message field. Continuation
|
|
206
|
-
prompts (unfinished quotes or heredocs) refuse a restart too. If the final
|
|
207
|
-
transcript read fails, both conversation choices remain available; a session
|
|
208
|
-
contradicted by a guessed match is resumed through Codex's picker.
|
|
209
|
-
- **Model and effort pickers** for Claude Code, Codex and omp (omp: its
|
|
210
|
-
"Switch Model" selector, `/switch` or `/model`, and its thinking level, changed with its own
|
|
211
|
-
⇧⇥ cycle). Changes apply **to the current session only**; wherdr never changes your default
|
|
212
|
-
model.
|
|
213
|
-
- **New agent**: pick an installed agent (or a plain terminal), a folder, optionally a separate
|
|
214
|
-
Git **worktree** and branch, resume the last conversation, and queue a first message.
|
|
215
|
-
- **Changes view**: Git status and diff of an agent's working folder; worktree management.
|
|
216
|
-
Review the diff like a pull request: comment on one line or on a **block of lines**. The code
|
|
217
|
-
itself is plain text — a click on it does nothing, so it can be selected and copied.
|
|
218
|
-
- **Mouse**: a **+** appears in the gutter of the hovered line; click it to comment that line,
|
|
219
|
-
or press it and drag over other lines to comment the block. With an editor open, Shift+click
|
|
220
|
-
another line to change its block.
|
|
221
|
-
- **Touch**: tap a line number to choose the line, tap another line to extend the block, then
|
|
222
|
-
**Comment** in the bar pinned at the bottom. Nothing is written by the first tap.
|
|
223
|
-
- **Keyboard**: one tab stop per file; arrow keys move between lines, Shift+arrows choose a
|
|
224
|
-
block, Enter opens the editor. In the editor, ⌘/Ctrl+Enter saves and Escape cancels (a second
|
|
225
|
-
Escape if text would be lost); the window stays open.
|
|
226
|
-
|
|
227
|
-
A block stays within one hunk (the other hunks dim while it is chosen) and may mix deleted and
|
|
228
|
-
new lines. Each comment is a thread under the last line of its block, headed by its lines
|
|
229
|
-
("Lines 12–18"), with **Edit** and a two-step **Delete**; the lines it covers are marked in
|
|
230
|
-
the gutter. Comments, including the text of an editor left open, are saved on this device for
|
|
231
|
-
that agent, across refreshes and reloads. A block that leaves the diff, or whose code changed
|
|
232
|
-
between its first and last lines, is flagged above the diff and keeps the excerpt that was
|
|
233
|
-
commented.
|
|
234
|
-
**Send review** stays pinned with the number of comments; it groups them by file as
|
|
235
|
-
`file:12-18 — remark` (`file:12` for one line) followed by a short excerpt of the block, and
|
|
236
|
-
appends them to the conversation's message draft, preserving existing text; it never sends a
|
|
237
|
-
message. Deleted lines use the old file's numbers, explicitly labelled. **Discard** clears the
|
|
238
|
-
local review. Works on remote agents and in the demo too, with no writes to the agent's
|
|
239
|
-
machine. Browser storage must be available to retain comments after closing the view.
|
|
240
|
-
- **Several machines**: agents of the SSH machines registered in Herdr (`herdr machine add`)
|
|
241
|
-
appear in their own section; everything works the same on a remote agent.
|
|
242
|
-
- **Named Herdr sessions**, **quotas** (remaining Claude and Codex usage limits), **Herdr plugin
|
|
243
|
-
actions** in the menus, **passkey lock** (Face ID, Touch ID, Windows Hello, Android…).
|
|
244
|
-
- **Themes**: wherdr Titanium (default), herdr.dev and Herdr's built-in themes, or follow your Herdr theme.
|
|
245
|
-
**English and French** interface.
|
|
246
|
-
|
|
247
|
-
### On a computer
|
|
248
|
-
|
|
249
|
-
- **Split panes, live**: a tab with several panes is drawn with Herdr's real layout; every pane
|
|
250
|
-
shows its conversation or terminal live. Every visible terminal fills its cell and follows
|
|
251
|
-
window and divider resizing, keeping its instance and connection when focus changes.
|
|
252
|
-
Click a cell to give it the keyboard. Leaving the tab restores each terminal's original size,
|
|
253
|
-
unless a Herdr client has resized it meanwhile.
|
|
254
|
-
- **Rearrange the layout**: drag a pane onto another to move it, drag a divider to resize
|
|
255
|
-
(arrow keys work on a focused divider); changes go to Herdr itself.
|
|
256
|
-
- **Spaces and tabs**: sidebar with every space, tabs at the top, active tab remembered per space.
|
|
257
|
-
The sidebar can be resized, or collapsed to a narrow rail (search, new agent, settings and
|
|
258
|
-
state counters) with the button next to Settings; the choice is kept on the device.
|
|
259
|
-
- **Keyboard shortcuts** (`Mod` is ⌘ on macOS, Ctrl elsewhere; `Mod+/` or `?` lists them all,
|
|
260
|
-
also in Settings › Computer):
|
|
261
|
-
- `Ctrl/⌘+K` global search across agents and conversations, `Alt+↑/↓` previous / next agent
|
|
262
|
-
in the list, `Mod+,` settings, `H` the [Board](#works-great-with-herdr-projects) (outside a
|
|
263
|
-
text field and the terminal).
|
|
264
|
-
- `Mod+Alt+N` new agent in a new space, `Mod+Alt+T` new tab in the current space,
|
|
265
|
-
`Mod+Alt+W` close the current pane (after confirmation). They work from the terminal too.
|
|
266
|
-
- With [herdr-projects](#works-great-with-herdr-projects) on the agent's machine: `Mod+Alt+P`
|
|
267
|
-
shows or collapses the Project panel of the current agent's project (from a thread, it opens
|
|
268
|
-
the coordinator), and `Mod+Alt+Shift+N` opens **New project**.
|
|
269
|
-
- In an agent: `Mod+F` searches the conversation, ``Ctrl+` `` switches between conversation
|
|
270
|
-
and terminal, and `Esc` stops a working agent when the message field is empty.
|
|
271
|
-
- `Alt+Shift+arrows` swaps the current pane with its neighbour, `Ctrl/⌘+Alt+arrows` moves
|
|
272
|
-
focus to the neighbouring pane in a side-by-side tab, `Esc` closes dialogs, `Enter` sends
|
|
273
|
-
and `Shift+Enter` adds a new line. On a "Your turn" card, `1`–`9` pick an option (on AZERTY
|
|
274
|
-
without Shift too, outside the message field); on the `/resume` card, `Ctrl+A` switches
|
|
275
|
-
between every project and the current one (`⌘A` still selects all on macOS).
|
|
276
|
-
- **Project board** for [herdr-projects](#works-great-with-herdr-projects): coordinator and threads grouped
|
|
277
|
-
under their project, and a side panel with the project's task lists (to test, to decide,
|
|
278
|
-
to review, in progress, in queue, blocked, to do, backlog — each optional) and one-click
|
|
279
|
-
replies to the coordinator.
|
|
280
|
-
- **Board**: every agent, and with herdr-projects every project, thread and task, in four columns
|
|
281
|
-
(Blocked, Todo, Ongoing, Finished). Drag a task card to another column, or use its **Move
|
|
282
|
-
to…** menu, to ask the coordinator to move it.
|
|
283
|
-
- **Settings** in a sidebar, with desktop-only options such as the content width.
|
|
284
|
-
|
|
285
|
-
### On a phone
|
|
286
|
-
|
|
287
|
-
- **Installable app (PWA)** on iPhone and Android, with safe-area and keyboard handling.
|
|
288
|
-
- **Web Push notifications** when an agent finishes or needs you (with its question), and for
|
|
289
|
-
`herdr notification show` (plugins, scripts).
|
|
290
|
-
- **Quick replies**: answer permissions and questions in one tap from the agent list.
|
|
291
|
-
- **Split tabs on a small screen**: a plan of the tab's panes in Herdr's proportions, then each
|
|
292
|
-
pane full screen with a mini-map and swipe to its neighbours.
|
|
293
|
-
- **Terminal key bar** (Esc, Enter, arrows, Shift+Tab, Ctrl+C…) and touch scrolling.
|
|
294
|
-
- **Offline reading** of the last known state and of recently opened conversations.
|
|
295
|
-
|
|
296
|
-
## How it works
|
|
297
|
-
|
|
298
|
-
```
|
|
299
|
-
phone / laptop ──private network──> HTTPS (tailscale serve, private proxy)
|
|
300
|
-
│
|
|
301
|
-
▼
|
|
302
|
-
wherdr (Nitro server, 127.0.0.1:7683)
|
|
303
|
-
├─ Herdr API socket ~/.config/herdr/herdr.sock
|
|
304
|
-
├─ Herdr client socket (notifications, passive client)
|
|
305
|
-
├─ herdr terminal session control <pane> (terminal view)
|
|
306
|
-
├─ transcripts ~/.claude, ~/.codex, ~/.omp (read only)
|
|
307
|
-
└─ ssh -M <machine> (other machines, optional)
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
wherdr runs on the same machine as the Herdr server (the "server" below). It uses Herdr's local
|
|
311
|
-
socket API and CLI, reads the agents' transcript files, and serves a client-only Nuxt app. Data
|
|
312
|
-
it keeps (VAPID keys, push subscriptions, passkeys, recent folders) lives in `data/`. So do the
|
|
313
|
-
long texts pasted into the message field, which the server keeps to show them as cards on every
|
|
314
|
-
device: `data/pastes.json`, in clear, readable by the server's user only (mode 600), at most the
|
|
315
|
-
last 100 texts and 2 MB, each one deleted 30 days after it was last sent.
|
|
316
|
-
|
|
317
|
-
Built with Nuxt 4, Nuxt UI 4, Tailwind 4 and TypeScript; the Herdr gateway (API, WebSockets,
|
|
318
|
-
Web Push, lock) runs in Nitro.
|
|
319
|
-
|
|
320
|
-
## Requirements
|
|
321
|
-
|
|
322
|
-
- **Herdr ≥ 0.9.1** running on the server (`herdr` or `herdr server`), with its Claude Code /
|
|
323
|
-
Codex / omp integrations installed if you use them (`herdr integration install claude|codex|omp`).
|
|
324
|
-
omp's conversation is only found through its integration, which reports the session file.
|
|
325
|
-
- **Linux or macOS** server with either **Docker** (Compose v2) or **Node.js 22**.
|
|
326
|
-
The Docker image is for Linux servers (tested on a Raspberry Pi too). **On macOS, run without
|
|
327
|
-
Docker**: Docker Desktop cannot reach Herdr's Unix socket on the host.
|
|
328
|
-
- **HTTPS** to use passkeys, push notifications and the installable app from another device.
|
|
329
|
-
Browsers only allow them on `https://` or `http://localhost`. The easiest way is
|
|
330
|
-
[`tailscale serve`](https://tailscale.com/kb/1312/serve); a reverse proxy **reachable only
|
|
331
|
-
from your private network** with a valid certificate also works (see
|
|
332
|
-
[Other private networks](#other-private-networks)).
|
|
333
|
-
|
|
334
|
-
## Installation
|
|
335
|
-
|
|
336
|
-
### Ask your AI agent
|
|
337
|
-
|
|
338
|
-
**Recommended.** Paste this into Claude Code, Codex or any coding agent: it installs and sets up wherdr for you.
|
|
27
|
+
Prefer help from your coding agent? Paste:
|
|
339
28
|
|
|
340
29
|
```text
|
|
341
30
|
Read https://wherdr.dev/agent.md and help me set up wherdr. Check what is already installed first, and ask me before installing anything.
|
|
342
31
|
```
|
|
343
32
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
[
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
published address by itself: no browser is needed on the server. Run the command again at
|
|
375
|
-
any time to check the phone address.
|
|
376
|
-
|
|
377
|
-
Settings, as environment variables (`curl -fsSL https://wherdr.dev/install | WHERDR_PORT=7684 sh`):
|
|
378
|
-
|
|
379
|
-
| Variable | Default | Meaning |
|
|
380
|
-
| --- | --- | --- |
|
|
381
|
-
| `WHERDR_DIR` | `~/wherdr` | Install folder (plugin settings, data, log). |
|
|
382
|
-
| `WHERDR_PORT` | `7683` | Local port. |
|
|
383
|
-
| `WHERDR_MODE` | — | `native`: Node.js even when Docker is there. `docker`: no plugin, Docker Compose by hand in `WHERDR_DIR` (Linux; see below). |
|
|
384
|
-
| `WHERDR_YES` | — | `1` answers yes to every question (non-interactive use). |
|
|
385
|
-
| `WHERDR_NO_BROWSER` | — | `1` never opens a browser. |
|
|
386
|
-
| `WHERDR_REF` | main | Git tag or branch of the plugin (or of `docker-compose.yml` with `WHERDR_MODE=docker`). |
|
|
387
|
-
| `WHERDR_PLUGIN` | `afloury/wherdr` | A local checkout to link and build instead (development, tests). |
|
|
388
|
-
|
|
389
|
-
**Docker by hand, with the script:** `curl -fsSL https://wherdr.dev/install | WHERDR_MODE=docker sh`
|
|
390
|
-
writes `docker-compose.yml` and `.env` to `~/wherdr`, creates the bind-mount folders, pulls the
|
|
391
|
-
published image and starts it, without the plugin; it asks before replacing a file it did not
|
|
392
|
-
create. The same steps by hand are in the next section.
|
|
393
|
-
|
|
394
|
-
Update: run the command again (it offers the update), or **U** in the wherdr panel.
|
|
395
|
-
|
|
396
|
-
### Which setup?
|
|
397
|
-
|
|
398
|
-
| | **Always-on server + Tailscale** (recommended) | **One computer** |
|
|
399
|
-
| --- | --- | --- |
|
|
400
|
-
| Runs on | A machine that never sleeps: Raspberry Pi, mini-PC, home server, private VPS (Linux) | The computer you work on (macOS or Linux) |
|
|
401
|
-
| Reach it from | Your phone and every computer on your tailnet | That computer only (`http://localhost:7683`) |
|
|
402
|
-
| Installable app, push notifications, passkeys | Yes (private HTTPS from `tailscale serve`) | Desktop browser only; no phone access |
|
|
403
|
-
| Other machines (a Mac, a laptop…) | Added in Herdr over SSH, shown in wherdr | Also possible, but they are only reachable while this computer is awake |
|
|
404
|
-
|
|
405
|
-
Both use [the one command](#the-one-command) and the same app. You can start with the second one
|
|
406
|
-
and move to the first later.
|
|
407
|
-
|
|
408
|
-
### Recommended: always-on server + Tailscale
|
|
409
|
-
|
|
410
|
-
Your agents keep running when your laptop is closed, and you follow them from your phone.
|
|
411
|
-
wherdr stays on your private network: it is **never published on the Internet**.
|
|
412
|
-
|
|
413
|
-
**1. Herdr on the server.** Install [Herdr](https://herdr.dev) (≥ 0.9.1) and start it
|
|
414
|
-
(`herdr` or `herdr server`), with the integrations of the agents you use
|
|
415
|
-
(`herdr integration install claude|codex`).
|
|
416
|
-
|
|
417
|
-
**2. wherdr.** Run [the one command](#the-one-command) on the server (over SSH, it prints the
|
|
418
|
-
address and the phone steps instead of opening a browser), then go to step 3. Or set up Docker
|
|
419
|
-
Compose by hand (Linux, Compose v2), as follows:
|
|
420
|
-
|
|
421
|
-
```sh
|
|
422
|
-
git clone https://github.com/afloury/wherdr.git
|
|
423
|
-
cd wherdr
|
|
424
|
-
cp .env.example .env
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
Edit `.env` (every value has a default; `HOST_HOME` defaults to your `$HOME`):
|
|
428
|
-
|
|
429
|
-
| Variable | Example | Meaning |
|
|
430
|
-
| --- | --- | --- |
|
|
431
|
-
| `HOST_HOME` | `/home/alice` | Your home folder on the server (default: `$HOME`). Mounted at the same path, read-only. |
|
|
432
|
-
| `PUID` / `PGID` | `1000` | Your user and group IDs (`id -u`, `id -g`). |
|
|
433
|
-
| `PORT` | `7683` | Listening port, published on `127.0.0.1` only. |
|
|
434
|
-
| `APP_URL` | `http://localhost:7683/` | Leave it with Tailscale: wherdr adopts the address of step 3 by itself. Set a private HTTPS address only for [another private network](#other-private-networks) (Web Push contact and allowed host; it then always wins). |
|
|
435
|
-
| `HOST_LABEL` | `server` | Name of this machine in the app. |
|
|
436
|
-
| `TZ` | `Europe/Paris` | Time zone for timestamps and logs. |
|
|
437
|
-
| `WHERDR_UPDATE_CHECK` | `off` | Disable the daily check for a new release (default `on`). |
|
|
438
|
-
|
|
439
|
-
Create the bind-mount folders **before** starting Compose, as your own user. Otherwise Docker
|
|
440
|
-
may create missing host folders as root, leaving the container unable to write to them.
|
|
441
|
-
|
|
442
|
-
```sh
|
|
443
|
-
mkdir -p data "$HOME/.config/herdr" "$HOME/.local/state/herdr/client" "$HOME/.cache/herdr-web" \
|
|
444
|
-
"$HOME/.herdr-projects"
|
|
445
|
-
docker compose up -d # pulls ghcr.io/afloury/wherdr (arm64 and amd64)
|
|
446
|
-
docker compose logs -f # optional: follow the logs and find the first-passkey token
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
The container runs as your user and mounts your home folder **read-only**, except
|
|
450
|
-
`~/.config/herdr` (Herdr sockets), `~/.local/state/herdr/client` (machine names),
|
|
451
|
-
`~/.cache/herdr-web` (uploaded photos and files, quotas) and `~/.herdr-projects` (herdr-projects
|
|
452
|
-
projects, see [Works great with herdr-projects](#works-great-with-herdr-projects)). It uses the
|
|
453
|
-
server's own `herdr` binary, so the client and the server always stay on the same version.
|
|
454
|
-
|
|
455
|
-
The repository is only needed for `docker-compose.yml` and `.env`: the image is prebuilt and
|
|
456
|
-
published for each release. To build it from source instead, see [Updating](#updating).
|
|
457
|
-
**Custom icons** (optional): copy
|
|
458
|
-
`docker-compose.override.example.yml` to `docker-compose.override.yml` and put your icons in
|
|
459
|
-
`branding/` (both untracked).
|
|
460
|
-
|
|
461
|
-
**3. Private HTTPS with Tailscale.** Install [Tailscale](https://tailscale.com) on the server,
|
|
462
|
-
your phone and your computers, on the same tailnet, with
|
|
463
|
-
[MagicDNS and HTTPS certificates](https://tailscale.com/kb/1153/enabling-https) enabled. Then,
|
|
464
|
-
on the server:
|
|
465
|
-
|
|
466
|
-
```sh
|
|
467
|
-
tailscale serve --bg --https=7683 http://127.0.0.1:7683
|
|
468
|
-
# → https://<machine>.<tailnet>.ts.net:7683/ (e.g. https://server.example.ts.net:7683/)
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
Tailscale Serve listens on HTTPS port 7683 of your tailnet name, with a valid certificate, and
|
|
472
|
-
forwards to wherdr's local HTTP port. Only devices of your tailnet can reach it.
|
|
473
|
-
|
|
474
|
-
**wherdr enables that address by itself**, on a server without a screen too: at startup, every
|
|
475
|
-
30 seconds, and when an address it does not know yet is opened, it reads what `tailscale serve`
|
|
476
|
-
publishes on this machine and adopts the HTTPS address that points to its own port (allowed
|
|
477
|
-
host and `APP_URL`, saved in `data/app-url.json`; an HTTPS `APP_URL` in `.env` wins). An address
|
|
478
|
-
removed from `tailscale serve` is refused again. Nothing a request says can enable an address:
|
|
479
|
-
only Tailscale's own state on the machine does, and an address that `tailscale funnel` opens to
|
|
480
|
-
the Internet is never adopted. Without Docker, wherdr runs the `tailscale` command; in Docker
|
|
481
|
-
it reads Tailscale's socket, mounted by `docker-compose.yml` (`/var/run/tailscale`). After
|
|
482
|
-
Tailscale itself restarts (an update), restart the container.
|
|
483
|
-
|
|
484
|
-
What that mount gives: Tailscale's local API, with the rights of the host user the container
|
|
485
|
-
runs as. Anyone may read it; if that user is Tailscale's operator
|
|
486
|
-
(`tailscale set --operator=$USER`), the API also accepts changes from it (serve, funnel…), and
|
|
487
|
-
`:ro` on the folder does not prevent that. wherdr itself only sends `GET` requests. This is not
|
|
488
|
-
a new privilege: the container already drives Herdr, which is a shell of that same user. To do
|
|
489
|
-
without it, remove the line from `docker-compose.yml`: wherdr then no longer finds the address
|
|
490
|
-
by itself, and the one command (or the plugin's phone step) run after `tailscale serve` hands
|
|
491
|
-
it over.
|
|
492
|
-
|
|
493
|
-
**Settings › Phone** on the server (`http://localhost:7683/#/settings?section=phone`) shows the
|
|
494
|
-
state and the QR code; without Docker it also publishes wherdr itself: one button, asked first.
|
|
495
|
-
From a terminal, `wherdr phone` (npm, Homebrew) or the [one command](#the-one-command) run again
|
|
496
|
-
(Docker, Herdr plugin) checks the same thing, and
|
|
497
|
-
`curl -fsS http://127.0.0.1:7683/api/phone` prints the state as JSON. A container started from
|
|
498
|
-
an older `docker-compose.yml` has no Tailscale socket: paste the address in Settings › Phone,
|
|
499
|
-
or run the one command again, which sends it.
|
|
500
|
-
|
|
501
|
-
If the phone shows **"This address isn't enabled yet"** (403), the address it opened is not one
|
|
502
|
-
`tailscale serve` publishes for wherdr's port on that machine: the page gives the command to run
|
|
503
|
-
there.
|
|
504
|
-
|
|
505
|
-
**Why HTTPS matters.** Browsers only allow three things on `https://` (or on `localhost` itself):
|
|
506
|
-
|
|
507
|
-
- **installing the app** on the home screen (PWA) on iPhone and Android;
|
|
508
|
-
- **push notifications** (on iOS, only in the installed app);
|
|
509
|
-
- **passkeys** for the lock screen (Face ID, Touch ID, Windows Hello, Android).
|
|
510
|
-
|
|
511
|
-
**4. On the phone:** open the address, [install the app](#install-it-as-an-app), then
|
|
512
|
-
Settings → **Enable notifications**. **On the computer:** use the same address in a browser; the
|
|
513
|
-
desktop layout can replace the Herdr terminal client for daily work.
|
|
514
|
-
|
|
515
|
-
**5. Passkey lock** (recommended, optional): Settings → Security → **Enable passkey lock**. The
|
|
516
|
-
first passkey asks for a bootstrap token, printed in the server logs (`docker compose logs`);
|
|
517
|
-
it changes on each restart and is only needed for that first passkey. Add a passkey on each of
|
|
518
|
-
your devices after that (see [New device](#new-device) below). Without one, every device of your
|
|
519
|
-
tailnet can control your agents.
|
|
520
|
-
|
|
521
|
-
**6. More machines** (optional): register your other computers in Herdr over SSH
|
|
522
|
-
(`herdr machine add`, see [Several machines (SSH)](#several-machines-ssh)). Their agents show
|
|
523
|
-
in wherdr in their own section, and everything works the same on them. The machine menu also has
|
|
524
|
-
**Keep awake**, which stops a Mac (`caffeinate`) or a Linux machine (`systemd-inhibit`) from
|
|
525
|
-
sleeping for an hour, four hours, the evening or until turned off.
|
|
526
|
-
|
|
527
|
-
> [!CAUTION]
|
|
528
|
-
> Never publish wherdr on the Internet: no port forwarding on your router, no public reverse
|
|
529
|
-
> proxy, no ngrok, no `tailscale funnel`, no Cloudflare Tunnel without Cloudflare Access. Use
|
|
530
|
-
> `tailscale serve`, which stays inside your tailnet. See [Security](#security).
|
|
531
|
-
|
|
532
|
-
### Other private networks
|
|
533
|
-
|
|
534
|
-
Tailscale is the built-in path (one click in **Settings › Phone**), not a requirement. wherdr
|
|
535
|
-
works with any private network that gives it an **HTTPS address with a valid certificate**:
|
|
536
|
-
|
|
537
|
-
- **HTTPS with a certificate your phone trusts.** The installed app (PWA), push notifications
|
|
538
|
-
and passkeys only work on `https://`. A self-signed certificate or plain `http://` on your
|
|
539
|
-
LAN is not enough, and is never a supported way to reach wherdr.
|
|
540
|
-
- **`APP_URL` set to that address** (`.env` with Docker, or the environment of `wherdr` /
|
|
541
|
-
`npm start`), then restart wherdr. Its hostname is allowed automatically and it enables Web
|
|
542
|
-
Push. Add `HERDR_WEB_ALLOWED_HOSTS` only if you also open wherdr under another name. The
|
|
543
|
-
automatic adoption and the address check in **Settings › Phone** only know the `*.ts.net`
|
|
544
|
-
addresses of `tailscale serve`; an HTTPS `APP_URL` in the environment always wins.
|
|
545
|
-
- **Never exposed without authentication.** wherdr keeps listening on `127.0.0.1`; only the
|
|
546
|
-
proxy listens on the private network. Turn on the passkey lock as soon as another device can
|
|
547
|
-
reach it.
|
|
548
|
-
|
|
549
|
-
**Headscale, NetBird, ZeroTier, WireGuard: the same recipe.** Join the server and the phone to
|
|
550
|
-
the network, give the server a DNS name that points to its **private** address, and put a reverse
|
|
551
|
-
proxy in front of wherdr that listens on that private address only. Let's Encrypt cannot reach a
|
|
552
|
-
private address, so the certificate comes from the
|
|
553
|
-
[DNS challenge](https://caddyserver.com/docs/automatic-https#dns-challenge). With
|
|
554
|
-
[Caddy](https://caddyserver.com) built with your DNS provider's
|
|
555
|
-
[module](https://github.com/caddy-dns) (here [Cloudflare DNS](https://github.com/caddy-dns/cloudflare)),
|
|
556
|
-
a `Caddyfile` like this ([`bind`](https://caddyserver.com/docs/caddyfile/directives/bind),
|
|
557
|
-
[`tls`](https://caddyserver.com/docs/caddyfile/directives/tls)):
|
|
558
|
-
|
|
559
|
-
```caddy
|
|
560
|
-
wherdr.example.com {
|
|
561
|
-
bind 100.64.0.10 # the server's address on the private network only
|
|
562
|
-
tls {
|
|
563
|
-
dns cloudflare {env.CF_API_TOKEN}
|
|
564
|
-
}
|
|
565
|
-
reverse_proxy 127.0.0.1:7683
|
|
566
|
-
}
|
|
567
|
-
```
|
|
568
|
-
|
|
569
|
-
Point `wherdr.example.com` (an `A` record in your DNS zone) to that private address: from
|
|
570
|
-
anywhere else it leads nowhere. Then set `APP_URL=https://wherdr.example.com/` and restart
|
|
571
|
-
wherdr. To join each device:
|
|
572
|
-
|
|
573
|
-
| Network | Join a device (official docs) | Server's private address |
|
|
574
|
-
| --- | --- | --- |
|
|
575
|
-
| [Headscale](https://headscale.net/stable/usage/getting-started/) | Tailscale clients: `tailscale up --login-server <YOUR_HEADSCALE_URL>` | `tailscale ip -4` |
|
|
576
|
-
| [NetBird](https://docs.netbird.io/get-started/cli) | `netbird up` (`--management-url` when self-hosted) | `netbird status` |
|
|
577
|
-
| [ZeroTier](https://docs.zerotier.com/start) | `zerotier-cli join <network ID>`, then authorize the device in the network's members | The members list of your network |
|
|
578
|
-
| [WireGuard](https://www.wireguard.com/quickstart/) | `wg-quick up wg0` with a peer per device | The `Address` of the server's interface |
|
|
579
|
-
|
|
580
|
-
Headscale does not support `tailscale serve` with HTTPS yet
|
|
581
|
-
([features](https://headscale.net/stable/about/features/),
|
|
582
|
-
[#1921](https://github.com/juanfont/headscale/issues/1921)): `tailscale cert` and Serve need
|
|
583
|
-
certificates that only Tailscale's own control server provisions. Use the reverse proxy above
|
|
584
|
-
instead of step 3.
|
|
585
|
-
|
|
586
|
-
**Cloudflare Tunnel + Cloudflare Access: only with Access.**
|
|
587
|
-
|
|
588
|
-
> [!WARNING]
|
|
589
|
-
> A Cloudflare Tunnel puts wherdr **on the Internet**: anyone can reach its address, and
|
|
590
|
-
> Cloudflare terminates TLS, so it sees all traffic. **Cloudflare Access is mandatory**, created
|
|
591
|
-
> *before* the route: without it, whoever finds the address controls your agents. Keep the
|
|
592
|
-
> passkey lock on as well. Prefer a private network when you can.
|
|
593
|
-
|
|
594
|
-
1. In Cloudflare One, create the Access application first:
|
|
595
|
-
[Self-hosted and private › Add public hostname](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/self-hosted-public-app/)
|
|
596
|
-
`wherdr.example.com`, with an **Allow** policy for your own e-mail only (Access applications
|
|
597
|
-
deny everyone else by default).
|
|
598
|
-
2. [Create a tunnel](https://developers.cloudflare.com/tunnel/setup/) and run `cloudflared` on the
|
|
599
|
-
server, then add a **published application** route: hostname `wherdr.example.com`, service
|
|
600
|
-
`http://localhost:7683`.
|
|
601
|
-
3. Set `APP_URL=https://wherdr.example.com/` and restart wherdr.
|
|
602
|
-
|
|
603
|
-
Installing the app and notifications behind Access are not tested by the project: the phone must
|
|
604
|
-
first sign in to Access in the browser.
|
|
605
|
-
|
|
606
|
-
### The `wherdr` command
|
|
607
|
-
|
|
608
|
-
The npm package holds the built app: nothing is compiled on your machine. Try it with
|
|
609
|
-
`npx wherdr` (or `bunx wherdr`, `pnpm dlx wherdr`); to keep it, install it once:
|
|
610
|
-
|
|
611
|
-
```sh
|
|
612
|
-
npm install -g wherdr # or: pnpm add -g wherdr, bun add -g wherdr
|
|
613
|
-
wherdr service install # starts at every login; prints the address
|
|
614
|
-
wherdr phone # phone state, the app's phone setup page, QR code once it answers
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
| Command | What it does |
|
|
618
|
-
| --- | --- |
|
|
619
|
-
| `wherdr` (or `wherdr run`) | Runs in the foreground; Ctrl+C stops it. |
|
|
620
|
-
| `wherdr start` · `stop` · `restart` | Background server, pid and log in `~/wherdr`. `start` says *started* only once the port answers; otherwise it prints the end of the log. With a login service installed, they drive the service. |
|
|
621
|
-
| `wherdr status` | Address, process, service, data folder and log. |
|
|
622
|
-
| `wherdr logs` (`-f` to follow, `-n 100`) | The log, `~/wherdr/wherdr.log`. |
|
|
623
|
-
| `wherdr open` | Opens wherdr in the browser: its tailnet address when published and answering, else `localhost`. |
|
|
624
|
-
| `wherdr phone` | Whether your phone can reach wherdr (Tailscale, `tailscale serve`, the address answering), the `tailscale serve` command to publish it when it is not (wherdr then enables the address by itself), the link to **Settings › Phone**, and the QR code once the address answers. |
|
|
625
|
-
| `wherdr service install` · `uninstall` | Start at login: a LaunchAgent on macOS (`~/Library/LaunchAgents/dev.wherdr.plist`), a `systemd --user` unit on Linux (`loginctl enable-linger` keeps it running while you are logged out). |
|
|
626
|
-
| `wherdr doctor` | Checks Node/Bun, Herdr and its socket, the port, the service, the data folder and `APP_URL`. |
|
|
627
|
-
|
|
628
|
-
Options: `--port` (default `7683`), `--host` (default `127.0.0.1`, keep it on loopback),
|
|
629
|
-
`--data-dir` (passkeys, push keys and settings; default `~/wherdr/data`, the folder the Herdr
|
|
630
|
-
plugin and the install script use), `--session` (a named Herdr session). Every
|
|
631
|
-
[environment variable](#configuration) works too, and so do `KEY=VALUE` lines in
|
|
632
|
-
`~/wherdr/wherdr.env` (for example `APP_URL=…`, read at each start); options win, then the
|
|
633
|
-
environment, then that file. `start` and `service install` remember the port and folders for
|
|
634
|
-
the other commands.
|
|
635
|
-
|
|
636
|
-
**Homebrew:** `brew install afloury/tap/wherdr` installs the same package; the first install also
|
|
637
|
-
brings Homebrew's Node.js (heavier than the Herdr plugin when Node.js 22 is already there). Then:
|
|
638
|
-
|
|
639
|
-
1. `brew services start wherdr`: runs `wherdr run` now and at every login, log in
|
|
640
|
-
`$(brew --prefix)/var/log/wherdr.log` (`wherdr logs` reads it).
|
|
641
|
-
2. `wherdr open`: the setup guide in your browser.
|
|
642
|
-
3. Its Phone step (or `wherdr phone`).
|
|
643
|
-
|
|
644
|
-
On a Homebrew install, `wherdr start`, `stop`, `restart` and `service install` point to
|
|
645
|
-
`brew services` (the installed path changes at each upgrade); `wherdr status` and `wherdr doctor`
|
|
646
|
-
show the Homebrew service. Update: `brew upgrade wherdr && brew services restart wherdr`.
|
|
647
|
-
|
|
648
|
-
The login service records the **absolute path** of the runtime (n, nvm, fnm, Volta or Homebrew
|
|
649
|
-
node), since launchd and systemd do not see your shell's `PATH`. It refuses to point at a
|
|
650
|
-
temporary `npx` / `pnpm dlx` / `bunx` folder: install the package globally first. To update:
|
|
651
|
-
`npm install -g wherdr@latest && wherdr restart` (with `npx`: stop it, then `npx wherdr@latest`).
|
|
652
|
-
|
|
653
|
-
Bun runs wherdr too (WebSockets, push, passkeys and Herdr's socket work); when Node.js 22 is
|
|
654
|
-
also installed, `bunx wherdr` hands the server to Node, the reference runtime. Set
|
|
655
|
-
`WHERDR_RUNTIME=bun` to keep Bun.
|
|
656
|
-
|
|
657
|
-
### From source, no Docker
|
|
658
|
-
|
|
659
|
-
For development, or to run a checkout of your own (Docker Desktop cannot reach Herdr's Unix socket
|
|
660
|
-
on macOS, so wherdr always runs natively there). With Herdr running, Git and Node.js 22 on the
|
|
661
|
-
same machine:
|
|
662
|
-
|
|
663
|
-
```sh
|
|
664
|
-
git clone https://github.com/afloury/wherdr.git
|
|
665
|
-
cd wherdr
|
|
666
|
-
npm ci
|
|
667
|
-
npm run build
|
|
668
|
-
npm start # listens on 127.0.0.1:7683
|
|
669
|
-
```
|
|
670
|
-
|
|
671
|
-
Open `http://localhost:7683` in a browser on that computer: the desktop layout, passkeys and the
|
|
672
|
-
installable app work there, since browsers treat `localhost` as secure. There is no phone access
|
|
673
|
-
and no push notification in this setup; for that, use the recommended setup (or publish this
|
|
674
|
-
instance with `tailscale serve` as above and start it with
|
|
675
|
-
`APP_URL=https://server.example.ts.net:7683/ npm start`).
|
|
676
|
-
|
|
677
|
-
Run it as the user who runs Herdr. It uses `$HOME` and `$HOME/.local/bin/herdr`, or `herdr` from
|
|
678
|
-
the `PATH` (Homebrew); set `HERDR_BIN` otherwise. `DATA_DIR` holds passkeys, push data and recent
|
|
679
|
-
folders (default `./data`). `npm start` listens on `127.0.0.1` (`HOST`) and port `7683` (`PORT`).
|
|
680
|
-
The first-passkey bootstrap token is printed in its output. Keep the process running with your
|
|
681
|
-
usual service manager (systemd user unit, launchd, tmux…).
|
|
682
|
-
|
|
683
|
-
With `APP_URL` empty or `http://localhost:7683/`, wherdr starts normally but disables Web Push
|
|
684
|
-
and logs a warning.
|
|
685
|
-
|
|
686
|
-
### Install as a Herdr plugin
|
|
687
|
-
|
|
688
|
-
This is what [the one command](#the-one-command) installs; you can also run it yourself, on Linux
|
|
689
|
-
and macOS, with Herdr ≥ 0.9.1 (no checks for Herdr or Node.js first). In three lines:
|
|
690
|
-
|
|
691
|
-
1. Run `herdr plugin install afloury/wherdr`: it installs wherdr, starts it
|
|
692
|
-
(`✓ wherdr is running → http://localhost:7683`) and, at the first install on a computer with a
|
|
693
|
-
screen, opens the setup guide in your browser (`open` on macOS, `xdg-open` on Linux with a
|
|
694
|
-
display; a headless server, or `WHERDR_NO_BROWSER=1` in the environment of the install, just
|
|
695
|
-
prints `http://localhost:7683/#/setup`). Updates open nothing.
|
|
696
|
-
2. In Herdr, press **prefix+i** (`ctrl+b` then `i` with the default prefix): the **wherdr** panel,
|
|
697
|
-
with the state, **O** Open wherdr and **P** Set up my phone. The install prints the key it
|
|
698
|
-
chose (see [Panel key](#panel-key)).
|
|
699
|
-
3. **P** opens Settings › Phone in the browser: **Make wherdr reachable from my phone** publishes
|
|
700
|
-
it on your tailnet, then the QR code shows once the address answers. Scan it with the iPhone,
|
|
701
|
-
then Share → Add to Home Screen.
|
|
702
|
-
|
|
703
|
-
Herdr shows the manifest and the commands it will run, then the plugin installs wherdr in
|
|
704
|
-
`~/wherdr` and starts it, unless something already answers on the port. If wherdr cannot start,
|
|
705
|
-
the install still succeeds and says why; press **S** in the panel once it is fixed. It picks the
|
|
706
|
-
setup by itself:
|
|
707
|
-
|
|
708
|
-
- **Docker** on Linux when Docker Compose v2 works for your user: the published image, from
|
|
709
|
-
`~/wherdr` (`docker-compose.yml`, `.env`), the same files as `WHERDR_MODE=docker` of
|
|
710
|
-
[the one command](#the-one-command).
|
|
711
|
-
- **Node.js 22** everywhere else, and always on macOS (Docker Desktop cannot reach Herdr's
|
|
712
|
-
socket): the plugin downloads the prebuilt package of its version from GitHub Releases and
|
|
713
|
-
verifies its SHA-256 checksum, copies it to `~/wherdr/app` and runs it detached from there, with
|
|
714
|
-
its pid and log (`wherdr.log`) in `~/wherdr`. Herdr builds plugins in a temporary folder and
|
|
715
|
-
then moves them, so the server never runs from the plugin folder. Bun works too when Node.js 22
|
|
716
|
-
is missing. Without either, the install stops and says so.
|
|
717
|
-
|
|
718
|
-
The **wherdr** panel (a Herdr popup) shows ● running / ○ stopped, the local address, the phone
|
|
719
|
-
address (with its QR code once it answers), and two main keys: **O** Open wherdr, **P** Set up my
|
|
720
|
-
phone (Settings › Phone in the browser). The other keys stay as a fallback: **S** start, **X**
|
|
721
|
-
stop, **R** restart, **L** log (Ctrl+C to go back), **U** update (reinstalls the plugin, or pulls
|
|
722
|
-
the image in Docker mode), **A** auto-start at login on/off, **Q** quit.
|
|
723
|
-
|
|
724
|
-
#### Panel key
|
|
725
|
-
|
|
726
|
-
Herdr plugins have no menu of their own: an action runs from a key or from the command line. The
|
|
727
|
-
install therefore appends one marked block to Herdr's `config.toml` (`HERDR_CONFIG_PATH`, or
|
|
728
|
-
`~/.config/herdr/config.toml`):
|
|
729
|
-
|
|
730
|
-
```toml
|
|
731
|
-
# wherdr: key that opens the wherdr panel (remove this block to unbind it)
|
|
732
|
-
[[keys.command]]
|
|
733
|
-
key = "prefix+i"
|
|
734
|
-
type = "plugin_action"
|
|
735
|
-
command = "afloury.wherdr.panel"
|
|
736
|
-
description = "wherdr"
|
|
737
|
-
# end wherdr
|
|
738
|
-
```
|
|
739
|
-
|
|
740
|
-
It takes the first key of `prefix+i`, `prefix+u`, `prefix+y`, `prefix+m`, `prefix+alt+w` that
|
|
741
|
-
neither Herdr's default keymap nor your config already binds, and prints
|
|
742
|
-
`Press prefix+i in Herdr to open the wherdr panel.` Your own lines are never changed. Nothing is
|
|
743
|
-
added when a key already runs the panel, when every candidate is taken, or when
|
|
744
|
-
`herdr config check` would reject the result. The install then runs `herdr server reload-config`:
|
|
745
|
-
keys apply without restarting Herdr. Pick another key by editing the block, or bind the panel
|
|
746
|
-
yourself (`command = "afloury.wherdr.panel"`) before installing.
|
|
747
|
-
|
|
748
|
-
Without a key: `herdr plugin action invoke panel --plugin afloury.wherdr`. The plugin's actions:
|
|
749
|
-
|
|
750
|
-
| Action | What it does |
|
|
751
|
-
|---|---|
|
|
752
|
-
| `panel` | The **wherdr** panel (a popup) |
|
|
753
|
-
| `open` | Opens wherdr in the browser (its tailnet address when published and answering) |
|
|
754
|
-
|
|
755
|
-
Up to 1.2.0 the panel action was called `wherdr`: change a key bound to `afloury.wherdr.wherdr` to
|
|
756
|
-
`afloury.wherdr.panel` (Herdr has no action aliases; the install warns about such a key and
|
|
757
|
-
leaves it alone). Remove the block with `sh scripts/herdr-plugin.sh key uninstall` in the plugin
|
|
758
|
-
folder (`~/.config/herdr/plugins/github/wherdr-…`), or delete the lines from `# wherdr:` to `# end wherdr`.
|
|
759
|
-
|
|
760
|
-
Auto-start: the plugin's startup hook starts wherdr each time the Herdr server starts, which is
|
|
761
|
-
all wherdr needs (it drives Herdr). Key **A** adds a login service as well (a LaunchAgent on
|
|
762
|
-
macOS, a `systemd --user` unit on Linux, pointing at `~/wherdr/app` with the absolute path of the
|
|
763
|
-
runtime): wherdr then runs from login and is restarted if it crashes. It is not installed on its
|
|
764
|
-
own because it changes your login session. In Docker mode the container's restart policy does
|
|
765
|
-
this job.
|
|
766
|
-
|
|
767
|
-
Herdr runs the plugin's commands with the environment of its server, whose `PATH` often lacks the
|
|
768
|
-
folders of version managers (n, nvm, fnm, Volta, asdf). The install therefore saves the absolute
|
|
769
|
-
path of the runtime in `~/wherdr/plugin.env` (`WHERDR_RUNTIME=/path/to/node`); if that binary
|
|
770
|
-
disappears, **Start** looks in the usual places (`~/.n/bin`, `~/.nvm`, `~/.volta/bin`, fnm,
|
|
771
|
-
`~/.asdf/shims`, Homebrew, `/usr/local/bin`, `~/.bun/bin`) and saves what it finds. Set
|
|
772
|
-
`WHERDR_RUNTIME` there by hand for anything else. **Start** only reports success once the
|
|
773
|
-
process is alive and the port answers; otherwise it prints the last lines of `wherdr.log`.
|
|
774
|
-
|
|
775
|
-
Force a setup with `WHERDR_MODE=native herdr plugin install afloury/wherdr` (or `docker`). The
|
|
776
|
-
choice, the port (`WHERDR_PORT`, default 7683) and, in native mode, `APP_URL` are kept in
|
|
777
|
-
`~/wherdr/plugin.env`; another folder can be set with `WHERDR_DIR` at install time. Nothing is
|
|
778
|
-
installed globally and nothing runs with sudo.
|
|
779
|
-
|
|
780
|
-
When something already answers on the port (a wherdr started with Docker, systemd or by hand),
|
|
781
|
-
the plugin starts nothing and never stops it: **Stop** only stops what the plugin started. The
|
|
782
|
-
output of the install and of the startup hook is in the plugin logs:
|
|
783
|
-
`herdr plugin log list --plugin afloury.wherdr`.
|
|
784
|
-
|
|
785
|
-
**Uninstall.** Herdr has no uninstall hook, so remove wherdr in this order: in the panel, **A**
|
|
786
|
-
(if the login service is on) and **X**; in the plugin folder, `sh scripts/herdr-plugin.sh key
|
|
787
|
-
uninstall` (the panel key) and `sh scripts/herdr-plugin.sh skill uninstall`; then
|
|
788
|
-
`herdr plugin uninstall afloury.wherdr && rm -rf ~/wherdr` (`~/wherdr/data` holds your passkeys
|
|
789
|
-
and push keys).
|
|
790
|
-
|
|
791
|
-
### wherdr in Docker on a Mac: Reveal in Finder, Open and Mod+Alt+O
|
|
792
|
-
|
|
793
|
-
The container is Linux: it has no `open` command, so "Reveal in Finder" / "Open" on file paths
|
|
794
|
-
and `Mod+Alt+O` (the agent's folder in your editor) do nothing by default — the app reports
|
|
795
|
-
"not a Mac". wherdr can ask your Mac to run them over SSH:
|
|
796
|
-
|
|
797
|
-
```sh
|
|
798
|
-
sh scripts/install-host-open.sh # on the Mac; --user/--target/--port optional
|
|
799
|
-
```
|
|
800
|
-
|
|
801
|
-
This creates a dedicated key pair in `data/` (`host-open-key`), installs
|
|
802
|
-
`~/.local/share/wherdr/{reveal.sh,wherdr-open.sh}` on the Mac, loads a small LaunchAgent
|
|
803
|
-
(`dev.wherdr.open`, in `~/Library/LaunchAgents`) that launches GUI apps on behalf of the SSH
|
|
804
|
-
session, and adds one `authorized_keys` line whose forced command only ever runs the reveal
|
|
805
|
-
checks on a path wherdr passes (existing, under your home folder, never an app or a script —
|
|
806
|
-
the same rules as a native install).
|
|
807
|
-
|
|
808
|
-
Then add to `docker-compose.override.yml` and restart the container:
|
|
809
|
-
|
|
810
|
-
```yaml
|
|
811
|
-
services:
|
|
812
|
-
herdr-web:
|
|
813
|
-
environment:
|
|
814
|
-
- HERDR_WEB_HOST_OPEN_TARGET=host.docker.internal # the Mac as the container sees it
|
|
815
|
-
- HERDR_WEB_HOST_OPEN_USER=<your mac user>
|
|
816
|
-
```
|
|
817
|
-
|
|
818
|
-
Without these variables nothing changes: a non-Mac server refuses with "not a Mac" as before.
|
|
819
|
-
|
|
820
|
-
The installer picks the editor that opens folders (first of Zed, Trae, Visual Studio Code,
|
|
821
|
-
Cursor, Sublime Text, TextMate). Change it any time:
|
|
822
|
-
|
|
823
|
-
```sh
|
|
824
|
-
printf %s "Visual Studio Code" > ~/.local/share/wherdr/editor
|
|
825
|
-
```
|
|
826
|
-
|
|
827
|
-
## Updating
|
|
828
|
-
|
|
829
|
-
wherdr checks the latest GitHub release at most once a day (an anonymous request to
|
|
830
|
-
`api.github.com`, nothing is sent) and shows a small **“wherdr X.Y.Z is available”** banner on the
|
|
831
|
-
home screen and in Settings › About, with the release notes and the exact command for your setup.
|
|
832
|
-
Hide it until the next version with ×. Set `WHERDR_UPDATE_CHECK=off` to disable the check. The
|
|
833
|
-
installed version is shown in Settings › About.
|
|
834
|
-
|
|
835
|
-
GitHub release packages (including the bundled agent skill), Docker images, and Homebrew
|
|
836
|
-
updates are published without waiting for npm moderation. Native plugin installs and updates
|
|
837
|
-
use the GitHub `.tgz` and verify its SHA-256 checksum before extraction. A failed one-tap
|
|
838
|
-
download or checksum check leaves the running version in place. Plugin installation tries
|
|
839
|
-
GitHub first, then the prebuilt npm package of the exact same version (with its registry integrity hash), and only builds from source when neither works.
|
|
840
|
-
This also keeps older 1.3.x releases without GitHub archives installable with Node.js or Bun.
|
|
841
|
-
Packages predating the bundled skill retain the plugin checkout's commands and skill, so
|
|
842
|
-
skill refresh, installation and uninstallation remain available.
|
|
843
|
-
The installer fetches `main` explicitly unless you set `WHERDR_REF`.
|
|
844
|
-
|
|
845
|
-
The SHA-256 checksum detects damaged downloads. It is served beside the archive, so someone
|
|
846
|
-
who can replace both files can make them match; it does not prove the publisher’s identity
|
|
847
|
-
or protect against replacement of the archive and checksum together.
|
|
848
|
-
|
|
849
|
-
`npm install -g`, `npx`, `bunx` and `pnpm dlx` still need npm to serve the wherdr package.
|
|
850
|
-
Their banner waits for registry availability; Homebrew's banner waits for its formula.
|
|
851
|
-
Versions through 1.3.2 use GitHub's `latest` marker and an npm-based updater. That marker only
|
|
852
|
-
advances once npm **and** Homebrew are ready, so their existing Update button continues to work.
|
|
853
|
-
To update an older native plugin earlier, rerun the installer or press **U** in its Herdr panel.
|
|
854
|
-
See [Releasing](docs/RELEASING.md) for the publication order and recovery steps.
|
|
855
|
-
|
|
856
|
-
**Update in one tap.** When wherdr runs from the Herdr plugin (Node.js mode), a global npm install
|
|
857
|
-
or Homebrew, the banner has an **Update** button instead of the command, for a session opened on
|
|
858
|
-
this computer (`localhost`) or unlocked with a passkey. wherdr installs the new version in a
|
|
859
|
-
process of its own, restarts, and checks that `/api/health` reports the new version within 60
|
|
860
|
-
seconds; the app shows the progress and reloads on the new version. If the new version does not
|
|
861
|
-
answer, wherdr goes back to the previous one by itself (the plugin keeps the previous copy in
|
|
862
|
-
`~/wherdr/app.prev`, npm reinstalls the previous version, Homebrew points back to the previous keg)
|
|
863
|
-
and the app says so. Every step is logged in `~/wherdr/update.log`.
|
|
864
|
-
|
|
865
|
-
| Setup | What Update does |
|
|
866
|
-
| --- | --- |
|
|
867
|
-
| Herdr plugin, Node.js | Verifies and unpacks the GitHub release package of the new version into `~/wherdr/app`, keeps the old copy, restarts |
|
|
868
|
-
| `npm install -g wherdr` | `npm install -g wherdr@X.Y.Z`, then restarts (login service included) |
|
|
869
|
-
| Homebrew | `brew upgrade wherdr`, then `brew services restart wherdr` |
|
|
870
|
-
|
|
871
|
-
Docker shows the command instead: replacing its own container would need the Docker socket (root
|
|
872
|
-
on the host) inside it. `npx wherdr` runs from a temporary folder: run `npx wherdr@latest` again.
|
|
873
|
-
|
|
874
|
-
| Setup | Update command (in the wherdr folder) |
|
|
875
|
-
| --- | --- |
|
|
876
|
-
| Docker, published image (default) | `docker compose pull && docker compose up -d` |
|
|
877
|
-
| Docker, built from source | `git pull && docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build` |
|
|
878
|
-
| From source | `git pull && npm ci && npm run build`, then restart wherdr |
|
|
879
|
-
| `wherdr` command (npm) | `npm install -g wherdr@latest && wherdr restart` (npx: stop it, then `npx wherdr@latest`) |
|
|
880
|
-
| `wherdr` command (Homebrew) | `brew upgrade wherdr`, then `brew services restart wherdr` |
|
|
881
|
-
| The one command | Run `curl -fsSL https://wherdr.dev/install \| sh` again: it offers the update |
|
|
882
|
-
| Herdr plugin, Docker | **U** in the **wherdr** panel (pulls the image, restarts the container) |
|
|
883
|
-
| Herdr plugin, Node.js | **U** in the **wherdr** panel, or `herdr plugin install afloury/wherdr --yes` (wherdr restarts on the new version) |
|
|
884
|
-
|
|
885
|
-
Also run `git pull` now and then with the published image: it brings the latest
|
|
886
|
-
`docker-compose.yml` and `.env.example`.
|
|
887
|
-
|
|
888
|
-
**Build from source.** `docker-compose.build.yml` builds the image locally (`wherdr:local`)
|
|
889
|
-
instead of pulling it: add `-f docker-compose.yml -f docker-compose.build.yml` to every
|
|
890
|
-
`docker compose` command (and `-f docker-compose.override.yml` if you use one). A build needs
|
|
891
|
-
about 2 GB of free memory.
|
|
892
|
-
|
|
893
|
-
**Automatic updates** (optional). [Watchtower](https://containrrr.dev/watchtower/) can pull new
|
|
894
|
-
images and restart the container for you:
|
|
895
|
-
|
|
896
|
-
```sh
|
|
897
|
-
docker run -d --name watchtower --restart unless-stopped \
|
|
898
|
-
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
899
|
-
containrrr/watchtower --cleanup --schedule "0 0 4 * * *" wherdr
|
|
900
|
-
```
|
|
901
|
-
|
|
902
|
-
Pin a version instead of `latest` with `WHERDR_IMAGE=ghcr.io/afloury/wherdr:1.2.0` in `.env`.
|
|
903
|
-
|
|
904
|
-
## Install it as an app
|
|
905
|
-
|
|
906
|
-
The app must be served over HTTPS (see [Requirements](#requirements)). Installed, wherdr opens
|
|
907
|
-
in its own window, without the browser's bars.
|
|
908
|
-
|
|
909
|
-
- **iPhone / iPad** (iOS 16.4 or later): open the address in **Safari** → Share →
|
|
910
|
-
**Add to Home Screen**. Open wherdr from the home screen, then Settings → **Enable
|
|
911
|
-
notifications**. On iOS, push notifications only work in the installed app.
|
|
912
|
-
- **Android**: open the address in **Chrome** → menu → **Install app** (or Add to Home
|
|
913
|
-
screen), then Settings → **Enable notifications**.
|
|
914
|
-
- **Mac, Safari** (macOS 14 Sonoma or later): File → **Add to Dock**.
|
|
915
|
-
- **Chrome, Edge, Brave** (macOS, Windows, Linux): the install icon at the right of the address
|
|
916
|
-
bar, or menu → **Cast, save and share** → **Install page as app** (Edge: Apps → **Install
|
|
917
|
-
this site as an app**).
|
|
918
|
-
- **Arc** cannot install web apps
|
|
919
|
-
([Arc Help Center](https://resources.arc.net/hc/en-us/articles/25678978728983-Does-Arc-for-Desktop-Support-Progressive-Web-Apps)):
|
|
920
|
-
open the address in Safari or Chrome for that, or keep wherdr as an Arc favorite.
|
|
921
|
-
|
|
922
|
-
Then Settings → Security → **Enable passkey lock** on each device.
|
|
923
|
-
|
|
924
|
-
## Configuration
|
|
925
|
-
|
|
926
|
-
All settings are environment variables (`.env` with Docker).
|
|
927
|
-
|
|
928
|
-
| Variable | Default | Meaning |
|
|
929
|
-
| --- | --- | --- |
|
|
930
|
-
| `PORT` | `7683` | Listening port. |
|
|
931
|
-
| `HOST` | `127.0.0.1` (`npm start`) | Listening address without Docker. Keep it on loopback. |
|
|
932
|
-
| `HOST_LABEL` | server hostname | Name of this machine in the app (can be renamed in the app). In Docker the installer writes the host's short name; left empty, wherdr shows the name Tailscale gives the machine, else `wherdr`. |
|
|
933
|
-
| `APP_URL` | *(empty)* | App address and allowed host; a valid HTTPS URL enables Web Push. HTTP, empty or invalid values disable Web Push with a warning. With Tailscale, leave it empty: wherdr adopts the address `tailscale serve` publishes. |
|
|
934
|
-
| `TAILNET_REFRESH_MS` | `30000` | How often wherdr reads again what `tailscale serve` publishes for its port. |
|
|
935
|
-
| `HERDR_WEB_ALLOWED_HOSTS` | *(empty)* | Additional hostnames or IP addresses allowed to open the app, comma-separated. |
|
|
936
|
-
| `PASSKEY_USER` | `wherdr` | User name stored with the passkeys. |
|
|
937
|
-
| `DATA_DIR` | `./data` | VAPID keys, push subscriptions, passkeys, recent folders. |
|
|
938
|
-
| `HERDR_BIN` | `~/.local/bin/herdr`, else `herdr` in `PATH` | Herdr binary. |
|
|
939
|
-
| `HERDR_WEB_SESSION` | *(default session)* | Named Herdr session to drive. Not `HERDR_SESSION`, which Herdr reads itself. |
|
|
940
|
-
| `HERDR_WEB_HOST_OPEN_TARGET` | *(unset)* | Docker on a Mac: SSH host that runs Reveal/Open for local panes (see [Docker on a Mac](#wherdr-in-docker-on-a-mac-reveal-in-finder-open-and-modalto)). |
|
|
941
|
-
| `HERDR_WEB_HOST_OPEN_USER` | `root` | SSH user for `HERDR_WEB_HOST_OPEN_TARGET` (your Mac user). |
|
|
942
|
-
| `HERDR_WEB_HOST_OPEN_KEY` | `data/host-open-key` | SSH private key for the host-open route. |
|
|
943
|
-
| `AGENT_KINDS` | all known agents | Agents offered in **New** (only installed ones are shown). |
|
|
944
|
-
| `HERDR_WEB_MACHINES` | `on` | `off` to ignore the SSH machines registered in Herdr. |
|
|
945
|
-
| `HERDR_WEB_SELF_HOSTS` | | Other names / IPs of this machine, comma-separated (profiles pointing to it are ignored). |
|
|
946
|
-
| `HERDR_WEB_REMOTE_SESSION` | | Herdr session to use on every remote machine instead of the profile's. |
|
|
947
|
-
| `HERDR_WEB_HERDR_NOTIFICATIONS` | `on` | `off` to stop relaying `herdr notification show` as push. |
|
|
948
|
-
| `UPLOAD_DIR` | `~/.cache/herdr-web/uploads` | Where photos sent to agents are stored (deleted after 7 days). |
|
|
949
|
-
| `ATTACH_DIR` | `~/.cache/herdr-web/files` | Where other files attached to messages are stored, on the agent's machine (mode 600, deleted after 7 days). |
|
|
950
|
-
| `TZ` | `UTC` | Time zone. |
|
|
951
|
-
|
|
952
|
-
In the app, **Settings** holds per-device preferences: language, theme, notifications, terminal
|
|
953
|
-
text size and renderer, typing effect, what the home screen shows, and the passkey lock.
|
|
954
|
-
|
|
955
|
-
**Settings › Conversation › Reply style** chooses how an agent's reply offers to be quoted in
|
|
956
|
-
the field, question by question and point by point (a list item or a paragraph that asks
|
|
957
|
-
nothing), so that one message answers them all:
|
|
958
|
-
|
|
959
|
-
| Style | A question | A point |
|
|
960
|
-
| --- | --- | --- |
|
|
961
|
-
| **Icon** (default) | A small `↳` after it, in the middle or at the end of the message. | A grey `↳` at its end: on hover on a computer, always there and pale on a phone. |
|
|
962
|
-
| **Tap the text** | No button: the question is underlined, tap or click it. | Click it on a computer. On a phone, tap it, then **↳ Discuss**: a single tap never quotes, so reading, scrolling and selecting text stay as they are. |
|
|
963
|
-
| **List under the message** | A number after it, and the questions listed under the message. | **+ Quote a point** under the message, then the point. Offered when the reply has a list item to quote, or two points or more: not under one paragraph of prose. |
|
|
964
|
-
|
|
965
|
-
Every one of these is a button reachable with the keyboard (`Tab`, then `Enter`), and selecting a
|
|
966
|
-
passage still offers **Reply** on it. A quoted question or point shows `✓`.
|
|
967
|
-
|
|
968
|
-
**One syntax for every reference.** Whatever you reply to, the field gets one line that names it,
|
|
969
|
-
and your answer goes on the lines below:
|
|
970
|
-
|
|
971
|
-
```
|
|
972
|
-
↳ Quote message 11:37 ~k3f : I can merge the PR —
|
|
973
|
-
Go ahead.
|
|
974
|
-
↳ Quote point 11:37 ~k3f : Shall I tag the release today? —
|
|
975
|
-
Yes
|
|
976
|
-
↳ Quote decision 11:37 #3 ~k3f : Publish the release notes on the site? —
|
|
977
|
-
No
|
|
978
|
-
```
|
|
979
|
-
|
|
980
|
-
| Line | Written by |
|
|
981
|
-
| --- | --- |
|
|
982
|
-
| `↳ Quote message <time> ~<code> : <start of the message> —` | **Reply** under an agent message. |
|
|
983
|
-
| `↳ Quote point <time> ~<code> : <excerpt> —` | `↳` on a question or a point, **Reply** on a selected passage. |
|
|
984
|
-
| `↳ Quote decision <time> #<n> ~<code> : <question> —` | **Yes**, **No** or `↳` on a row of a decision table. |
|
|
985
|
-
| `↳ Quote decisions <time> #<rows> ~<code> : <questions> —` | **OK to all** on a decision table. |
|
|
986
|
-
| `↳ Decision: <task> —`, `↳ Problem: <task> —`, `↳ Question: <task> —`, `↳ Detail on <task> —`, `↳ Feedback on <task> —` (and their French labels) | **Answer**, **Problem**, **Question**, **Clarify** and **Comment** in the Project panel. |
|
|
987
|
-
|
|
988
|
-
- The time is the one shown on the agent's message, so an answer to an earlier message is never
|
|
989
|
-
read as an answer to the last one. The types are the same words in every language.
|
|
990
|
-
- The code after `~` is three characters wherdr derives from the message (and the rank of the
|
|
991
|
-
table, from the second table of a message on). It tells apart two messages of the same minute
|
|
992
|
-
and two tables of one message, whatever the excerpt shows. The field draws it dimmed, like
|
|
993
|
-
the words before the excerpt; the quote of a sent message leaves it out. The rows of **OK to
|
|
994
|
-
all** are the numbers the table really has: `#1-4`, or `#1,4` when rows 2 and 3 do not exist.
|
|
995
|
-
- A reference is a whole line: it starts with `↳` in the first column and ends with ` —` at the
|
|
996
|
-
end of the line, and only there, so an excerpt may hold dashes of its own. The excerpt is plain
|
|
997
|
-
text on one line; a long one is cut with `…` (a message keeps its start, a passage its start
|
|
998
|
-
and its end).
|
|
999
|
-
- Several references fit in one message, each followed by its answer.
|
|
1000
|
-
- Anything else is your own text: a `>` at the start of a line, a `↳` line of an unknown type or
|
|
1001
|
-
with words after its dash. Text you paste never becomes a reference either: a pasted line that
|
|
1002
|
-
reads like one gets a `\` before its `↳`. A long paste, shown as a **Pasted text** card, is
|
|
1003
|
-
sent exactly as it was pasted.
|
|
1004
|
-
- In the conversation, each reference of a sent message shows as a quote; tap it to see the
|
|
1005
|
-
message it names. Messages sent earlier with `> ` lines still show as quotes.
|
|
1006
|
-
|
|
1007
|
-
**Settings › Conversation › Quoted replies** chooses how the field shows these lines: **Tokens,
|
|
1008
|
-
native field** (default) draws each one as a token in the usual field, **Plain text** shows the
|
|
1009
|
-
same lines as text, a fallback if the tokens misbehave. The message sent is the same.
|
|
1010
|
-
|
|
1011
|
-
A **closed question**, one that yes or no answers in full, also gets **Yes** and **No** next to
|
|
1012
|
-
its `↳` (at the end of its row in the list style). A tap refers to the question and writes the
|
|
1013
|
-
answer under it, in the language of the interface, exactly as Reply and typing would; nothing is sent, so several questions can be
|
|
1014
|
-
answered in a row before sending. The keyboard stays down on a phone. Tapping the other answer
|
|
1015
|
-
replaces the first; words you typed under a quote are never replaced. The rule is cautious, and
|
|
1016
|
-
in doubt only Reply is offered: a single sentence that opens like a yes/no question (`Shall I…`,
|
|
1017
|
-
`Do you…`, `Is it…`, `Want me to…`, `Est-ce que…`, `Je lance… ?`, `On…`, `Dois-je…`) or ends
|
|
1018
|
-
with `, OK?` / `, d'accord ?`, with no alternative (`or`, `ou`), no interrogative word (`which`,
|
|
1019
|
-
`how`, `quel`, `comment`…) and no negation.
|
|
1020
|
-
|
|
1021
|
-
A **decision table**, a Markdown table whose rows are numbered questions as a coordinator writes
|
|
1022
|
-
them, gets a panel right under it: **OK to all** refers to the whole table and writes `OK to all`
|
|
1023
|
-
under it (a second tap takes it back), and each row has **Yes**, **No** and `↳`, which refer to
|
|
1024
|
-
the row by its number and write `Yes` or `No` under it, or nothing for an answer in words; a row
|
|
1025
|
-
that asks an open question has `↳` alone. Every answer names the message and the row, even for
|
|
1026
|
-
the last message: the agent may write another one before you send. In French: `OK tout`, `Oui`,
|
|
1027
|
-
`Non`. Here too everything is composed in the field and you send. A table qualifies when it has a
|
|
1028
|
-
number column (a header `#`, `N°`, `No`, `Num`, `ID` or `Q` over short numbers, each once) and a
|
|
1029
|
-
question column, known by its header (`Question`, `Decision`, `Choix`…), by cells that all end
|
|
1030
|
-
with `?`, or by an advice column next to it (`Avis`, `Recommendation`, `Suggestion`…). Any other
|
|
1031
|
-
table is left as it is.
|
|
1032
|
-
|
|
1033
|
-
Above the field, **N questions unanswered** counts the questions of the agent's last message
|
|
1034
|
-
(and the rows of its decision tables) that your draft does not answer yet; tap it to scroll to
|
|
1035
|
-
the first one. A question counts once it is quoted, a row once it is answered, quoted or covered
|
|
1036
|
-
by **OK to all**; when the message asks one thing only, any words you type answer it. The line
|
|
1037
|
-
goes when everything is answered and when you send, and it never shows for earlier messages,
|
|
1038
|
-
while the agent is working, or while it waits on a prompt. It hangs from the top edge of the
|
|
1039
|
-
field, so nothing moves when it comes or goes.
|
|
1040
|
-
|
|
1041
|
-
## Several machines (SSH)
|
|
1042
|
-
|
|
1043
|
-
wherdr also shows the agents of the **SSH machines registered in Herdr** — the ones the Herdr
|
|
1044
|
-
terminal client shows in its sidebar. It opens one SSH master connection per machine, forwards
|
|
1045
|
-
the remote Herdr socket through it, and reads transcripts and files over the same connection.
|
|
1046
|
-
|
|
1047
|
-
Requirements on the remote machine: the **same Herdr version** (`herdr --version`), its server
|
|
1048
|
-
running, and key-based SSH access from the server. Example, on the server:
|
|
1049
|
-
|
|
1050
|
-
```sh
|
|
1051
|
-
ssh-keygen -t ed25519 -f ~/.ssh/wherdr_ed25519 -N ''
|
|
1052
|
-
cat >> ~/.ssh/config <<'CFG'
|
|
1053
|
-
Host laptop
|
|
1054
|
-
HostName laptop
|
|
1055
|
-
User alice
|
|
1056
|
-
IdentityFile ~/.ssh/wherdr_ed25519
|
|
1057
|
-
IdentitiesOnly yes
|
|
1058
|
-
CFG
|
|
1059
|
-
ssh-copy-id -i ~/.ssh/wherdr_ed25519 laptop
|
|
1060
|
-
ssh laptop true # accept the host key once
|
|
1061
|
-
herdr machine add laptop --label Laptop # see `herdr machine --help`
|
|
1062
|
-
```
|
|
1063
|
-
|
|
1064
|
-
wherdr picks it up within 30 seconds; `herdr machine rename|disable|remove` work the same way.
|
|
1065
|
-
Machines can also be renamed from the app (machine menu → Options).
|
|
1066
|
-
|
|
1067
|
-
With Docker, the image includes `openssh-client` and reads `~/.ssh` through the read-only home
|
|
1068
|
-
mount. At startup the container creates its user with your `PUID`/`PGID` and `HOST_HOME` as its
|
|
1069
|
-
home folder, so ssh finds `~/.ssh` there.
|
|
1070
|
-
|
|
1071
|
-
## Claude and Codex quotas
|
|
1072
|
-
|
|
1073
|
-
The home screen shows what is left of the Claude and Codex usage limits (5-hour window and
|
|
1074
|
-
week, with reset times), for every online machine.
|
|
1075
|
-
|
|
1076
|
-
- **Codex**: nothing to do, Codex writes its limits in its session files. Machines on different
|
|
1077
|
-
Codex accounts get one block each (told apart by a short hash of the account ID found in the
|
|
1078
|
-
session files; `~/.codex/auth.json` is never read).
|
|
1079
|
-
- **Claude Code** only gives them to its status line. On each machine, install wherdr's
|
|
1080
|
-
invisible status line (it displays nothing, and chains to your existing status line if you
|
|
1081
|
-
have one):
|
|
1082
|
-
|
|
1083
|
-
```sh
|
|
1084
|
-
sh scripts/install-claude-statusline.sh
|
|
1085
|
-
```
|
|
1086
|
-
|
|
1087
|
-
The app offers the same thing: a banner "Claude quotas not configured" with **Install** (runs
|
|
1088
|
-
it over SSH on a remote machine) and **Copy command**. Quotas show after the next exchange with
|
|
1089
|
-
a Claude agent. The status line stores the limits in `~/.cache/herdr-web/claude-status.json`
|
|
1090
|
-
and a short hash of the account ID (never a token or email) to tell accounts apart.
|
|
1091
|
-
|
|
1092
|
-
**New machine**: run `sh scripts/install-claude-statusline.sh` on it once (or click **Install** in
|
|
1093
|
-
the banner), then send a message to any Claude agent there; its quotas appear on the home screen.
|
|
1094
|
-
|
|
1095
|
-
## Notifications
|
|
1096
|
-
|
|
1097
|
-
With notifications enabled on a device, wherdr sends a Web Push when an agent goes from working
|
|
1098
|
-
to **blocked** (with its question and options) or to **done** (with the start of its answer).
|
|
1099
|
-
There is no push for the agent currently on screen. Settings → Notifications lets each device
|
|
1100
|
-
choose between all agents and main agents only (herdr-projects threads are muted by default).
|
|
1101
|
-
|
|
1102
|
-
**Do not disturb** (top of Settings → Notifications) stops every push, for this device or for all
|
|
1103
|
-
devices, for 1 hour, until 8 am or until turned back on. The server applies it before sending and
|
|
1104
|
-
keeps it across restarts (`data/quiet.json` for all devices). While it is on, a crossed-out bell
|
|
1105
|
-
next to Settings on the home screen turns it off in one tap.
|
|
1106
|
-
|
|
1107
|
-
wherdr also relays custom notifications sent with `herdr notification show` (plugins, scripts).
|
|
1108
|
-
To receive them it connects to Herdr's client socket as a **passive** client: it never becomes
|
|
1109
|
-
the foreground client and never resizes anything. Side effects: with wherdr connected,
|
|
1110
|
-
`notification show` reports `shown: true` even with no terminal attached, and an empty session
|
|
1111
|
-
gets a default workspace, as with any attached client.
|
|
1112
|
-
|
|
1113
|
-
## Works great with herdr-projects
|
|
1114
|
-
|
|
1115
|
-
[herdr-projects](https://github.com/eliasstravik/herdr-projects) is a Herdr plugin that runs a
|
|
1116
|
-
project as one **coordinator** conversation and parallel **threads**, each in its own Git
|
|
1117
|
-
worktree. It is optional; with it installed, wherdr adds:
|
|
1118
|
-
|
|
1119
|
-
- **Project groups** in the agent list: the coordinator first, its threads indented under it,
|
|
1120
|
-
grouped by repository (`Repo web-shop · 2 worktrees`), with their thread number and state.
|
|
1121
|
-
- **Project panel** next to the coordinator (a side panel on a computer, a tab on a phone). It
|
|
1122
|
-
reads the project's `TASKS.md` and shows the lists it has, always in this order, with the open
|
|
1123
|
-
threads live (state, progress, report) and resolved threads under Done (**Tasks** tab; the
|
|
1124
|
-
**Threads**, **Inbox**, **Routines**, **Settings** and **Memory** tabs are described below):
|
|
1125
|
-
|
|
1126
|
-
| List | Meaning | Buttons |
|
|
1127
|
-
| --- | --- | --- |
|
|
1128
|
-
| **To test** | You confirm the work | Confirm, Problem, Question |
|
|
1129
|
-
| **To decide** | Only you can unblock it | Question, Answer |
|
|
1130
|
-
| **To review** | Pull requests for you | Reviewed, Comment |
|
|
1131
|
-
| **In progress** | What threads are really doing | (live threads) Add info |
|
|
1132
|
-
| **In queue** | Decided: the coordinator launches the first one as soon as a thread slot frees | Move up / down, Launch now, Clarify, Remove from queue |
|
|
1133
|
-
| **Blocked** | Waiting for someone or something external | Unblock, Clarify |
|
|
1134
|
-
| **To do** | To do soon, in your priority order; never launched on its own | Move up / down, Queue it, Launch now, Clarify, Back to backlog |
|
|
1135
|
-
| **Backlog** | Everything else | Launch, Clarify |
|
|
1136
|
-
| **Done** | Resolved threads | |
|
|
1137
|
-
|
|
1138
|
-
Every list is **optional**: you and your coordinator pick the ones a project uses (a small
|
|
1139
|
-
project may only need Backlog, In progress and Done); any other `##` list shows in a neutral
|
|
1140
|
-
style after Backlog. French titles and common synonyms are recognized (À tester, En file,
|
|
1141
|
-
À faire, Plus tard…). Every button sends a ready-made message to the coordinator, which edits
|
|
1142
|
-
`TASKS.md` (wherdr never writes it). **Add info**, on a working thread, opens a text field:
|
|
1143
|
-
what you type goes to the coordinator, which passes it on to that thread as is. The In queue
|
|
1144
|
-
header shows the thread slots in use (`max_parallel_threads` in `PROJECT.md`) and the next
|
|
1145
|
-
task, plus the machine's global limit when one is set
|
|
1146
|
-
(`2 of 2 thread slots in use on <machine> (all projects)`).
|
|
1147
|
-
- **Threads tab** of the Project panel: every thread of the project, resolved ones included.
|
|
1148
|
-
Open threads are grouped as herdr-projects groups them (Your turn, Ready for review, Working…),
|
|
1149
|
-
each with its machine and agent profile; resolved threads stay in their own folded section,
|
|
1150
|
-
shown by pages of 30. One filter covers both (ID, title, machine, profile). A tap opens the
|
|
1151
|
-
thread's record: state, its **Next** list, branch, dates, and these actions:
|
|
1152
|
-
|
|
1153
|
-
| Action | Command | Asks first |
|
|
1154
|
-
| --- | --- | --- |
|
|
1155
|
-
| **Acknowledge** | `thread ack`: you have seen its report | |
|
|
1156
|
-
| **Restart** | `thread restart`: bring back a thread whose pane is gone or whose start failed | |
|
|
1157
|
-
| **Stop** | `thread stop`: Escape in its agent's pane | Yes |
|
|
1158
|
-
| **Resolve** | `thread resolve`: final copy, then its worktree, merged branch and tab are cleaned up (report and library kept) | Yes |
|
|
1159
|
-
|
|
1160
|
-
Open agent, View report and Pull request are there too when they apply. wherdr only runs
|
|
1161
|
-
these `herdr-projects` commands on the coordinator's machine (and reads `thread show --json`);
|
|
1162
|
-
it writes no thread file itself, and herdr-projects has the last word (it refuses to restart a
|
|
1163
|
-
running thread, for instance): its answer is shown as is. With Docker, **Resolve** runs in
|
|
1164
|
-
the container, where the home folder is read-only: a thread of this machine is resolved and
|
|
1165
|
-
its open workspace removed through Herdr, but its merged branch, or a worktree whose workspace
|
|
1166
|
-
was already closed, may stay. herdr-projects lists what it kept, and
|
|
1167
|
-
`herdr-projects sweep <project>` on the host removes it.
|
|
1168
|
-
- **Inbox tab**: the project's unhandled inbox items, what herdr-projects tells the coordinator
|
|
1169
|
-
(a thread's new report, a routine due, a machine out of reach…), oldest first, up to 100. An
|
|
1170
|
-
item about a thread shows the thread's title and opens it. **Done** (✓) marks one item as
|
|
1171
|
-
handled with `herdr-projects inbox done`; a coordinator that has not read it yet will not see
|
|
1172
|
-
it any more. Summaries are shown as plain text.
|
|
1173
|
-
- **Routines tab**: each routine's schedule, whether it is enabled, its kind (prompt only, or a
|
|
1174
|
-
command and its approval) and its last and next run. Read-only: wherdr never approves,
|
|
1175
|
-
switches or runs a routine, and does not expose the plugin's safety settings.
|
|
1176
|
-
- **Settings tab**, read-only: what the project's `PROJECT.md` sets, the keys
|
|
1177
|
-
`herdr-projects set` changes (name, goal, coordinator and thread profiles,
|
|
1178
|
-
`max_parallel_threads`, `auto_resolve_days`, `nudge`, `mute`, repositories). A key the file
|
|
1179
|
-
leaves out shows the plugin's default, marked as such. Safety settings are not shown.
|
|
1180
|
-
- **Memory tab**, read-only: `MEMORY.md` (the index) and the `.md` files of `memory/`, with the
|
|
1181
|
-
title and hook the index gives them; a file the index does not mention is listed by name. A
|
|
1182
|
-
tap opens one, rendered as Markdown. The coordinator writes memory, wherdr never does.
|
|
1183
|
-
|
|
1184
|
-
With six tabs, the bar scrolls sideways on a phone and in a narrow side panel; a fade on its
|
|
1185
|
-
edge says there are more.
|
|
1186
|
-
|
|
1187
|
-
herdr-projects (0.2.34) has no JSON output for the inbox or the routines: wherdr reads the
|
|
1188
|
-
front matter of the project's `inbox/*.md` files, and the tab-separated lines of
|
|
1189
|
-
`herdr-projects routine list`, showing the run texts (`last … · next …`) as printed. No command
|
|
1190
|
-
prints a project's settings or memory either: those two tabs read the files named above.
|
|
1191
|
-
- **Board** (`H`, or the board button above the agent list): every project, thread, task and
|
|
1192
|
-
agent at a glance, in four columns. Agents outside projects show too. Each card opens what it
|
|
1193
|
-
stands for: a project or task opens its Project panel, a thread opens its agent (or its report
|
|
1194
|
-
once closed), an agent opens its conversation. A project card takes the most urgent column of
|
|
1195
|
-
its cards and counts them; Finished shows the 5 most recent threads and done tasks of each
|
|
1196
|
-
project, **Show all** the rest.
|
|
1197
|
-
|
|
1198
|
-
| Card | Blocked | Todo | Ongoing | Finished |
|
|
1199
|
-
| --- | --- | --- | --- | --- |
|
|
1200
|
-
| **Task** (its `TASKS.md` list) | Blocked | To test, To decide, To review, In queue, To do, Backlog, other lists | In progress without an open thread | Done, or a checked line |
|
|
1201
|
-
| **Thread** | Its agent is your turn, or *waiting on you* | *Ready for review* | Working, landing, idle | Resolved |
|
|
1202
|
-
| **Agent** | Your turn | | Working, starting, restarting | Done, ready |
|
|
1203
|
-
|
|
1204
|
-
Moving a **task** card (drag and drop on a computer, **Move to…** everywhere) sends the
|
|
1205
|
-
coordinator the Project panel's message when one says it (**Launch now** from To do or In
|
|
1206
|
-
queue, **Launch** from Backlog, **Unblock** from Blocked, **Tested** from To test, **Reviewed**
|
|
1207
|
-
from To review), otherwise `↳ Mark blocked: …`, `↳ Move to To do: …` or `↳ Mark done: …`. The
|
|
1208
|
-
card shows *Sent* until the coordinator has moved the line; only the allowed columns are
|
|
1209
|
-
offered, and To decide tasks wait for your answer. Thread, agent and project cards follow
|
|
1210
|
-
what they do and do not move. The server reads every project at once
|
|
1211
|
-
(`GET /api/board`); a project on an offline machine keeps its last read, marked as such.
|
|
1212
|
-
- **Max threads per machine**, across all projects: herdr-projects only limits threads per
|
|
1213
|
-
project, so three coordinators could still start a dozen threads on one small machine. Set a
|
|
1214
|
-
limit per machine in **Settings → Plugins → herdr-projects** (empty `—` = no global limit;
|
|
1215
|
-
saved on the server, in `data/thread-limits.json`). wherdr counts the active threads of every
|
|
1216
|
-
project from the live panes (herdr-projects marks each open thread's pane): a thread whose agent
|
|
1217
|
-
has finished (idle or done, waiting for review or for you) gives its slot back right away; one
|
|
1218
|
-
started less than 3 minutes ago still counts while it waits for its brief. wherdr writes
|
|
1219
|
-
`.wherdr-limits.json` in each herdr-projects folder where a coordinator runs, refreshed on
|
|
1220
|
-
every change:
|
|
1221
|
-
|
|
1222
|
-
```json
|
|
1223
|
-
{ "updated": "…", "this": "Server", "machines": { "Server": { "max": 2, "open": 2, "free": 0, "threads": ["shop/t-0012", "docs/t-0003"] } } }
|
|
1224
|
-
```
|
|
1225
|
-
|
|
1226
|
-
The coordinator rules (Settings) tell the coordinator to read it before starting a thread and
|
|
1227
|
-
to wait while `free` is `0`, even if its project still has slots; In queue tasks start when the
|
|
1228
|
-
machine has a free slot. `herdr-projects overview` is the fallback when the file is missing.
|
|
1229
|
-
The same data is available read-only at `GET /api/plugins/thread-limits`.
|
|
1230
|
-
- **Badges**, the only decoration on task lines, chosen by the coordinator (wherdr adds none
|
|
1231
|
-
on its own): `[b:color(text)]`, or `[b:color(text)](target)` for a clickable badge with a small ↗.
|
|
1232
|
-
The target is an `https://` / `http://` link (new tab) or a thread ID such as `t-0140` (opens
|
|
1233
|
-
its tab); any other scheme is ignored. Color is `red`, `orange`, `amber`, `green`, `teal`,
|
|
1234
|
-
`blue`, `violet`, `pink`, `gray` (theme-aware), a `#rgb` / `#rrggbb` hex, or empty for neutral;
|
|
1235
|
-
at most 24 characters are shown. A bare URL in the text shows as a plain link, and the owner
|
|
1236
|
-
`(me)` / `(agent)` is read but not shown. The coordinator rules (Settings) tell the coordinator
|
|
1237
|
-
to use badges on its own when they help (status, thread, PR, device, priority…), sparingly,
|
|
1238
|
-
and to follow your preferences. Example:
|
|
1239
|
-
|
|
1240
|
-
```markdown
|
|
1241
|
-
- [ ] Stop button on iPhone [b:red(bug)] [b:gray(t-0140)](t-0140) [b:blue(PR #12)](https://github.com/owner/repo/pull/12) (me)
|
|
1242
|
-
```
|
|
1243
|
-
- **Settings → Plugins → herdr-projects**: install status per machine, the `TASKS.md` convention,
|
|
1244
|
-
a template, the max threads per machine and the coordinator rules to copy.
|
|
1245
|
-
- **Projects entries** in the menus when the plugin is on the agent's machine: an agent's menu
|
|
1246
|
-
(`…`) and its card's (right click, long press on the phone) offer the Project panel, **New
|
|
1247
|
-
project…**, **Continue as a project…** (an agent outside a project), **Pause this project…**
|
|
1248
|
-
and **Check herdr-projects setup**; the project's header in the list has its own menu with
|
|
1249
|
-
the same entries. Shortcuts: `Mod+Alt+P` (Project panel) and `Mod+Alt+Shift+N` (New project).
|
|
1250
|
-
The plugin's other actions stay under **Plugin actions**.
|
|
1251
|
-
- **New project** with the same machine and folder picker as **New agent**, and the agent
|
|
1252
|
-
profiles of its coordinator and threads (see [Herdr plugins](#herdr-plugins)).
|
|
1253
|
-
|
|
1254
|
-
<p align="center">
|
|
1255
|
-
<img src="docs/screenshots/project-panel.png" alt="A coordinator with the Project panel open, and its project group with threads in the sidebar" width="900">
|
|
1256
|
-
</p>
|
|
1257
|
-
<p align="center">
|
|
1258
|
-
<img src="docs/screenshots/project-phone.png" alt="The Project panel on a phone" width="340">
|
|
1259
|
-
</p>
|
|
1260
|
-
<p align="center"><sub>A coordinator with its Project panel, on a computer and on a phone (demo data).</sub></p>
|
|
1261
|
-
|
|
1262
|
-
### Using wherdr with herdr-projects
|
|
1263
|
-
|
|
1264
|
-
The Project panel only works well when the coordinator follows its conventions: which lists
|
|
1265
|
-
mean what, the badges, `(agent → t-NNNN)`, and what to do with each message the buttons send
|
|
1266
|
-
(`✓ Tested: …`, `↳ Queue: …`, `↳ Problem: … —` or `↳ Decision: … —` with the answer below it…). They are written once, as an agent skill:
|
|
1267
|
-
[`skills/wherdr/SKILL.md`](skills/wherdr/SKILL.md).
|
|
1268
|
-
|
|
1269
|
-
The npm package (also used by Homebrew) and Docker image include the skill from that
|
|
1270
|
-
release. Installation is best effort: a missing agent or an unwritable home never blocks
|
|
1271
|
-
wherdr. It links `~/.claude/skills/wherdr` (or `$CLAUDE_CONFIG_DIR/skills/wherdr`) only
|
|
1272
|
-
when the Claude configuration folder exists, and `~/.agents/skills/wherdr` only when
|
|
1273
|
-
`.agents`, `.codex` or `.omp` exists. Links point to a stable copy in
|
|
1274
|
-
`${WHERDR_DIR:-$HOME/wherdr}/skills/wherdr`. Existing skills from elsewhere are left alone.
|
|
1275
|
-
Relative `WHERDR_DIR` resolves from the working directory; relative `CLAUDE_CONFIG_DIR`
|
|
1276
|
-
resolves from HOME, including when saved in a login service. Unchanged content is not rewritten.
|
|
1277
|
-
A broken link is repaired only when local metadata proves wherdr created it.
|
|
1278
|
-
|
|
1279
|
-
| Install mode | Skill installation and refresh |
|
|
1280
|
-
| --- | --- |
|
|
1281
|
-
| npm / npx / Bun / pnpm | On `wherdr`, `wherdr run`, `wherdr start` or `wherdr service install`; after updating the package, the next start refreshes it. |
|
|
1282
|
-
| Homebrew | On `wherdr` startup, including the Homebrew service; restart the service after `brew upgrade`. |
|
|
1283
|
-
| One-tap update (npm, Homebrew, native plugin) | Restarting wherdr refreshes the skill from the newly installed package; rollback restores the previous copy when that package also bundles the skill. |
|
|
1284
|
-
| Herdr plugin, Node.js | On plugin installation / reinstallation, and on startup from its installed package. |
|
|
1285
|
-
| Herdr plugin, Docker | On plugin installation and Update, extracted from the pulled image on the host. |
|
|
1286
|
-
| One-command installer, Docker | After each pull, including reruns and `WHERDR_MODE=docker`; saves host helpers in `$WHERDR_DIR/skill-installer`. |
|
|
1287
|
-
| Docker by hand | Run the host-side command below after the initial pull and after each image update. |
|
|
1288
|
-
| From source | Use `node bin/wherdr.mjs` after building, or run `sh bin/lib/install-skill.sh` yourself after updating; `npm start` alone does not install the skill. |
|
|
1289
|
-
|
|
1290
|
-
Docker cannot write the host's agent folders through its read-only home mount. From the
|
|
1291
|
-
repository checkout on the **host**, after pulling your image, run:
|
|
1292
|
-
|
|
1293
|
-
```sh
|
|
1294
|
-
sh bin/install-docker-skill.sh ghcr.io/afloury/wherdr:latest
|
|
1295
|
-
```
|
|
1296
|
-
|
|
1297
|
-
Pass your image tag if different. The helper creates a stopped temporary container, copies
|
|
1298
|
-
only `/app/skills/wherdr/SKILL.md` out of it, uses the local host installer, then removes
|
|
1299
|
-
the container. No host home is mounted into it. Repeat this command after each manual Docker update; Docker has no one-tap
|
|
1300
|
-
update in the app. Manual failures (including older images without the skill) return a
|
|
1301
|
-
nonzero status with diagnostics; plugin and one-command installation warn and continue. Without a checkout, use
|
|
1302
|
-
`sh "${WHERDR_DIR:-$HOME/wherdr}/skill-installer/bin/install-docker-skill.sh" <image>` after the one-command install.
|
|
1303
|
-
|
|
1304
|
-
Remove managed links with `sh bin/lib/install-skill.sh uninstall` (or
|
|
1305
|
-
`sh scripts/herdr-plugin.sh skill uninstall` in the plugin folder). Uninstall leaves
|
|
1306
|
-
`~/.local/state/wherdr/skill-disabled`: subsequent starts and updates respect it, even if
|
|
1307
|
-
`WHERDR_DIR` changes. To re-enable, explicitly run `sh bin/lib/install-skill.sh install`
|
|
1308
|
-
or the plugin's `skill install` (or the Docker host helper above). `WHERDR_SKILL=off`
|
|
1309
|
-
disables all installation and refresh, including explicit install; export it for interactive
|
|
1310
|
-
starts or set it when installing the login service to preserve it there.
|
|
1311
|
-
|
|
1312
|
-
Two limitations remain: rollback to an older package without the helper (such as 1.3.2)
|
|
1313
|
-
leaves the failed release's skill in place. With multiple wherdr versions sharing an account,
|
|
1314
|
-
the last one started supplies the skill; there is no version comparison.
|
|
1315
|
-
|
|
1316
|
-
You can also paste **Settings → Plugins → herdr-projects → Copy rules for the coordinator** to your agent.
|
|
1317
|
-
|
|
1318
|
-
## Herdr plugins
|
|
1319
|
-
|
|
1320
|
-
Actions declared by the plugins installed on a machine (`herdr plugin install|link`) appear in
|
|
1321
|
-
the menus: workspace / tab / pane actions in the agent menu, global actions in the machine menu.
|
|
1322
|
-
wherdr always passes the agent's pane explicitly. It asks for confirmation before running an
|
|
1323
|
-
action, except for actions that only show or check something (list, show, status, doctor…).
|
|
1324
|
-
Actions that open a plugin pane or popup open it in the attached Herdr terminal client, not on the
|
|
1325
|
-
phone: the menu marks them **Opens in Herdr**. wherdr's own plugin actions (its panel, open in the
|
|
1326
|
-
browser) are not listed. With a single machine, the machine's actions are in the **…** menu of
|
|
1327
|
-
its session row, under the agent list header.
|
|
1328
|
-
|
|
1329
|
-
**herdr-projects** actions that ask for input (New project, continue this workspace as a
|
|
1330
|
-
project, open, pause, resume) are run by wherdr itself as `herdr-projects` commands, because
|
|
1331
|
-
Herdr's API cannot pass them arguments. **New project** picks its repository like **New agent**
|
|
1332
|
-
picks a folder: the machine (when the project is created from this machine, a repository on an
|
|
1333
|
-
SSH machine is passed as `PATH@MACHINE`), a folder browser and recent folders. It suggests the
|
|
1334
|
-
root of the current space's Git repository (nothing outside a repository, never your home
|
|
1335
|
-
folder itself), offers the root when you pick a subfolder, and names the project after the
|
|
1336
|
-
repository. It also asks which agent profile runs the **coordinator** and which one the
|
|
1337
|
-
**threads** start with: the profiles of the project's machine (`herdr-projects profile list`:
|
|
1338
|
-
yours and the signed-in built-ins such as Claude or omp) that the plugin allows for each role,
|
|
1339
|
-
with the plugin's defaults for new projects selected (or the first profile listed, when a default
|
|
1340
|
-
is not available there). The choice is written to the project's
|
|
1341
|
-
`PROJECT.md` (`coordinator_profile`, `thread_profile`); with a herdr-projects without profiles,
|
|
1342
|
-
the choice is not shown.
|
|
1343
|
-
With Docker, these commands write to the projects folder, so `docker-compose.yml` mounts
|
|
1344
|
-
`~/.herdr-projects` read-write. If you set another `root` in
|
|
1345
|
-
`~/.config/herdr-projects/config.toml`, mount that folder instead (same path on both sides);
|
|
1346
|
-
otherwise wherdr reports that the home folder is read-only. Check setup only reads; the other
|
|
1347
|
-
actions (Configure…) run on the host through Herdr, as in the terminal client. When no herdr-projects
|
|
1348
|
-
ticker is running yet, the one these commands start inside the container is stopped right away;
|
|
1349
|
-
the next `herdr-projects` command run on the host (a coordinator starting a thread, Herdr
|
|
1350
|
-
starting) starts it there.
|
|
1351
|
-
|
|
1352
|
-
## Security
|
|
1353
|
-
|
|
1354
|
-
wherdr drives Herdr, so **it can run anything as your user**. Docker does not change that: the
|
|
1355
|
-
container talks to the Herdr server running on the host.
|
|
1356
|
-
|
|
1357
|
-
- It listens on `127.0.0.1` only. Reach it from other devices **only through a private
|
|
1358
|
-
network** (Tailscale or [equivalent](#other-private-networks)). Never add a public route
|
|
1359
|
-
(port forwarding, public reverse proxy, ngrok…); the only exception is a Cloudflare Tunnel
|
|
1360
|
-
behind Cloudflare Access, with its risks.
|
|
1361
|
-
- Requests are accepted only for `localhost`, loopback addresses, the runtime's hostname, the
|
|
1362
|
-
hostname in `APP_URL`, the tailnet name `tailscale serve` publishes for wherdr's own port
|
|
1363
|
-
(read from Tailscale on the machine, never from a request, and only while it is published),
|
|
1364
|
-
and the runtime's network interface addresses (those of the container
|
|
1365
|
-
with Docker). Add other private names or IP addresses to `HERDR_WEB_ALLOWED_HOSTS`
|
|
1366
|
-
(comma-separated) before using them. An unlisted name returns HTTP 403 (`host` / "Host not
|
|
1367
|
-
allowed"); add it to `HERDR_WEB_ALLOWED_HOSTS`, then restart wherdr. This host check also
|
|
1368
|
-
applies to WebSockets.
|
|
1369
|
-
- **Passkey lock** (Settings → Security, recommended): once a passkey is registered, the API,
|
|
1370
|
-
images, photos and WebSockets require an unlocked session (signed HttpOnly cookie). Two limits
|
|
1371
|
-
apply:
|
|
1372
|
-
- **12 hours without use**: the session slides, so the app only locks again after 12 hours
|
|
1373
|
-
without any request (or when you lock it from Settings).
|
|
1374
|
-
- **Maximum duration** since the last passkey unlock, set in Settings → Security for every
|
|
1375
|
-
device: 1 day, 7 days (default), 30 days, 90 days or 1 year. The unlock time is signed in the
|
|
1376
|
-
cookie and the slide never moves it. Past it, the device asks for the passkey again at the
|
|
1377
|
-
next opening of the app (launch, reload, or back in the foreground), never in the middle of
|
|
1378
|
-
use; the app warns in the last hours. A client that never reopens is cut 12 hours after the
|
|
1379
|
-
deadline. A shorter duration applies to the sessions already open.
|
|
1380
|
-
|
|
1381
|
-
A network error or a server restart never locks the app. **Lock all devices** (Settings →
|
|
1382
|
-
Security, for a lost or stolen device) ends every session, this one included, and keeps the
|
|
1383
|
-
passkeys: each device unlocks again with its own. Turning the lock off removes the passkeys and
|
|
1384
|
-
invalidates every session. Lost passkey: delete `data/auth.json` on the server; the app is open
|
|
1385
|
-
again to whoever can reach it.
|
|
1386
|
-
- <a id="new-device"></a>**New device**: it can only get in with an existing passkey. Unlock it
|
|
1387
|
-
with a passkey synced by your password manager (iCloud Keychain, Google Password Manager,
|
|
1388
|
-
1Password…), or with the passkey of another device through the browser's "use a phone or
|
|
1389
|
-
tablet" QR code, then Settings → Security → **Add this device** to give it its own passkey.
|
|
1390
|
-
- Without a passkey, **anyone who can reach the address can control your agents**: every device
|
|
1391
|
-
on your tailnet, and every user of your tailnet if you share it.
|
|
1392
|
-
- Writes require `Content-Type: application/json` (or `image/*` for photos, `application/octet-stream` for attached files) and an `Origin`
|
|
1393
|
-
matching the host, WebSockets included. API reads sent from another site (links, images,
|
|
1394
|
-
`no-cors` fetches, reported by the browser's `Sec-Fetch-Site`) are refused too.
|
|
1395
|
-
- A strict Content Security Policy limits scripts to the app's own origin and hashed inline
|
|
1396
|
-
scripts, and connections to that origin and its WebSockets. It also blocks embedded objects
|
|
1397
|
-
and framing.
|
|
1398
|
-
- wherdr never changes your agents' global settings: model and effort changes are for the
|
|
1399
|
-
current session only.
|
|
1400
|
-
|
|
1401
|
-
See [SECURITY.md](SECURITY.md) to report a vulnerability.
|
|
1402
|
-
|
|
1403
|
-
## Troubleshooting
|
|
1404
|
-
|
|
1405
|
-
- **"The Herdr server is not responding"**: start Herdr on the server (`herdr` or
|
|
1406
|
-
`herdr server`). With Docker, check that `HOST_HOME` and `PUID` match your user.
|
|
1407
|
-
- **No conversation, only the terminal**: the agent was started before the Herdr integration
|
|
1408
|
-
was installed, or it is not Claude Code / Codex / omp. Install the integration
|
|
1409
|
-
(`herdr integration install claude|codex|omp`) and start a new agent.
|
|
1410
|
-
- **Passkeys or notifications unavailable**: the page must be served over HTTPS (or
|
|
1411
|
-
`localhost`). On iPhone, notifications need the app installed on the home screen.
|
|
1412
|
-
- **HTTP 403 "This address isn't enabled yet"**: for a tailnet address, run the command the
|
|
1413
|
-
page gives on the machine: it tells whether `tailscale serve` publishes wherdr's port there.
|
|
1414
|
-
For another private name, add the hostname or IP address to `HERDR_WEB_ALLOWED_HOSTS`, or use
|
|
1415
|
-
the hostname in `APP_URL`, then restart wherdr.
|
|
1416
|
-
- **Remote machine offline**: `docker compose logs | grep machine` shows its state. Test SSH as
|
|
1417
|
-
wherdr does, from the container:
|
|
1418
|
-
`docker compose exec herdr-web ssh -o BatchMode=yes laptop '~/.local/bin/herdr status server'`.
|
|
1419
|
-
- `Could not resolve hostname`: container DNS (use the full name in `HostName`);
|
|
1420
|
-
- `Permission denied (publickey)` / `Host key verification failed`: key or `known_hosts`
|
|
1421
|
-
(run `ssh laptop true` once on the server);
|
|
1422
|
-
- `Bad owner or permissions`: `~/.ssh/config` must belong to you and not be group-writable.
|
|
1423
|
-
- **Claude says "Not logged in" on a Mac** whose Herdr server was started over SSH: the macOS
|
|
1424
|
-
keychain is locked in SSH sessions. Start the Herdr server from a terminal on the Mac.
|
|
1425
|
-
- **`Session open refused by peer` / `Connection closed by UNKNOWN port 65535`** in the logs: the
|
|
1426
|
-
shared SSH connection to a remote machine reached sshd's session limit (`MaxSessions`, 10 by
|
|
1427
|
-
default). wherdr stays under it and retries, so this is rare; if it persists (many terminals
|
|
1428
|
-
open at once), raise `MaxSessions` in the remote machine's `sshd_config`.
|
|
1429
|
-
- **Quotas missing**: see [Claude and Codex quotas](#claude-and-codex-quotas).
|
|
1430
|
-
- **iPhone layout cut at the bottom** after changing the status bar style: reinstall the home
|
|
1431
|
-
screen icon (iOS reads this setting at install time).
|
|
1432
|
-
|
|
1433
|
-
## Contributing
|
|
1434
|
-
|
|
1435
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md). Changes are listed in [CHANGELOG.md](CHANGELOG.md).
|
|
1436
|
-
|
|
1437
|
-
Every commit is public: a leak check (gitleaks plus your own forbidden patterns) covers the files
|
|
1438
|
-
and the commits about to be pushed; see [Leak check](CONTRIBUTING.md#leak-check).
|
|
1439
|
-
|
|
1440
|
-
## License
|
|
1441
|
-
|
|
1442
|
-
[MIT](LICENSE). The embedded font subsets keep their own licenses (`app/assets/fonts/`).
|
|
33
|
+
**wherdr is a remote shell as your user.** Keep it on localhost or a private network such as
|
|
34
|
+
Tailscale; never expose it to the public Internet. Read [Security](docs/security.md) before remote access.
|
|
35
|
+
|
|
36
|
+
## Documentation
|
|
37
|
+
|
|
38
|
+
- **[Overview and architecture](docs/overview.md)** — What wherdr does, why it exists and how it connects to Herdr. Includes the launch film and license.
|
|
39
|
+
- **[First launch](docs/quick-start.md)** — Install the native service and follow the setup guide. Includes the desktop, Board and phone screenshots.
|
|
40
|
+
- **[Home and agent list](docs/home.md)** — Find agents, create a session and filter machines or projects. Covers compact rows and state grouping.
|
|
41
|
+
- **[Conversation and replies](docs/conversation.md)** — Read transcripts, answer questions, attach files and manage drafts. Includes quoted replies and decision tables.
|
|
42
|
+
- **[Terminal](docs/terminal.md)** — Use the real terminal, copy text, open links and paste images. Explains take-over and size restoration.
|
|
43
|
+
- **[Files and changes](docs/files.md)** — Review Git changes and comment on lines or blocks. Compose a review for the agent.
|
|
44
|
+
- **[Computer, phone and iPad](docs/devices.md)** — Use split panes, keyboard shortcuts and touch controls. Describes device-specific layouts.
|
|
45
|
+
- **[Installation and updates](docs/installation.md)** — Requirements, native installer, agent PATH, updates and migration from Docker.
|
|
46
|
+
- **[Migrate from a 1.x Docker install](docs/migrate-from-1.x-docker.md)** — Move a wherdr 1.x container to the native 2.0 service, keeping its port, passkeys and data. Written so an AI agent can run it step by step.
|
|
47
|
+
- **[Phone access and private networks](docs/networks.md)** — Set up Tailscale or another private network with HTTPS. Includes always-on servers and keep-awake.
|
|
48
|
+
- **[The wherdr command](docs/command.md)** — Install through npm or Homebrew and manage the native service from the command line.
|
|
49
|
+
- **[Herdr plugins](docs/plugins.md)** — Install and configure the wherdr plugin, its panel key and plugin actions.
|
|
50
|
+
- **[Install as an app](docs/phone.md)** — Add wherdr to your home screen on a phone or install it in a desktop browser.
|
|
51
|
+
- **[Settings and configuration](docs/configuration.md)** — Configure the service through environment variables and the app through shared and device-specific preferences.
|
|
52
|
+
- **[Machines and SSH](docs/machines.md)** — Register remote machines and use their agents through SSH. Includes sessions and connection troubleshooting.
|
|
53
|
+
- **[Claude and Codex quotas](docs/quotas.md)** — Show remaining usage and handle Codex updates, limits and restarts.
|
|
54
|
+
- **[Notifications](docs/notifications.md)** — Enable Web Push, relay Herdr notifications and configure Do not disturb.
|
|
55
|
+
- **[Board and coordinators](docs/board.md)** — Work with herdr-projects tasks, reviews, approvals and coordinators across machines.
|
|
56
|
+
- **[Security](docs/security.md)** — Understand remote-shell access, passkeys and network restrictions. Read this before enabling remote access.
|
|
57
|
+
- **[Development and tests](docs/development.md)** — Run from source and follow the contribution and agent test rules.
|
|
58
|
+
- **[Troubleshooting](docs/troubleshooting.md)** — Resolve startup, permissions, network, SSH and conversation problems.
|
|
59
|
+
|
|
60
|
+
[All documentation](docs/README.md) · [Contributing](CONTRIBUTING.md) · [Changelog](CHANGELOG.md) · [MIT license](LICENSE)
|
|
61
|
+
|
|
62
|
+
Unofficial project; not affiliated with or endorsed by Herdr, Anthropic or OpenAI.
|