@geohar/un-bien 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +726 -0
  3. package/dist/actions/handlers.d.ts +77 -0
  4. package/dist/actions/handlers.js +26 -0
  5. package/dist/actions/handlers.js.map +1 -0
  6. package/dist/actions/registry.d.ts +39 -0
  7. package/dist/actions/registry.js +62 -0
  8. package/dist/actions/registry.js.map +1 -0
  9. package/dist/bin/supervisord.d.ts +2 -0
  10. package/dist/bin/supervisord.js +86 -0
  11. package/dist/bin/supervisord.js.map +1 -0
  12. package/dist/config.d.ts +84 -0
  13. package/dist/config.js +115 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/daemon/client.d.ts +20 -0
  16. package/dist/daemon/client.js +131 -0
  17. package/dist/daemon/client.js.map +1 -0
  18. package/dist/daemon/control_protocol.d.ts +176 -0
  19. package/dist/daemon/control_protocol.js +63 -0
  20. package/dist/daemon/control_protocol.js.map +1 -0
  21. package/dist/daemon/cron_log.d.ts +45 -0
  22. package/dist/daemon/cron_log.js +71 -0
  23. package/dist/daemon/cron_log.js.map +1 -0
  24. package/dist/daemon/cron_registry.d.ts +80 -0
  25. package/dist/daemon/cron_registry.js +198 -0
  26. package/dist/daemon/cron_registry.js.map +1 -0
  27. package/dist/daemon/id.d.ts +24 -0
  28. package/dist/daemon/id.js +36 -0
  29. package/dist/daemon/id.js.map +1 -0
  30. package/dist/daemon/install.d.ts +170 -0
  31. package/dist/daemon/install.js +603 -0
  32. package/dist/daemon/install.js.map +1 -0
  33. package/dist/daemon/registry.d.ts +63 -0
  34. package/dist/daemon/registry.js +153 -0
  35. package/dist/daemon/registry.js.map +1 -0
  36. package/dist/daemon/rpc_child.d.ts +194 -0
  37. package/dist/daemon/rpc_child.js +419 -0
  38. package/dist/daemon/rpc_child.js.map +1 -0
  39. package/dist/daemon/supervisor.d.ts +93 -0
  40. package/dist/daemon/supervisor.js +666 -0
  41. package/dist/daemon/supervisor.js.map +1 -0
  42. package/dist/extension_ui_bridge.d.ts +26 -0
  43. package/dist/extension_ui_bridge.js +341 -0
  44. package/dist/extension_ui_bridge.js.map +1 -0
  45. package/dist/index.d.ts +197 -0
  46. package/dist/index.js +5750 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/mcp/mesh_result.d.ts +10 -0
  49. package/dist/mcp/mesh_result.js +42 -0
  50. package/dist/mcp/mesh_result.js.map +1 -0
  51. package/dist/mcp/mesh_server.d.ts +16 -0
  52. package/dist/mcp/mesh_server.js +373 -0
  53. package/dist/mcp/mesh_server.js.map +1 -0
  54. package/dist/mesh/canonical.d.ts +30 -0
  55. package/dist/mesh/canonical.js +63 -0
  56. package/dist/mesh/canonical.js.map +1 -0
  57. package/dist/mesh/client.d.ts +18 -0
  58. package/dist/mesh/client.js +127 -0
  59. package/dist/mesh/client.js.map +1 -0
  60. package/dist/mesh/encoding.d.ts +38 -0
  61. package/dist/mesh/encoding.js +217 -0
  62. package/dist/mesh/encoding.js.map +1 -0
  63. package/dist/mesh/self_revoke.d.ts +73 -0
  64. package/dist/mesh/self_revoke.js +407 -0
  65. package/dist/mesh/self_revoke.js.map +1 -0
  66. package/dist/mesh/siblings.d.ts +49 -0
  67. package/dist/mesh/siblings.js +159 -0
  68. package/dist/mesh/siblings.js.map +1 -0
  69. package/dist/mesh/types.d.ts +34 -0
  70. package/dist/mesh/types.js +11 -0
  71. package/dist/mesh/types.js.map +1 -0
  72. package/dist/mesh/verify.d.ts +17 -0
  73. package/dist/mesh/verify.js +86 -0
  74. package/dist/mesh/verify.js.map +1 -0
  75. package/dist/pairing/crypto.d.ts +8 -0
  76. package/dist/pairing/crypto.js +22 -0
  77. package/dist/pairing/crypto.js.map +1 -0
  78. package/dist/pairing/qr.d.ts +59 -0
  79. package/dist/pairing/qr.js +127 -0
  80. package/dist/pairing/qr.js.map +1 -0
  81. package/dist/pairing/storage.d.ts +120 -0
  82. package/dist/pairing/storage.js +614 -0
  83. package/dist/pairing/storage.js.map +1 -0
  84. package/dist/panel_bridge.d.ts +15 -0
  85. package/dist/panel_bridge.js +409 -0
  86. package/dist/panel_bridge.js.map +1 -0
  87. package/dist/paths.d.ts +39 -0
  88. package/dist/paths.js +51 -0
  89. package/dist/paths.js.map +1 -0
  90. package/dist/protocol/codec.d.ts +7 -0
  91. package/dist/protocol/codec.js +48 -0
  92. package/dist/protocol/codec.js.map +1 -0
  93. package/dist/protocol/types.d.ts +404 -0
  94. package/dist/protocol/types.js +2 -0
  95. package/dist/protocol/types.js.map +1 -0
  96. package/dist/rooms.d.ts +42 -0
  97. package/dist/rooms.js +78 -0
  98. package/dist/rooms.js.map +1 -0
  99. package/dist/session/bridge.d.ts +37 -0
  100. package/dist/session/bridge.js +134 -0
  101. package/dist/session/bridge.js.map +1 -0
  102. package/dist/session/broker.d.ts +189 -0
  103. package/dist/session/broker.js +527 -0
  104. package/dist/session/broker.js.map +1 -0
  105. package/dist/session/broker_remote.d.ts +74 -0
  106. package/dist/session/broker_remote.js +581 -0
  107. package/dist/session/broker_remote.js.map +1 -0
  108. package/dist/session/cwd_lock.d.ts +33 -0
  109. package/dist/session/cwd_lock.js +127 -0
  110. package/dist/session/cwd_lock.js.map +1 -0
  111. package/dist/session/debug_log.d.ts +3 -0
  112. package/dist/session/debug_log.js +32 -0
  113. package/dist/session/debug_log.js.map +1 -0
  114. package/dist/session/envelope.d.ts +33 -0
  115. package/dist/session/envelope.js +117 -0
  116. package/dist/session/envelope.js.map +1 -0
  117. package/dist/session/global_config.d.ts +33 -0
  118. package/dist/session/global_config.js +73 -0
  119. package/dist/session/global_config.js.map +1 -0
  120. package/dist/session/ipc.d.ts +27 -0
  121. package/dist/session/ipc.js +22 -0
  122. package/dist/session/ipc.js.map +1 -0
  123. package/dist/session/leader_election.d.ts +32 -0
  124. package/dist/session/leader_election.js +108 -0
  125. package/dist/session/leader_election.js.map +1 -0
  126. package/dist/session/local_config.d.ts +67 -0
  127. package/dist/session/local_config.js +193 -0
  128. package/dist/session/local_config.js.map +1 -0
  129. package/dist/session/mesh_node.d.ts +116 -0
  130. package/dist/session/mesh_node.js +596 -0
  131. package/dist/session/mesh_node.js.map +1 -0
  132. package/dist/session/peer.d.ts +139 -0
  133. package/dist/session/peer.js +474 -0
  134. package/dist/session/peer.js.map +1 -0
  135. package/dist/session/peer_inventory.d.ts +13 -0
  136. package/dist/session/peer_inventory.js +48 -0
  137. package/dist/session/peer_inventory.js.map +1 -0
  138. package/dist/session/peer_limits.d.ts +9 -0
  139. package/dist/session/peer_limits.js +24 -0
  140. package/dist/session/peer_limits.js.map +1 -0
  141. package/dist/session/rpc_envelope.d.ts +108 -0
  142. package/dist/session/rpc_envelope.js +221 -0
  143. package/dist/session/rpc_envelope.js.map +1 -0
  144. package/dist/session/rpc_inbound.d.ts +55 -0
  145. package/dist/session/rpc_inbound.js +136 -0
  146. package/dist/session/rpc_inbound.js.map +1 -0
  147. package/dist/session/setup_wizard.d.ts +39 -0
  148. package/dist/session/setup_wizard.js +63 -0
  149. package/dist/session/setup_wizard.js.map +1 -0
  150. package/dist/session/tools.d.ts +24 -0
  151. package/dist/session/tools.js +272 -0
  152. package/dist/session/tools.js.map +1 -0
  153. package/dist/session/wizard.d.ts +13 -0
  154. package/dist/session/wizard.js +20 -0
  155. package/dist/session/wizard.js.map +1 -0
  156. package/dist/transport/peer_channel.d.ts +52 -0
  157. package/dist/transport/peer_channel.js +145 -0
  158. package/dist/transport/peer_channel.js.map +1 -0
  159. package/dist/transport/pi_forward_client.d.ts +29 -0
  160. package/dist/transport/pi_forward_client.js +68 -0
  161. package/dist/transport/pi_forward_client.js.map +1 -0
  162. package/dist/transport/relay_client.d.ts +96 -0
  163. package/dist/transport/relay_client.js +210 -0
  164. package/dist/transport/relay_client.js.map +1 -0
  165. package/dist/ui/footer.d.ts +32 -0
  166. package/dist/ui/footer.js +34 -0
  167. package/dist/ui/footer.js.map +1 -0
  168. package/docs/daemon.md +289 -0
  169. package/package.json +92 -0
  170. package/service-templates/launchd.plist.template +35 -0
  171. package/service-templates/systemd.service.template +19 -0
  172. package/service-templates/task-launcher.vbs.template +10 -0
  173. package/service-templates/task-scheduler.xml.template +38 -0
  174. package/skills/agent-network/SKILL.md +310 -0
package/README.md ADDED
@@ -0,0 +1,726 @@
1
+ > **Derived from [remote-pi](https://github.com/jacobaraujo7/remote_pi)** by Jacob
2
+ > Moura, used under the MIT License (preserved in [`LICENSE`](LICENSE)). This tree
3
+ > is part of the [un-bien](../README.md) monorepo and is mid-rework — the content
4
+ > below still carries upstream branding and is being rebranded to un-bien.
5
+
6
+ <p align="center">
7
+ <img src="https://raw.githubusercontent.com/jacobaraujo7/remote_pi/main/branding/logo-full.svg" width="160" alt="Remote Pi logo" />
8
+ </p>
9
+
10
+ <h1 align="center">Remote Pi</h1>
11
+
12
+ > Extend the [Pi coding agent](https://github.com/earendil-works/pi) with two
13
+ > superpowers: agents that talk to each other on the same machine, and a mobile
14
+ > app that drives Pi from your phone.
15
+
16
+ **Homepage:** <https://remote-pi.jacobmoura.work>
17
+
18
+ `/remote-pi` is a single slash command that wires both at once. Run it; the
19
+ first time it asks a couple of questions and you are done.
20
+
21
+ ## Protocol & Security
22
+
23
+ For wire format, identity model, ACK protocol, cross-PC routing, mesh
24
+ membership, and the trust model (what the relay sees and doesn't see),
25
+ read [`PROTOCOL.md`](../PROTOCOL.md) at the repo root. It is the canonical
26
+ document — this README only covers user-facing setup.
27
+
28
+ ---
29
+
30
+ ## Quick start
31
+
32
+ Install the extension (one-time):
33
+
34
+ ```bash
35
+ pi install npm:remote-pi
36
+ ```
37
+
38
+ Then in any Pi terminal:
39
+
40
+ ```text
41
+ /remote-pi
42
+ ```
43
+
44
+ The first run shows a short interactive wizard (agent name, default session,
45
+ whether to auto-start the relay). On every following run, `/remote-pi` joins
46
+ the local agent session and starts the relay automatically — no extra typing.
47
+
48
+ ### Try the agent network in 30 seconds
49
+
50
+ Open **two** Pi terminals in the same directory and run `/remote-pi` in each.
51
+ Both join the same session. Now just talk to the LLM — it has the tools.
52
+
53
+ In terminal A (say it ended up named `agent-A`):
54
+
55
+ ```text
56
+ Who else is connected in our agent session? List them.
57
+ ```
58
+
59
+ The LLM calls `list_peers` and reports the complete routing addresses it sees.
60
+
61
+ Then, still in terminal A:
62
+
63
+ ```text
64
+ Send a ping to agent-B using its listed address and ask it to reply later.
65
+ ```
66
+
67
+ Pi calls `agent_send({ to: "<exact address from list_peers>", body: {
68
+ type: "ping" } })`. For unicast, the call waits only for the broker's delivery
69
+ ACK. Terminal B receives the message as a user-facing turn and can answer later
70
+ with `agent_send`, setting `re` to the ping's message id; that reply arrives in
71
+ terminal A's inbox or a later turn. It does not block terminal A waiting for
72
+ agent-B's content reply.
73
+
74
+ Copy the complete address exactly as listed. Do not build, parse, decode, or
75
+ normalize it.
76
+
77
+ ---
78
+
79
+ ## What it does
80
+
81
+ Remote Pi adds two independent layers on top of Pi. You can use either, or
82
+ both:
83
+
84
+ ### 1) Agent network (local broker, optional cross-PC relay)
85
+
86
+ Several Pi instances running side-by-side in different terminals can discover
87
+ each other and exchange messages. Each instance is a peer in a named
88
+ *session*. The LLM uses:
89
+
90
+ - `list_peers` — discover current peer routing addresses
91
+ - `agent_send` — unicast waits for the broker delivery ACK; broadcast is
92
+ fire-and-forget
93
+
94
+ The legacy Pi-only `agent_request` tool is deprecated because it blocks while
95
+ waiting for another agent's content reply. Use `agent_send`, continue the
96
+ current turn, and receive any later reply through the inbox/turn flow with
97
+ `re` correlating it to the original message id.
98
+
99
+ Peers on the same machine talk over a Unix domain socket at
100
+ `~/.pi/remote/sessions/<session-name>/broker.sock`. When sibling PCs are paired,
101
+ a leader-capable Extension or MCP participant bridges the opaque cross-PC
102
+ addresses over the relay; local-only use stays on UDS when relay access is off.
103
+ Useful for splitting work across roles (`backend`, `frontend`, `tests`,
104
+ `orchestrator`, …) and letting them coordinate.
105
+
106
+ The first agent to enter a session becomes the *leader* (hosts the broker);
107
+ the rest are *followers*. If the leader exits, a follower automatically takes
108
+ over — the failover is invisible to the LLMs.
109
+
110
+ ### 2) Mobile app (over the relay)
111
+
112
+ The companion mobile app lets you send prompts to Pi and read its responses
113
+ from your phone. The phone and the Pi process find each other through a
114
+ **relay**: a small WebSocket server that ferries messages between them.
115
+ Pairing is one-time and per device, via QR code.
116
+
117
+ Communication uses WebSocket over TLS to the relay. Fields such as `ct` are
118
+ wire containers, not a systemwide end-to-end confidentiality guarantee: current
119
+ Pi-forward, cross-PC, app, and control envelopes visible to the relay are not
120
+ fully opaque or E2E encrypted. A relay operator can see routed plaintext
121
+ protocol content and metadata; see [`PROTOCOL.md`](../PROTOCOL.md) for the exact
122
+ trust boundaries.
123
+
124
+ **Get the app** — all current download options (Google Play, App Store, and
125
+ direct builds while public releases roll out):
126
+
127
+ <https://remote-pi.jacobmoura.work/#get-the-app>
128
+
129
+ ---
130
+
131
+ ## Mobile app actions
132
+
133
+ Beyond the chat, the app surfaces a small set of typed actions you can run
134
+ on the paired Pi session. Tap the ⚙ button next to the message input (visible
135
+ when the input is empty) to open the Quick Actions sheet:
136
+
137
+ | Action | What it does |
138
+ | --- | --- |
139
+ | **Compact context** | Runs `ctx.compact()` — same as `/compact` in the TUI. |
140
+ | **New session** | Runs `ctx.newSession()` — equivalent to `/new`, asks for confirmation first. |
141
+ | **Model** | Opens a model picker fed by your authenticated providers (same source the TUI uses) and switches via `pi.setModel(model)`. |
142
+ | **Thinking** | Segmented control with the 6 SDK levels (`off` · `minimal` · `low` · `medium` · `high` · `xhigh`). Changes via `pi.setThinkingLevel(level)`. |
143
+
144
+ Each action gets a structured `action_ok` / `action_error` reply so the app
145
+ can show a SnackBar on failure. Visible side-effects (chat output, model
146
+ change broadcasts, compaction notice) still flow through the normal chat
147
+ channels. The wire schema is documented in [`PROTOCOL.md`](../PROTOCOL.md)
148
+ under "App actions".
149
+
150
+ It is **not** a generic slash-command picker. The Pi SDK does not expose
151
+ programmatic invocation for most builtins (those live in the TUI's
152
+ interactive loop), so the app exposes only the actions that have a clean
153
+ SDK call. The [`pi-telegram`](https://github.com/llblab/pi-telegram) adapter
154
+ follows the same pattern.
155
+
156
+ ### Images
157
+
158
+ The app can attach **one image** (camera or gallery) to a message. It's
159
+ compressed on the device and rides **inline** in the `user_message` — the
160
+ optional `images` field carries `{ data: <base64>, mime }`. The pi-extension
161
+ turns it into the SDK's multimodal content (an `ImageContent` followed by the
162
+ caption `TextContent`) and calls `sendUserMessage(content)`, so the model sees
163
+ the picture plus your text.
164
+
165
+ Whether a model accepts images is surfaced as a `vision` flag on each
166
+ `WireModel` (derived from the SDK's `Model.input` including `"image"`); the app
167
+ greys out the attach button when the active model is text-only.
168
+
169
+ The **relay is unchanged** — the image travels inside the same application
170
+ message container as the text, so there's no binary channel (large files are a
171
+ future track). Base64 or a field named `ct` is not an E2E confidentiality
172
+ boundary; the current Relay visibility follows the trust model above. Text-only
173
+ messages are unaffected.
174
+
175
+ ---
176
+
177
+ ## Install
178
+
179
+ Requirements: Node 20+, Pi (the host coding agent).
180
+
181
+ ```bash
182
+ pi install npm:remote-pi
183
+ ```
184
+
185
+ The extension self-registers the `/remote-pi` slash command and deploys an
186
+ agent skill that teaches the LLM how to use `list_peers`, `agent_send`, and the
187
+ event-driven inbox/reply flow.
188
+
189
+ To verify:
190
+
191
+ ```text
192
+ /remote-pi config
193
+ ```
194
+
195
+ It should print the effective relay URL and where it came from
196
+ (`env` / `config` / `default`).
197
+
198
+ ---
199
+
200
+ ## Using `/remote-pi`
201
+
202
+ The bare command is the everyday entry point:
203
+
204
+ ```text
205
+ /remote-pi
206
+ ```
207
+
208
+ Behavior depends on whether there's a local config for this directory:
209
+
210
+ | State | What happens |
211
+ | --- | --- |
212
+ | First run (no `.pi/remote-pi/config.json`) | Interactive wizard → saves config → joins agent session → starts relay (if you opted in) |
213
+ | Returning user, auto-start enabled | Joins agent session + starts relay automatically, then prints status |
214
+ | Returning user, auto-start disabled | Prints status only; join/relay must be run manually |
215
+
216
+ The wizard asks three questions:
217
+
218
+ 1. **Agent name** — the presentation leaf name for this agent. Senders still
219
+ copy the complete opaque address returned by `list_peers`; they never build
220
+ an address from this name. Defaults to the directory name.
221
+ 2. **Default session** — the name of the agent-network room for this
222
+ directory. Multiple terminals in the same directory join the same session.
223
+ 3. **Auto-start relay (for mobile app access)?** — `Yes` if you want
224
+ `/remote-pi` to also connect to the relay so the mobile app can reach this
225
+ Pi. `No` for local-only use (agent network without mobile access).
226
+
227
+ Re-run the wizard later with `/remote-pi setup`.
228
+
229
+ ---
230
+
231
+ ## Pairing a mobile device
232
+
233
+ Once the relay is up (`/remote-pi relay status` shows `started` or `paired`):
234
+
235
+ ```text
236
+ /remote-pi pair
237
+ ```
238
+
239
+ A QR code is printed in the terminal. Scan it with the Remote Pi mobile app.
240
+ Pairing is **per machine** — once a device is paired, every Pi process on
241
+ this machine accepts it (it lives in `~/.pi/remote/peers.json`).
242
+
243
+ To list paired devices:
244
+
245
+ ```text
246
+ /remote-pi devices
247
+ ```
248
+
249
+ To remove one:
250
+
251
+ ```text
252
+ /remote-pi revoke <shortid>
253
+ ```
254
+
255
+ The shortid is the first 8 chars shown by `devices`.
256
+
257
+ ---
258
+
259
+ ## The relay
260
+
261
+ The relay is the network boundary. TLS protects transit, but the Relay can see
262
+ routed plaintext protocol content and metadata; use a relay you trust or
263
+ self-host. There is no systemwide or PC-mesh E2E guarantee. For Pi-to-Pi
264
+ forwarding, the Relay currently permits a route when any correctly signed Owner
265
+ blob lists both canonical Pi keys. That does not prove the Owner paired with or
266
+ controls either Pi.
267
+
268
+ ### Upgrade order (Relay 0.3 first, then Extension 0.6)
269
+
270
+ Upgrade the **Relay to 0.3 first**: an old Extension can consume the new
271
+ Relay's UUID errors. Extension 0.6 carries a one-release legacy wire-label
272
+ shim, so mixed new/old Extensions interoperate when both select the same unique
273
+ colon-free signed nickname label, or when neither has one and both use the
274
+ canonical standard-padded key prefix. Delimiter or collision cases, like
275
+ divergent nickname views, are unsupported and may be silently dropped by the
276
+ old receiver. Upgrade all Extension/MCP participants in one maintenance window.
277
+ The shim does not replace the receiver-local aliases returned by `list_peers`;
278
+ addresses remain opaque.
279
+
280
+ Extension 0.6 accepts an old Relay's lowercase 32-hex trusted error ID only as
281
+ a narrow shim for an old Relay or Relay rollback; that shim is not why
282
+ Relay-first is safe.
283
+
284
+ You have two options:
285
+
286
+ ### Option A — Use the community relay
287
+
288
+ `https://relay-rp1.jacobmoura.work` (default). Zero setup. Good for trying
289
+ things out or for casual use. (The extension converts to `wss://…`
290
+ internally when opening the connection — both schemes point at the same
291
+ endpoint.)
292
+
293
+ Caveats:
294
+
295
+ - Shared infrastructure — availability is best-effort.
296
+ - **There is no IP allow-listing or VPN gating**.
297
+
298
+ ### Option B — Self-host (recommended for privacy)
299
+
300
+ Run the relay yourself in Docker and put it behind a VPN like
301
+ [Tailscale](https://tailscale.com), [WireGuard](https://www.wireguard.com),
302
+ or your own VPC. Because the relay's network-level protection is just TLS +
303
+ keypair authentication, layering a VPN on top means **only your devices** can
304
+ even reach the WebSocket port — defense in depth.
305
+
306
+ Quick Docker outline (see the
307
+ [relay README](https://github.com/jacobaraujo7/remote_pi/blob/main/relay/README.md#self-hosted-relay-recommended-for-privacy)
308
+ for the full setup, environment variables, and reverse-proxy guidance):
309
+
310
+ ```bash
311
+ docker run -d \
312
+ --name remote-pi-relay \
313
+ -p 3000:3000 \
314
+ --restart unless-stopped \
315
+ ghcr.io/jacobaraujo7/remote-pi-relay:latest
316
+ ```
317
+
318
+ Bind the container to your VPN interface, terminate TLS in a reverse proxy,
319
+ and point both your Pi and your phone at the resulting `https://…` URL.
320
+
321
+ ### Pointing Pi at your own relay
322
+
323
+ Once your relay is reachable, tell the extension:
324
+
325
+ ```text
326
+ /remote-pi relay url https://relay.yourdomain.tld
327
+ ```
328
+
329
+ The URL **must** be `http://` or `https://` — `ws://` / `wss://` are
330
+ rejected at validation. The extension converts to WebSocket internally when
331
+ it opens the connection. Same canonical form for the mobile app and any
332
+ self-hosting docs: paste the URL your reverse proxy exposes.
333
+
334
+ This writes `~/.pi/remote/config.json` with `{ "relay": "..." }`. Resolution
335
+ order (highest precedence first):
336
+
337
+ 1. `REMOTE_PI_RELAY` environment variable (CI / one-off overrides)
338
+ 2. `~/.pi/remote/config.json`
339
+ 3. The built-in default (`https://relay-rp1.jacobmoura.work`)
340
+
341
+ Verify the active URL and its source with:
342
+
343
+ ```text
344
+ /remote-pi config
345
+ ```
346
+
347
+ If you change the URL while connected, run `/remote-pi relay stop` then
348
+ `/remote-pi relay start` (or `/remote-pi relay` to toggle).
349
+
350
+ The mobile app has its own relay-URL setting in its preferences pane — keep
351
+ both pointing at the same relay.
352
+
353
+ ---
354
+
355
+ ## Agent network: deeper look
356
+
357
+ Each session is one Unix-domain-socket broker plus N peers. The broker
358
+ multiplexes messages by opaque `to` address and broadcasts system events
359
+ (`peer_joined`, `peer_left`).
360
+
361
+ Inside the LLM, the agent skill uses `list_peers` for discovery and
362
+ `agent_send` for delivery:
363
+
364
+ ```jsonc
365
+ list_peers() // copy a complete address from this result
366
+
367
+ agent_send({
368
+ to: "/repo/api@backend", // exact opaque address returned by list_peers
369
+ body: { task: "add /healthz endpoint" },
370
+ re: "<id>" // set to the received message id when replying
371
+ })
372
+ ```
373
+
374
+ A unicast `agent_send` waits for the broker delivery ACK and returns the public
375
+ status `received`, `denied`, or `timeout`; broadcast is fire-and-forget. A
376
+ trusted Relay's closed transport reason is returned in `details` without
377
+ changing those statuses: `offline` maps to `timeout`, while `not_authorized`
378
+ and `bad_envelope` map to `denied`. Genuine silence is a reasonless `timeout`.
379
+ Do not blindly retry authorization or envelope failures. Trusted Relay errors
380
+ are consumed internally to settle pending sends; forged or invalid reserved
381
+ bodies do not gain that authority.
382
+
383
+ Mesh addresses are opaque routing values: echo them verbatim, including
384
+ receiver-local PC aliases with percent-encoded bytes (such as `%3A` or `%25`)
385
+ or collision suffixes containing `~`. Never parse, build, decode, or normalize
386
+ an address for routing or security. A PC alias is receiver-local presentation
387
+ and routing only, so different PCs may list the same sibling under different
388
+ aliases. The canonical 32-byte Ed25519 Pi public key is the PC's technical
389
+ identity; never use an alias as proof of identity.
390
+
391
+ `agent_request` remains available only as a deprecated legacy Pi tool. Prefer
392
+ `agent_send`, then handle any later inbox/turn reply whose `re` matches the
393
+ original message id.
394
+
395
+ The wire format is a 5-field envelope `{ from, to, id, re, body }` serialized
396
+ as one JSON line per message. The leader's broker writes an `audit.jsonl`
397
+ log at `~/.pi/remote/sessions/<name>/audit.jsonl` for postmortem inspection.
398
+
399
+ Useful commands:
400
+
401
+ | Command | What it does |
402
+ | --- | --- |
403
+ | `/remote-pi` | Join the local mesh (and start the relay, if enabled) |
404
+ | `/remote-pi peers` | List local + cross-PC mesh peers, grouped by PC |
405
+ | `/remote-pi rename <new>` | Rename this agent in the current session |
406
+ | `/remote-pi stop` | Leave the local mesh and disconnect the relay |
407
+
408
+ Name collisions inside a session get a numeric suffix automatically
409
+ (`backend`, `backend#2`, `backend#3`). The broker assigns it and returns the
410
+ real name to the peer.
411
+
412
+ ---
413
+
414
+ ## Command reference
415
+
416
+ ### Local session (one Pi, one terminal)
417
+
418
+ | Command | Description |
419
+ | --- | --- |
420
+ | `/remote-pi` | Connect (join local mesh + start relay), or run setup on first use |
421
+ | `/remote-pi setup` | Run the setup wizard and update local config |
422
+ | `/remote-pi status` | Show local mesh + relay status |
423
+ | `/remote-pi stop` | Stop everything for **this** terminal (mesh + relay) |
424
+ | `/remote-pi pair` | Show QR code + copy-paste pairing URI for a new mobile device |
425
+ | `/remote-pi devices` | List paired mobile devices (online/offline per device) |
426
+ | `/remote-pi revoke <shortid>` | Revoke a paired device by its shortid |
427
+ | `/remote-pi set-relay <url>` | Persist a new relay URL (http:// or https://) |
428
+ | `/remote-pi relay [start\|stop\|status]` | Relay-only control — leaves local mesh membership untouched (no verb = toggle) |
429
+ | `/remote-pi relay url <url>` | Same as `set-relay` |
430
+ | `/remote-pi config` | Show the effective relay URL and its source (env / config / default) |
431
+
432
+ ### Daemon fleet (one supervisor, N background Pis — see [Daemon mode](#daemon-mode))
433
+
434
+ | Command | Description |
435
+ | --- | --- |
436
+ | `/remote-pi create <cwd> [--name X]` | Register a folder as a daemon |
437
+ | `/remote-pi remove <id>` | Unregister a daemon (local config preserved) |
438
+ | `/remote-pi daemons` | List registered daemons + state |
439
+ | `/remote-pi daemon start` | Start every registered daemon |
440
+ | `/remote-pi daemon stop` | Stop every running daemon (`/remote-pi stop` stops only the local terminal) |
441
+ | `/remote-pi daemon restart` | Stop + start all daemons |
442
+ | `/remote-pi daemon status` | Detailed runtime status (pid, uptime, restart count) |
443
+ | `/remote-pi daemon send <id> "<text>"` | Send a prompt to a specific daemon |
444
+ | `/remote-pi cron add <id> "<expr>" "<prompt>"` | Schedule a recurring prompt (`--tz`, `--wake`, `--no-skip-busy`, `--catchup`) |
445
+ | `/remote-pi cron list` | List scheduled jobs (schedule, enabled, next run, last status) |
446
+ | `/remote-pi cron run <jobId>` | Fire a job now (ignores its schedule) |
447
+ | `/remote-pi cron enable\|disable <jobId>` | Toggle a job on/off |
448
+ | `/remote-pi cron remove <jobId>` | Delete a job |
449
+ | `/remote-pi cron log [<jobId>] [--tail N]` | Read the fire/skip audit log |
450
+ | `/remote-pi install` | Install `pi-supervisord` as a system service |
451
+ | `/remote-pi uninstall` | Remove the system service (registry preserved) |
452
+
453
+ All commands above work both as Pi slash commands (interactive) and as
454
+ shell-level `remote-pi <subcommand>` when the package is installed
455
+ globally (`npm install -g remote-pi`).
456
+
457
+ ### Scheduled prompts (`cron`)
458
+
459
+ `remote-pi cron` schedules **recurring prompts** to daemons through the
460
+ supervisor — e.g. a daily "summarise new PRs". Output flows fire-and-forget to
461
+ the mesh/app like any prompt; the cron layer only audits the dispatch.
462
+
463
+ - **Schedule** is a cron expression (croner syntax; an optional 6th *seconds*
464
+ field is supported), with an optional IANA timezone via `--tz`:
465
+
466
+ ```sh
467
+ remote-pi cron add a1b2c3d4 "0 9 * * *" "Summarise new PRs" --tz America/Sao_Paulo
468
+ ```
469
+
470
+ - **Minimum interval is 60s** — more frequent schedules are rejected (guards
471
+ token cost + pileup). A fire is **skipped when the daemon is mid-turn**
472
+ (`--no-skip-busy` to override); `--wake` starts a stopped daemon first;
473
+ `--catchup` runs once on supervisor start if the previous run was missed.
474
+ - **Prerequisite**: the supervisor must run as a service (`remote-pi install`).
475
+ Without it there is no scheduler, and `cron` commands say so instead of
476
+ silently pretending to schedule.
477
+ - **Audit**: every fire **and** every skip appends one line to
478
+ `~/.pi/remote/cron.jsonl` with a `result` of `delivered`,
479
+ `woke_and_delivered`, `deliver_failed`, `skipped_busy`, `skipped_down`, or
480
+ `skipped_disabled` — read it with `remote-pi cron log`.
481
+
482
+ Step-by-step walkthrough: the [daemon tutorial](https://remote-pi.jacobmoura.work/tutorials/daemon).
483
+
484
+ ### Footer + title
485
+
486
+ - `📡 local (N)` — current agent session and peer count (local mesh)
487
+ - `🟢 relay` — relay connected, at least one device paired (globally)
488
+ - `🟡 relay waiting for pairing` — relay connected, no device paired yet
489
+ - `📱 <shortid>` — a mobile device is actively connected right now
490
+
491
+ Window title: `<agent-name> · On` when relay is up, `<agent-name> · Off`
492
+ otherwise. Tells your terminals apart at a glance in `cmux`/`tmux`/iTerm
493
+ tabs.
494
+
495
+ ---
496
+
497
+ ## Daemon mode
498
+
499
+ When you want a Pi to keep running in the background (responding to
500
+ mobile prompts at 3am, processing cron jobs, monitoring a folder while
501
+ you're not at the keyboard), promote it to a **daemon** managed by a
502
+ single OS-level supervisor.
503
+
504
+ See [`docs/daemon.md`](./docs/daemon.md) for troubleshooting.
505
+
506
+ ### One-time setup
507
+
508
+ ```bash
509
+ # Install the package globally so `remote-pi` and `pi-supervisord`
510
+ # are on your PATH (`pi install npm:remote-pi` alone makes the Pi
511
+ # extension available but does NOT expose the CLI binaries — see
512
+ # https://docs.npmjs.com/cli/v10/configuring-npm/package-json#bin).
513
+ npm install -g remote-pi
514
+
515
+ # Install the supervisor as a user-level system service. Linux uses
516
+ # systemd --user; macOS uses launchd LaunchAgent. Both auto-start at
517
+ # login and survive reboots.
518
+ remote-pi install
519
+ ```
520
+
521
+ The `install` command:
522
+
523
+ - Writes `~/.config/systemd/user/remote-pi-supervisord.service` (Linux)
524
+ or `~/Library/LaunchAgents/dev.remotepi.supervisord.plist` (macOS)
525
+ - Activates it via `systemctl --user enable --now` or `launchctl bootstrap`
526
+ - The supervisor starts immediately and re-starts on every login
527
+
528
+ ### Per-folder workflow
529
+
530
+ For each agent you want to keep alive 24/7:
531
+
532
+ ```bash
533
+ # 1. Configure the agent interactively first (one time).
534
+ cd ~/Movies
535
+ pi # /remote-pi → setup wizard, /remote-pi pair, etc
536
+
537
+ # 2. Promote to a daemon. The id is derived from the cwd
538
+ # (sha256(realpath)[:8]), stable across machines.
539
+ remote-pi create ~/Movies --name "Video Editor"
540
+ # → Daemon registered: id=4e39152d name="Video Editor" cwd=/Users/x/Movies
541
+
542
+ # 3. Start it (supervisor spawns `pi --mode rpc` for this folder).
543
+ remote-pi daemon start
544
+ ```
545
+
546
+ Now you can:
547
+
548
+ ```bash
549
+ remote-pi daemons # list + state
550
+ remote-pi daemon status # uptime, pid, restart count
551
+ remote-pi daemon send 4e39152d "Cut the first 30 seconds of latest clip"
552
+ remote-pi daemon stop # stop all
553
+ remote-pi daemon restart # restart all
554
+ ```
555
+
556
+ The agent receives the prompt as if a user typed it; its response flows
557
+ back through the relay/mesh you configured during interactive setup —
558
+ mobile app sees it live, other agents on the same machine can see it
559
+ via the local UDS mesh.
560
+
561
+ ### Removing or uninstalling
562
+
563
+ ```bash
564
+ remote-pi remove <id> # unregister one daemon (config preserved)
565
+ remote-pi uninstall # remove the supervisor service (registry kept)
566
+ ```
567
+
568
+ `uninstall` is reversible — re-running `install` later brings every
569
+ registered daemon back. To wipe the registry entirely, `rm
570
+ ~/.pi/remote/daemons.json`.
571
+
572
+ ### Where to find logs
573
+
574
+ | Platform | Command |
575
+ | --- | --- |
576
+ | Linux | `journalctl --user -u remote-pi-supervisord -f` |
577
+ | macOS | `tail -f ~/.pi/remote/supervisord.log` |
578
+
579
+ Each spawned daemon's stderr is forwarded into the supervisor's log
580
+ with a `[<cwd>]` prefix, so a single log stream shows every agent.
581
+
582
+ ### Caveats (plan/26 trade-offs)
583
+
584
+ - **Tool approval is not gated.** Daemons inherit the same Pi config
585
+ the interactive run uses — Bash, Edit, Write etc. all execute without
586
+ prompting. Configure Pi's tool permissions to taste before promoting
587
+ a folder to daemon.
588
+ - **Pairing still happens interactively.** Daemons don't show a QR
589
+ themselves; the keypair + paired devices come from the prior `pi`
590
+ session in the same folder.
591
+ - **Single supervisor.** If `pi-supervisord` crashes all daemons go
592
+ down with it. systemd/launchd restarts it within seconds; daemons
593
+ come back automatically.
594
+ - **One daemon per cwd.** The `roomIdForCwd` derivation makes daemons
595
+ by-path; two daemons in the same folder is rejected at `create` time.
596
+
597
+ ---
598
+
599
+ ## Configuration files
600
+
601
+ | Path | Scope | What's in it |
602
+ | --- | --- | --- |
603
+ | `<cwd>/.pi/remote-pi/config.json` | Per-directory | `agent_name`, `session_name`, `auto_start_relay` |
604
+ | `~/.pi/remote/config.json` | Per-user | `relay` URL; optional `defaults.auto_start_relay` (machine-wide fallback for per-directory configs) |
605
+ | `~/.pi/remote/peers.json` | Per-machine | Paired mobile devices |
606
+ | `~/.pi/remote/sessions/<name>/` | Per-session | Broker socket + `audit.jsonl` |
607
+ | `~/.pi/remote/skills/agent-network/SKILL.md` | Per-user | Agent skill the LLM reads |
608
+
609
+ Every path above **except the global `config.json`** derives from a single
610
+ **state root** — by default `~/.pi/remote`. Two environment variables relocate
611
+ that state root:
612
+
613
+ | Variable | Behaviour |
614
+ | --- | --- |
615
+ | `REMOTE_PI_DIR` | Absolute path to the state root. No suffix appended — set this to an XDG-style location like `~/.config/pi/remote-pi`. Takes priority over every other variable. |
616
+ | `REMOTE_PI_HOME` | Stand-in for `$HOME`; state lives at `<REMOTE_PI_HOME>/.pi/remote`. Kept for backward compatibility. |
617
+
618
+ When both are set, `REMOTE_PI_DIR` wins. Use these to put remote-pi **state**
619
+ (sessions, daemon registries, cwd locks, paired identity) on a specific disk,
620
+ inside an XDG directory, or anywhere your system's conventions dictate.
621
+
622
+ The global `config.json` (relay URL + defaults) is resolved separately, so it
623
+ can sit beside the coding agent's own settings rather than the state tree:
624
+
625
+ | Variable | Behaviour |
626
+ |---|---|
627
+ | `PI_CODING_AGENT_DIR` | The Pi host's settings root (default `~/.pi`). `config.json` lives at `<PI_CODING_AGENT_DIR>/remote/config.json`, so with the default agent dir the path stays exactly `~/.pi/remote/config.json`. Takes priority for config. |
628
+
629
+ When `PI_CODING_AGENT_DIR` is unset, `config.json` falls back to the state root
630
+ above (so a pure `REMOTE_PI_DIR` relocation still keeps config beside the rest).
631
+
632
+ ### Global defaults
633
+
634
+ The global `~/.pi/remote/config.json` can include a `defaults` block that acts
635
+ as a fallback for every per-directory config that doesn't set the field:
636
+
637
+ ```jsonc
638
+ {
639
+ "relay": "https://relay.yourdomain.tld",
640
+ "defaults": {
641
+ "auto_start_relay": true
642
+ }
643
+ }
644
+ ```
645
+
646
+ This lets you pin `auto_start_relay` once — beside `relay` — instead of
647
+ dropping a `config.json` into every repo. A per-directory file (or the
648
+ `REMOTE_PI_DIRECT_CONFIG` env var) still overrides the global default.
649
+
650
+ The `defaults` block is read from wherever the global `config.json` resolves, so
651
+ it follows a relocated config: with `PI_CODING_AGENT_DIR` set it lives at
652
+ `<PI_CODING_AGENT_DIR>/remote/config.json` (see the environment overrides above).
653
+
654
+ Override the relay for a single run without persisting:
655
+
656
+ ```bash
657
+ REMOTE_PI_RELAY=https://staging.example.tld pi
658
+ ```
659
+
660
+ ---
661
+
662
+ ## Troubleshooting
663
+
664
+ **Footer says `🟡 relay waiting for pairing` even though I paired a device.**
665
+ The icon reflects whether *any* device has been paired on this machine, not
666
+ whether one is connected right now. If you really have a paired device in
667
+ `/remote-pi devices`, restart Pi — the cache may be stale (fixed in current
668
+ release; report a bug if it recurs).
669
+
670
+ **Mobile app times out connecting.** Verify the same relay URL is configured
671
+ on both sides. If you self-host behind a VPN, your phone must also be on the
672
+ VPN (Tailscale on iOS/Android works fine).
673
+
674
+ **`agent_request` keeps timing out.** It is deprecated because it blocks the
675
+ turn while waiting for another agent's content reply. Migrate to `agent_send`;
676
+ a unicast waits only for the delivery ACK, and the receiver can reply later
677
+ with `agent_send` including `re: "<original-id>"` for correlation.
678
+
679
+ **Multiple terminals in the same directory.** Supported. They share the same
680
+ agent-network session (UDS broker) and the relay handles each Pi process
681
+ independently. If the relay refuses with `RoomAlreadyOpenError`, stop the
682
+ other terminal first.
683
+
684
+ ---
685
+
686
+ ## Branding
687
+
688
+ Official brand assets live in
689
+ [`/branding`](https://github.com/jacobaraujo7/remote_pi/tree/main/branding) —
690
+ SVG sources for the logo (full, foreground, background, monochrome) plus a
691
+ banner. See the
692
+ [branding README](https://github.com/jacobaraujo7/remote_pi/blob/main/branding/README.md)
693
+ for palette and export sizes.
694
+
695
+ <table>
696
+ <tr>
697
+ <td align="center">
698
+ <img src="https://raw.githubusercontent.com/jacobaraujo7/remote_pi/main/branding/logo-full.svg" width="96" alt="logo-full" /><br/>
699
+ <sub><code>logo-full</code></sub>
700
+ </td>
701
+ <td align="center">
702
+ <img src="https://raw.githubusercontent.com/jacobaraujo7/remote_pi/main/branding/logo-foreground.svg" width="96" alt="logo-foreground" /><br/>
703
+ <sub><code>logo-foreground</code></sub>
704
+ </td>
705
+ <td align="center">
706
+ <img src="https://raw.githubusercontent.com/jacobaraujo7/remote_pi/main/branding/logo-monochrome.svg" width="96" alt="logo-monochrome" /><br/>
707
+ <sub><code>logo-monochrome</code></sub>
708
+ </td>
709
+ </tr>
710
+ </table>
711
+
712
+ ---
713
+
714
+ ## Links
715
+
716
+ - Homepage: <https://remote-pi.jacobmoura.work>
717
+ - Source: <https://github.com/jacobaraujo7/remote_pi>
718
+ - Pi coding agent: <https://github.com/earendil-works/pi>
719
+ - Relay (self-hosting guide): <https://github.com/jacobaraujo7/remote_pi/blob/main/relay/README.md>
720
+ - Issues / bugs: <https://github.com/jacobaraujo7/remote_pi/issues>
721
+
722
+ ---
723
+
724
+ ## License
725
+
726
+ MIT