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.
Files changed (252) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/BDNMzG2s.js +1 -0
  3. package/.output/public/_nuxt/BETID3_E.js +1 -0
  4. package/.output/public/_nuxt/BHenDMeH.js +1 -0
  5. package/.output/public/_nuxt/BYZVrreW.js +1 -0
  6. package/.output/public/_nuxt/BhgehRdJ.js +1 -0
  7. package/.output/public/_nuxt/{yzABg5xl.js → Bpoxvzh6.js} +1 -1
  8. package/.output/public/_nuxt/BtH82ZuZ.js +9 -0
  9. package/.output/public/_nuxt/C25ROUa9.js +3 -0
  10. package/.output/public/_nuxt/C3Bkn4ac.js +62 -0
  11. package/.output/public/_nuxt/CDWjMk9C.js +2 -0
  12. package/.output/public/_nuxt/{Ci8N4mWR.js → CXJnLmWv.js} +1 -1
  13. package/.output/public/_nuxt/CuUQ4mEf.js +1 -0
  14. package/.output/public/_nuxt/DFGEl0v2.js +23 -0
  15. package/.output/public/_nuxt/{CKhgznq4.js → DFmgQVme.js} +42 -39
  16. package/.output/public/_nuxt/DZGKxROq.js +6 -0
  17. package/.output/public/_nuxt/DmkWvmnE.js +2 -0
  18. package/.output/public/_nuxt/DrKKeFFE.js +31 -0
  19. package/.output/public/_nuxt/DyfFwwGq.js +1 -0
  20. package/.output/public/_nuxt/FaNto0ga.js +105 -0
  21. package/.output/public/_nuxt/NIF0VwHk.js +25 -0
  22. package/.output/public/_nuxt/R0Y4VoeX.js +1 -0
  23. package/.output/public/_nuxt/board.CEEcFc6M.css +1 -0
  24. package/.output/public/_nuxt/builds/latest.json +1 -1
  25. package/.output/public/_nuxt/builds/meta/0b28b3e9-664e-4347-a35b-afb0a711d5c5.json +1 -0
  26. package/.output/public/_nuxt/entry.CKoXcwRa.css +2 -0
  27. package/.output/public/_nuxt/pdf.min.t82Z80JU.mjs +28 -0
  28. package/.output/public/_nuxt/pdf.worker.min.wgc6bjNh.mjs +28 -0
  29. package/.output/public/_nuxt/settings.Cdb21nNw.css +1 -0
  30. package/.output/public/sw.js +1 -1
  31. package/.output/server/chunks/_/branding.mjs +1 -1
  32. package/.output/server/chunks/_/changes.mjs +4 -4
  33. package/.output/server/chunks/_/fileDownload.mjs +278 -0
  34. package/.output/server/chunks/_/files.mjs +60 -20
  35. package/.output/server/chunks/_/machineOrder.mjs +1 -1
  36. package/.output/server/chunks/_/onboarding.mjs +1 -1
  37. package/.output/server/chunks/_/package.mjs +11 -3
  38. package/.output/server/chunks/_/plugins.mjs +92 -122
  39. package/.output/server/chunks/_/restart.mjs +1 -1
  40. package/.output/server/chunks/_/selfupdate.mjs +1 -1
  41. package/.output/server/chunks/_/settingstabs.mjs +36 -0
  42. package/.output/server/chunks/_/updates.mjs +1 -1
  43. package/.output/server/chunks/_/worktrees.mjs +1 -1
  44. package/.output/server/chunks/_/ws.mjs +43 -6
  45. package/.output/server/chunks/nitro/nitro.mjs +14126 -10238
  46. package/.output/server/chunks/raw/check-doc-links.mjs +5 -0
  47. package/.output/server/chunks/raw/check-leaks.mjs +1 -1
  48. package/.output/server/chunks/raw/check-release-notes.mjs +5 -0
  49. package/.output/server/chunks/raw/herdr-plugin.mjs +1 -1
  50. package/.output/server/chunks/raw/install-claude-statusline.mjs +1 -1
  51. package/.output/server/chunks/raw/prerelease-summary.mjs +5 -0
  52. package/.output/server/chunks/raw/prerelease.mjs +7 -0
  53. package/.output/server/chunks/raw/reveal.mjs +1 -1
  54. package/.output/server/chunks/raw/test-affected.mjs +5 -0
  55. package/.output/server/chunks/routes/api/_..._.mjs +6 -5
  56. package/.output/server/chunks/routes/api/agents.post.mjs +6 -5
  57. package/.output/server/chunks/routes/api/ask-tab.post.mjs +6 -5
  58. package/.output/server/chunks/routes/api/attach.post.mjs +6 -5
  59. package/.output/server/chunks/routes/api/auth/disable.post.mjs +6 -5
  60. package/.output/server/chunks/routes/api/auth/lock-all.post.mjs +6 -5
  61. package/.output/server/chunks/routes/api/auth/lock.post.mjs +6 -5
  62. package/.output/server/chunks/routes/api/auth/login/options.post.mjs +6 -5
  63. package/.output/server/chunks/routes/api/auth/login/verify.post.mjs +6 -5
  64. package/.output/server/chunks/routes/api/auth/max-session.post.mjs +6 -5
  65. package/.output/server/chunks/routes/api/auth/register/options.post.mjs +6 -5
  66. package/.output/server/chunks/routes/api/auth/register/verify.post.mjs +6 -5
  67. package/.output/server/chunks/routes/api/auth/status.get.mjs +6 -5
  68. package/.output/server/chunks/routes/api/changes.get.mjs +7 -6
  69. package/.output/server/chunks/routes/api/chat/image.get.mjs +7 -6
  70. package/.output/server/chunks/routes/api/chat.get.mjs +44 -11
  71. package/.output/server/chunks/routes/api/choose.post.mjs +44 -30
  72. package/.output/server/chunks/routes/api/claude-setup.post.mjs +42 -0
  73. package/.output/server/chunks/routes/api/claude-statusline.post.mjs +7 -6
  74. package/.output/server/chunks/routes/api/clear-queued-input.post.mjs +42 -0
  75. package/.output/server/chunks/routes/api/close/status.get.mjs +7 -6
  76. package/.output/server/chunks/routes/api/close.post.mjs +6 -5
  77. package/.output/server/chunks/routes/api/codex-update.post.mjs +7 -6
  78. package/.output/server/chunks/routes/api/commands.get.mjs +9 -7
  79. package/.output/server/chunks/routes/api/config.get.mjs +10 -13
  80. package/.output/server/chunks/routes/api/dirs.get.mjs +7 -6
  81. package/.output/server/chunks/routes/api/dismiss-stopped.post.mjs +7 -6
  82. package/.output/server/chunks/routes/api/dismiss.post.mjs +7 -6
  83. package/.output/server/chunks/routes/api/effort.post.mjs +7 -6
  84. package/.output/server/chunks/routes/api/efforts.get.mjs +7 -6
  85. package/.output/server/chunks/routes/api/file.post.mjs +7 -6
  86. package/.output/server/chunks/routes/api/files/download.get.mjs +12 -24
  87. package/.output/server/chunks/routes/api/files/download.head.mjs +41 -0
  88. package/.output/server/chunks/routes/api/files/read.get.mjs +8 -7
  89. package/.output/server/chunks/routes/api/files.get.mjs +7 -6
  90. package/.output/server/chunks/routes/api/health.get.mjs +6 -5
  91. package/.output/server/chunks/routes/api/input.post.mjs +33 -15
  92. package/.output/server/chunks/routes/api/interrupt.post.mjs +24 -21
  93. package/.output/server/chunks/routes/api/isgit.get.mjs +8 -8
  94. package/.output/server/chunks/routes/api/machine/awake.get.mjs +7 -6
  95. package/.output/server/chunks/routes/api/machine/awake.post.mjs +7 -6
  96. package/.output/server/chunks/routes/api/machine/order.get.mjs +6 -5
  97. package/.output/server/chunks/routes/api/machine/order.post.mjs +6 -5
  98. package/.output/server/chunks/routes/api/machine/reconnect.post.mjs +40 -0
  99. package/.output/server/chunks/routes/api/machine/rename.post.mjs +7 -6
  100. package/.output/server/chunks/routes/api/machine/status.post.mjs +39 -0
  101. package/.output/server/chunks/routes/api/menu-left.post.mjs +40 -0
  102. package/.output/server/chunks/routes/api/menu.post.mjs +10 -6
  103. package/.output/server/chunks/routes/api/model.post.mjs +7 -6
  104. package/.output/server/chunks/routes/api/models.get.mjs +7 -6
  105. package/.output/server/chunks/routes/api/nav.post.mjs +40 -26
  106. package/.output/server/chunks/routes/api/onboarding.get.mjs +6 -5
  107. package/.output/server/chunks/routes/api/onboarding.post.mjs +6 -5
  108. package/.output/server/chunks/routes/api/phone.get.mjs +8 -10
  109. package/.output/server/chunks/routes/api/phone.post.mjs +8 -10
  110. package/.output/server/chunks/routes/api/plugins/actions.get.mjs +6 -8
  111. package/.output/server/chunks/routes/api/plugins/invoke.post.mjs +6 -8
  112. package/.output/server/chunks/routes/api/plugins/projects.get.mjs +10 -8
  113. package/.output/server/chunks/routes/api/plugins/projects.post.mjs +11 -7
  114. package/.output/server/chunks/routes/api/prompt.post.mjs +22 -13
  115. package/.output/server/chunks/routes/api/push/quiet.post.mjs +7 -6
  116. package/.output/server/chunks/routes/api/push/subscribe.post.mjs +7 -7
  117. package/.output/server/chunks/routes/api/push/test.post.mjs +7 -6
  118. package/.output/server/chunks/routes/api/push/unsubscribe.post.mjs +7 -6
  119. package/.output/server/chunks/routes/api/quotas.get.mjs +7 -6
  120. package/.output/server/chunks/routes/api/rename.post.mjs +6 -5
  121. package/.output/server/chunks/routes/api/requeue.post.mjs +7 -6
  122. package/.output/server/chunks/routes/api/restart.get.mjs +6 -5
  123. package/.output/server/chunks/routes/api/restart.post.mjs +6 -5
  124. package/.output/server/chunks/routes/api/reveal.post.mjs +91 -8
  125. package/.output/server/chunks/routes/api/screen-key.post.mjs +19 -15
  126. package/.output/server/chunks/routes/api/screen.get.mjs +8 -40
  127. package/.output/server/chunks/routes/api/search.get.mjs +7 -6
  128. package/.output/server/chunks/routes/api/seen.post.mjs +7 -6
  129. package/.output/server/chunks/routes/api/sessions.get.mjs +7 -6
  130. package/.output/server/chunks/routes/api/sessions.post.mjs +7 -6
  131. package/.output/server/chunks/routes/api/settings-tab.post.mjs +90 -0
  132. package/.output/server/chunks/routes/api/{plugins/thread-limits.get.mjs → settings.get.mjs} +10 -9
  133. package/.output/server/chunks/routes/api/settings.post.mjs +40 -0
  134. package/.output/server/chunks/routes/api/space.post.mjs +34 -18
  135. package/.output/server/chunks/routes/api/state.get.mjs +7 -6
  136. package/.output/server/chunks/routes/api/tasks/action.post.mjs +39 -0
  137. package/.output/server/chunks/routes/api/{plugins/profiles.get.mjs → tasks/add.post.mjs} +12 -14
  138. package/.output/server/chunks/routes/api/tasks/after.post.mjs +39 -0
  139. package/.output/server/chunks/routes/api/tasks/approve.post.mjs +39 -0
  140. package/.output/server/chunks/routes/api/{project/report.get.mjs → tasks/archived.get.mjs} +12 -13
  141. package/.output/server/chunks/routes/api/{gitroot.get.mjs → tasks/autopilot.get.mjs} +13 -14
  142. package/.output/server/chunks/routes/api/tasks/autopilot.post.mjs +39 -0
  143. package/.output/server/chunks/routes/api/{index.get.mjs → tasks/board-settings.get.mjs} +12 -13
  144. package/.output/server/chunks/routes/api/tasks/board-settings.post.mjs +39 -0
  145. package/.output/server/chunks/routes/api/tasks/coordinator.get.mjs +40 -0
  146. package/.output/server/chunks/routes/api/tasks/coordinator.post.mjs +39 -0
  147. package/.output/server/chunks/routes/api/tasks/details.get.mjs +40 -0
  148. package/.output/server/chunks/routes/api/tasks/limit.post.mjs +39 -0
  149. package/.output/server/chunks/routes/api/tasks/links.get.mjs +39 -0
  150. package/.output/server/chunks/routes/api/tasks/models.get.mjs +40 -0
  151. package/.output/server/chunks/routes/api/tasks/pick.post.mjs +39 -0
  152. package/.output/server/chunks/routes/api/tasks/question.post.mjs +39 -0
  153. package/.output/server/chunks/routes/api/tasks/scheduled.get.mjs +40 -0
  154. package/.output/server/chunks/routes/api/{project → tasks}/settings.get.mjs +10 -11
  155. package/.output/server/chunks/routes/api/tasks/start.get.mjs +40 -0
  156. package/.output/server/chunks/routes/api/tasks/tell.post.mjs +39 -0
  157. package/.output/server/chunks/routes/api/tasks.get.mjs +39 -0
  158. package/.output/server/chunks/routes/api/unqueue.post.mjs +7 -6
  159. package/.output/server/chunks/routes/api/update/status.get.mjs +6 -5
  160. package/.output/server/chunks/routes/api/update.get.mjs +8 -10
  161. package/.output/server/chunks/routes/api/update.post.mjs +8 -10
  162. package/.output/server/chunks/routes/api/upload.post.mjs +7 -6
  163. package/.output/server/chunks/routes/api/worktrees/remove.post.mjs +6 -5
  164. package/.output/server/chunks/routes/api/worktrees.get.mjs +6 -5
  165. package/.output/server/chunks/routes/favicon.ico.get.mjs +6 -5
  166. package/.output/server/chunks/routes/icons/_name_.get.mjs +7 -6
  167. package/.output/server/chunks/routes/renderer.mjs +8 -7
  168. package/.output/server/chunks/routes/uploads/_...name_.get.mjs +7 -6
  169. package/.output/server/chunks/routes/ws/_..._.mjs +7 -6
  170. package/.output/server/chunks/routes/ws/events.mjs +9 -7
  171. package/.output/server/chunks/routes/ws/term.mjs +7 -6
  172. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  173. package/.output/server/index.mjs +7 -6
  174. package/.output/server/package.json +1 -1
  175. package/README.md +44 -1424
  176. package/bin/lib/commands.mjs +44 -9
  177. package/bin/lib/panel.mjs +3 -11
  178. package/bin/lib/service.mjs +41 -2
  179. package/bin/lib/skill.mjs +1 -1
  180. package/bin/lib/tailnet.d.mts +6 -3
  181. package/bin/lib/tailnet.mjs +70 -32
  182. package/bin/lib/userpath.d.mts +14 -0
  183. package/bin/lib/userpath.mjs +142 -0
  184. package/docs/README.md +58 -0
  185. package/docs/RELEASING.md +174 -0
  186. package/docs/board.md +164 -0
  187. package/docs/command.md +58 -0
  188. package/docs/configuration.md +39 -0
  189. package/docs/conversation.md +132 -0
  190. package/docs/development.md +38 -0
  191. package/docs/devices.md +58 -0
  192. package/docs/files.md +29 -0
  193. package/docs/herdr-projects-1x.md +278 -0
  194. package/docs/home.md +52 -0
  195. package/docs/installation.md +174 -0
  196. package/docs/machines.md +69 -0
  197. package/docs/migrate-from-1.x-docker.md +160 -0
  198. package/docs/networks.md +154 -0
  199. package/docs/notifications.md +83 -0
  200. package/docs/overview.md +71 -0
  201. package/docs/phone.md +26 -0
  202. package/docs/plugins.md +109 -0
  203. package/docs/quick-start.md +89 -0
  204. package/docs/quotas.md +65 -0
  205. package/docs/release-notes-2.0.0.md +84 -0
  206. package/docs/security.md +117 -0
  207. package/docs/terminal.md +15 -0
  208. package/docs/troubleshooting.md +45 -0
  209. package/package.json +11 -3
  210. package/skills/wherdr/SKILL.md +24 -167
  211. package/.output/public/_nuxt/B4Yk2tB1.js +0 -23
  212. package/.output/public/_nuxt/BBw_0a4M.js +0 -6
  213. package/.output/public/_nuxt/BSy6OCIY.js +0 -1
  214. package/.output/public/_nuxt/BX5PM2JQ.js +0 -1
  215. package/.output/public/_nuxt/BgPDnf2S.js +0 -1
  216. package/.output/public/_nuxt/BkOxQzbS.js +0 -2
  217. package/.output/public/_nuxt/CP8o925U.js +0 -1
  218. package/.output/public/_nuxt/CWwyK2XK.js +0 -2
  219. package/.output/public/_nuxt/CkbmecwS.js +0 -1
  220. package/.output/public/_nuxt/Cr3SrGky.js +0 -1
  221. package/.output/public/_nuxt/DAg9fiVh.js +0 -89
  222. package/.output/public/_nuxt/DT8qZ0Wv.js +0 -1
  223. package/.output/public/_nuxt/DaaJIRB3.js +0 -1
  224. package/.output/public/_nuxt/DcJi16XA.js +0 -1
  225. package/.output/public/_nuxt/SiL1FJ5s.js +0 -3
  226. package/.output/public/_nuxt/VpjCgSXu.js +0 -36
  227. package/.output/public/_nuxt/builds/meta/c735b6c0-1bd0-47d7-a208-d310f0a06287.json +0 -1
  228. package/.output/public/_nuxt/dPxB2L94.js +0 -89
  229. package/.output/public/_nuxt/entry.CGWKmYU3.css +0 -2
  230. package/.output/public/_nuxt/xYX85SPI.js +0 -1
  231. package/.output/server/chunks/_/pluginPolicy.mjs +0 -93
  232. package/.output/server/chunks/_/projectBoard.mjs +0 -366
  233. package/.output/server/chunks/_/projectDocs.mjs +0 -106
  234. package/.output/server/chunks/_/projectInbox.mjs +0 -135
  235. package/.output/server/chunks/_/projectThreads.mjs +0 -46
  236. package/.output/server/chunks/_/projectThreads2.mjs +0 -61
  237. package/.output/server/chunks/_/projectsActions.mjs +0 -143
  238. package/.output/server/chunks/_/projectsPlugin.mjs +0 -56
  239. package/.output/server/chunks/_/reveal.mjs +0 -115
  240. package/.output/server/chunks/raw/dev.wherdr.open.mjs +0 -7
  241. package/.output/server/chunks/raw/docker-entrypoint.mjs +0 -7
  242. package/.output/server/chunks/raw/install-host-open.mjs +0 -7
  243. package/.output/server/chunks/raw/wherdr-open.mjs +0 -7
  244. package/.output/server/chunks/routes/api/board.get.mjs +0 -147
  245. package/.output/server/chunks/routes/api/plugins/thread-limits.post.mjs +0 -45
  246. package/.output/server/chunks/routes/api/project/inbox.get.mjs +0 -43
  247. package/.output/server/chunks/routes/api/project/inbox.post.mjs +0 -43
  248. package/.output/server/chunks/routes/api/project/memory.get.mjs +0 -42
  249. package/.output/server/chunks/routes/api/project/routines.get.mjs +0 -43
  250. package/.output/server/chunks/routes/api/project/thread.get.mjs +0 -44
  251. package/.output/server/chunks/routes/api/project/thread.post.mjs +0 -45
  252. package/bin/install-docker-skill.sh +0 -20
package/README.md CHANGED
@@ -1,1442 +1,62 @@
1
- <p align="center">
2
- <img src="server/assets/branding/icons/icon-512.png" alt="wherdr logo" width="120">
3
- </p>
1
+ # wherdr
4
2
 
5
- <h1 align="center">wherdr</h1>
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
- <p align="center"><b>A complete workspace for the coding agents running in <a href="https://herdr.dev">Herdr</a>, on your
8
- computer and on your phone.</b></p>
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
- <p align="center"><a href="https://wherdr.dev"><b>wherdr.dev</b></a> · <a href="#ask-your-ai-agent">Ask your AI agent</a></p>
9
+ ![Compact agent list and a task conversation on desktop](docs/screenshots/desktop.png)
11
10
 
12
- <p align="center"><a href="https://wherdr.dev/demo/"><b>Try it in your browser — no install</b></a>: the real app on scripted agents, nothing runs.</p>
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
- ### Ask your AI agent
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
- It installs the wherdr [Herdr plugin](#install-as-a-herdr-plugin), starts wherdr on
38
- `http://localhost:7683` and opens its setup guide in your browser; then **prefix+i** in Herdr opens
39
- the wherdr panel. Without Herdr, it says where to get it; on a Mac without Node.js 22, it offers
40
- `brew install node`; on Linux, Docker works too. On a server without a screen (or over SSH), it
41
- prints the address and the phone steps instead. See [The one command](#the-one-command).
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
- &nbsp;&nbsp;&nbsp;&nbsp;
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
- *Unofficial project. Not affiliated with or endorsed by Herdr, Anthropic or OpenAI. Claude,
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
- It follows [our setup guide for agents](https://wherdr.dev/agent.md): checks what you already have,
345
- asks before installing anything, sets up your phone over Tailscale, never exposes wherdr to the Internet.
346
-
347
- <a id="the-one-command"></a>
348
-
349
- ### Or run the installer yourself
350
-
351
- On the computer that runs Herdr, as your user, on macOS or Linux:
352
-
353
- ```sh
354
- curl -fsSL https://wherdr.dev/install | sh
355
- ```
356
-
357
- The script ([read it first](https://wherdr.dev/install), source in
358
- [`website/public/install`](website/public/install)) never uses sudo. It:
359
-
360
- 1. checks **Herdr ≥ 0.9.1**; without it, it stops with the link to [herdr.dev](https://herdr.dev);
361
- 2. leaves alone a wherdr that **already answers** on the port: a Herdr plugin install is offered
362
- its update, a Docker install shows its update command, anything else is left as it is. A
363
- `wherdr` container managed from another folder is never replaced or stopped;
364
- 3. checks what wherdr runs on: Docker Compose v2 on Linux when your user can use it, otherwise
365
- **Node.js 22** or Bun (always on macOS). On a Mac without them, it offers
366
- `brew install node` when Homebrew is there; elsewhere it stops with the install links;
367
- 4. runs `herdr plugin install afloury/wherdr --yes`: the [Herdr plugin](#install-as-a-herdr-plugin)
368
- installs wherdr in `~/wherdr`, starts it on `http://localhost:7683`, starts it again with Herdr,
369
- opens the setup guide in your browser (first install) and binds **prefix+i** to its panel;
370
- 5. on a computer **without a screen** (no `DISPLAY` on Linux, or an SSH session): opens nothing,
371
- prints the local address, an `ssh -L` tunnel to reach it from your computer, and the phone
372
- steps (`wherdr phone`: Tailscale state, the address, the QR code once it answers). With
373
- Docker, it offers to run `tailscale serve` for you (asked first), and wherdr enables the
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.