pi-courier 0.1.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 (86) hide show
  1. package/README.md +363 -0
  2. package/README.zh-CN.md +362 -0
  3. package/deploy/pi-msg-bridge.service +26 -0
  4. package/deploy/pi-msg-bridge.user.service +37 -0
  5. package/dist/auth/challenge-auth.d.ts +76 -0
  6. package/dist/auth/challenge-auth.d.ts.map +1 -0
  7. package/dist/auth/challenge-auth.js +337 -0
  8. package/dist/auth/challenge-auth.js.map +1 -0
  9. package/dist/cli.d.ts +3 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +265 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/config.d.ts +10 -0
  14. package/dist/config.d.ts.map +1 -0
  15. package/dist/config.js +49 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/formatting.d.ts +22 -0
  18. package/dist/formatting.d.ts.map +1 -0
  19. package/dist/formatting.js +74 -0
  20. package/dist/formatting.js.map +1 -0
  21. package/dist/lock.d.ts +3 -0
  22. package/dist/lock.d.ts.map +1 -0
  23. package/dist/lock.js +75 -0
  24. package/dist/lock.js.map +1 -0
  25. package/dist/rpc/command-map.d.ts +19 -0
  26. package/dist/rpc/command-map.d.ts.map +1 -0
  27. package/dist/rpc/command-map.js +191 -0
  28. package/dist/rpc/command-map.js.map +1 -0
  29. package/dist/rpc/message-router.d.ts +30 -0
  30. package/dist/rpc/message-router.d.ts.map +1 -0
  31. package/dist/rpc/message-router.js +110 -0
  32. package/dist/rpc/message-router.js.map +1 -0
  33. package/dist/rpc/pi-rpc.d.ts +79 -0
  34. package/dist/rpc/pi-rpc.d.ts.map +1 -0
  35. package/dist/rpc/pi-rpc.js +199 -0
  36. package/dist/rpc/pi-rpc.js.map +1 -0
  37. package/dist/setup.d.ts +10 -0
  38. package/dist/setup.d.ts.map +1 -0
  39. package/dist/setup.js +159 -0
  40. package/dist/setup.js.map +1 -0
  41. package/dist/standalone.d.ts +13 -0
  42. package/dist/standalone.d.ts.map +1 -0
  43. package/dist/standalone.js +184 -0
  44. package/dist/standalone.js.map +1 -0
  45. package/dist/transports/discord.d.ts +33 -0
  46. package/dist/transports/discord.d.ts.map +1 -0
  47. package/dist/transports/discord.js +216 -0
  48. package/dist/transports/discord.js.map +1 -0
  49. package/dist/transports/interface.d.ts +42 -0
  50. package/dist/transports/interface.d.ts.map +1 -0
  51. package/dist/transports/interface.js +2 -0
  52. package/dist/transports/interface.js.map +1 -0
  53. package/dist/transports/manager.d.ts +54 -0
  54. package/dist/transports/manager.d.ts.map +1 -0
  55. package/dist/transports/manager.js +98 -0
  56. package/dist/transports/manager.js.map +1 -0
  57. package/dist/transports/matrix-utils.d.ts +31 -0
  58. package/dist/transports/matrix-utils.d.ts.map +1 -0
  59. package/dist/transports/matrix-utils.js +99 -0
  60. package/dist/transports/matrix-utils.js.map +1 -0
  61. package/dist/transports/matrix-utils.test.d.ts +2 -0
  62. package/dist/transports/matrix-utils.test.d.ts.map +1 -0
  63. package/dist/transports/matrix-utils.test.js +246 -0
  64. package/dist/transports/matrix-utils.test.js.map +1 -0
  65. package/dist/transports/matrix.d.ts +34 -0
  66. package/dist/transports/matrix.d.ts.map +1 -0
  67. package/dist/transports/matrix.js +240 -0
  68. package/dist/transports/matrix.js.map +1 -0
  69. package/dist/transports/slack.d.ts +31 -0
  70. package/dist/transports/slack.d.ts.map +1 -0
  71. package/dist/transports/slack.js +203 -0
  72. package/dist/transports/slack.js.map +1 -0
  73. package/dist/transports/telegram.d.ts +38 -0
  74. package/dist/transports/telegram.d.ts.map +1 -0
  75. package/dist/transports/telegram.js +167 -0
  76. package/dist/transports/telegram.js.map +1 -0
  77. package/dist/transports/whatsapp.d.ts +31 -0
  78. package/dist/transports/whatsapp.d.ts.map +1 -0
  79. package/dist/transports/whatsapp.js +235 -0
  80. package/dist/transports/whatsapp.js.map +1 -0
  81. package/dist/types.d.ts +69 -0
  82. package/dist/types.d.ts.map +1 -0
  83. package/dist/types.js +2 -0
  84. package/dist/types.js.map +1 -0
  85. package/extensions/index.ts +42 -0
  86. package/package.json +71 -0
package/README.md ADDED
@@ -0,0 +1,363 @@
1
+ # pi-courier
2
+
3
+ [English](README.md) | [简体中文](README.zh-CN.md)
4
+
5
+ Run the [pi coding agent](https://pi.dev) headlessly from **Matrix**. Slash commands, skills and prompt templates fully work from your chat client.
6
+
7
+ Unlike the classic extension mode, this project drives pi over the [RPC protocol](https://pi.dev/docs/latest/rpc), so **slash commands work from messengers** (`/new`, `/compact`, `/model`, `/skill:name`, prompt templates, extension commands) — the extension mode can't do this because pi's `sendUserMessage()` deliberately skips command handling.
8
+
9
+ > **Upstream**: this project is a rework of [tintinweb/pi-messenger-bridge](https://github.com/tintinweb/pi-messenger-bridge) — the Matrix transport layer and challenge auth come from there; the RPC-based standalone architecture, slash-command mapping, CLI and setup wizard are new.
10
+
11
+ ## Features
12
+
13
+ - 📱 Matrix transport (E2EE-capable), challenge-based auth (6-digit codes)
14
+ - 🎛️ Full slash-command support: `/new`, `/compact`, `/model`, `/thinking`, `/bash`, `/reload`, ...
15
+ - 🧩 Skills & prompt templates pass through: `/skill:name`, `/template`
16
+ - 💾 Session persistence: pi sessions live on disk, resume across restarts
17
+ - 🔄 `/reload` restarts the pi process (after installing extensions/config) — lossless
18
+ - 🔌 pi is **not bundled**: installed independently on the system, upgraded on its own
19
+ - 🧭 One-command CLI: `pi-courier setup` wizard, `pi-courier enable` auto-start, `pi-courier update` self-update
20
+
21
+ ## Architecture
22
+
23
+ ```
24
+ Messenger ──> pi-courier (dist/standalone.js) ──> pi --mode rpc (system-installed)
25
+ Messenger <── replies <────────────────────── <── agent events (stdout JSONL)
26
+ ```
27
+
28
+ - pi-courier spawns and manages the `pi --mode rpc` child process
29
+ - Sessions persist to `~/.pi/agent/sessions`, resumed automatically after restarts
30
+ - systemd only needs to manage the pi-courier service
31
+
32
+ ## Requirements
33
+
34
+ | Component | Requirement | Check |
35
+ |---|---|---|
36
+ | Node.js | >= 20 (tested on 24.x) | `node --version` |
37
+ | pi | >= 0.83, **installed globally** (not bundled) | `pi --version` |
38
+ | Network | access to your homeserver and LLM provider | — |
39
+
40
+ Install pi first — pi-courier is a companion app that connects to it over RPC:
41
+
42
+ ```bash
43
+ npm install -g @earendil-works/pi-coding-agent
44
+ pi --version
45
+ ```
46
+
47
+ > Using nvm? Run `source ~/.nvm/nvm.sh` (or your nvm init) in each new terminal and make sure `pi` is on PATH.
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ git clone https://github.com/Hi-Barry/pi-courier.git
53
+ cd pi-courier
54
+ npm install
55
+ npm link # make the `pi-courier` command available globally
56
+ npm run build
57
+ ```
58
+
59
+ **Do not use `--ignore-scripts`**: the Matrix E2EE library (`@matrix-org/matrix-sdk-crypto-nodejs`) downloads its native binary via postinstall. If your npm blocks it (allow-scripts) and you hit `Cannot find module '@matrix-org/matrix-sdk-crypto-nodejs-linux-x64-gnu'`, run:
60
+
61
+ ```bash
62
+ cd node_modules/@matrix-org/matrix-sdk-crypto-nodejs
63
+ node download-lib.js
64
+ cd ../..
65
+ ```
66
+
67
+ A successful build produces `dist/standalone.js`.
68
+
69
+ ## Configuration
70
+
71
+ ### pi's LLM provider (`~/.pi/agent/`)
72
+
73
+ **a) models.json** — model metadata. Best pulled from models.dev so fields are complete and correct (opencode-go example):
74
+
75
+ ```bash
76
+ curl -s https://models.dev/api.json -o /tmp/modelsdev.json
77
+ python3 -c "
78
+ import json, os
79
+ md = json.load(open('/tmp/modelsdev.json')) # top level is the provider dict
80
+ out = {'providers': {'opencode-go': md['opencode-go']}}
81
+ json.dump(out, open(os.path.expanduser('~/.pi/agent/models.json'), 'w'), indent=2)
82
+ "
83
+ ```
84
+
85
+ **b) auth.json** — API key (chmod 600):
86
+
87
+ ```json
88
+ {
89
+ "opencode-go": { "type": "api_key", "key": "sk-your-key" }
90
+ }
91
+ ```
92
+
93
+ **c) settings.json** — default provider & model:
94
+
95
+ ```json
96
+ {
97
+ "defaultProvider": "opencode-go",
98
+ "defaultModel": "deepseek-v4-flash"
99
+ }
100
+ ```
101
+
102
+ > Field names are `defaultProvider`/`defaultModel` (not `provider`/`model`).
103
+
104
+ Verify (run inside the pi-courier project dir):
105
+
106
+ ```bash
107
+ node --input-type=module -e "
108
+ import { RpcClient } from '@earendil-works/pi-coding-agent';
109
+ import { fileURLToPath } from 'node:url';
110
+ import path from 'node:path';
111
+ const entry = fileURLToPath(import.meta.resolve('@earendil-works/pi-coding-agent'));
112
+ const c = new RpcClient({ cliPath: path.join(path.dirname(entry), 'cli.js') });
113
+ await c.start();
114
+ console.log('models:', (await c.getAvailableModels()).length);
115
+ console.log('current:', (await c.getState()).model?.id);
116
+ await c.stop();
117
+ "
118
+ ```
119
+
120
+ ### Messenger (Matrix example)
121
+
122
+ **Option A (recommended): setup wizard**
123
+
124
+ ```bash
125
+ pi-courier setup
126
+ ```
127
+
128
+ Follow the prompts: platform → homeserver URL → token (password login or paste an existing one) → trusted admin user MXID → E2EE toggle → pi workdir. The wizard verifies the token and writes `~/.pi/msg-bridge.json`.
129
+
130
+ **Option B: manual `~/.pi/msg-bridge.json`** (chmod 600):
131
+
132
+ ```json
133
+ {
134
+ "matrix": {
135
+ "homeserverUrl": "https://your-homeserver",
136
+ "accessToken": "syt_...",
137
+ "encryption": true
138
+ },
139
+ "auth": {
140
+ "trustedUsers": ["matrix:@you:your-homeserver"],
141
+ "adminUserId": "matrix:@you:your-homeserver"
142
+ },
143
+ "workdir": "/path/to/pi/workdir",
144
+ "autoConnect": true,
145
+ "debug": true
146
+ }
147
+ ```
148
+
149
+ - Get an access token: `POST /_matrix/client/v3/login` (password login) or from Element's settings page
150
+ - `trustedUsers`/`adminUserId` format: `<transport>:<full userId>`, e.g. `matrix:@barry:matrix.example.com`
151
+ - `workdir`: pi's working directory (spawned automatically if missing); `pi-courier run --workdir <dir>` overrides it
152
+ - `sessionDir` / `cliPath`: optional overrides (defaults: pi's session dir, and `which pi` for the CLI)
153
+ - `encryption: true` for encrypted rooms (works for plain rooms too)
154
+ - Env var alternatives: `PI_MATRIX_HOMESERVER` / `PI_MATRIX_ACCESS_TOKEN`
155
+
156
+ ## Usage
157
+
158
+ Everything goes through a single command:
159
+
160
+ ```
161
+ pi-courier setup first-run configuration wizard (Matrix account, trusted user, workdir)
162
+ pi-courier run run in the foreground (workdir from config; --workdir overrides)
163
+ pi-courier enable install a user-level systemd service (auto-start) and start it
164
+ pi-courier start start the service
165
+ pi-courier stop stop the service
166
+ pi-courier restart restart the service
167
+ pi-courier status show service status + recent logs
168
+ pi-courier logs tail the service logs
169
+ pi-courier disable uninstall the service (stop + remove autostart + delete unit file)
170
+ pi-courier update update this project (git pull + npm install + build)
171
+ ```
172
+
173
+ Typical first deployment:
174
+
175
+ ```bash
176
+ pi-courier setup # answer the prompts (or edit ~/.pi/msg-bridge.json manually)
177
+ pi-courier enable # auto-start on boot, running as your user
178
+ ```
179
+
180
+ For a quick foreground test: `pi-courier run` (Ctrl+C to stop). The old `node dist/standalone.js --workdir ...` form still works if you prefer it.
181
+
182
+ Startup success looks like:
183
+
184
+ ```
185
+ ✅ Matrix connected as @bot:your-homeserver (2 rooms, E2EE enabled)
186
+ ✅ pi RPC connected (model: deepseek-v4-flash, session: 019f...)
187
+ 🚀 msg-bridge standalone ready. Waiting for messages...
188
+ ```
189
+
190
+ ### Commands (send in the bot's DM)
191
+
192
+ **Pi commands (mapped to RPC):**
193
+
194
+ | Command | Action |
195
+ |---|---|
196
+ | `/new` `/clear` | New session |
197
+ | `/compact [notes]` | Compact context |
198
+ | `/model` / `/model <provider/id>` | Show / switch model |
199
+ | `/models` | List models |
200
+ | `/thinking [level]` | Show / set thinking level |
201
+ | `/session` `/cost` | Session stats & cost |
202
+ | `/status` | Current model & state |
203
+ | `/name <name>` | Name the session |
204
+ | `/export [path]` | Export session HTML |
205
+ | `/bash <cmd>` | Run a shell command (goes into context) |
206
+ | `/abort` | Abort current operation |
207
+ | `/reload` | Restart pi process (after installing extensions/config) |
208
+ | `/help` | Full help |
209
+
210
+ **Bridge admin commands:** `/trusted`, `/revoke <userId>`, `/channels`, `/enable <chatId> <mode>`, `/disable <chatId>`, `/toggletools`
211
+
212
+ **Pass-through:** any other `/`-prefixed command goes to pi directly — extension commands, `/skill:name` and prompt templates are expanded by pi. Plain text is a normal conversation turn.
213
+
214
+ ### First run: authentication
215
+
216
+ 1. DM the bot account from your account
217
+ 2. The bridge terminal (or `journalctl --user -u pi-msg-bridge -f`) prints a 6-digit code
218
+ 3. Reply with the code → you become a trusted user (the first trusted user becomes admin)
219
+
220
+ Users pre-listed in `msg-bridge.json` → `auth.trustedUsers` skip this step.
221
+
222
+ ## systemd deployment
223
+
224
+ **User-level (recommended, no sudo):**
225
+
226
+ ```bash
227
+ pi-courier enable
228
+ ```
229
+
230
+ That's it — it writes a systemd unit to `~/.config/systemd/user/pi-msg-bridge.service` (using the absolute node path and your configured workdir), enables auto-start and starts the service. For fully headless operation (keep running after logout), run once: `sudo loginctl enable-linger $USER`.
231
+
232
+ Commands: `pi-courier status`, `pi-courier logs`, `pi-courier stop`, `pi-courier start` (or `systemctl --user restart pi-msg-bridge`).
233
+
234
+ **System-level (needs sudo):** copy `deploy/pi-msg-bridge.service` to `/etc/systemd/system/`, adjust the three marked values (`User`, `WorkingDirectory`, `NVM_DIR`), then `sudo systemctl enable --now pi-msg-bridge`.
235
+
236
+ ## Upgrading pi
237
+
238
+ pi is managed independently on the system — upgrade it, no bridge code changes:
239
+
240
+ ```bash
241
+ npm install -g @earendil-works/pi-coding-agent@latest
242
+ pi --version
243
+ pi-courier restart # or: systemctl --user restart pi-msg-bridge
244
+ ```
245
+
246
+ pi-courier always connects to the system pi via `which pi`. Only a breaking change to pi's RPC protocol would require bridge code changes (the protocol is a documented stable interface and has never broken).
247
+
248
+ ## FAQ
249
+
250
+ ### Installation & deployment
251
+
252
+ **Q: `git clone` fails with 404?**
253
+ A: Wrong repo URL. Use `https://github.com/Hi-Barry/pi-courier.git` (install from npm instead: `npm install -g pi-courier`).
254
+
255
+ **Q: `npm install` hangs / crawls at ~20-60 kB/s?**
256
+ A: Two downloads are involved:
257
+ - npm registry packages → set npm proxy: `npm config set proxy http://...` and `npm config set https-proxy http://...`
258
+ - the 21 MB E2EE native lib (downloaded from GitHub Releases by matrix-sdk-crypto-nodejs) → does **not** use npm's proxy; export `https_proxy`/`http_proxy` env vars before installing (e.g. `export https_proxy=http://10.88.88.8:10809`). Add them to `~/.bashrc` to make it permanent.
259
+
260
+ **Q: `Cannot find module '@matrix-org/matrix-sdk-crypto-nodejs-linux-x64-gnu'`?**
261
+ A: The native binary wasn't downloaded (postinstall blocked or interrupted). Run manually (with proxy env vars set if needed):
262
+ ```bash
263
+ cd node_modules/@matrix-org/matrix-sdk-crypto-nodejs
264
+ node download-lib.js
265
+ cd ../..
266
+ ```
267
+
268
+ **Q: `npm install -g pi-courier` fails with EEXIST?**
269
+ A: A previous `npm link` left a conflicting `pi-courier` bin. Remove it first:
270
+ ```bash
271
+ npm unlink -g pi-courier
272
+ rm -f ~/.nvm/versions/node/v24.18.1/bin/pi-courier
273
+ npm install -g pi-courier
274
+ ```
275
+
276
+ **Q: `pi-courier` command not found after `npm link`?**
277
+ A: The link was created before `npm run build`, so `dist/cli.js` didn't exist yet. Re-run `npm link` after building (or just install via npm instead).
278
+
279
+ **Q: The systemd service keeps restarting in a loop?**
280
+ A: The pi child process crashed — almost always a Node version mismatch. The bridge spawns pi via PATH, and systemd's default PATH may find a system node (e.g. v20) that pi's undici is incompatible with (`webidl.util.markAsUncloneable is not a function`). Fix: load nvm (`source ~/.nvm/nvm.sh`) and re-run `pi-courier enable` (v0.1.2+ writes `Environment=PATH=<nvm bin first>` into the unit automatically). Stick to one Node version (nvm v24) everywhere.
281
+
282
+ ### Configuration
283
+
284
+ **Q: Startup logs `model: unknown`?**
285
+ A: pi's provider isn't configured. Check the three files in `~/.pi/agent/`: `models.json` (model metadata), `auth.json` (API key, chmod 600), `settings.json` (`defaultProvider` / `defaultModel` — note the exact field names).
286
+
287
+ **Q: `getAvailableModels` returns nothing?**
288
+ A: `models.json` is malformed. Regenerate it with the models.dev extraction command in the Configuration section.
289
+
290
+ **Q: `no transports configured` at startup?**
291
+ A: The bridge config is empty. Check `~/.pi/msg-bridge.json` (run `pi-courier setup`) or the `PI_*` env vars.
292
+
293
+ **Q: Weird `DeprecationWarning: util._extend` appears during setup?**
294
+ A: Known noise from a transport dependency, filtered since v0.1.0 — update pi-courier if you still see it.
295
+
296
+ ### Messaging & encryption
297
+
298
+ **Q: Lots of `Decryption error` lines in the log?**
299
+ A: Historical events that can't be decrypted (new device without old keys). Normal — new messages decrypt fine.
300
+
301
+ **Q: Encrypted room: no reply / can't decrypt new messages?**
302
+ A: The bot's new device never received the room keys from your client. Options:
303
+ - In Element (web: Settings → Security & Privacy → Encryption), make sure "Only share keys with verified devices" is unchecked, then send a message in the room
304
+ - The bot account has no cross-signing, so user verification shows "unavailable" — device-level trust is the relevant one, but the simplest reliable fix is: **use a non-encrypted room** (create a room without encryption enabled and invite the bot). The bridge handles plain rooms fine even with `encryption: true`.
305
+
306
+ **Q: `M_BAD_JSON: Provided device_id in device_keys does not match...` at startup?**
307
+ A: The crypto store holds an old device identity but your access token belongs to a newer device (token was re-logged). Delete the store and restart:
308
+ ```bash
309
+ rm -rf ~/.pi/msg-bridge-matrix-crypto
310
+ pi-courier restart
311
+ ```
312
+ Remember this whenever you re-run setup / change the token.
313
+
314
+ **Q: Matrix connection fails (wrong homeserver/token)?**
315
+ A: Verify the token: `curl -H "Authorization: Bearer <token>" https://homeserver/_matrix/client/v3/account/whoami`.
316
+
317
+ **Q: First message to the bot asks for a 6-digit code?**
318
+ A: That's the challenge auth — reply with the code to become a trusted user (the first trusted user becomes admin). Users pre-listed in `auth.trustedUsers` skip this.
319
+
320
+ **Q: No reply to messages at all?**
321
+ A: Check in order: (1) `pi-courier status` / logs — is Matrix connected? any Decryption errors (encrypted room)? (2) is pi RPC connected? (3) the model call itself — run `curl` against the provider endpoint with your key to isolate it.
322
+
323
+ ### Running & maintenance
324
+
325
+ **Q: `pi RPC did not become ready`?**
326
+ A: The pi child failed to start. Run it manually to see the real error:
327
+ ```bash
328
+ node node_modules/@earendil-works/pi-coding-agent/dist/cli.js --mode rpc
329
+ ```
330
+ Common causes: Node version mismatch (see service restart loop above), invalid provider config, no network access to the provider.
331
+
332
+ **Q: After a restart the conversation context is gone?**
333
+ A: Since v0.1.1 the bridge passes `--continue` to pi, resuming the most recent session per workdir (same as `pi -c`). Update pi-courier and restart; `/new` starts a fresh session and the next restart resumes that one.
334
+
335
+ **Q: Element (web client) intercepts `/`-prefixed messages?**
336
+ A: Prefix with `//` to send a literal slash (`//compact` sends `/compact`).
337
+
338
+ **Q: Proxy environment tips?**
339
+ A: npm registry → `npm config set proxy/https-proxy`; GitHub downloads & bridge runtime → `export https_proxy`/`http_proxy` (systemd: add to `EnvironmentFile`).
340
+
341
+ ## Tips
342
+
343
+ - **Element clients** treat `/`-prefixed messages as client commands — prefix with `//` to send a literal slash (`//compact` sends `/compact`)
344
+ - **Proxy environments**: set `HTTP_PROXY`/`HTTPS_PROXY` (systemd: via `EnvironmentFile`)
345
+ - **Group chats**: enable with `/enable <roomId> all` first; DMs need no setup
346
+
347
+ ## Development
348
+
349
+ ```bash
350
+ npm run build # compile
351
+ npm run typecheck # type-check
352
+ npm run test # unit tests (vitest)
353
+ npm run lint # biome lint
354
+ ```
355
+
356
+ The full development history — research, design decisions, every pitfall hit in
357
+ real deployments — is documented in [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md).
358
+
359
+ ## License
360
+
361
+ MIT
362
+
363
+ Upstream: [tintinweb/pi-messenger-bridge](https://github.com/tintinweb/pi-messenger-bridge) (MIT)