wherdr 1.3.4 → 2.0.1

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