pi-harness-whatsapp 0.1.0 → 0.1.2
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.
- package/AGENT_INSTALL.md +135 -12
- package/CHANGELOG.md +20 -0
- package/README.md +113 -23
- package/index.ts +6 -1
- package/package.json +2 -2
- package/src/bridge.ts +29 -4
- package/src/cleanup/npm-cache.ts +173 -0
- package/src/cleanup/run.ts +43 -0
- package/src/cleanup/setup-record.ts +161 -0
- package/src/commands/session.ts +5 -2
- package/src/commands/settings-commands.ts +12 -3
- package/src/controller.ts +150 -35
- package/src/log.ts +3 -2
- package/src/media.ts +3 -3
- package/src/panel-components.ts +3 -1
- package/src/panel-model.ts +145 -45
- package/src/panel.ts +143 -139
- package/src/paths.ts +62 -0
- package/src/removed.ts +17 -0
- package/src/settings.ts +30 -2
- package/src/voice/models.ts +51 -10
- package/src/voice/service.ts +31 -30
- package/src/wa/connection.ts +61 -6
- package/src/wa/lock.ts +8 -1
- package/src/wa/recents.ts +8 -1
package/AGENT_INSTALL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Installing pi-harness-whatsapp: notes for AI agents
|
|
1
|
+
# Installing (and uninstalling) pi-harness-whatsapp: notes for AI agents
|
|
2
2
|
|
|
3
|
-
This file is for an AI agent (Pi, Claude Code, Codex, …) asked to install this extension for a user. Read it fully before changing anything.
|
|
3
|
+
This file is for an AI agent (Pi, Claude Code, Codex, …) asked to install or remove this extension for a user. Read it fully before changing anything. **Asked to remove it?** Go to [Uninstalling](#uninstalling).
|
|
4
4
|
|
|
5
5
|
## What you can and can't do
|
|
6
6
|
|
|
@@ -22,8 +22,16 @@ Do these yourself before asking anything, so your questions can include the resu
|
|
|
22
22
|
echo '{"type":"get_commands"}' | pi --mode rpc | grep '"get_commands"'
|
|
23
23
|
```
|
|
24
24
|
If another package already registers `/whatsapp` (each entry shows its `source`), tell the user it has to be removed first, and ask before removing it. Other WhatsApp extensions that use different command names can stay.
|
|
25
|
-
- **ffmpeg
|
|
26
|
-
- **Voice support on this machine:** voice
|
|
25
|
+
- **ffmpeg** (only needed if the user wants voice): run `ffmpeg -version`. Without it, text, images and files still work, but voice notes don't. If it's missing and they want voice, ask before installing it (it usually needs sudo: `sudo apt install ffmpeg`, `brew install ffmpeg`, …), and **record what you install** (see [Record what you install](#record-what-you-install-outside-pi)).
|
|
26
|
+
- **Voice support on this machine:** voice is optional and off by default. It works on Linux (x64 or ARM64), Windows (x64) and macOS on Apple Silicon, not on Intel Macs or Windows on ARM. What each part needs (check with `free -m` and `df -h ~`):
|
|
27
|
+
|
|
28
|
+
| Part | Disk | Free RAM while a voice note is handled |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| Understand voice notes (speech-to-text) | 1.0 GB | about 2 GB |
|
|
31
|
+
| Reply with voice notes (text-to-speech) | 0.6 GB | about 1.2 GB |
|
|
32
|
+
| Both | 1.4 GB | about 3.4 GB |
|
|
33
|
+
|
|
34
|
+
If the machine can't do voice, say so when you ask about it, and leave voice off.
|
|
27
35
|
|
|
28
36
|
## 2. Ask the basics
|
|
29
37
|
|
|
@@ -32,7 +40,8 @@ Ask these together, in one message, and wait for the answers:
|
|
|
32
40
|
1. **Which WhatsApp number will Pi use?** Recommend a **second number**. The extension uses an unofficial WhatsApp Web client (Baileys), and WhatsApp may restrict numbers that use one. Using their own number also works: they then talk to Pi in the "Message yourself" chat.
|
|
33
41
|
2. **Owner:** their own phone number, with country code. The owner is the only one who can change Pi from WhatsApp (`/model`, `/compact`, `/quit`, …). If they don't know it yet, skip it: the first person who messages the bot is offered as owner in the terminal.
|
|
34
42
|
3. **Languages:** which languages they'll write and speak in. The default is English only. Supported: English, Turkish, German, French, Spanish, Italian, Portuguese, Dutch, Polish, Russian, Ukrainian, Arabic, Hindi, Swedish and Finnish. Hindi voice notes are less accurate than the others (see the README's table). Adding languages downloads nothing extra.
|
|
35
|
-
4. **
|
|
43
|
+
4. **Voice notes:** off (the default), understand voice notes, reply with voice notes, or both? Give them the disk and RAM from the table in step 1, and the result of your check. Off means a voice note gets "🎤 Voice notes are off. Please type your message."; they can switch voice on later in `/whatsapp` → Voice notes.
|
|
44
|
+
5. **Background service:** should Pi keep running without a terminal (see "Run in the background" in the README)? Don't set it up unless they say yes. If they do, ask which project folder Pi should work in.
|
|
36
45
|
|
|
37
46
|
## 3. Offer the other settings
|
|
38
47
|
|
|
@@ -40,11 +49,11 @@ Then show the remaining settings in **one** message, with the default for each,
|
|
|
40
49
|
|
|
41
50
|
| Setting (name in `/whatsapp`) | What it does | Default | Other choices |
|
|
42
51
|
|---|---|---|---|
|
|
43
|
-
| Reply type | How Pi answers | **mirror me**: a voice note gets a voice reply, text gets text | text only · voice note · voice note + text |
|
|
52
|
+
| Reply type (only with "reply with voice notes") | How Pi answers | **mirror me**: a voice note gets a voice reply, text gets text | text only · voice note · voice note + text. With "reply" but not "understand", mirror me means text replies only, so suggest "voice note" if they want to hear Pi. |
|
|
44
53
|
| Reply language | Which language Pi answers in | **mirror me**: the language they used | always one of their languages |
|
|
45
54
|
| Fallback language | Used only when Pi can't tell the language (e.g. very short replies) | **the first language** they named | any of their languages |
|
|
46
|
-
| Voice per language | Which voice speaks each language | **F1** (female) | F2–F5 (female), M1–M5 (male); the same 10 voices for every language |
|
|
47
|
-
| Voice reply speed | How fast voice replies are spoken | **1.0** | 0.5–2.0 (0.9 slower, 1.2 faster) |
|
|
55
|
+
| Voice per language (only with "reply with voice notes") | Which voice speaks each language | **F1** (female) | F2–F5 (female), M1–M5 (male); the same 10 voices for every language |
|
|
56
|
+
| Voice reply speed (only with "reply with voice notes") | How fast voice replies are spoken | **1.0** | 0.5–2.0 (0.9 slower, 1.2 faster) |
|
|
48
57
|
| Terminal answers | Also sends the owner Pi's answers to prompts typed in the terminal, as text | **off** | on |
|
|
49
58
|
| Progress messages | Sends what Pi writes while it's still working | **off** | on |
|
|
50
59
|
| Live tool log | One message, updated live, with the latest tool steps | **off** | on |
|
|
@@ -71,11 +80,12 @@ Settings live in `~/.pi/agent/pi-harness-whatsapp/config.json`. Unknown or inval
|
|
|
71
80
|
|
|
72
81
|
Pi reads this file when a session starts, and saves it when a setting changes. If a Pi with this extension is already running, ask the user to quit it before you write the file, so it doesn't keep the old settings or save over yours.
|
|
73
82
|
|
|
74
|
-
Example: English and Turkish, a male voice for Turkish, the owner set, stats on, a slightly faster voice:
|
|
83
|
+
Example: English and Turkish, voice notes both ways, a male voice for Turkish, the owner set, stats on, a slightly faster voice:
|
|
75
84
|
|
|
76
85
|
```json
|
|
77
86
|
{
|
|
78
87
|
"version": 1,
|
|
88
|
+
"voice": { "understand": true, "reply": true },
|
|
79
89
|
"languages": ["en", "tr"],
|
|
80
90
|
"voices": { "tr": "M1" },
|
|
81
91
|
"voiceMode": "mirror",
|
|
@@ -88,9 +98,10 @@ Example: English and Turkish, a male voice for Turkish, the owner set, stats on,
|
|
|
88
98
|
|
|
89
99
|
| Field | Setting | Values |
|
|
90
100
|
|---|---|---|
|
|
101
|
+
| `voice` | Voice notes | `{ "understand": <bool>, "reply": <bool> }`, both `false` by default. Always write this field, even when both are `false`. |
|
|
91
102
|
| `languages` | My languages, Fallback language | Language codes from the table below. The **first** one is the fallback language. |
|
|
92
103
|
| `voices` | Voice per language | `{ "<code>": "<voice>" }` with a voice from `F1`–`F5`, `M1`–`M5`, for the languages whose voice should differ from the default (`F1`) |
|
|
93
|
-
| `voiceMode` | Reply type | `mirror` (default) · `off` (text only) · `voice` · `voice+text` |
|
|
104
|
+
| `voiceMode` | Reply type | `mirror` (default) · `off` (text only) · `voice` · `voice+text`. Only used while `voice.reply` is `true`. |
|
|
94
105
|
| `replyLanguage` | Reply language | `mirror` (default) or one of `languages` |
|
|
95
106
|
| `voiceSpeed` | Voice reply speed | 0.5–2.0 (default 1.0) |
|
|
96
107
|
| `replies` | Terminal answers, Progress messages, Live tool log, Changed files, Stats | `{ "terminal", "interim", "toolActivity", "touchedFiles", "summary" }`, booleans, all `false` by default |
|
|
@@ -106,7 +117,46 @@ Language codes: `en` English · `tr` Turkish · `de` German · `fr` French · `e
|
|
|
106
117
|
|
|
107
118
|
If the user can't decide on a voice, keep the default and tell them they can hear each one later: `/whatsapp` → Voice per language → a voice → "Send me a sample on WhatsApp".
|
|
108
119
|
|
|
109
|
-
Speech files download by themselves when first needed. You don't have to fetch them.
|
|
120
|
+
Speech files download by themselves when first needed: with voice switched on in the file, the first voice note (or the first spoken reply) waits for the download, and the chat is told. You don't have to fetch them. Tell the user. If they'd rather download right away, leave `voice` off in the file and have them tick the parts in `/whatsapp` → Voice notes after starting Pi: leaving that menu asks "Download now (recommended)" or "When first needed".
|
|
121
|
+
|
|
122
|
+
## Record what you install outside Pi
|
|
123
|
+
|
|
124
|
+
Anything you install on the computer for this extension (ffmpeg, and tmux for the background service) must be recorded in `setup.json` in the data folder (`~/.pi/agent/pi-harness-whatsapp/setup.json`, or the folder from `PI_HARNESS_WHATSAPP_DIR`). When the user later uninstalls, `/whatsapp` → Remove everything reads it, asks the user about each entry, and removes **exactly those packages** again. A program that was already installed is not recorded.
|
|
125
|
+
|
|
126
|
+
Record every package the install added, not just the one you asked for: installing ffmpeg with apt pulls in about 100 libraries. List the packages before and after, and record the difference:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
dpkg-query -W -f='${Package}\n' | sort > /tmp/phw-before.txt
|
|
130
|
+
sudo apt-get install -y ffmpeg
|
|
131
|
+
dpkg-query -W -f='${Package}\n' | sort > /tmp/phw-after.txt
|
|
132
|
+
comm -13 /tmp/phw-before.txt /tmp/phw-after.txt # these are the "packages"
|
|
133
|
+
rm /tmp/phw-before.txt /tmp/phw-after.txt
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Other package managers: list with `brew list --formula -1 | sort` (Homebrew), `rpm -qa --qf '%{NAME}\n' | sort` (dnf, zypper), `pacman -Qq` (pacman) or `apk info | sort` (apk), the same way. For winget, record the package id you installed.
|
|
137
|
+
|
|
138
|
+
Then write (or add to) `setup.json`, and keep it private (`chmod 600`):
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"version": 1,
|
|
143
|
+
"installed": [
|
|
144
|
+
{
|
|
145
|
+
"what": "ffmpeg",
|
|
146
|
+
"manager": "apt",
|
|
147
|
+
"packages": ["ffmpeg", "libavcodec61", "libavformat61", "…every name from the comm output…"],
|
|
148
|
+
"at": "2026-10-15",
|
|
149
|
+
"by": "Claude Code"
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- `manager` is one of `apt`, `dnf`, `pacman`, `zypper`, `apk`, `brew`, `winget`. Entries with anything else, or with package names that aren't plain names, are ignored.
|
|
156
|
+
- `what` is the name the user will see ("ffmpeg", "tmux"); `at` and `by` are optional and shown to the user.
|
|
157
|
+
- If the install added nothing (it was already there), don't add an entry.
|
|
158
|
+
|
|
159
|
+
A background service isn't recorded here (Pi can't remove the service it runs in); tell the user about it, and see Uninstalling, step 3.
|
|
110
160
|
|
|
111
161
|
## 6. Hand over to the user
|
|
112
162
|
|
|
@@ -120,11 +170,15 @@ Tell the user:
|
|
|
120
170
|
|
|
121
171
|
Then tell them, briefly:
|
|
122
172
|
|
|
123
|
-
- Anything that isn't a command goes to Pi as a prompt.
|
|
173
|
+
- Anything that isn't a command goes to Pi as a prompt. Images and files work too: Pi sees images, gets files saved for it to open, and can send images and files back when asked ("send me the screenshot").
|
|
174
|
+
- Voice notes, if they turned them on: understanding and replying can each be switched on or off in `/whatsapp` → Voice notes; switching a part off deletes its files after asking. To stop voice replies without deleting anything, set Reply type to text.
|
|
175
|
+
- To remove it later, the README's **Uninstall** section has the steps (in Pi: `/whatsapp` → Danger → Remove everything, or `/whatsapp remove`, then `pi uninstall`).
|
|
124
176
|
- If they want Pi in a group: add the bot's number to the group, have someone write there, then allow it in `/whatsapp` → Allowed chats → Pick from recent chats. In the group, people @mention Pi or reply to its messages; Pi also sees the last 20 text messages before that for context. You can't allow a group for them in advance, because the group's id is only known once it appears in recent chats. Remind them that every member of an allowed group can prompt Pi.
|
|
125
177
|
- From WhatsApp they can switch the model (`/model`), the reasoning level (`/think`), compact (`/compact`), stop a run (`/stop`), start a new session (`/new`) and change the reply type, language, speed and extras (`/voice`, `/lang`, `/speed`, `/replies`). Menus come as polls: tap an option.
|
|
126
178
|
- Everything else, including on/off, allowed chats and voices, is in `/whatsapp` in the terminal.
|
|
127
179
|
|
|
180
|
+
Finally, tell the user **everything you set up outside Pi**: for example "installed ffmpeg with apt (recorded in setup.json, so Remove everything can take it out again)", "created `~/.config/systemd/user/pi-whatsapp.service` and enabled lingering". If you have a memory or notes file for this user, write it there too.
|
|
181
|
+
|
|
128
182
|
## Check it worked
|
|
129
183
|
|
|
130
184
|
- The footer of Pi shows `WA: connected as +<number> · <n> chats`.
|
|
@@ -138,6 +192,75 @@ Then tell them, briefly:
|
|
|
138
192
|
- Don't set `commandAccess` to `allowed` unless the user asks.
|
|
139
193
|
- Don't install the systemd service, or enable it at boot, without the user's yes.
|
|
140
194
|
- Don't publish, fork or re-upload this package on the user's behalf.
|
|
195
|
+
- Don't edit or delete Pi's session files (`~/.pi/agent/sessions/`), not even to remove WhatsApp traces; see [Uninstalling](#uninstalling).
|
|
196
|
+
- When uninstalling, don't delete more than the steps say: no whole `PI_HARNESS_WHATSAPP_DIR` folder that holds other files, and no ffmpeg, tmux or npm cache without the user's yes.
|
|
197
|
+
|
|
198
|
+
## Uninstalling
|
|
199
|
+
|
|
200
|
+
Goal: **nothing left behind** on the user's computer, and Pi unlinked from their phone. Follow these steps in order. Don't improvise extra deletions.
|
|
201
|
+
|
|
202
|
+
### What the extension leaves on a computer
|
|
203
|
+
|
|
204
|
+
| What | Where | Removed by |
|
|
205
|
+
|---|---|---|
|
|
206
|
+
| The code | Pi's package folder, plus an entry in `~/.pi/agent/settings.json` | `pi uninstall` |
|
|
207
|
+
| All its data: WhatsApp login (`auth/`), speech files (`models/`, ~1.5 GB), settings (`config.json`), recent chats, received files (`inbox/`), log (`log/`), lock, `setup.json` | `~/.pi/agent/pi-harness-whatsapp/` (or `$PI_HARNESS_WHATSAPP_DIR` if set; or `$PI_CODING_AGENT_DIR/pi-harness-whatsapp/` if that is set) | "Remove everything", or deleting the folder |
|
|
208
|
+
| The linked device on the phone | WhatsApp → Settings → Linked devices | "Remove everything", or the user on their phone |
|
|
209
|
+
| A background service, only if one was set up | a systemd unit (usually `~/.config/systemd/user/*.service` or `/etc/systemd/system/*.service`), maybe lingering and a tmux session | you, step 3 |
|
|
210
|
+
| Copies of it and its npm packages | npm's download cache (`~/.npm/_cacache`) | "Remove everything" (only these entries) |
|
|
211
|
+
| Programs installed for it, only if they were | `ffmpeg`, `tmux`; listed in `setup.json` when recorded | "Remove everything" asks about each; or you, step 6, with the user's yes |
|
|
212
|
+
|
|
213
|
+
The extension writes nowhere else. It never edits shell profiles or creates files outside its data folder.
|
|
214
|
+
|
|
215
|
+
### Steps
|
|
216
|
+
|
|
217
|
+
1. **Look first, change nothing yet.**
|
|
218
|
+
- `pi list`: note the exact source (`npm:pi-harness-whatsapp`, or a `git:` source).
|
|
219
|
+
- The data folder: check `PI_HARNESS_WHATSAPP_DIR` and `PI_CODING_AGENT_DIR` in the environment; otherwise it's `~/.pi/agent/pi-harness-whatsapp/`.
|
|
220
|
+
- A service: `grep -rlE "pi-harness-whatsapp|PI_HARNESS_WHATSAPP|--whatsapp" ~/.config/systemd/user /etc/systemd/system 2>/dev/null`.
|
|
221
|
+
- Programs installed just for this: read `setup.json` in the data folder. If there's none, check your notes or ask the user.
|
|
222
|
+
2. **Tell the user what will be removed and get a yes.** The WhatsApp login is deleted, so using it again means a new QR scan; the speech files would have to download again.
|
|
223
|
+
3. **Stop the service, if there is one** (so it can't start Pi again): `systemctl --user disable --now <unit>` (system unit: `sudo systemctl disable --now <unit>`). Delete its unit file, then run `systemctl --user daemon-reload` (or `sudo systemctl daemon-reload`). Kill its tmux session if it's still there (`tmux ls`, then `tmux kill-session -t <name>`). If lingering was turned on only for this service (your notes, or ask), run `loginctl disable-linger`.
|
|
224
|
+
4. **Clean up the data from inside Pi.** First ask the user to close every Pi window. Then either the user does it, or you do it through Pi's RPC mode.
|
|
225
|
+
|
|
226
|
+
**You, over RPC** (from the user's usual project folder, with no other Pi running): start `pi --mode rpc`, and send this line on its stdin:
|
|
227
|
+
```json
|
|
228
|
+
{"id": "1", "type": "prompt", "message": "/whatsapp remove"}
|
|
229
|
+
```
|
|
230
|
+
Pi then sends `extension_ui_request` records on stdout. For each one with `"method": "confirm"`, show the user its `title` and `message`, and answer with their decision, using the request's `id`:
|
|
231
|
+
```json
|
|
232
|
+
{"type": "extension_ui_response", "id": "<the request's id>", "confirmed": true}
|
|
233
|
+
```
|
|
234
|
+
The first question is "Remove everything?"; then one "Also remove …?" per program in `setup.json`. Records with `"method": "notify"` are progress and the result; the last one starts with `WhatsApp removed.` and lists what was done and any command left for you to run. Show it to the user, then close stdin so Pi quits. Read stdout as a stream and split records on `\n` only (see Pi's `docs/rpc.md`).
|
|
235
|
+
|
|
236
|
+
**Or the user**, in Pi:
|
|
237
|
+
1. run `/whatsapp` → **Danger** → **Remove everything (before uninstalling)…** (or type `/whatsapp remove`) and confirm. It unlinks the phone, deletes the data folder, removes this extension's entries from npm's cache, and asks about each program in `setup.json` (removing exactly the recorded packages, after an apt dry run that keeps everything if anything else would go too),
|
|
238
|
+
2. read you its summary (the last notice), then quit Pi.
|
|
239
|
+
|
|
240
|
+
**Already uninstalled?** The cleanest way is to install it again (`pi install npm:pi-harness-whatsapp`) and do the step above (over RPC, or the user); it then also cleans what the first uninstall left behind.
|
|
241
|
+
|
|
242
|
+
**By hand** (only if the user doesn't want that): read `setup.json` first (step 6 needs it). Make sure no Pi is running (the user quits them all; `connection.lock` in the data folder shows the pid of a Pi that holds WhatsApp), then delete the data folder: `rm -rf ~/.pi/agent/pi-harness-whatsapp`. **If `PI_HARNESS_WHATSAPP_DIR` points at a folder that also holds other things, delete only these entries in it:** `config.json*`, `recents.json*`, `auth/`, `models/`, `inbox/`, `log/`, `connection.lock`, `announce.json`, `setup.json`. This way, npm's cache keeps its copies: only "Remove everything" knows which entries are this extension's, and `npm cache clean --force` (only if the user asks) empties the whole cache. Then tell the user to unlink Pi on their phone: WhatsApp → Settings → Linked devices → tap the device that belongs to Pi → Log out.
|
|
243
|
+
5. **Remove the code:** `pi uninstall <source from step 1>`, with Pi not running. npm leaves some empty folders behind (e.g. `@types`); remove only empty ones:
|
|
244
|
+
`find ~/.pi/agent/npm/node_modules ~/.pi/agent/git -mindepth 1 -type d -empty -delete 2>/dev/null`. Don't touch anything else in those folders; other Pi packages live there.
|
|
245
|
+
6. **Programs:** "Remove everything" already asked about each `setup.json` entry; its summary says what was removed, and gives the command for anything it couldn't do (for example when `sudo` needs a password). Run those commands only if the user says yes. If you went the manual way, for each entry, with the user's yes: for apt, first run `apt-get -s remove --purge <packages>` and continue only if every line starting with `Remv` or `Purg` names a package from the entry; then `sudo apt-get remove --purge -y <packages>` and delete those packages' downloaded files, `/var/cache/apt/archives/<name>_*.deb`. Never remove a program that wasn't recorded or confirmed by the user; other programs may need it.
|
|
246
|
+
7. **Verify, and show the user the output.** All of these must come back empty (or "No such file or directory"):
|
|
247
|
+
```bash
|
|
248
|
+
pi list | grep -i whatsapp
|
|
249
|
+
ls -a ~/.pi/agent/pi-harness-whatsapp
|
|
250
|
+
find ~/.pi ~/.config/systemd /etc/systemd -iname "*pi-harness-whatsapp*" 2>/dev/null
|
|
251
|
+
find ~/.pi/agent/npm/node_modules ~/.pi/agent/git -mindepth 1 -type d -empty 2>/dev/null
|
|
252
|
+
grep -rlE "pi-harness-whatsapp|PI_HARNESS_WHATSAPP" ~/.config/systemd /etc/systemd ~/.bashrc ~/.profile ~/.zshrc 2>/dev/null
|
|
253
|
+
npm cache ls 2>/dev/null | grep -i "pi-harness-whatsapp"
|
|
254
|
+
```
|
|
255
|
+
For each removed `setup.json` entry, `dpkg-query -W <packages>` (or your package manager's equivalent) must no longer show them as installed.
|
|
256
|
+
8. **Report** what you removed, and what stays on purpose (below).
|
|
257
|
+
|
|
258
|
+
### What stays, on purpose; don't delete it
|
|
259
|
+
|
|
260
|
+
- **Pi's sessions** (`~/.pi/agent/sessions/`). Conversations with Pi from WhatsApp are normal Pi sessions and contain small `pi-harness-whatsapp/session` entries (WhatsApp on/off). Pi ignores them without the extension. **Never edit session files**: entries are linked to each other, and cutting some out breaks the session. If the user wants those conversations gone, they delete whole sessions in Pi.
|
|
261
|
+
- **Other packages in npm's cache.** Only this extension's entries are removed. Emptying the whole cache (`npm cache clean --force`) is only for when the user asks for it.
|
|
262
|
+
- **npm's own logs** (`~/.npm/_logs`, its last few runs) may mention the install; npm replaces them as it runs again.
|
|
263
|
+
- **The package manager's history** (`/var/log/apt/history.log`, `/var/log/dpkg.log`, …) records installs and removals of every program. Never edit system logs.
|
|
141
264
|
|
|
142
265
|
## Troubleshooting
|
|
143
266
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.2 — 2026-10-16
|
|
4
|
+
|
|
5
|
+
- **Fixed file permissions:** the data folder and the WhatsApp login in it were readable by other users on the same machine. Every folder the extension creates is now private (only your user can open it), and existing installs are fixed the next time Pi starts.
|
|
6
|
+
|
|
7
|
+
## 0.1.1 — 2026-10-15
|
|
8
|
+
|
|
9
|
+
- **Voice is optional, and off by default.** `/whatsapp` → Voice → **Voice notes** has two ticks: understand voice notes (speech-to-text, 1.0 GB) and reply with voice notes (text-to-speech, 0.6 GB), plus "Delete all speech files". Leaving the menu asks before deleting the files of a part you unticked ("You set speech-to-text for removal. 828 MB will be freed."; No puts the tick back) and offers to download a part you ticked (now, or when first needed). Nothing is downloaded while voice is off.
|
|
10
|
+
- While understanding is off, a voice note gets "🎤 Voice notes are off. Please type your message." (in groups, only one that replies to Pi) and never reaches the model.
|
|
11
|
+
- Reply type, the voices and the voice speed are greyed out until voice replies are on, and say which tick turns them on. `/voice` and `/speed` on WhatsApp say so instead of changing anything; `/help` and `/status` show both switches.
|
|
12
|
+
- The panel has a VOICE section and a DANGER section (Remove everything, and where the log file is); the Speech files row is gone.
|
|
13
|
+
- Existing setups keep the voice parts whose files are already on disk. Leftovers from older versions (Piper voices, older Whisper models, unfinished downloads nothing needs) are deleted on start.
|
|
14
|
+
- README and AGENT_INSTALL: what voice costs in disk and RAM per part, the new settings, and an install question about voice.
|
|
15
|
+
|
|
16
|
+
- **Uninstall without leftovers:** `/whatsapp` → Connection → **Remove everything (before uninstalling)**, or `/whatsapp remove`, unlinks Pi from the phone (connecting first if needed), stops running downloads, deletes every file the extension made, removes its copies (and its dependencies') from npm's download cache, and keeps Pi from writing any more until it quits. It refuses while another Pi holds WhatsApp. AI agents can run it over Pi's RPC mode.
|
|
17
|
+
- **`setup.json`:** programs installed just for the extension (ffmpeg, tmux) are recorded with the exact packages that came with them; Remove everything asks about each and removes exactly those (apt: after a dry run that keeps everything if anything else would go too; Homebrew: `brew uninstall`; otherwise it shows the command). AGENT_INSTALL tells agents how to record them.
|
|
18
|
+
- The README and AGENT_INSTALL describe the full uninstall, the background service, what stays on purpose and how to check that nothing is left.
|
|
19
|
+
- A Pi that never turned WhatsApp on no longer creates a log or recent-chats file when it quits.
|
|
20
|
+
- README: a section on sending and receiving images and files.
|
|
21
|
+
- The examples in Pi's instructions for spoken replies are in English.
|
|
22
|
+
|
|
3
23
|
## 0.1.0 — 2026-10-15
|
|
4
24
|
|
|
5
25
|
First release.
|
package/README.md
CHANGED
|
@@ -6,23 +6,36 @@
|
|
|
6
6
|
|
|
7
7
|
Talk to the [Pi coding agent](https://pi.dev) from WhatsApp: text, voice notes, images and files. Pi answers automatically, and you can switch models, compact the context or stop a run from your phone.
|
|
8
8
|
|
|
9
|
-
> 🤖 **AI agent installing this for someone?** Read [AGENT_INSTALL.md](AGENT_INSTALL.md) first: what to ask the user, how to configure, and what never to touch.
|
|
9
|
+
> 🤖 **AI agent installing or removing this for someone?** Read [AGENT_INSTALL.md](AGENT_INSTALL.md) first: what to ask the user, how to configure, how to uninstall without leaving anything behind, and what never to touch.
|
|
10
10
|
|
|
11
11
|
- **Scan a QR code once and you're in.** Pi runs as a linked device of a WhatsApp number. The login is saved, and Pi reconnects on its own.
|
|
12
12
|
- **Replies are automatic.** Pi's final answer is always sent back. The model doesn't have to call a tool.
|
|
13
13
|
- **You choose what else you get:** a summary line (`✅ 4/6 tool calls ok · ⏱ 2m 14s`), in-between messages, the list of touched files, and a live tool-activity message.
|
|
14
|
-
- **
|
|
14
|
+
- **Optional voice notes, both ways, on your own CPU.** [Whisper](https://github.com/openai/whisper) turbo understands them and [Supertonic 3](https://huggingface.co/Supertone/supertonic-3) speaks the replies, both through Intel's [OpenVINO](https://github.com/openvinotoolkit/openvino). No GPU or cloud service needed, in 15 languages. Off by default; see below for what it costs.
|
|
15
|
+
- **Images and files both ways.** Send Pi a photo or screenshot and it sees it; send a file (PDF, log, zip, …) and it's saved for Pi to open. Ask Pi for an image or a file and it sends it to your chat, and screenshots its tools take can come automatically.
|
|
15
16
|
- **Control Pi from WhatsApp** with commands and poll menus: `/model`, `/think`, `/compact`, `/stop`, `/new`, `/status`, …
|
|
16
17
|
- **A real off switch.** Off means disconnected: nothing is read or sent, and the WhatsApp tools disappear from the model.
|
|
17
18
|
|
|
19
|
+
> **Voice is optional and off by default.** Turn it on in `/whatsapp` → **Voice notes**, one part or both:
|
|
20
|
+
>
|
|
21
|
+
> | Part | Disk | RAM while a voice note is handled |
|
|
22
|
+
> |---|---|---|
|
|
23
|
+
> | Understand voice notes (speech-to-text) | 1.0 GB | about 2 GB |
|
|
24
|
+
> | Reply with voice notes (text-to-speech) | 0.6 GB | about 1.2 GB |
|
|
25
|
+
> | Both | 1.4 GB (they share a 202 MB engine) | about 3.4 GB |
|
|
26
|
+
>
|
|
27
|
+
> The files download only once you switch a part on, and switching it off deletes them again. The models are unloaded after 10 minutes without voice work, so idle RAM use is zero. Everything runs on the CPU; nothing goes to the cloud.
|
|
28
|
+
|
|
18
29
|
> **Use a second number if you can.** This extension uses [Baileys](https://github.com/WhiskeySockets/Baileys), an unofficial WhatsApp Web client. WhatsApp can restrict numbers that use unofficial clients, so a spare number is the safe choice.
|
|
19
30
|
|
|
20
31
|
## Requirements
|
|
21
32
|
|
|
22
33
|
- Pi 1.2 or newer
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
34
|
+
|
|
35
|
+
Only if you turn voice on:
|
|
36
|
+
- `ffmpeg` on the PATH: `sudo apt install ffmpeg`
|
|
37
|
+
- Linux (x64 or ARM64), Windows (x64) or macOS on Apple Silicon. OpenVINO has no build for Intel Macs or Windows on ARM; text, images and files work there, voice notes don't.
|
|
38
|
+
- The disk and free RAM from the table above.
|
|
26
39
|
|
|
27
40
|
## Install
|
|
28
41
|
|
|
@@ -78,21 +91,23 @@ Every row opens a small menu with the choices and a short explanation. Nothing c
|
|
|
78
91
|
| | Who can use commands | **Only me** (the owner) · all allowed chats. |
|
|
79
92
|
| | Ask on/off at session start | On · **Off**. Asks whether WhatsApp should be on when a session opens. |
|
|
80
93
|
| Replies | Final answer | Always on. |
|
|
81
|
-
| | Reply type | **Mirror me** (voice note in → voice reply) · text · voice · voice + text. |
|
|
82
94
|
| | Terminal answers | On · **Off**. Also sends the owner Pi's answers to prompts typed in the terminal, as text. |
|
|
83
95
|
| | Progress messages | On · **Off**. What Pi writes while it's still working. |
|
|
84
96
|
| | Live tool log | On · **Off**. One message, updated live, with the latest tool steps. |
|
|
85
97
|
| | Changed files | On · **Off**. The files Pi wrote or edited. |
|
|
86
98
|
| | Stats (tools · time) | On · **Off**. A line like `✅ 4/6 tool calls ok · ⏱ 2m 14s`. |
|
|
87
99
|
| | Auto-send tool images | On · **Off**. Every image a tool makes while answering you (e.g. browser screenshots). You can always ask Pi to send a specific image instead. |
|
|
100
|
+
| Voice | Voice notes | Two ticks, both **off**: **Understand voice notes** (1.0 GB) and **Reply with voice notes** (0.6 GB), plus **Delete all speech files**. See [Voice](#voice). |
|
|
101
|
+
| | Reply type | **Mirror me** (voice note in → voice reply, text in → text reply) · text · voice · voice + text. Greyed out until "Reply with voice notes" is on. Without "Understand voice notes", mirror me answers everything with text (Pi gets no voice notes to mirror), so pick voice if you want spoken replies. |
|
|
102
|
+
| | Voice per language | Which voice speaks each language: one of 10 voices, **F1**–F5 (female) and M1–M5 (male), the same list for every language. "Send me a sample" sends a short voice note to your WhatsApp first. Greyed out until "Reply with voice notes" is on. |
|
|
103
|
+
| | Voice reply speed | 0.9 · **1.0** · 1.2 · custom (0.5–2.0). Greyed out until "Reply with voice notes" is on. |
|
|
88
104
|
| Languages | Reply language | **Mirror me** (the language you used) or one of your languages. |
|
|
89
105
|
| | My languages | Tick the languages you use (**English**). See [Voice](#voice) for the 15 languages. |
|
|
90
106
|
| | Fallback language | Used only when Pi can't tell which language to use (**the first of your languages**). |
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
| | Speech files | Download what your settings need, or delete unused files (shown as a ticked list; untick what you want to keep). |
|
|
107
|
+
| Danger | Remove everything (before uninstalling) | Unlinks Pi from your phone and deletes everything it made. Asks first. See [Uninstall](#uninstall). |
|
|
108
|
+
| | Log file | Where the log is (shown, not a setting). |
|
|
94
109
|
|
|
95
|
-
|
|
110
|
+
Greyed-out rows can still be selected: they say which tick in **Voice notes** turns them on.
|
|
96
111
|
|
|
97
112
|
Settings are stored in `~/.pi/agent/pi-harness-whatsapp/config.json`.
|
|
98
113
|
|
|
@@ -103,13 +118,13 @@ Commands are handled by the extension and never reach the model. Send `/help` to
|
|
|
103
118
|
| Command | What it does |
|
|
104
119
|
|---|---|
|
|
105
120
|
| `/help` | Commands and current settings |
|
|
106
|
-
| `/status` | Connection, model, thinking level, context use, voice
|
|
121
|
+
| `/status` | Connection, model, thinking level, context use, voice |
|
|
107
122
|
| `/stop` | Stop what Pi is doing |
|
|
108
123
|
| `/compact [focus]` | Compact the context, then report e.g. `✅ Compacted in 18s · 142k → 23k tokens (−84%) · context 89% free` |
|
|
109
124
|
| `/autocompact [on\|off]` | Automatic compaction for this session (Pi still compacts if the context is completely full) |
|
|
110
125
|
| `/model [name]` | Switch the model for this session; lists only models with a login or API key |
|
|
111
126
|
| `/think [level]` | Reasoning level, limited to what the model supports |
|
|
112
|
-
| `/voice [text\|voice\|both\|mirror]` · `/lang` · `/speed [x]` | Reply type, reply language, voice reply speed |
|
|
127
|
+
| `/voice [text\|voice\|both\|mirror]` · `/lang` · `/speed [x]` | Reply type, reply language, voice reply speed. `/voice` and `/speed` work once voice replies are on; voice itself is switched on only in the terminal, since it downloads files. |
|
|
113
128
|
| `/replies` | Switch the reply extras on and off (multi-choice poll) |
|
|
114
129
|
| `/languages` | Show your languages (change them in the terminal) |
|
|
115
130
|
| `/new` | Start a fresh Pi session (the old one stays saved) |
|
|
@@ -121,6 +136,18 @@ Commands are handled by the extension and never reach the model. Send `/help` to
|
|
|
121
136
|
|
|
122
137
|
## Voice
|
|
123
138
|
|
|
139
|
+
Voice is optional and **off by default**. In `/whatsapp` → **Voice notes**, tick what you want:
|
|
140
|
+
|
|
141
|
+
- **Understand voice notes** (speech-to-text, 1.0 GB): Pi transcribes the voice notes you send. While it's off, a voice note gets "🎤 Voice notes are off. Please type your message." and never reaches the model; in a group, only a voice note that replies to Pi gets that answer.
|
|
142
|
+
- **Reply with voice notes** (text-to-speech, 0.6 GB): Pi can answer with voice notes; **Reply type**, the voices and the speed then become available. While it's off, Pi always answers with text.
|
|
143
|
+
|
|
144
|
+
Downloads and deletions happen when you leave that menu:
|
|
145
|
+
- **A part you ticked** whose files aren't there yet: Pi asks "Download now (recommended)" or "When first needed". "When first needed" downloads with the first voice note (the chat is told while it waits).
|
|
146
|
+
- **A part you unticked:** Pi asks, e.g. "You set speech-to-text for removal. 828 MB will be freed." Yes deletes the files; No puts the tick back. The shared engine stays as long as the other part needs it.
|
|
147
|
+
- **"Delete all speech files"** unticks both, and asks the same way.
|
|
148
|
+
|
|
149
|
+
You don't have to untick anything to stop voice replies for a while: set **Reply type** to text.
|
|
150
|
+
|
|
124
151
|
Voice notes are transcribed on your machine and given to Pi with a label, e.g. `[WhatsApp · Name · voice note · your reply is spoken to Name] …`. When the reply will be spoken, the model is told so, and writes plain speech. Long answers are read in full as several voice notes (up to ~10 minutes; anything longer also comes as text). Answers that are mostly code come as text too, since code can't be read aloud. With **Mirror me**, Pi answers in the language you used, and the voice reply uses that language's voice.
|
|
125
152
|
|
|
126
153
|
Supported languages: **English, Turkish, German, French, Spanish, Italian, Portuguese, Dutch, Polish, Russian, Ukrainian, Arabic, Hindi, Swedish and Finnish**. Pi understands and speaks all of them with the same two models, so adding a language downloads nothing.
|
|
@@ -131,7 +158,7 @@ Supported languages: **English, Turkish, German, French, Spanish, Italian, Portu
|
|
|
131
158
|
| Speaking replies | [Supertonic 3](https://huggingface.co/Supertone/supertonic-3) by Supertone (10 voices) | 401 MB |
|
|
132
159
|
| Running both | Intel's OpenVINO for Node | 202 MB |
|
|
133
160
|
|
|
134
|
-
|
|
161
|
+
Models are unloaded after 10 minutes without voice work, so idle RAM use is zero. While a voice note is handled, Pi needed about 2 GB of RAM to understand it, about 1.2 GB to speak a reply, and about 3.4 GB with both, measured on the machine below.
|
|
135
162
|
|
|
136
163
|
Measured on an Intel i5-13500, CPU only, on real recordings from Google's [FLEURS](https://huggingface.co/datasets/google/fleurs) dataset: 10 sentences per language, read by real people. Wrong words are counted against the dataset's transcript, ignoring punctuation and capitals; a number written as digits ("18") where the transcript spells it out counts as wrong. For the voice, Supertonic read a short sentence in each language and Whisper listened to it again.
|
|
137
164
|
|
|
@@ -153,18 +180,23 @@ Measured on an Intel i5-13500, CPU only, on real recordings from Google's [FLEUR
|
|
|
153
180
|
| Swedish | 12 % | 2.9 s | ✓ |
|
|
154
181
|
| Hindi | 31 % | 3.2 s | 1 word off |
|
|
155
182
|
|
|
156
|
-
Supertonic makes speech about 10× faster than real time, so a 20-second reply is ready in about 2 seconds. Symbols and units are hard for any voice to read ("13°C" is not "13
|
|
183
|
+
Supertonic makes speech about 10× faster than real time, so a 20-second reply is ready in about 2 seconds. Symbols and units are hard for any voice to read ("13°C" is not "13 degrees"), so when a reply will be spoken, Pi is told to write everything out as it should be said.
|
|
157
184
|
|
|
158
|
-
|
|
185
|
+
## Images and files
|
|
159
186
|
|
|
160
|
-
|
|
187
|
+
**To Pi:**
|
|
188
|
+
- **Images** (photos, screenshots) go to the model with their caption, so you can ask "what's wrong in this screenshot?". In a group, an image counts when its caption @mentions Pi, or when you reply to an image and @mention Pi.
|
|
189
|
+
- **Files** (PDFs, documents, logs, archives, videos; up to 50 MB) are saved in `~/.pi/agent/pi-harness-whatsapp/inbox/`, and Pi is told the name, type, size and where it is, so it can open it with its own tools. Received files are deleted after 7 days.
|
|
190
|
+
- **Voice notes** are transcribed when **Understand voice notes** is on; see [Voice](#voice).
|
|
161
191
|
|
|
162
|
-
|
|
192
|
+
**From Pi:** ask for it ("send me the screenshot", "send me report.pdf") and Pi sends images and files from this computer to your chat, up to 60 MB. With **Auto-send tool images** on, every image a tool makes while answering you (e.g. a browser screenshot) is sent without asking.
|
|
163
193
|
|
|
164
|
-
|
|
165
|
-
- `whatsapp_send_file(path, caption?, to?)`
|
|
194
|
+
Pi does this with two tools that the model sees only while WhatsApp is on, and that send only to allowed chats:
|
|
166
195
|
|
|
167
|
-
|
|
196
|
+
- `whatsapp_send_image(path, caption?, to?)`: a PNG, JPEG, WebP or GIF, shown as a photo
|
|
197
|
+
- `whatsapp_send_file(path, caption?, to?)`: any file, sent as a document
|
|
198
|
+
|
|
199
|
+
`to` is an allowed contact's name or number; without it, Pi sends to the chat it's answering (or the owner). Normal replies never need these tools. There's no voice tool on purpose: when your reply is spoken, Pi's answer itself becomes the voice note, so the model can't send the same thing twice.
|
|
168
200
|
|
|
169
201
|
## Run in the background
|
|
170
202
|
|
|
@@ -178,11 +210,68 @@ Everything lives in `~/.pi/agent/pi-harness-whatsapp/`:
|
|
|
178
210
|
|---|---|
|
|
179
211
|
| `config.json` | Settings |
|
|
180
212
|
| `auth/` | The WhatsApp login. Keep it private; deleting it means scanning a new QR code. |
|
|
181
|
-
| `models/` | Speech models and the OpenVINO engine that runs them |
|
|
213
|
+
| `models/` | Speech models and the OpenVINO engine that runs them (only while voice is on) |
|
|
182
214
|
| `inbox/` | Files people sent (kept 7 days) |
|
|
183
215
|
| `recents.json` | Recent chat ids and names, for picking contacts (no message text) |
|
|
184
216
|
| `connection.lock` | Which Pi process holds the connection right now |
|
|
185
|
-
| `
|
|
217
|
+
| `announce.json` | For a moment around `/new` and `/restart`: which chat to tell that Pi is back |
|
|
218
|
+
| `setup.json` | Only if the installer recorded it: programs installed just for the extension (see [Uninstall](#what-was-installed-for-it)) |
|
|
219
|
+
| `log/pi-harness-whatsapp.log` | Log: connection events and errors, no message text (unless debug logging is on). Rotated at 5 MB into `.log.1`. |
|
|
220
|
+
|
|
221
|
+
The folder is private: only your user can open it. If you set `PI_HARNESS_WHATSAPP_DIR`, everything is in that folder instead. The extension writes nothing anywhere else, apart from Pi's own files (your sessions and its package list).
|
|
222
|
+
|
|
223
|
+
## Uninstall
|
|
224
|
+
|
|
225
|
+
Do it in this order, so nothing is left behind.
|
|
226
|
+
|
|
227
|
+
1. **Clean up from inside Pi.** Close every other Pi window first. Then run `/whatsapp` → **Danger** → **Remove everything (before uninstalling)…** (or type `/whatsapp remove`) and confirm. Pi:
|
|
228
|
+
- unlinks itself from your phone (WhatsApp → Settings → Linked devices),
|
|
229
|
+
- stops any speech-file download that's still running,
|
|
230
|
+
- deletes `~/.pi/agent/pi-harness-whatsapp/` with everything in it: the WhatsApp login, speech files, settings, allowed chats, recent chats, log and received files,
|
|
231
|
+
- deletes this extension's copies (and those of the packages it brought along) from npm's download cache, and nothing else there,
|
|
232
|
+
- asks about each program that was installed just for it, if the installer recorded one (see [What was installed for it](#what-was-installed-for-it)), and removes exactly the packages you say yes to,
|
|
233
|
+
- and writes nothing more until you quit, even if you keep using Pi.
|
|
234
|
+
2. **If Pi runs as a background service, stop and remove it now** (see [Removing the background service](#removing-the-background-service)); otherwise systemd starts Pi again.
|
|
235
|
+
3. **Quit Pi and remove the code:**
|
|
236
|
+
```bash
|
|
237
|
+
pi uninstall npm:pi-harness-whatsapp
|
|
238
|
+
```
|
|
239
|
+
If you installed it from GitHub, use the source that `pi list` shows (e.g. `pi uninstall git:github.com/mkemalemdar/pi-harness-whatsapp`).
|
|
240
|
+
4. **Tidy up after npm.** npm removes every file, but leaves a few empty folders (like `@types`) in Pi's package folder. This deletes only folders that are empty:
|
|
241
|
+
```bash
|
|
242
|
+
find ~/.pi/agent/npm/node_modules ~/.pi/agent/git -mindepth 1 -type d -empty -delete 2>/dev/null
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Check that it's gone:** `pi list` no longer shows it, and `ls ~/.pi/agent/pi-harness-whatsapp` says "No such file or directory".
|
|
246
|
+
|
|
247
|
+
### Already uninstalled without step 1?
|
|
248
|
+
|
|
249
|
+
The easiest clean way: install it again (`pi install npm:pi-harness-whatsapp`), start Pi, and do the steps above. "Remove everything" then also cleans up what the first uninstall left.
|
|
250
|
+
|
|
251
|
+
By hand instead: make sure no Pi is running, and look at `~/.pi/agent/pi-harness-whatsapp/setup.json` first, if it's there, for programs installed for the extension. Then delete the folder: `rm -rf ~/.pi/agent/pi-harness-whatsapp` (if you set `PI_HARNESS_WHATSAPP_DIR` to a folder that holds other things too, delete only the entries listed under [Files](#files)). On your phone, remove Pi under WhatsApp → Settings → Linked devices.
|
|
252
|
+
|
|
253
|
+
### Removing the background service
|
|
254
|
+
|
|
255
|
+
Use your unit's name. For a system service, run the same with `sudo` and without `--user`; its file is in `/etc/systemd/system/`.
|
|
256
|
+
```bash
|
|
257
|
+
systemctl --user disable --now pi-whatsapp.service
|
|
258
|
+
rm ~/.config/systemd/user/pi-whatsapp.service
|
|
259
|
+
systemctl --user daemon-reload
|
|
260
|
+
```
|
|
261
|
+
If you turned on lingering only for this service, turn it off again with `loginctl disable-linger`. Remove the tmux session it used with `tmux kill-session -t <name>` if it's still running.
|
|
262
|
+
|
|
263
|
+
### What was installed for it
|
|
264
|
+
|
|
265
|
+
The extension itself never installs programs; you or an AI agent install `ffmpeg` (and `tmux` for the background service) when they're missing. Whoever does that can record it in `setup.json` in the data folder, and "Remove everything" then offers to remove it again. [AGENT_INSTALL.md](AGENT_INSTALL.md) has the format, and AI agents installing the extension write it for you.
|
|
266
|
+
|
|
267
|
+
Removal is careful: with apt, it first does a dry run and keeps everything if removing those packages would also remove anything not on the list (like a program you installed later that needs them). It runs as root or with `sudo` that needs no password; otherwise it shows you the command. With Homebrew it runs `brew uninstall`; with other package managers it shows the command.
|
|
268
|
+
|
|
269
|
+
### What isn't removed, on purpose
|
|
270
|
+
|
|
271
|
+
- **Your Pi sessions** (`~/.pi/agent/sessions/`). Chats you had with Pi from WhatsApp are ordinary Pi sessions, and they contain small `pi-harness-whatsapp/session` entries (whether WhatsApp was on). Pi ignores those once the extension is gone; cutting them out would break the sessions. Delete whole sessions in Pi if you don't want them.
|
|
272
|
+
- **Programs nobody recorded** in `setup.json`: if you installed `ffmpeg` or `tmux` yourself just for this, remove them with your package manager (e.g. `sudo apt remove --purge ffmpeg tmux`, then `sudo apt autoremove` for what came with them, after checking its list).
|
|
273
|
+
- **npm's own logs** (`~/.npm/_logs`): npm keeps a log of its last few runs, which can mention the install. They're replaced as npm runs again.
|
|
274
|
+
- **Your package manager's history** (e.g. `/var/log/apt/history.log`, `/var/log/dpkg.log`) notes every install and removal, like it does for every program.
|
|
186
275
|
|
|
187
276
|
## Troubleshooting
|
|
188
277
|
|
|
@@ -190,7 +279,8 @@ Everything lives in `~/.pi/agent/pi-harness-whatsapp/`:
|
|
|
190
279
|
- **"needs a new QR scan":** WhatsApp logged the device out (for example, it was removed from Linked devices). Connection → Show a new QR code.
|
|
191
280
|
- **"in use by Pi #…":** another Pi holds the connection; turn WhatsApp off there.
|
|
192
281
|
- **Messages are ignored:** the sender isn't in Allowed chats. Pi shows a notice in the terminal the first time.
|
|
193
|
-
- **Voice notes
|
|
282
|
+
- **"Voice notes are off":** tick **Understand voice notes** in `/whatsapp` → Voice notes.
|
|
283
|
+
- **Voice notes fail:** check that `ffmpeg -version` works. With "When first needed", the first voice note waits for the download (up to 1.4 GB); a download that drops continues where it stopped, and one that stalls for a minute is given up and tried again with the next voice note. On Intel Macs and Windows on ARM, voice notes aren't available.
|
|
194
284
|
- **More detail:** set `PI_HARNESS_WHATSAPP_DEBUG=1` before starting Pi, and check the log. Debug logging also writes WhatsApp's own internal messages, which can contain message content, so turn it off again afterwards.
|
|
195
285
|
|
|
196
286
|
## Development
|
package/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ export default function piHarnessWhatsApp(pi: ExtensionAPI) {
|
|
|
22
22
|
controller.onToolsChanged = (on) => setToolsActive(pi, on);
|
|
23
23
|
|
|
24
24
|
pi.registerCommand("whatsapp", {
|
|
25
|
-
description: "WhatsApp settings: on/off, replies, voice, languages, contacts, connection",
|
|
25
|
+
description: "WhatsApp settings: on/off, replies, voice, languages, contacts, connection. /whatsapp remove: remove everything before uninstalling",
|
|
26
26
|
handler: async (args, ctx) => {
|
|
27
27
|
// Internal: "/new" from WhatsApp dispatches this so Pi gives us a real command context.
|
|
28
28
|
if (args.trim() === NEW_SESSION_ARG) {
|
|
@@ -30,6 +30,11 @@ export default function piHarnessWhatsApp(pi: ExtensionAPI) {
|
|
|
30
30
|
await ctx.newSession();
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
|
+
// Same as Connection → Remove everything; works without the panel (e.g. for an AI agent over RPC).
|
|
34
|
+
if (args.trim() === "remove") {
|
|
35
|
+
await controller.removeEverything(ctx);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
33
38
|
await controller.openSettings(ctx);
|
|
34
39
|
},
|
|
35
40
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-harness-whatsapp",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Talk to
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Talk to Pi from WhatsApp: images, files and optional voice notes in and out, automatic replies, group chats, poll menus to control Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "mkemalemdar",
|
package/src/bridge.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { contactLabel } from "./contacts-flow.ts";
|
|
|
12
12
|
import { languageLabel } from "./languages.ts";
|
|
13
13
|
import { createLogger } from "./log.ts";
|
|
14
14
|
import { formatActivity, formatTouchedFiles, RunReport } from "./run-report.ts";
|
|
15
|
-
import { type Contact, MIRROR, type SettingsStore } from "./settings.ts";
|
|
15
|
+
import { type Contact, MIRROR, replyMode, type SettingsStore } from "./settings.ts";
|
|
16
16
|
import type { WhatsAppConnection } from "./wa/connection.ts";
|
|
17
17
|
import { type Access, addressedToSelf, checkAccess, type InboundMessage, parseMessage, SeenIds, senderIdentity } from "./wa/inbound.ts";
|
|
18
18
|
import { contactJid, isLidJid, jidUser, phoneJid } from "./wa/jid.ts";
|
|
@@ -33,6 +33,8 @@ const BUSY_POLL_MS = 500;
|
|
|
33
33
|
/** Group messages not meant for Pi that are kept as context for when it is addressed. */
|
|
34
34
|
const GROUP_CONTEXT_MESSAGES = 20;
|
|
35
35
|
const CUT_OFF_NOTICE = "⚠️ The model hit its output limit before writing an answer. Try again, or raise the model's output limit.";
|
|
36
|
+
/** The answer to a voice note while "Understand voice notes" is off. */
|
|
37
|
+
export const VOICE_OFF_NOTICE = "🎤 Voice notes are off. Please type your message.";
|
|
36
38
|
|
|
37
39
|
type PromptContent = string | Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }>;
|
|
38
40
|
|
|
@@ -214,9 +216,18 @@ export class Bridge {
|
|
|
214
216
|
return;
|
|
215
217
|
}
|
|
216
218
|
|
|
219
|
+
// Voice notes while "Understand voice notes" is off: nothing reaches the model and nothing downloads.
|
|
220
|
+
const voiceOff = message.kind === "audio" && !this.deps.settings.get().voice.understand;
|
|
217
221
|
if (message.isGroup) {
|
|
218
222
|
// WhatsApp writes a mention as "@<number>"; the model reads "@Pi" better.
|
|
219
223
|
message.text = this.namePi(message.text);
|
|
224
|
+
if (voiceOff) {
|
|
225
|
+
// In a group, only a voice note that replies to Pi gets the answer; others are just context.
|
|
226
|
+
const { repliedTo } = addressedToSelf(message, this.deps.connection.selfIds(), (id) => this.deps.connection.isOwnMessage(id));
|
|
227
|
+
if (repliedTo) await this.deps.queue.sendText(message.chatJid, VOICE_OFF_NOTICE);
|
|
228
|
+
else this.rememberGroupMessage(message);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
220
231
|
if (!this.isForPi(message)) {
|
|
221
232
|
// Not meant for Pi: keep it as context for the next time Pi is addressed.
|
|
222
233
|
this.rememberGroupMessage(message);
|
|
@@ -230,6 +241,13 @@ export class Bridge {
|
|
|
230
241
|
await this.forwardToPi(message, access);
|
|
231
242
|
}
|
|
232
243
|
|
|
244
|
+
/** Voice notes while "Understand voice notes" is off get a short answer instead of reaching Pi. */
|
|
245
|
+
private async answeredVoiceOff(message: InboundMessage): Promise<boolean> {
|
|
246
|
+
if (message.kind !== "audio" || this.deps.settings.get().voice.understand) return false;
|
|
247
|
+
await this.deps.queue.sendText(message.chatJid, VOICE_OFF_NOTICE);
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
|
|
233
251
|
/** In a group: does this message ask Pi something (per "In groups, answer")? Commands and menu answers always do. */
|
|
234
252
|
private isForPi(message: InboundMessage): boolean {
|
|
235
253
|
const mode = this.deps.settings.get().groupReplies;
|
|
@@ -391,7 +409,7 @@ export class Bridge {
|
|
|
391
409
|
const from = message.isGroup ? `WhatsApp group "${access.contact?.name ?? "group"}" · ${sender}` : `WhatsApp · ${sender}`;
|
|
392
410
|
const to = message.isGroup ? "the group" : sender;
|
|
393
411
|
return (voiceTag) => {
|
|
394
|
-
const mode = this.deps.settings.get()
|
|
412
|
+
const mode = replyMode(this.deps.settings.get());
|
|
395
413
|
const spoken = mode === "voice" || mode === "voice+text" || (mode === "mirror" && !!voiceTag);
|
|
396
414
|
return `[${[from, voiceTag, spoken ? `your reply is spoken to ${to}` : `your reply goes to ${to}`].filter(Boolean).join(" · ")}]`;
|
|
397
415
|
};
|
|
@@ -399,6 +417,7 @@ export class Bridge {
|
|
|
399
417
|
|
|
400
418
|
private async forwardToPi(message: InboundMessage, access: Access): Promise<void> {
|
|
401
419
|
if (!this.deps.getCtx()) return;
|
|
420
|
+
if (await this.answeredVoiceOff(message)) return;
|
|
402
421
|
void this.deps.connection.presence(message.chatJid, "composing");
|
|
403
422
|
const header = this.header(message, access);
|
|
404
423
|
const context = message.isGroup ? this.takeGroupContext(message, access) : undefined;
|
|
@@ -482,8 +501,14 @@ export class Bridge {
|
|
|
482
501
|
"- Name is talking to you directly from their phone. Answer Name directly, as \"you\" — don't describe their message to someone else or ask what to reply to them.",
|
|
483
502
|
"- Your final answer is sent to them automatically. Don't use a tool just to reply.",
|
|
484
503
|
"- Keep answers short and chat-friendly. Summarize long code, and write files to disk instead.",
|
|
485
|
-
|
|
486
|
-
|
|
504
|
+
...(settings.voice.understand
|
|
505
|
+
? ["- Voice notes arrive already transcribed, e.g. `[WhatsApp · Name · voice note · your reply goes to Name]`. Transcripts can contain misheard words."]
|
|
506
|
+
: []),
|
|
507
|
+
...(settings.voice.reply
|
|
508
|
+
? [
|
|
509
|
+
"- When the label says \"your reply is spoken\", your answer becomes voice notes automatically (long answers are split into parts). Write it exactly as it should be said: no symbols, units or abbreviations (\"13 degrees\", not \"13°C\"; \"40 percent\", not \"40%\"; \"25 kilometers\", not \"25 km\"), and no code, tables, links or emoji.",
|
|
510
|
+
]
|
|
511
|
+
: []),
|
|
487
512
|
"- Group labels look like `[WhatsApp group \"Team\" · Name · your reply goes to the group]`: answer the group. You only get group messages that mention or reply to you; the ones before it may come first as `earlier messages, for context`. Use them to understand the question, but answer only the last message.",
|
|
488
513
|
`- ${language}`,
|
|
489
514
|
].join("\n");
|