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
@@ -0,0 +1,74 @@
1
+ # Machines and SSH
2
+
3
+ [Documentation](README.md) · [Project](../README.md)
4
+
5
+ - **Several machines**: agents of the SSH machines registered in Herdr (`herdr machine add`)
6
+ appear in their own section; everything works the same on a remote agent.
7
+ ## Several machines (SSH)
8
+
9
+ wherdr also shows the agents of the **SSH machines registered in Herdr** — the ones the Herdr
10
+ terminal client shows in its sidebar. It opens one SSH master connection per machine, forwards
11
+ the remote Herdr socket through it, and reads transcripts and files over the same connection.
12
+
13
+ Requirements on the remote machine: the **same Herdr version** (`herdr --version`), its server
14
+ running, and key-based SSH access from the server. Example, on the server:
15
+
16
+ ```sh
17
+ ssh-keygen -t ed25519 -f ~/.ssh/wherdr_ed25519 -N ''
18
+ cat >> ~/.ssh/config <<'CFG'
19
+ Host laptop
20
+ HostName laptop
21
+ User alice
22
+ IdentityFile ~/.ssh/wherdr_ed25519
23
+ IdentitiesOnly yes
24
+ CFG
25
+ ssh-copy-id -i ~/.ssh/wherdr_ed25519 laptop
26
+ ssh laptop true # accept the host key once
27
+ herdr machine add laptop --label Laptop # see `herdr machine --help`
28
+ ```
29
+
30
+ wherdr picks it up within 30 seconds; `herdr machine rename|disable|remove` work the same way.
31
+ Machines can also be renamed from the app (machine menu → Options).
32
+ With Herdr 0.9.2 or later, **Options → Connection status** checks the saved profile
33
+ with `herdr machine status --json`: reachable, authentication required, error or disabled.
34
+ It also gives a `herdr machine reconnect <profile-id>` command to run interactively
35
+ on the wherdr host. **Retry connection** restarts wherdr's own SSH connection;
36
+ Herdr's approved SSH connection and wherdr's connection are separate, so key-based
37
+ SSH access is still required for wherdr. Older Herdr keeps automatic retries and
38
+ manual **Retry connection**, without the native diagnostic.
39
+
40
+ The terminal view uses `terminal.mouse` on Herdr 0.9.2+ (both CLI and server): clicks,
41
+ touch taps, drags and movement are encoded by Herdr for the application's mouse mode.
42
+ Use **Shift + drag** to select text, and Ctrl/⌘+click to open links. Older or unknown
43
+ versions keep the existing terminal keyboard, selection and scrolling behavior.
44
+
45
+ ### Windows remote machines
46
+
47
+ The wherdr host must support Unix sockets and OpenSSH multiplexing (for example,
48
+ Linux/WSL or macOS). On the **remote Windows machine**, install Git Bash and set
49
+ it as the OpenSSH server's default shell. In an elevated PowerShell:
50
+
51
+ ```powershell
52
+ New-ItemProperty -Path 'HKLM:\SOFTWARE\OpenSSH' -Name DefaultShell -Value 'C:\Program Files\Git\bin\bash.exe' -PropertyType String -Force
53
+ Restart-Service sshd
54
+ ```
55
+
56
+ Reconnect SSH after changing the shell. Adjust the path if Git is installed
57
+ elsewhere. wherdr's bootstrap and file commands require a POSIX shell; with
58
+ cmd.exe or PowerShell as the default, they fail before Herdr is discovered.
59
+ With `cmd.exe` as the default shell, the error comes back localized and can be
60
+ unreadable (for example GBK-encoded Chinese). When the probe does not answer
61
+ as a POSIX shell, wherdr shows “Remote SSH shell must support POSIX commands”
62
+ and points to this section; the same hint covers other failures before the
63
+ probe runs.
64
+
65
+ Herdr must be available in this non-interactive shell (`ssh laptop 'command -v herdr'`) and its server must already be running. Windows remotes require Herdr
66
+ 0.9.3 or later with `remote-api-bridge` support.
67
+
68
+ Since 2.0.0 (#144), wherdr reaches a Windows machine through a relay instead of
69
+ `ssh -L` to the socket path, so Herdr sockets under `C:\…` work. It selects a stdio relay for
70
+ socket paths containing a drive letter or backslashes, rather than putting the
71
+ Windows path into `ssh -L`. It uses Herdr's native `remote-api-bridge` for API
72
+ requests and `remote-client-bridge` for notifications, over the same SSH master.
73
+ No additional relay program or TCP listening port is needed. Linux and macOS
74
+ POSIX socket paths continue to use `ssh -L`.
@@ -0,0 +1,160 @@
1
+ # Migrate a wherdr 1.x Docker install to wherdr 2.0
2
+
3
+ wherdr 2.0.0 has no Docker image: it runs natively, as your user, so the herdr-projects Board can act.
4
+ This guide moves a wherdr 1.x container to the native service. It keeps the port, the data
5
+ folder (passkeys, push subscriptions, phone address, settings) and therefore the address your
6
+ devices open.
7
+
8
+ **For an AI agent.** The user can say "read this file and migrate me". Run the steps in order,
9
+ on the machine where the `wherdr` container runs, as the user who runs Herdr (never with `sudo`
10
+ unless a step says so). Every step ends with a **Check**. When a check fails, stop, do not run
11
+ the next step, and report the output to the user. Run [Roll back](#roll-back) if the user asks,
12
+ or if wherdr no longer answers after step 4. Ask the user before deleting anything; this guide never deletes.
13
+
14
+ The 1.x in-app banner shows `docker compose pull && docker compose up -d` for 2.0. That
15
+ command cannot reach 2.0: the `wherdr:latest` image on GHCR stays on the last 1.x release.
16
+ Running it changes nothing. This guide is the way to 2.0.
17
+
18
+ ## 0. Collect the facts
19
+
20
+ ```sh
21
+ docker inspect wherdr --format '{{.State.Status}} {{.Config.Image}}'
22
+ OLD="$(docker inspect wherdr --format '{{ index .Config.Labels "com.docker.compose.project.working_dir" }}')"
23
+ DATA_SRC="$(docker inspect wherdr --format '{{ range .Mounts }}{{ if eq .Destination "/data" }}{{ .Source }}{{ end }}{{ end }}')"
24
+ PORT="$(docker inspect wherdr --format '{{ range .Config.Env }}{{ println . }}{{ end }}' | sed -n 's/^PORT=//p')"
25
+ echo "OLD=$OLD DATA_SRC=$DATA_SRC PORT=${PORT:-7683}"
26
+ curl -fsS "http://127.0.0.1:${PORT:-7683}/api/health"
27
+ herdr --version
28
+ grep -s WHERDR_MODE ~/wherdr/plugin.env
29
+ ```
30
+
31
+ **Check:** the container exists (`running`), `OLD` and `DATA_SRC` are absolute paths, `DATA_SRC`
32
+ holds files (`ls -la "$DATA_SRC"`), and the health answer says `"name":"wherdr"` with a `1.x`
33
+ version. Herdr must be 0.9.1 or newer. Linux or macOS only.
34
+
35
+ If the container has another name, use that name in every command. Keep `OLD`, `DATA_SRC` and
36
+ `PORT` for the next steps; in a new shell, set them again with the same commands.
37
+ If `~/wherdr/plugin.env` says `WHERDR_MODE=docker`, the Herdr plugin started this container: that is
38
+ expected, the plugin switches to native at step 4.
39
+
40
+ If the user also runs herdr-projects 0.2 (the Project panel, `TASKS.md`, threads), stop here
41
+ and tell them. wherdr 2.0's Board needs herdr-projects 1.2.1 or later; they must follow
42
+ [the herdr-projects migration guide](herdr-projects-1x.md) as well, or stay on wherdr 1.3.4.
43
+
44
+ ## 1. Back up
45
+
46
+ ```sh
47
+ BACKUP=~/wherdr-docker-backup-$(date +%F-%H%M).tgz
48
+ tar czf "$BACKUP" -C "$DATA_SRC" . && tar czf "$BACKUP.compose.tgz" -C "$OLD" $(cd "$OLD" && ls -A | grep -E '^(\.env|docker-compose.*\.ya?ml)$')
49
+ docker inspect wherdr --format '{{.Image}}' > "$BACKUP.image"
50
+ tar tzf "$BACKUP" | head; tar tzf "$BACKUP.compose.tgz"; cat "$BACKUP.image"
51
+ ```
52
+
53
+ **Check:** the first archive lists the data files (for example `./auth.json`, `./push.json`), the
54
+ second one lists `docker-compose.yml` (and `.env` when there is one), and the image file holds a
55
+ `sha256:` id. That image stays on the machine for the way back: do not prune it.
56
+
57
+ ## 2. Keep the settings
58
+
59
+ ```sh
60
+ cat "$OLD/.env" 2>/dev/null
61
+ ```
62
+
63
+ Keep the lines the user set, which the native service reads too: `APP_URL`, `HOST_LABEL`,
64
+ `HERDR_WEB_SESSION`, `HERDR_WEB_MACHINES`, `HERDR_WEB_SELF_HOSTS`, `HERDR_WEB_ALLOWED_HOSTS`,
65
+ `WHERDR_TRUSTED_NETWORKS`, `WHERDR_UPDATE_CHECK`, `PASSKEY_USER`,
66
+ `AGENT_KINDS`, `TZ`. Drop the container's own: `PORT` (it becomes `WHERDR_PORT` at step 4),
67
+ `HOST_HOME`, `HOME`, `PUID`, `PGID`, `DATA_DIR`, `HERDR_BIN`, `WHERDR_IMAGE`,
68
+ `WHERDR_DOCKER_LOOPBACK_ONLY`, `WHERDR_TASKS_FROM_FILES`, `HERDR_WEB_HOST_OPEN_*`.
69
+ Show the user the kept lines before writing them at step 3.
70
+
71
+ **Check:** the user agrees with the list (it may be empty).
72
+
73
+ ## 3. Stop the container and put the data in place
74
+
75
+ ```sh
76
+ (cd "$OLD" && docker compose down)
77
+ docker ps -a --filter name='^wherdr$' --format '{{.Names}}'
78
+ curl -fsS --max-time 3 "http://127.0.0.1:${PORT:-7683}/api/health" || echo "port free"
79
+ ```
80
+
81
+ **Check:** no container is listed and the last line says `port free`. `docker compose down`
82
+ removes the container only; its folder, data and image stay.
83
+
84
+ The native service reads `~/wherdr/data`. When `DATA_SRC` is already `~/wherdr/data` (installs
85
+ made by the 1.x installer or plugin), leave it. Otherwise copy it; the command refuses when the
86
+ target exists (then show the user what `~/wherdr/data` holds and ask):
87
+
88
+ ```sh
89
+ mkdir -p ~/wherdr
90
+ [ "$(cd "$DATA_SRC" && pwd -P)" = "$(cd ~/wherdr 2>/dev/null && pwd -P)/data" ] || { [ ! -e ~/wherdr/data ] && cp -a "$DATA_SRC" ~/wherdr/data; }
91
+ find ~/wherdr/data ! -user "$(id -u)" | head
92
+ ```
93
+
94
+ **Check:** `ls ~/wherdr/data` shows the same files as `DATA_SRC`, and `find` prints nothing. If it
95
+ lists files owned by someone else (the container ran with another `PUID`), ask the user to run
96
+ `sudo chown -R "$(id -un)" ~/wherdr/data`.
97
+
98
+ Write the settings kept at step 2, one `KEY=VALUE` per line, in `~/wherdr/wherdr.env` (add them
99
+ to the file if it exists; never overwrite other lines).
100
+
101
+ ## 4. Install the native service
102
+
103
+ ```sh
104
+ curl -fsSL https://wherdr.dev/install | WHERDR_PORT="${PORT:-7683}" sh
105
+ ```
106
+
107
+ The installer asks before installing Node.js 22 when it is missing; let the user answer
108
+ (`WHERDR_YES=1` answers yes to everything). It installs the Herdr plugin into `~/wherdr`, starts
109
+ wherdr on that port and installs its login service: `systemd --user` (`wherdr.service`) on Linux,
110
+ a LaunchAgent (`dev.wherdr.plist`) on macOS. A plugin left in Docker mode says
111
+ `switching this plugin to the native service`.
112
+
113
+ **Check:** the installer ends without an error and prints the wherdr address.
114
+
115
+ ## 5. Verify
116
+
117
+ ```sh
118
+ curl -fsS "http://127.0.0.1:${PORT:-7683}/api/health"
119
+ grep -s WHERDR_MODE ~/wherdr/plugin.env || echo "no Docker mode"
120
+ systemctl --user is-active wherdr 2>/dev/null || launchctl list 2>/dev/null | grep dev.wherdr
121
+ docker ps -a --filter name='^wherdr$' --format '{{.Names}}'
122
+ ```
123
+
124
+ **Check:** health says `"name":"wherdr"` with a `2.x` version; `no Docker mode`; the service is
125
+ `active` (Linux) or listed (macOS); no container came back. If wherdr was reached over Tailscale,
126
+ `tailscale serve status` still points to the same port.
127
+
128
+ Then ask the user to open wherdr at the address they used before, on the phone and on each
129
+ browser. It must unlock with the same passkey, and notifications must still arrive. Each browser
130
+ copies its display settings to the server once, at its first visit (they are then shared).
131
+ If something fails here, go to [Roll back](#roll-back).
132
+
133
+ Leave the backup, the old compose folder and the image in place for a few days. Remove them later
134
+ only when the user asks (`docker image rm <id from $BACKUP.image>`).
135
+
136
+ ## Roll back
137
+
138
+ Back to the 1.x container, with the data as it is now:
139
+
140
+ ```sh
141
+ herdr plugin disable afloury.wherdr
142
+ systemctl --user disable --now wherdr 2>/dev/null || launchctl bootout "gui/$(id -u)/dev.wherdr" 2>/dev/null
143
+ curl -fsS --max-time 3 "http://127.0.0.1:${PORT:-7683}/api/health" || echo "port free"
144
+ ```
145
+
146
+ When the data was copied at step 3, copy back what changed since (skip this when `DATA_SRC` is
147
+ `~/wherdr/data`): `cp -a ~/wherdr/data/. "$DATA_SRC/"`. To return to the data as it was before
148
+ the migration instead, restore the backup: `tar xzf "$BACKUP" -C "$DATA_SRC"`.
149
+
150
+ ```sh
151
+ (cd "$OLD" && docker compose up -d)
152
+ curl -fsS "http://127.0.0.1:${PORT:-7683}/api/health"
153
+ ```
154
+
155
+ **Check:** `port free` before starting, then health answers with the `1.x` version. If
156
+ `docker compose up` pulls a different image, pin the saved one: `WHERDR_IMAGE=<id from $BACKUP.image>`
157
+ in `"$OLD/.env"`, then run it again.
158
+
159
+ To stop the 1.x banner from offering 2.0, set `WHERDR_UPDATE_CHECK=off` in `"$OLD/.env"` and run
160
+ `docker compose up -d` again.
@@ -0,0 +1,154 @@
1
+ # Phone access and private networks
2
+
3
+ [Documentation](README.md) · [Project](../README.md)
4
+
5
+ ### Recommended: always-on server + Tailscale
6
+
7
+ Your agents keep running when your laptop is closed, and you follow them from your phone.
8
+ wherdr stays on your private network: it is **never published on the Internet**.
9
+
10
+ **1. Herdr on the server.** Install [Herdr](https://herdr.dev) (≥ 0.9.1) and start it
11
+ (`herdr` or `herdr server`), with the integrations of the agents you use
12
+ (`herdr integration install claude|codex`).
13
+
14
+ **2. wherdr.** Run [the one command](installation.md#the-one-command) on the server, as your user (over SSH, it
15
+ prints the address and the phone steps instead of opening a browser). wherdr runs as a service
16
+ that starts with the machine and keeps running when you log out.
17
+
18
+ **3. Private HTTPS with Tailscale.** Install [Tailscale](https://tailscale.com) on the server,
19
+ your phone and your computers, on the same tailnet, with
20
+ [MagicDNS and HTTPS certificates](https://tailscale.com/kb/1153/enabling-https) enabled. Then,
21
+ on the server:
22
+
23
+ ```sh
24
+ tailscale serve --bg --https=7683 http://127.0.0.1:7683
25
+ # → https://<machine>.<tailnet>.ts.net:7683/ (e.g. https://server.example.ts.net:7683/)
26
+ ```
27
+
28
+ Tailscale Serve listens on HTTPS port 7683 of your tailnet name, with a valid certificate, and
29
+ forwards to wherdr's local HTTP port. Only devices of your tailnet can reach it.
30
+
31
+ **wherdr enables that address by itself**, on a server without a screen too: at startup, every
32
+ 30 seconds, and when an address it does not know yet is opened, it reads what `tailscale serve`
33
+ publishes on this machine (the `tailscale` command) and adopts the HTTPS address that points to
34
+ its own port (allowed host and `APP_URL`, saved in `data/app-url.json`; an HTTPS `APP_URL` in the
35
+ environment or `~/wherdr/wherdr.env` wins). An address removed from `tailscale serve` is refused
36
+ again. Nothing a request says can enable an address: only Tailscale's own state on the machine
37
+ does, and an address that `tailscale funnel` opens to the Internet is never adopted.
38
+
39
+ **Settings › Phone** on the server (`http://localhost:7683/#/settings?section=phone`) shows the
40
+ state and the QR code and publishes wherdr itself: one button, asked first. From a terminal,
41
+ **P** in the wherdr panel, `wherdr phone` (npm, Homebrew) or the [one command](installation.md#the-one-command)
42
+ run again checks the same thing, and `curl -fsS http://127.0.0.1:7683/api/phone` prints the state
43
+ as JSON.
44
+
45
+ If the phone shows **"This address isn't enabled yet"** (403), the address it opened is not one
46
+ `tailscale serve` publishes for wherdr's port on that machine: the page gives the command to run
47
+ there.
48
+
49
+ **Why HTTPS matters.** Browsers only allow three things on `https://` (or on `localhost` itself):
50
+
51
+ - **installing the app** on the home screen (PWA) on iPhone and Android;
52
+ - **push notifications** (on iOS, only in the installed app);
53
+ - **passkeys** for the lock screen (Face ID, Touch ID, Windows Hello, Android).
54
+
55
+ **4. On the phone:** open the address, [install the app](phone.md#install-it-as-an-app), then
56
+ Settings → **Enable notifications**. **On the computer:** use the same address in a browser; the
57
+ desktop layout can replace the Herdr terminal client for daily work.
58
+
59
+ **5. Passkey lock** (optional): Settings → Security → **Enable passkey lock**. The
60
+ first passkey asks for a bootstrap token, printed in the server log (`~/wherdr/wherdr.log`,
61
+ **L** in the wherdr panel); it changes on each restart and is only needed for that first passkey. Add a passkey on each of
62
+ your devices after that (see [New device](security.md#new-device)). Without one, every device of your
63
+ tailnet can control your agents: enough when the tailnet is yours alone, a passkey adds a lock
64
+ on each device when someone else shares the tailnet or your devices.
65
+
66
+ **6. More machines** (optional): register your other computers in Herdr over SSH
67
+ (`herdr machine add`, see [Several machines (SSH)](machines.md#several-machines-ssh)). Their agents show
68
+ in wherdr in their own section, and everything works the same on them. The machine menu also has
69
+ **Keep awake**, which stops a Mac (`caffeinate`) or a Linux machine (`systemd-inhibit`) from
70
+ sleeping for an hour, four hours, the evening or until turned off.
71
+
72
+ > [!CAUTION]
73
+ > Never publish wherdr on the Internet: no port forwarding on your router, no public reverse
74
+ > proxy, no ngrok, no `tailscale funnel`, no Cloudflare Tunnel without Cloudflare Access. Use
75
+ > `tailscale serve`, which stays inside your tailnet. See [Security](security.md#security).
76
+
77
+ ### Other private networks
78
+
79
+ Tailscale is the built-in path (one click in **Settings › Phone**), not a requirement. wherdr
80
+ works with any private network that gives it an **HTTPS address with a valid certificate**:
81
+
82
+ - **HTTPS with a certificate your phone trusts.** The installed app (PWA), push notifications
83
+ and passkeys only work on `https://`. A self-signed certificate or plain `http://` on your
84
+ LAN is not enough, and is never a supported way to reach wherdr.
85
+ - **`APP_URL` set to that address** (the environment of `wherdr` / `npm start`), then restart wherdr. Its hostname is allowed automatically and it enables Web
86
+ Push. Add `HERDR_WEB_ALLOWED_HOSTS` only if you also open wherdr under another name. The
87
+ automatic adoption and the address check in **Settings › Phone** only know the `*.ts.net`
88
+ addresses of `tailscale serve`; an HTTPS `APP_URL` in the environment always wins.
89
+ - **Never exposed without authentication.** wherdr keeps listening on `127.0.0.1`; only the
90
+ proxy listens on the private network. Turn on the passkey lock as soon as another device can
91
+ reach it.
92
+
93
+ **Headscale, NetBird, ZeroTier, WireGuard: the same recipe.** Join the server and the phone to
94
+ the network, give the server a DNS name that points to its **private** address, and put a reverse
95
+ proxy in front of wherdr that listens on that private address only. Let's Encrypt cannot reach a
96
+ private address, so the certificate comes from the
97
+ [DNS challenge](https://caddyserver.com/docs/automatic-https#dns-challenge). With
98
+ [Caddy](https://caddyserver.com) built with your DNS provider's
99
+ [module](https://github.com/caddy-dns) (here [Cloudflare DNS](https://github.com/caddy-dns/cloudflare)),
100
+ a `Caddyfile` like this ([`bind`](https://caddyserver.com/docs/caddyfile/directives/bind),
101
+ [`tls`](https://caddyserver.com/docs/caddyfile/directives/tls)):
102
+
103
+ ```caddy
104
+ wherdr.example.com {
105
+ bind 100.64.0.10 # the server's address on the private network only
106
+ tls {
107
+ dns cloudflare {env.CF_API_TOKEN}
108
+ }
109
+ reverse_proxy 127.0.0.1:7683
110
+ }
111
+ ```
112
+
113
+ Point `wherdr.example.com` (an `A` record in your DNS zone) to that private address: from
114
+ anywhere else it leads nowhere. Then set `APP_URL=https://wherdr.example.com/` and restart
115
+ wherdr. Caddy is a relay on this computer ([Network access](security.md#network-access)): keep it bound to
116
+ the private address and turn the passkey lock on. Caddy passes each client's address on: with
117
+ Headscale or NetBird (`100.64.0.0/10`) wherdr accepts it as is; for another range (ZeroTier,
118
+ WireGuard), also set `WHERDR_TRUSTED_NETWORKS` to it, such as `10.147.17.0/24`. To join each
119
+ device:
120
+
121
+ | Network | Join a device (official docs) | Server's private address |
122
+ | --- | --- | --- |
123
+ | [Headscale](https://headscale.net/stable/usage/getting-started/) | Tailscale clients: `tailscale up --login-server <YOUR_HEADSCALE_URL>` | `tailscale ip -4` |
124
+ | [NetBird](https://docs.netbird.io/get-started/cli) | `netbird up` (`--management-url` when self-hosted) | `netbird status` |
125
+ | [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 |
126
+ | [WireGuard](https://www.wireguard.com/quickstart/) | `wg-quick up wg0` with a peer per device | The `Address` of the server's interface |
127
+
128
+ Headscale does not support `tailscale serve` with HTTPS yet
129
+ ([features](https://headscale.net/stable/about/features/),
130
+ [#1921](https://github.com/juanfont/headscale/issues/1921)): `tailscale cert` and Serve need
131
+ certificates that only Tailscale's own control server provisions. Use the reverse proxy above
132
+ instead of step 3.
133
+
134
+ **Cloudflare Tunnel + Cloudflare Access: only with Access.**
135
+
136
+ > [!WARNING]
137
+ > A Cloudflare Tunnel puts wherdr **on the Internet**: anyone can reach its address, and
138
+ > Cloudflare terminates TLS, so it sees all traffic. **Cloudflare Access is mandatory**, created
139
+ > *before* the route: without it, whoever finds the address controls your agents. Keep the
140
+ > passkey lock on as well. Prefer a private network when you can.
141
+
142
+ 1. In Cloudflare One, create the Access application first:
143
+ [Self-hosted and private › Add public hostname](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/self-hosted-public-app/)
144
+ `wherdr.example.com`, with an **Allow** policy for your own e-mail only (Access applications
145
+ deny everyone else by default).
146
+ 2. [Create a tunnel](https://developers.cloudflare.com/tunnel/setup/) and run `cloudflared` on the
147
+ server, then add a **published application** route: hostname `wherdr.example.com`, service
148
+ `http://localhost:7683`.
149
+ 3. Set `APP_URL=https://wherdr.example.com/` and `WHERDR_TRUSTED_NETWORKS=any` (the tunnel brings
150
+ clients from the Internet, which wherdr refuses otherwise; it then shows a warning), and
151
+ restart wherdr.
152
+
153
+ Installing the app and notifications behind Access are not tested by the project: the phone must
154
+ first sign in to Access in the browser.
@@ -0,0 +1,83 @@
1
+ # Notifications
2
+
3
+ [Documentation](README.md) · [Project](../README.md)
4
+
5
+ ## Notifications
6
+
7
+ With notifications enabled on a device, wherdr sends a Web Push when an agent goes from working
8
+ to **blocked** (with its question and options) or to **done** (with the start of its answer).
9
+ There is no push for the agent currently on screen.
10
+
11
+ **Do not disturb** (top of Settings → Notifications) stops every push, for this device or for all
12
+ devices, for 1 hour, until 8 am or until turned back on. The server applies it before sending and
13
+ keeps it across restarts (`data/quiet.json` for all devices). While it is on, the agent list
14
+ shows one line, “Notifications silenced until 08:00”, that opens these settings (a crossed-out bell
15
+ under the counters when the list is collapsed); the header keeps its buttons as they are.
16
+
17
+ wherdr also relays custom notifications sent with `herdr notification show` (plugins, scripts).
18
+ To receive them it connects to Herdr's client socket as a **passive** client: it never becomes
19
+ the foreground client and never resizes anything. Side effects: with wherdr connected,
20
+ `notification show` reports `shown: true` even with no terminal attached, and an empty session
21
+ gets a default workspace, as with any attached client.
22
+
23
+ ## A default and exceptions
24
+
25
+ Settings → Notifications stores one policy, synchronized across devices
26
+ (`notificationRules` in `settings.json`): **a default for everything, and
27
+ exceptions to that default**. Every setting uses the same scale:
28
+
29
+ | Level | Notifies for |
30
+ |---|---|
31
+ | **Muted** | nothing |
32
+ | **Needs you** | a task or a coordinator waiting for you (questions, permission requests, board approvals) |
33
+ | **Needs you + finished** | also a finished task, or a coordinator that finished answering you |
34
+ | **All activity** | also every intermediate message and scheduled tasks |
35
+
36
+ Under each level, **Customize** keeps the individual events (needs you, finished,
37
+ new message, scheduled task) for edge cases.
38
+
39
+ The screen reads top to bottom:
40
+
41
+ 1. **Do not disturb** turns off every notification, no exceptions: the default and
42
+ the exceptions are ignored while it is on.
43
+ 2. **Default (applies to everything)**: a level (needs you + finished at first)
44
+ and a **Sub-agents** switch. Off, only coordinators, standalone agents and board
45
+ events notify; on, task agents (workers, reviewers, planners, attempts) notify
46
+ too, at the same level. Sub-agents are all on or all off, never task by task.
47
+ 3. **Exceptions**: **Add an exception** picks a coordinator (main, machine or project
48
+ coordinator, grouped by machine) or a standalone agent conversation. Each one has
49
+ its own level, Customize and, for a coordinator, a Sub-agents switch: on, the task
50
+ agents of its board follow the exception instead of the default (a project
51
+ coordinator covers its project's tasks; the main and machine coordinators cover the
52
+ others). Each says how it differs from the default; removing one goes back to it.
53
+ 4. **This device's notifications** only asks this browser or phone for permission to
54
+ show push notifications, and shows whether it is allowed, blocked or not asked yet.
55
+
56
+ A coordinator's exception is kept by its identity (machine, Herdr session, level and
57
+ repo), not its pane: it still applies after `coordinator new` or a restart.
58
+
59
+ Right-click → **Notifications** on a coordinator or a standalone agent sets that
60
+ conversation's exception: the four levels (with a check mark on the current one, and
61
+ whether it is an exception or the default), **Mute for 1 hour / 8 hours** (then back
62
+ to what it was), and **Follow the default** to remove the exception. On a task agent,
63
+ the menu points to the Sub-agents switch instead.
64
+
65
+ Board events (`herdr-projects` approvals and failures) follow the default even with
66
+ Sub-agents off, or their project coordinator's exception in the task's own Herdr
67
+ session (the default when that session is not known: a task with no pane or space linked). Custom `herdr notification
68
+ show` notices with a pane follow that conversation; others follow the default. Scripts
69
+ can mark a scheduled notice with a `[scheduled]` title prefix. New assistant messages
70
+ are identified by their transcript position, so streaming updates do not ring
71
+ repeatedly.
72
+
73
+ Settings saved by the earlier machine → project → conversation rules are converted
74
+ once: a permanent machine rule becomes the default (so "Only main coordinators"
75
+ becomes default Muted plus one exception), project rules are dropped, and
76
+ conversation rules become exceptions (moved to the coordinator's identity when the
77
+ conversation is a coordinator).
78
+
79
+ An open conversation suppresses push to **every** device while the page is visible,
80
+ focused and recently used (within two minutes). A WebSocket heartbeat renews a
81
+ 30-second lease every 10 seconds. Hiding, closing, losing the connection or leaving
82
+ an idle tab stops renewing it; push resumes when the last lease expires. The global
83
+ Do not disturb setting and notification deduplication still apply.
@@ -0,0 +1,71 @@
1
+ # Overview and architecture
2
+
3
+ [Documentation](README.md) · [Project](../README.md)
4
+
5
+ <p align="center">
6
+ <img src="../server/assets/branding/icons/icon-512.png" alt="wherdr logo" width="120">
7
+ </p>
8
+
9
+ <h1 align="center">wherdr</h1>
10
+
11
+ <p align="center"><b>Start your <a href="https://herdr.dev">Herdr</a> agents on your computer.
12
+ Follow their work and answer from your phone.</b></p>
13
+
14
+ <p align="center">Add it to your home screen like an app — no App Store needed.</p>
15
+
16
+ <p align="center">
17
+ <a href="../assets/wherdr-launch.mp4"><img src="../assets/wherdr-launch.webp" width="720" alt="Animated wherdr launch film with real app captures: launch a Claude agent on desktop, continue the same conversation on a phone, approve its test request, then open project tasks and the Board."></a>
18
+ </p>
19
+ <p align="center"><sub>Start on PC. Stay in control from your phone. Real app, demo data. <a href="../assets/wherdr-launch.mp4">Watch the 15-second video</a>.</sub></p>
20
+
21
+ <p align="center"><a href="https://wherdr.dev"><b>wherdr.dev</b></a> · <a href="quick-start.md#ask-your-ai-agent">Ask your AI agent</a></p>
22
+
23
+ <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>
24
+
25
+ <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>
26
+
27
+ ## Why wherdr
28
+
29
+ - **Herdr's splits, live.** A tab with several panes is drawn in Herdr's real layout, every pane
30
+ live; drag a pane to move it or a divider to resize, and the change goes to Herdr itself.
31
+ - **Private by default, passkey lock optional.** wherdr answers only its own computer and your
32
+ tailnet. Add a passkey if you like: Face ID, Touch ID, Windows Hello or Android unlock the app,
33
+ and nothing about your agents is served to a locked session.
34
+ - **The agent's diff.** Git status and diff of each agent's working folder, one click away from
35
+ its conversation.
36
+ - **The herdr-projects board.** Every machine's herdr-projects tasks in the board's own groups,
37
+ with replies to their agents, approval of finished work and a box for the Coordinator.
38
+ - **Keyboard and global search.** One shortcut searches every agent and conversation; next
39
+ agent, new tab, pane swaps and `1`–`9` answers never need the mouse.
40
+ - **Real time, and private.** The real terminal over a WebSocket, a push when an agent needs
41
+ you, and your phone through Tailscale: nothing is on the Internet.
42
+
43
+ Details in [Features](home.md#features) and [Security](security.md#security).
44
+
45
+ ## How it works
46
+
47
+ ```
48
+ phone / laptop ──private network──> HTTPS (tailscale serve, private proxy)
49
+ │
50
+ ▼
51
+ wherdr (Nitro server, 127.0.0.1:7683)
52
+ ├─ Herdr API socket ~/.config/herdr/herdr.sock
53
+ ├─ Herdr client socket (notifications, passive client)
54
+ ├─ herdr terminal session control <pane> (terminal view)
55
+ ├─ transcripts ~/.claude, ~/.codex, ~/.omp (read only)
56
+ └─ ssh -M <machine> (other machines, optional)
57
+ ```
58
+
59
+ wherdr runs on the same machine as the Herdr server (the "server" in these guides). It uses Herdr's local
60
+ socket API and CLI, reads the agents' transcript files, and serves a client-only Nuxt app. Data
61
+ it keeps (VAPID keys, push subscriptions, passkeys, recent folders) lives in `data/`. So do the
62
+ long texts pasted into the message field, which the server keeps to show them as cards on every
63
+ device: `data/pastes.json`, in clear, readable by the server's user only (mode 600), at most the
64
+ last 100 texts and 2 MB, each one deleted 30 days after it was last sent.
65
+
66
+ Built with Nuxt 4, Nuxt UI 4, Tailwind 4 and TypeScript; the Herdr gateway (API, WebSockets,
67
+ Web Push, lock) runs in Nitro.
68
+
69
+ ## License
70
+
71
+ [MIT](../LICENSE). The embedded font subsets keep their own licenses (`app/assets/fonts/`).
package/docs/phone.md ADDED
@@ -0,0 +1,26 @@
1
+ # Install as an app
2
+
3
+ [Documentation](README.md) · [Project](../README.md)
4
+
5
+ ## Install it as an app
6
+
7
+ For phone access, first configure [private HTTPS](networks.md). For UI development, see the
8
+ [iOS conventions](../CONTRIBUTING.md#conventions), including the iOS 26 status-bar pitfall.
9
+
10
+ The app must be served over HTTPS (see [Requirements](installation.md#requirements)). Installed, wherdr opens
11
+ in its own window, without the browser's bars.
12
+
13
+ - **iPhone / iPad** (iOS 16.4 or later): open the address in **Safari** → Share →
14
+ **Add to Home Screen**. Open wherdr from the home screen, then Settings → **Enable
15
+ notifications**. On iOS, push notifications only work in the installed app.
16
+ - **Android**: open the address in **Chrome** → menu → **Install app** (or Add to Home
17
+ screen), then Settings → **Enable notifications**.
18
+ - **Mac, Safari** (macOS 14 Sonoma or later): File → **Add to Dock**.
19
+ - **Chrome, Edge, Brave** (macOS, Windows, Linux): the install icon at the right of the address
20
+ bar, or menu → **Cast, save and share** → **Install page as app** (Edge: Apps → **Install
21
+ this site as an app**).
22
+ - **Arc** cannot install web apps
23
+ ([Arc Help Center](https://resources.arc.net/hc/en-us/articles/25678978728983-Does-Arc-for-Desktop-Support-Progressive-Web-Apps)):
24
+ open the address in Safari or Chrome for that, or keep wherdr as an Arc favorite.
25
+
26
+ Then Settings → Security → **Enable passkey lock** on each device.