wherdr 1.3.2 → 1.3.3

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 (174) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/{AgentView.C4YOwvZG.css → AgentView.DzFWBd2A.css} +1 -1
  3. package/.output/public/_nuxt/B4Yk2tB1.js +23 -0
  4. package/.output/public/_nuxt/BBw_0a4M.js +6 -0
  5. package/.output/public/_nuxt/{D7sxNXx1.js → BSy6OCIY.js} +1 -1
  6. package/.output/public/_nuxt/BX5PM2JQ.js +1 -0
  7. package/.output/public/_nuxt/BgPDnf2S.js +1 -0
  8. package/.output/public/_nuxt/BkOxQzbS.js +2 -0
  9. package/.output/public/_nuxt/{8ksjcuDu.js → CKhgznq4.js} +45 -56
  10. package/.output/public/_nuxt/{Cd8j9O5w.js → CP8o925U.js} +1 -1
  11. package/.output/public/_nuxt/CkbmecwS.js +1 -0
  12. package/.output/public/_nuxt/Cr3SrGky.js +1 -0
  13. package/.output/public/_nuxt/DAg9fiVh.js +89 -0
  14. package/.output/public/_nuxt/DT8qZ0Wv.js +1 -0
  15. package/.output/public/_nuxt/DaaJIRB3.js +1 -0
  16. package/.output/public/_nuxt/{DViwOclv.js → DcJi16XA.js} +1 -1
  17. package/.output/public/_nuxt/VpjCgSXu.js +36 -0
  18. package/.output/public/_nuxt/builds/latest.json +1 -1
  19. package/.output/public/_nuxt/builds/meta/c735b6c0-1bd0-47d7-a208-d310f0a06287.json +1 -0
  20. package/.output/public/_nuxt/dPxB2L94.js +89 -0
  21. package/.output/public/_nuxt/{entry.Crt1r4l3.css → entry.CGWKmYU3.css} +1 -1
  22. package/.output/public/_nuxt/xYX85SPI.js +1 -0
  23. package/.output/public/sw.js +1 -1
  24. package/.output/server/chunks/_/branding.mjs +1 -1
  25. package/.output/server/chunks/_/filePaths.mjs +25 -0
  26. package/.output/server/chunks/_/files.mjs +204 -0
  27. package/.output/server/chunks/_/machineOrder.mjs +1 -1
  28. package/.output/server/chunks/_/onboarding.mjs +1 -1
  29. package/.output/server/chunks/_/package.mjs +3 -2
  30. package/.output/server/chunks/_/pluginPolicy.mjs +93 -0
  31. package/.output/server/chunks/_/plugins.mjs +4 -94
  32. package/.output/server/chunks/_/projectBoard.mjs +120 -29
  33. package/.output/server/chunks/_/projectDocs.mjs +106 -0
  34. package/.output/server/chunks/_/projectInbox.mjs +135 -0
  35. package/.output/server/chunks/_/projectThreads.mjs +46 -0
  36. package/.output/server/chunks/_/projectThreads2.mjs +61 -0
  37. package/.output/server/chunks/_/projectsActions.mjs +1 -1
  38. package/.output/server/chunks/_/projectsPlugin.mjs +1 -1
  39. package/.output/server/chunks/_/restart.mjs +2 -312
  40. package/.output/server/chunks/_/reveal.mjs +2 -24
  41. package/.output/server/chunks/_/selfupdate.mjs +3 -2
  42. package/.output/server/chunks/_/updates.mjs +51 -10
  43. package/.output/server/chunks/_/worktrees.mjs +1 -1
  44. package/.output/server/chunks/_/ws.mjs +1 -1
  45. package/.output/server/chunks/nitro/nitro.mjs +1096 -268
  46. package/.output/server/chunks/raw/bump-homebrew.mjs +1 -1
  47. package/.output/server/chunks/raw/herdr-plugin.mjs +1 -1
  48. package/.output/server/chunks/raw/release-ready.mjs +5 -0
  49. package/.output/server/chunks/routes/api/_..._.mjs +1 -1
  50. package/.output/server/chunks/routes/api/agents.post.mjs +1 -1
  51. package/.output/server/chunks/routes/api/ask-tab.post.mjs +1 -1
  52. package/.output/server/chunks/routes/api/attach.post.mjs +1 -1
  53. package/.output/server/chunks/routes/api/auth/disable.post.mjs +1 -1
  54. package/.output/server/chunks/routes/api/auth/lock-all.post.mjs +1 -1
  55. package/.output/server/chunks/routes/api/auth/lock.post.mjs +1 -1
  56. package/.output/server/chunks/routes/api/auth/login/options.post.mjs +1 -1
  57. package/.output/server/chunks/routes/api/auth/login/verify.post.mjs +1 -1
  58. package/.output/server/chunks/routes/api/auth/max-session.post.mjs +1 -1
  59. package/.output/server/chunks/routes/api/auth/register/options.post.mjs +1 -1
  60. package/.output/server/chunks/routes/api/auth/register/verify.post.mjs +1 -1
  61. package/.output/server/chunks/routes/api/auth/status.get.mjs +1 -1
  62. package/.output/server/chunks/routes/api/board.get.mjs +147 -0
  63. package/.output/server/chunks/routes/api/changes.get.mjs +2 -2
  64. package/.output/server/chunks/routes/api/chat/image.get.mjs +2 -2
  65. package/.output/server/chunks/routes/api/chat.get.mjs +2 -2
  66. package/.output/server/chunks/routes/api/choose.post.mjs +29 -3
  67. package/.output/server/chunks/routes/api/claude-statusline.post.mjs +2 -2
  68. package/.output/server/chunks/routes/api/close/status.get.mjs +2 -2
  69. package/.output/server/chunks/routes/api/close.post.mjs +1 -1
  70. package/.output/server/chunks/routes/api/codex-update.post.mjs +2 -2
  71. package/.output/server/chunks/routes/api/commands.get.mjs +2 -2
  72. package/.output/server/chunks/routes/api/config.get.mjs +3 -2
  73. package/.output/server/chunks/routes/api/dirs.get.mjs +2 -2
  74. package/.output/server/chunks/routes/api/dismiss-stopped.post.mjs +43 -0
  75. package/.output/server/chunks/routes/api/dismiss.post.mjs +2 -2
  76. package/.output/server/chunks/routes/api/effort.post.mjs +2 -2
  77. package/.output/server/chunks/routes/api/efforts.get.mjs +2 -2
  78. package/.output/server/chunks/routes/api/file.post.mjs +2 -2
  79. package/.output/server/chunks/routes/api/files/download.get.mjs +53 -0
  80. package/.output/server/chunks/routes/api/files/read.get.mjs +45 -0
  81. package/.output/server/chunks/routes/api/files.get.mjs +44 -0
  82. package/.output/server/chunks/routes/api/gitroot.get.mjs +2 -2
  83. package/.output/server/chunks/routes/api/health.get.mjs +1 -1
  84. package/.output/server/chunks/routes/api/index.get.mjs +1 -1
  85. package/.output/server/chunks/routes/api/input.post.mjs +13 -3
  86. package/.output/server/chunks/routes/api/interrupt.post.mjs +2 -2
  87. package/.output/server/chunks/routes/api/isgit.get.mjs +2 -2
  88. package/.output/server/chunks/routes/api/machine/awake.get.mjs +2 -2
  89. package/.output/server/chunks/routes/api/machine/awake.post.mjs +2 -2
  90. package/.output/server/chunks/routes/api/machine/order.get.mjs +1 -1
  91. package/.output/server/chunks/routes/api/machine/order.post.mjs +1 -1
  92. package/.output/server/chunks/routes/api/machine/rename.post.mjs +2 -2
  93. package/.output/server/chunks/routes/api/menu.post.mjs +2 -2
  94. package/.output/server/chunks/routes/api/model.post.mjs +2 -2
  95. package/.output/server/chunks/routes/api/models.get.mjs +2 -2
  96. package/.output/server/chunks/routes/api/nav.post.mjs +5 -4
  97. package/.output/server/chunks/routes/api/onboarding.get.mjs +1 -1
  98. package/.output/server/chunks/routes/api/onboarding.post.mjs +1 -1
  99. package/.output/server/chunks/routes/api/phone.get.mjs +2 -2
  100. package/.output/server/chunks/routes/api/phone.post.mjs +2 -2
  101. package/.output/server/chunks/routes/api/plugins/actions.get.mjs +2 -1
  102. package/.output/server/chunks/routes/api/plugins/invoke.post.mjs +2 -1
  103. package/.output/server/chunks/routes/api/plugins/profiles.get.mjs +2 -1
  104. package/.output/server/chunks/routes/api/plugins/projects.get.mjs +2 -2
  105. package/.output/server/chunks/routes/api/plugins/projects.post.mjs +2 -2
  106. package/.output/server/chunks/routes/api/plugins/thread-limits.get.mjs +2 -2
  107. package/.output/server/chunks/routes/api/plugins/thread-limits.post.mjs +2 -2
  108. package/.output/server/chunks/routes/api/project/inbox.get.mjs +43 -0
  109. package/.output/server/chunks/routes/api/project/inbox.post.mjs +43 -0
  110. package/.output/server/chunks/routes/api/project/memory.get.mjs +42 -0
  111. package/.output/server/chunks/routes/api/project/report.get.mjs +2 -2
  112. package/.output/server/chunks/routes/api/project/routines.get.mjs +43 -0
  113. package/.output/server/chunks/routes/api/project/settings.get.mjs +41 -0
  114. package/.output/server/chunks/routes/api/project/thread.get.mjs +44 -0
  115. package/.output/server/chunks/routes/api/project/thread.post.mjs +45 -0
  116. package/.output/server/chunks/routes/api/prompt.post.mjs +21 -3
  117. package/.output/server/chunks/routes/api/push/quiet.post.mjs +2 -2
  118. package/.output/server/chunks/routes/api/push/subscribe.post.mjs +2 -2
  119. package/.output/server/chunks/routes/api/push/test.post.mjs +2 -2
  120. package/.output/server/chunks/routes/api/push/unsubscribe.post.mjs +2 -2
  121. package/.output/server/chunks/routes/api/quotas.get.mjs +2 -2
  122. package/.output/server/chunks/routes/api/rename.post.mjs +1 -1
  123. package/.output/server/chunks/routes/api/requeue.post.mjs +2 -2
  124. package/.output/server/chunks/routes/api/restart.get.mjs +1 -1
  125. package/.output/server/chunks/routes/api/restart.post.mjs +1 -1
  126. package/.output/server/chunks/routes/api/reveal.post.mjs +2 -1
  127. package/.output/server/chunks/routes/api/screen-key.post.mjs +2 -2
  128. package/.output/server/chunks/routes/api/screen.get.mjs +5 -3
  129. package/.output/server/chunks/routes/api/search.get.mjs +2 -2
  130. package/.output/server/chunks/routes/api/seen.post.mjs +2 -2
  131. package/.output/server/chunks/routes/api/sessions.get.mjs +2 -2
  132. package/.output/server/chunks/routes/api/sessions.post.mjs +2 -2
  133. package/.output/server/chunks/routes/api/space.post.mjs +14 -14
  134. package/.output/server/chunks/routes/api/state.get.mjs +2 -2
  135. package/.output/server/chunks/routes/api/unqueue.post.mjs +2 -2
  136. package/.output/server/chunks/routes/api/update/status.get.mjs +1 -1
  137. package/.output/server/chunks/routes/api/update.get.mjs +2 -2
  138. package/.output/server/chunks/routes/api/update.post.mjs +2 -2
  139. package/.output/server/chunks/routes/api/upload.post.mjs +2 -2
  140. package/.output/server/chunks/routes/api/worktrees/remove.post.mjs +1 -1
  141. package/.output/server/chunks/routes/api/worktrees.get.mjs +1 -1
  142. package/.output/server/chunks/routes/favicon.ico.get.mjs +1 -1
  143. package/.output/server/chunks/routes/icons/_name_.get.mjs +2 -2
  144. package/.output/server/chunks/routes/renderer.mjs +3 -3
  145. package/.output/server/chunks/routes/uploads/_...name_.get.mjs +2 -2
  146. package/.output/server/chunks/routes/ws/_..._.mjs +2 -2
  147. package/.output/server/chunks/routes/ws/events.mjs +2 -2
  148. package/.output/server/chunks/routes/ws/term.mjs +2 -2
  149. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  150. package/.output/server/index.mjs +2 -2
  151. package/.output/server/package.json +1 -1
  152. package/README.md +332 -25
  153. package/bin/install-docker-skill.sh +20 -0
  154. package/bin/lib/commands.mjs +5 -1
  155. package/bin/lib/core.mjs +1 -0
  156. package/bin/lib/install-skill.sh +100 -0
  157. package/bin/lib/skill.mjs +16 -0
  158. package/bin/lib/updater.mjs +65 -12
  159. package/bin/lib/verify-release.mjs +20 -0
  160. package/package.json +3 -2
  161. package/skills/wherdr/SKILL.md +180 -0
  162. package/.output/public/_nuxt/B7QP1TFs.js +0 -23
  163. package/.output/public/_nuxt/BDNMzG2s.js +0 -1
  164. package/.output/public/_nuxt/BOpBIziZ.js +0 -6
  165. package/.output/public/_nuxt/BWXdFfeg.js +0 -17
  166. package/.output/public/_nuxt/COFCNFpG.js +0 -83
  167. package/.output/public/_nuxt/CXG-cOdh.js +0 -1
  168. package/.output/public/_nuxt/CkZ8t6uB.js +0 -1
  169. package/.output/public/_nuxt/D4Yz4rat.js +0 -65
  170. package/.output/public/_nuxt/DoD7Qb5P.js +0 -30
  171. package/.output/public/_nuxt/DyBZDSCV.js +0 -2
  172. package/.output/public/_nuxt/bLP7Jj-b.js +0 -1
  173. package/.output/public/_nuxt/builds/meta/7afbf062-5369-4002-b20d-7a582ff6e0f1.json +0 -1
  174. package/.output/server/chunks/routes/ws/mirror.mjs +0 -161
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  <p align="center"><b>A complete workspace for the coding agents running in <a href="https://herdr.dev">Herdr</a>, on your
8
8
  computer and on your phone.</b></p>
9
9
 
10
- <p align="center"><a href="https://wherdr.dev"><b>wherdr.dev</b></a> · <code>curl -fsSL https://wherdr.dev/install | sh</code> · <code>herdr plugin install afloury/wherdr</code></p>
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>
11
11
 
12
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
13
 
@@ -15,7 +15,20 @@ computer and on your phone.</b></p>
15
15
 
16
16
  ## Quick start
17
17
 
18
- On the computer that runs [Herdr](https://herdr.dev) (≥ 0.9.1), macOS or Linux, one command:
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:
19
32
 
20
33
  ```sh
21
34
  curl -fsSL https://wherdr.dev/install | sh
@@ -102,7 +115,8 @@ Details in [Features](#features) and [Security](#security).
102
115
  - [Features](#features)
103
116
  - [How it works](#how-it-works)
104
117
  - [Requirements](#requirements)
105
- - [Installation](#installation): [the one command](#the-one-command) ·
118
+ - [Installation](#installation): [ask your AI agent](#ask-your-ai-agent-1) ·
119
+ [or run the installer yourself](#the-one-command) ·
106
120
  [which setup?](#which-setup) ·
107
121
  [always-on server + Tailscale](#recommended-always-on-server--tailscale) ·
108
122
  [other private networks](#other-private-networks) ·
@@ -126,9 +140,12 @@ Details in [Features](#features) and [Security](#security).
126
140
 
127
141
  ### Everywhere
128
142
 
129
- - **Live agent list**, one line per Herdr space, grouped by state (your turn, working, ready),
130
- with a preview of each agent's last answer. When an agent asks for permission or asks a
131
- question, the options show on its card: **answer in one tap** without opening it.
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.
132
149
  - **Conversation view**: the agent's real transcript (Claude Code, Codex and omp), rendered as
133
150
  Markdown, with grouped tool calls, images (full-screen viewer with pinch / wheel zoom and pan), timestamps, search and infinite scroll back to the
134
151
  first message. Reading a conversation never touches the terminal, so it never resizes a pane.
@@ -136,7 +153,7 @@ Details in [Features](#features) and [Security](#security).
136
153
  its prompt, or run in the agent's shell mode (`!`, Claude Code, Codex and omp) after you
137
154
  confirm the full command.
138
155
  - **Terminal view**: the real terminal (xterm.js, WebGL or DOM renderer) with take-over. Opening
139
- it resizes the real pane to your screen; closing it gives the pane the size it had before
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
140
157
  (after 30 seconds when the connection just dropped, e.g. a locked phone), unless a Herdr
141
158
  client attached meanwhile laid it out its own way. On a
142
159
  computer, drag over the text on screen to select it: releasing the button copies it, as do
@@ -146,6 +163,22 @@ Details in [Features](#features) and [Security](#security).
146
163
  line wrapped by the width is copied in one piece, without the breaks between its rows. An image
147
164
  pasted with `⌘V` (Mac), here or in a side-by-side terminal, goes to the agent's machine and its
148
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.
149
182
  - **Composer**: send messages while the agent works (queued, cancellable), attach or paste
150
183
  photos and the files the agent can read (Claude Code: PDFs, notebooks, text and code of any
151
184
  extension; Codex: notebooks, text and code), run slash commands (`/compact`, `/clear`, `/context`, `/usage`…) and see the output
@@ -159,6 +192,20 @@ Details in [Features](#features) and [Security](#security).
159
192
  resumes the same conversation (only while Codex is idle). Codex's own weekly-limit warning
160
193
  (its footer gauge, or its startup heads-up) is repeated above the field, with its values, while
161
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.
162
209
  - **Model and effort pickers** for Claude Code, Codex and omp (omp: its
163
210
  "Switch Model" selector, `/switch` or `/model`, and its thinking level, changed with its own
164
211
  ⇧⇥ cycle). Changes apply **to the current session only**; wherdr never changes your default
@@ -166,6 +213,30 @@ Details in [Features](#features) and [Security](#security).
166
213
  - **New agent**: pick an installed agent (or a plain terminal), a folder, optionally a separate
167
214
  Git **worktree** and branch, resume the last conversation, and queue a first message.
168
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.
169
240
  - **Several machines**: agents of the SSH machines registered in Herdr (`herdr machine add`)
170
241
  appear in their own section; everything works the same on a remote agent.
171
242
  - **Named Herdr sessions**, **quotas** (remaining Claude and Codex usage limits), **Herdr plugin
@@ -176,7 +247,10 @@ Details in [Features](#features) and [Security](#security).
176
247
  ### On a computer
177
248
 
178
249
  - **Split panes, live**: a tab with several panes is drawn with Herdr's real layout; every pane
179
- shows its conversation or terminal live, and the one you click becomes interactive.
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.
180
254
  - **Rearrange the layout**: drag a pane onto another to move it, drag a divider to resize
181
255
  (arrow keys work on a focused divider); changes go to Herdr itself.
182
256
  - **Spaces and tabs**: sidebar with every space, tabs at the top, active tab remembered per space.
@@ -185,7 +259,8 @@ Details in [Features](#features) and [Security](#security).
185
259
  - **Keyboard shortcuts** (`Mod` is ⌘ on macOS, Ctrl elsewhere; `Mod+/` or `?` lists them all,
186
260
  also in Settings › Computer):
187
261
  - `Ctrl/⌘+K` global search across agents and conversations, `Alt+↑/↓` previous / next agent
188
- in the list, `Mod+,` settings.
262
+ in the list, `Mod+,` settings, `H` the [Board](#works-great-with-herdr-projects) (outside a
263
+ text field and the terminal).
189
264
  - `Mod+Alt+N` new agent in a new space, `Mod+Alt+T` new tab in the current space,
190
265
  `Mod+Alt+W` close the current pane (after confirmation). They work from the terminal too.
191
266
  - With [herdr-projects](#works-great-with-herdr-projects) on the agent's machine: `Mod+Alt+P`
@@ -202,6 +277,9 @@ Details in [Features](#features) and [Security](#security).
202
277
  under their project, and a side panel with the project's task lists (to test, to decide,
203
278
  to review, in progress, in queue, blocked, to do, backlog — each optional) and one-click
204
279
  replies to the coordinator.
280
+ - **Board**: every agent, and with herdr-projects every project, thread and task, in four columns
281
+ (Blocked, Todo, Ongoing, Finished). Drag a task card to another column, or use its **Move
282
+ to…** menu, to ask the coordinator to move it.
205
283
  - **Settings** in a sidebar, with desktop-only options such as the content width.
206
284
 
207
285
  ### On a phone
@@ -231,7 +309,10 @@ phone / laptop ──private network──> HTTPS (tailscale serve, private prox
231
309
 
232
310
  wherdr runs on the same machine as the Herdr server (the "server" below). It uses Herdr's local
233
311
  socket API and CLI, reads the agents' transcript files, and serves a client-only Nuxt app. Data
234
- it keeps (VAPID keys, push subscriptions, passkeys, recent folders) lives in `data/`.
312
+ it keeps (VAPID keys, push subscriptions, passkeys, recent folders) lives in `data/`. So do the
313
+ long texts pasted into the message field, which the server keeps to show them as cards on every
314
+ device: `data/pastes.json`, in clear, readable by the server's user only (mode 600), at most the
315
+ last 100 texts and 2 MB, each one deleted 30 days after it was last sent.
235
316
 
236
317
  Built with Nuxt 4, Nuxt UI 4, Tailwind 4 and TypeScript; the Herdr gateway (API, WebSockets,
237
318
  Web Push, lock) runs in Nitro.
@@ -252,7 +333,20 @@ Web Push, lock) runs in Nitro.
252
333
 
253
334
  ## Installation
254
335
 
255
- ### The one command
336
+ ### Ask your AI agent
337
+
338
+ **Recommended.** Paste this into Claude Code, Codex or any coding agent: it installs and sets up wherdr for you.
339
+
340
+ ```text
341
+ Read https://wherdr.dev/agent.md and help me set up wherdr. Check what is already installed first, and ask me before installing anything.
342
+ ```
343
+
344
+ It follows [our setup guide for agents](https://wherdr.dev/agent.md): checks what you already have,
345
+ asks before installing anything, sets up your phone over Tailscale, never exposes wherdr to the Internet.
346
+
347
+ <a id="the-one-command"></a>
348
+
349
+ ### Or run the installer yourself
256
350
 
257
351
  On the computer that runs Herdr, as your user, on macOS or Linux:
258
352
 
@@ -289,7 +383,7 @@ Settings, as environment variables (`curl -fsSL https://wherdr.dev/install | WHE
289
383
  | `WHERDR_MODE` | — | `native`: Node.js even when Docker is there. `docker`: no plugin, Docker Compose by hand in `WHERDR_DIR` (Linux; see below). |
290
384
  | `WHERDR_YES` | — | `1` answers yes to every question (non-interactive use). |
291
385
  | `WHERDR_NO_BROWSER` | — | `1` never opens a browser. |
292
- | `WHERDR_REF` | latest | Git tag or branch of the plugin (or of `docker-compose.yml` with `WHERDR_MODE=docker`). |
386
+ | `WHERDR_REF` | main | Git tag or branch of the plugin (or of `docker-compose.yml` with `WHERDR_MODE=docker`). |
293
387
  | `WHERDR_PLUGIN` | `afloury/wherdr` | A local checkout to link and build instead (development, tests). |
294
388
 
295
389
  **Docker by hand, with the script:** `curl -fsSL https://wherdr.dev/install | WHERDR_MODE=docker sh`
@@ -615,8 +709,8 @@ setup by itself:
615
709
  `~/wherdr` (`docker-compose.yml`, `.env`), the same files as `WHERDR_MODE=docker` of
616
710
  [the one command](#the-one-command).
617
711
  - **Node.js 22** everywhere else, and always on macOS (Docker Desktop cannot reach Herdr's
618
- socket): the plugin downloads the prebuilt npm package of its version (or builds wherdr in its
619
- own folder when that fails), copies it to `~/wherdr/app` and runs it detached from there, with
712
+ socket): the plugin downloads the prebuilt package of its version from GitHub Releases and
713
+ verifies its SHA-256 checksum, copies it to `~/wherdr/app` and runs it detached from there, with
620
714
  its pid and log (`wherdr.log`) in `~/wherdr`. Herdr builds plugins in a temporary folder and
621
715
  then moves them, so the server never runs from the plugin folder. Bun works too when Node.js 22
622
716
  is missing. Without either, the install stops and says so.
@@ -738,6 +832,27 @@ home screen and in Settings › About, with the release notes and the exact comm
738
832
  Hide it until the next version with ×. Set `WHERDR_UPDATE_CHECK=off` to disable the check. The
739
833
  installed version is shown in Settings › About.
740
834
 
835
+ GitHub release packages (including the bundled agent skill), Docker images, and Homebrew
836
+ updates are published without waiting for npm moderation. Native plugin installs and updates
837
+ use the GitHub `.tgz` and verify its SHA-256 checksum before extraction. A failed one-tap
838
+ download or checksum check leaves the running version in place. Plugin installation tries
839
+ GitHub first, then the prebuilt npm package of the exact same version (with its registry integrity hash), and only builds from source when neither works.
840
+ This also keeps older 1.3.x releases without GitHub archives installable with Node.js or Bun.
841
+ Packages predating the bundled skill retain the plugin checkout's commands and skill, so
842
+ skill refresh, installation and uninstallation remain available.
843
+ The installer fetches `main` explicitly unless you set `WHERDR_REF`.
844
+
845
+ The SHA-256 checksum detects damaged downloads. It is served beside the archive, so someone
846
+ who can replace both files can make them match; it does not prove the publisher’s identity
847
+ or protect against replacement of the archive and checksum together.
848
+
849
+ `npm install -g`, `npx`, `bunx` and `pnpm dlx` still need npm to serve the wherdr package.
850
+ Their banner waits for registry availability; Homebrew's banner waits for its formula.
851
+ Versions through 1.3.2 use GitHub's `latest` marker and an npm-based updater. That marker only
852
+ advances once npm **and** Homebrew are ready, so their existing Update button continues to work.
853
+ To update an older native plugin earlier, rerun the installer or press **U** in its Herdr panel.
854
+ See [Releasing](docs/RELEASING.md) for the publication order and recovery steps.
855
+
741
856
  **Update in one tap.** When wherdr runs from the Herdr plugin (Node.js mode), a global npm install
742
857
  or Homebrew, the banner has an **Update** button instead of the command, for a session opened on
743
858
  this computer (`localhost`) or unlocked with a passkey. wherdr installs the new version in a
@@ -749,7 +864,7 @@ and the app says so. Every step is logged in `~/wherdr/update.log`.
749
864
 
750
865
  | Setup | What Update does |
751
866
  | --- | --- |
752
- | Herdr plugin, Node.js | Unpacks the npm package of the new version into `~/wherdr/app`, keeps the old copy, restarts |
867
+ | Herdr plugin, Node.js | Verifies and unpacks the GitHub release package of the new version into `~/wherdr/app`, keeps the old copy, restarts |
753
868
  | `npm install -g wherdr` | `npm install -g wherdr@X.Y.Z`, then restarts (login service included) |
754
869
  | Homebrew | `brew upgrade wherdr`, then `brew services restart wherdr` |
755
870
 
@@ -837,6 +952,92 @@ All settings are environment variables (`.env` with Docker).
837
952
  In the app, **Settings** holds per-device preferences: language, theme, notifications, terminal
838
953
  text size and renderer, typing effect, what the home screen shows, and the passkey lock.
839
954
 
955
+ **Settings › Conversation › Reply style** chooses how an agent's reply offers to be quoted in
956
+ the field, question by question and point by point (a list item or a paragraph that asks
957
+ nothing), so that one message answers them all:
958
+
959
+ | Style | A question | A point |
960
+ | --- | --- | --- |
961
+ | **Icon** (default) | A small `↳` after it, in the middle or at the end of the message. | A grey `↳` at its end: on hover on a computer, always there and pale on a phone. |
962
+ | **Tap the text** | No button: the question is underlined, tap or click it. | Click it on a computer. On a phone, tap it, then **↳ Discuss**: a single tap never quotes, so reading, scrolling and selecting text stay as they are. |
963
+ | **List under the message** | A number after it, and the questions listed under the message. | **+ Quote a point** under the message, then the point. Offered when the reply has a list item to quote, or two points or more: not under one paragraph of prose. |
964
+
965
+ Every one of these is a button reachable with the keyboard (`Tab`, then `Enter`), and selecting a
966
+ passage still offers **Reply** on it. A quoted question or point shows `✓`.
967
+
968
+ **One syntax for every reference.** Whatever you reply to, the field gets one line that names it,
969
+ and your answer goes on the lines below:
970
+
971
+ ```
972
+ ↳ Quote message 11:37 ~k3f : I can merge the PR —
973
+ Go ahead.
974
+ ↳ Quote point 11:37 ~k3f : Shall I tag the release today? —
975
+ Yes
976
+ ↳ Quote decision 11:37 #3 ~k3f : Publish the release notes on the site? —
977
+ No
978
+ ```
979
+
980
+ | Line | Written by |
981
+ | --- | --- |
982
+ | `↳ Quote message <time> ~<code> : <start of the message> —` | **Reply** under an agent message. |
983
+ | `↳ Quote point <time> ~<code> : <excerpt> —` | `↳` on a question or a point, **Reply** on a selected passage. |
984
+ | `↳ Quote decision <time> #<n> ~<code> : <question> —` | **Yes**, **No** or `↳` on a row of a decision table. |
985
+ | `↳ Quote decisions <time> #<rows> ~<code> : <questions> —` | **OK to all** on a decision table. |
986
+ | `↳ Decision: <task> —`, `↳ Problem: <task> —`, `↳ Question: <task> —`, `↳ Detail on <task> —`, `↳ Feedback on <task> —` (and their French labels) | **Answer**, **Problem**, **Question**, **Clarify** and **Comment** in the Project panel. |
987
+
988
+ - The time is the one shown on the agent's message, so an answer to an earlier message is never
989
+ read as an answer to the last one. The types are the same words in every language.
990
+ - The code after `~` is three characters wherdr derives from the message (and the rank of the
991
+ table, from the second table of a message on). It tells apart two messages of the same minute
992
+ and two tables of one message, whatever the excerpt shows. The field draws it dimmed, like
993
+ the words before the excerpt; the quote of a sent message leaves it out. The rows of **OK to
994
+ all** are the numbers the table really has: `#1-4`, or `#1,4` when rows 2 and 3 do not exist.
995
+ - A reference is a whole line: it starts with `↳` in the first column and ends with ` —` at the
996
+ end of the line, and only there, so an excerpt may hold dashes of its own. The excerpt is plain
997
+ text on one line; a long one is cut with `…` (a message keeps its start, a passage its start
998
+ and its end).
999
+ - Several references fit in one message, each followed by its answer.
1000
+ - Anything else is your own text: a `>` at the start of a line, a `↳` line of an unknown type or
1001
+ with words after its dash. Text you paste never becomes a reference either: a pasted line that
1002
+ reads like one gets a `\` before its `↳`. A long paste, shown as a **Pasted text** card, is
1003
+ sent exactly as it was pasted.
1004
+ - In the conversation, each reference of a sent message shows as a quote; tap it to see the
1005
+ message it names. Messages sent earlier with `> ` lines still show as quotes.
1006
+
1007
+ **Settings › Conversation › Quoted replies** chooses how the field shows these lines: **Tokens,
1008
+ native field** (default) draws each one as a token in the usual field, **Plain text** shows the
1009
+ same lines as text, a fallback if the tokens misbehave. The message sent is the same.
1010
+
1011
+ A **closed question**, one that yes or no answers in full, also gets **Yes** and **No** next to
1012
+ its `↳` (at the end of its row in the list style). A tap refers to the question and writes the
1013
+ answer under it, in the language of the interface, exactly as Reply and typing would; nothing is sent, so several questions can be
1014
+ answered in a row before sending. The keyboard stays down on a phone. Tapping the other answer
1015
+ replaces the first; words you typed under a quote are never replaced. The rule is cautious, and
1016
+ in doubt only Reply is offered: a single sentence that opens like a yes/no question (`Shall I…`,
1017
+ `Do you…`, `Is it…`, `Want me to…`, `Est-ce que…`, `Je lance… ?`, `On…`, `Dois-je…`) or ends
1018
+ with `, OK?` / `, d'accord ?`, with no alternative (`or`, `ou`), no interrogative word (`which`,
1019
+ `how`, `quel`, `comment`…) and no negation.
1020
+
1021
+ A **decision table**, a Markdown table whose rows are numbered questions as a coordinator writes
1022
+ them, gets a panel right under it: **OK to all** refers to the whole table and writes `OK to all`
1023
+ under it (a second tap takes it back), and each row has **Yes**, **No** and `↳`, which refer to
1024
+ the row by its number and write `Yes` or `No` under it, or nothing for an answer in words; a row
1025
+ that asks an open question has `↳` alone. Every answer names the message and the row, even for
1026
+ the last message: the agent may write another one before you send. In French: `OK tout`, `Oui`,
1027
+ `Non`. Here too everything is composed in the field and you send. A table qualifies when it has a
1028
+ number column (a header `#`, `N°`, `No`, `Num`, `ID` or `Q` over short numbers, each once) and a
1029
+ question column, known by its header (`Question`, `Decision`, `Choix`…), by cells that all end
1030
+ with `?`, or by an advice column next to it (`Avis`, `Recommendation`, `Suggestion`…). Any other
1031
+ table is left as it is.
1032
+
1033
+ Above the field, **N questions unanswered** counts the questions of the agent's last message
1034
+ (and the rows of its decision tables) that your draft does not answer yet; tap it to scroll to
1035
+ the first one. A question counts once it is quoted, a row once it is answered, quoted or covered
1036
+ by **OK to all**; when the message asks one thing only, any words you type answer it. The line
1037
+ goes when everything is answered and when you send, and it never shows for earlier messages,
1038
+ while the agent is working, or while it waits on a prompt. It hangs from the top edge of the
1039
+ field, so nothing moves when it comes or goes.
1040
+
840
1041
  ## Several machines (SSH)
841
1042
 
842
1043
  wherdr also shows the agents of the **SSH machines registered in Herdr** — the ones the Herdr
@@ -919,7 +1120,8 @@ worktree. It is optional; with it installed, wherdr adds:
919
1120
  grouped by repository (`Repo web-shop · 2 worktrees`), with their thread number and state.
920
1121
  - **Project panel** next to the coordinator (a side panel on a computer, a tab on a phone). It
921
1122
  reads the project's `TASKS.md` and shows the lists it has, always in this order, with the open
922
- threads live (state, progress, report) and resolved threads under Done:
1123
+ threads live (state, progress, report) and resolved threads under Done (**Tasks** tab; the
1124
+ **Threads**, **Inbox**, **Routines**, **Settings** and **Memory** tabs are described below):
923
1125
 
924
1126
  | List | Meaning | Buttons |
925
1127
  | --- | --- | --- |
@@ -942,6 +1144,71 @@ worktree. It is optional; with it installed, wherdr adds:
942
1144
  header shows the thread slots in use (`max_parallel_threads` in `PROJECT.md`) and the next
943
1145
  task, plus the machine's global limit when one is set
944
1146
  (`2 of 2 thread slots in use on <machine> (all projects)`).
1147
+ - **Threads tab** of the Project panel: every thread of the project, resolved ones included.
1148
+ Open threads are grouped as herdr-projects groups them (Your turn, Ready for review, Working…),
1149
+ each with its machine and agent profile; resolved threads stay in their own folded section,
1150
+ shown by pages of 30. One filter covers both (ID, title, machine, profile). A tap opens the
1151
+ thread's record: state, its **Next** list, branch, dates, and these actions:
1152
+
1153
+ | Action | Command | Asks first |
1154
+ | --- | --- | --- |
1155
+ | **Acknowledge** | `thread ack`: you have seen its report | |
1156
+ | **Restart** | `thread restart`: bring back a thread whose pane is gone or whose start failed | |
1157
+ | **Stop** | `thread stop`: Escape in its agent's pane | Yes |
1158
+ | **Resolve** | `thread resolve`: final copy, then its worktree, merged branch and tab are cleaned up (report and library kept) | Yes |
1159
+
1160
+ Open agent, View report and Pull request are there too when they apply. wherdr only runs
1161
+ these `herdr-projects` commands on the coordinator's machine (and reads `thread show --json`);
1162
+ it writes no thread file itself, and herdr-projects has the last word (it refuses to restart a
1163
+ running thread, for instance): its answer is shown as is. With Docker, **Resolve** runs in
1164
+ the container, where the home folder is read-only: a thread of this machine is resolved and
1165
+ its open workspace removed through Herdr, but its merged branch, or a worktree whose workspace
1166
+ was already closed, may stay. herdr-projects lists what it kept, and
1167
+ `herdr-projects sweep <project>` on the host removes it.
1168
+ - **Inbox tab**: the project's unhandled inbox items, what herdr-projects tells the coordinator
1169
+ (a thread's new report, a routine due, a machine out of reach…), oldest first, up to 100. An
1170
+ item about a thread shows the thread's title and opens it. **Done** (✓) marks one item as
1171
+ handled with `herdr-projects inbox done`; a coordinator that has not read it yet will not see
1172
+ it any more. Summaries are shown as plain text.
1173
+ - **Routines tab**: each routine's schedule, whether it is enabled, its kind (prompt only, or a
1174
+ command and its approval) and its last and next run. Read-only: wherdr never approves,
1175
+ switches or runs a routine, and does not expose the plugin's safety settings.
1176
+ - **Settings tab**, read-only: what the project's `PROJECT.md` sets, the keys
1177
+ `herdr-projects set` changes (name, goal, coordinator and thread profiles,
1178
+ `max_parallel_threads`, `auto_resolve_days`, `nudge`, `mute`, repositories). A key the file
1179
+ leaves out shows the plugin's default, marked as such. Safety settings are not shown.
1180
+ - **Memory tab**, read-only: `MEMORY.md` (the index) and the `.md` files of `memory/`, with the
1181
+ title and hook the index gives them; a file the index does not mention is listed by name. A
1182
+ tap opens one, rendered as Markdown. The coordinator writes memory, wherdr never does.
1183
+
1184
+ With six tabs, the bar scrolls sideways on a phone and in a narrow side panel; a fade on its
1185
+ edge says there are more.
1186
+
1187
+ herdr-projects (0.2.34) has no JSON output for the inbox or the routines: wherdr reads the
1188
+ front matter of the project's `inbox/*.md` files, and the tab-separated lines of
1189
+ `herdr-projects routine list`, showing the run texts (`last … · next …`) as printed. No command
1190
+ prints a project's settings or memory either: those two tabs read the files named above.
1191
+ - **Board** (`H`, or the board button above the agent list): every project, thread, task and
1192
+ agent at a glance, in four columns. Agents outside projects show too. Each card opens what it
1193
+ stands for: a project or task opens its Project panel, a thread opens its agent (or its report
1194
+ once closed), an agent opens its conversation. A project card takes the most urgent column of
1195
+ its cards and counts them; Finished shows the 5 most recent threads and done tasks of each
1196
+ project, **Show all** the rest.
1197
+
1198
+ | Card | Blocked | Todo | Ongoing | Finished |
1199
+ | --- | --- | --- | --- | --- |
1200
+ | **Task** (its `TASKS.md` list) | Blocked | To test, To decide, To review, In queue, To do, Backlog, other lists | In progress without an open thread | Done, or a checked line |
1201
+ | **Thread** | Its agent is your turn, or *waiting on you* | *Ready for review* | Working, landing, idle | Resolved |
1202
+ | **Agent** | Your turn | | Working, starting, restarting | Done, ready |
1203
+
1204
+ Moving a **task** card (drag and drop on a computer, **Move to…** everywhere) sends the
1205
+ coordinator the Project panel's message when one says it (**Launch now** from To do or In
1206
+ queue, **Launch** from Backlog, **Unblock** from Blocked, **Tested** from To test, **Reviewed**
1207
+ from To review), otherwise `↳ Mark blocked: …`, `↳ Move to To do: …` or `↳ Mark done: …`. The
1208
+ card shows *Sent* until the coordinator has moved the line; only the allowed columns are
1209
+ offered, and To decide tasks wait for your answer. Thread, agent and project cards follow
1210
+ what they do and do not move. The server reads every project at once
1211
+ (`GET /api/board`); a project on an offline machine keeps its last read, marked as such.
945
1212
  - **Max threads per machine**, across all projects: herdr-projects only limits threads per
946
1213
  project, so three coordinators could still start a dozen threads on one small machine. Set a
947
1214
  limit per machine in **Settings → Plugins → herdr-projects** (empty `—` = no global limit;
@@ -996,17 +1263,57 @@ worktree. It is optional; with it installed, wherdr adds:
996
1263
 
997
1264
  The Project panel only works well when the coordinator follows its conventions: which lists
998
1265
  mean what, the badges, `(agent → t-NNNN)`, and what to do with each message the buttons send
999
- (`✓ Tested: …`, `✗ Problem: … — …`, `↳ Queue: …`…). They are written once, as an agent skill:
1266
+ (`✓ Tested: …`, `↳ Queue: …`, `↳ Problem: … —` or `↳ Decision: … —` with the answer below it…). They are written once, as an agent skill:
1000
1267
  [`skills/wherdr/SKILL.md`](skills/wherdr/SKILL.md).
1001
1268
 
1002
- - **Herdr plugin**: the install links the skill for every coordinator, whatever its agent:
1003
- `~/.claude/skills/wherdr` (Claude Code, or `$CLAUDE_CONFIG_DIR/skills`) and
1004
- `~/.agents/skills/wherdr` (Codex, omp), each only when that agent is installed. Both point to a
1005
- copy in `~/wherdr/skills/wherdr`, refreshed on each update. An existing `wherdr` skill that is
1006
- not this link is left alone. Remove the links with `sh scripts/herdr-plugin.sh skill uninstall`
1007
- in the plugin folder.
1008
- - **Other installs**: link or copy `skills/wherdr` into your agent's skills folder yourself, or
1009
- paste **Settings → Plugins → herdr-projects → Copy rules for the coordinator** to it.
1269
+ The npm package (also used by Homebrew) and Docker image include the skill from that
1270
+ release. Installation is best effort: a missing agent or an unwritable home never blocks
1271
+ wherdr. It links `~/.claude/skills/wherdr` (or `$CLAUDE_CONFIG_DIR/skills/wherdr`) only
1272
+ when the Claude configuration folder exists, and `~/.agents/skills/wherdr` only when
1273
+ `.agents`, `.codex` or `.omp` exists. Links point to a stable copy in
1274
+ `${WHERDR_DIR:-$HOME/wherdr}/skills/wherdr`. Existing skills from elsewhere are left alone.
1275
+ Relative `WHERDR_DIR` resolves from the working directory; relative `CLAUDE_CONFIG_DIR`
1276
+ resolves from HOME, including when saved in a login service. Unchanged content is not rewritten.
1277
+ A broken link is repaired only when local metadata proves wherdr created it.
1278
+
1279
+ | Install mode | Skill installation and refresh |
1280
+ | --- | --- |
1281
+ | npm / npx / Bun / pnpm | On `wherdr`, `wherdr run`, `wherdr start` or `wherdr service install`; after updating the package, the next start refreshes it. |
1282
+ | Homebrew | On `wherdr` startup, including the Homebrew service; restart the service after `brew upgrade`. |
1283
+ | One-tap update (npm, Homebrew, native plugin) | Restarting wherdr refreshes the skill from the newly installed package; rollback restores the previous copy when that package also bundles the skill. |
1284
+ | Herdr plugin, Node.js | On plugin installation / reinstallation, and on startup from its installed package. |
1285
+ | Herdr plugin, Docker | On plugin installation and Update, extracted from the pulled image on the host. |
1286
+ | One-command installer, Docker | After each pull, including reruns and `WHERDR_MODE=docker`; saves host helpers in `$WHERDR_DIR/skill-installer`. |
1287
+ | Docker by hand | Run the host-side command below after the initial pull and after each image update. |
1288
+ | From source | Use `node bin/wherdr.mjs` after building, or run `sh bin/lib/install-skill.sh` yourself after updating; `npm start` alone does not install the skill. |
1289
+
1290
+ Docker cannot write the host's agent folders through its read-only home mount. From the
1291
+ repository checkout on the **host**, after pulling your image, run:
1292
+
1293
+ ```sh
1294
+ sh bin/install-docker-skill.sh ghcr.io/afloury/wherdr:latest
1295
+ ```
1296
+
1297
+ Pass your image tag if different. The helper creates a stopped temporary container, copies
1298
+ only `/app/skills/wherdr/SKILL.md` out of it, uses the local host installer, then removes
1299
+ the container. No host home is mounted into it. Repeat this command after each manual Docker update; Docker has no one-tap
1300
+ update in the app. Manual failures (including older images without the skill) return a
1301
+ nonzero status with diagnostics; plugin and one-command installation warn and continue. Without a checkout, use
1302
+ `sh "${WHERDR_DIR:-$HOME/wherdr}/skill-installer/bin/install-docker-skill.sh" <image>` after the one-command install.
1303
+
1304
+ Remove managed links with `sh bin/lib/install-skill.sh uninstall` (or
1305
+ `sh scripts/herdr-plugin.sh skill uninstall` in the plugin folder). Uninstall leaves
1306
+ `~/.local/state/wherdr/skill-disabled`: subsequent starts and updates respect it, even if
1307
+ `WHERDR_DIR` changes. To re-enable, explicitly run `sh bin/lib/install-skill.sh install`
1308
+ or the plugin's `skill install` (or the Docker host helper above). `WHERDR_SKILL=off`
1309
+ disables all installation and refresh, including explicit install; export it for interactive
1310
+ starts or set it when installing the login service to preserve it there.
1311
+
1312
+ Two limitations remain: rollback to an older package without the helper (such as 1.3.2)
1313
+ leaves the failed release's skill in place. With multiple wherdr versions sharing an account,
1314
+ the last one started supplies the skill; there is no version comparison.
1315
+
1316
+ You can also paste **Settings → Plugins → herdr-projects → Copy rules for the coordinator** to your agent.
1010
1317
 
1011
1318
  ## Herdr plugins
1012
1319
 
@@ -0,0 +1,20 @@
1
+ #!/bin/sh
2
+ # Run on the HOST after pulling an image; no container is started, no home mounted.
3
+ # Usage: sh bin/install-docker-skill.sh <image> [refresh|install]
4
+ extract_skill() (
5
+ set -eu
6
+ [ -n "${1:-}" ] || { echo 'usage: install-docker-skill.sh <image> [refresh|install]' >&2; exit 1; }
7
+ [ "${WHERDR_SKILL:-}" != off ] || { echo 'wherdr skill: disabled by WHERDR_SKILL=off'; exit 0; }
8
+ if [ "${2:-install}" != install ] && [ -e "${HOME:-}/.local/state/wherdr/skill-disabled" ]; then exit 0; fi
9
+ helper=$(CDPATH= cd -- "$(dirname "$0")" && pwd -P)/lib/install-skill.sh
10
+ tmp=$(mktemp -d)
11
+ container=
12
+ trap '[ -z "$container" ] || docker rm "$container" >/dev/null 2>&1; rm -rf "$tmp"' EXIT HUP INT TERM
13
+ container=$(docker create -- "$1")
14
+ mkdir -p "$tmp/skills/wherdr"
15
+ # Only data crosses the image boundary. The executed helper is local.
16
+ docker cp "$container:/app/skills/wherdr/SKILL.md" "$tmp/skills/wherdr/SKILL.md"
17
+ sh "$helper" "${2:-install}" "$tmp"
18
+ )
19
+ # Unlike startup, manual and plugin callers see failures and useful diagnostics.
20
+ extract_skill "$@"
@@ -11,6 +11,7 @@ import { BIN, CliError, SERVER, VERSION, bad, c, homebrewInstall, localUrl, ok,
11
11
  import { RUNTIME_HELP, findRuntime, isBun, runtimeOk, runtimeVersion, temporaryInstall } from './runtime.mjs'
12
12
  import { brewDoctor, brewRedirect, brewService, brewStatusRows } from './brew.mjs'
13
13
  import { controlService, installService, serviceFile, servicePlatform, serviceState, uninstallService } from './service.mjs'
14
+ import { refreshSkill } from './skill.mjs'
14
15
  import { LINKS, inspect, publishCommand, reachable } from './tailnet.mjs'
15
16
 
16
17
  export const HELP = `${'wherdr'}: your Herdr agents from your phone and browser.
@@ -115,6 +116,7 @@ function needServer() {
115
116
 
116
117
  // ------------------------------------------------------------------- run
117
118
  export async function run(opts) {
119
+ refreshSkill(context(opts).env)
118
120
  needServer()
119
121
  const ctx = context(opts)
120
122
  if (!process.versions.bun && Number(process.versions.node.split('.')[0]) < 22) throw new CliError(`Node.js 22 or newer is required (found ${process.version}).`)
@@ -138,6 +140,7 @@ export async function run(opts) {
138
140
 
139
141
  // ----------------------------------------------------------------- start
140
142
  export async function start(opts) {
143
+ refreshSkill(context(opts).env)
141
144
  needServer()
142
145
  const ctx = context(opts)
143
146
  const brew = brewService()
@@ -375,6 +378,7 @@ export async function phone(opts) {
375
378
 
376
379
  // --------------------------------------------------------------- service
377
380
  export async function service(opts) {
381
+ if (opts.sub === 'install') refreshSkill(context(opts).env)
378
382
  const ctx = context(opts)
379
383
  if (opts.sub === 'uninstall') {
380
384
  const file = uninstallService()
@@ -406,7 +410,7 @@ export async function service(opts) {
406
410
  if (ctx.env.HERDR_WEB_SESSION) args.push('--session', ctx.env.HERDR_WEB_SESSION)
407
411
  const PATH = [...new Set([path.dirname(runtime), path.join(os.homedir(), '.local/bin'), '/opt/homebrew/bin', '/usr/local/bin', '/usr/bin', '/bin'])].join(':')
408
412
  const env = { PATH, WHERDR_DIR: ctx.dir, WHERDR_RUNTIME: runtime }
409
- for (const k of ['APP_URL', 'HERDR_BIN', 'HOST_LABEL']) if (process.env[k]) env[k] = process.env[k]
413
+ for (const k of ['APP_URL', 'HERDR_BIN', 'HOST_LABEL', 'CLAUDE_CONFIG_DIR', 'WHERDR_SKILL']) if (ctx.env[k]) env[k] = ctx.env[k]
410
414
  const file = installService({ runtime, args, env, log: ctx.files.log })
411
415
  ok(`login service installed: ${file}`)
412
416
  say(c.dim(` ${runtime} ${args.join(' ')}`))
package/bin/lib/core.mjs CHANGED
@@ -115,6 +115,7 @@ export function serverEnv(opts, env, home = os.homedir(), bin = BIN) {
115
115
  out.WHERDR_INSTALL = installKind(bin, dir)
116
116
  out.WHERDR_ROOT = path.resolve(bin, '..', '..')
117
117
  out.WHERDR_DIR = dir
118
+ if (out.CLAUDE_CONFIG_DIR) out.CLAUDE_CONFIG_DIR = path.resolve(home, expandHome(out.CLAUDE_CONFIG_DIR, home))
118
119
  // Nitro reads NITRO_PORT / NITRO_HOST before PORT / HOST.
119
120
  delete out.NITRO_PORT
120
121
  delete out.NITRO_HOST